diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /utils/vrad | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'utils/vrad')
34 files changed, 19935 insertions, 0 deletions
diff --git a/utils/vrad/disp_vrad.cpp b/utils/vrad/disp_vrad.cpp new file mode 100644 index 0000000..b1d63b6 --- /dev/null +++ b/utils/vrad/disp_vrad.cpp @@ -0,0 +1,332 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "disp_vrad.h" +#include "utllinkedlist.h" +#include "utlvector.h" +#include "iscratchpad3d.h" +#include "scratchpadutils.h" + + +//#define USE_SCRATCHPAD +#if defined( USE_SCRATCHPAD ) + static IScratchPad3D *g_pPad = 0; +#endif + + +int FindNeighborCornerVert( CCoreDispInfo *pDisp, const Vector &vTest ) +{ + CDispUtilsHelper *pDispHelper = pDisp; + + int iClosest = 0; + float flClosest = 1e24; + for ( int iCorner=0; iCorner < 4; iCorner++ ) + { + // Has it been touched? + CVertIndex cornerVert = pDispHelper->GetPowerInfo()->GetCornerPointIndex( iCorner ); + int iCornerVert = pDispHelper->VertIndexToInt( cornerVert ); + const Vector &vCornerVert = pDisp->GetVert( iCornerVert ); + + float flDist = vCornerVert.DistTo( vTest ); + if ( flDist < flClosest ) + { + iClosest = iCorner; + flClosest = flDist; + } + } + + if ( flClosest <= 0.1f ) + return iClosest; + else + return -1; +} + + +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 < ARRAYSIZE( iNeighbors ) ) + 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; +} + + +void BlendCorners( CCoreDispInfo **ppListBase, int listSize ) +{ + CUtlVector<int> nbCornerVerts; + + for ( int iDisp=0; iDisp < listSize; 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 ); + + 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 ); + } + } + + + // Blend all the neighbor normals with this one. + VectorNormalize( vAverage ); + pDisp->SetNormal( iCornerVert, vAverage ); + +#if defined( USE_SCRATCHPAD ) + ScratchPad_DrawArrowSimple( + g_pPad, + pDisp->GetVert( iCornerVert ), + pDisp->GetNormal( iCornerVert ), + Vector( 0, 0, 1 ), + 25 ); +#endif + + for ( int iNeighbor=0; iNeighbor < nNeighbors; iNeighbor++ ) + { + int iNBListIndex = iNeighbors[iNeighbor]; + if ( nbCornerVerts[iNeighbor] == -1 ) + continue; + + CCoreDispInfo *pNeighbor = ppListBase[iNBListIndex]; + pNeighbor->SetNormal( nbCornerVerts[iNeighbor], vAverage ); + } + } + } +} + + +void BlendTJuncs( CCoreDispInfo **ppListBase, int listSize ) +{ + for ( int iDisp=0; iDisp < listSize; iDisp++ ) + { + CCoreDispInfo *pDisp = ppListBase[iDisp]; + + for ( int iEdge=0; iEdge < 4; iEdge++ ) + { + CDispNeighbor *pEdge = pDisp->GetEdgeNeighbor( iEdge ); + + CVertIndex viMidPoint = pDisp->GetEdgeMidPoint( iEdge ); + int iMidPoint = pDisp->VertIndexToInt( viMidPoint ); + + if ( pEdge->m_SubNeighbors[0].IsValid() && pEdge->m_SubNeighbors[1].IsValid() ) + { + const Vector &vMidPoint = pDisp->GetVert( iMidPoint ); + + CCoreDispInfo *pNeighbor1 = ppListBase[pEdge->m_SubNeighbors[0].GetNeighborIndex()]; + CCoreDispInfo *pNeighbor2 = ppListBase[pEdge->m_SubNeighbors[1].GetNeighborIndex()]; + + int iNBCorners[2]; + iNBCorners[0] = FindNeighborCornerVert( pNeighbor1, vMidPoint ); + iNBCorners[1] = FindNeighborCornerVert( pNeighbor2, vMidPoint ); + + if ( iNBCorners[0] != -1 && iNBCorners[1] != -1 ) + { + CVertIndex viNBCorners[2] = + { + pNeighbor1->GetCornerPointIndex( iNBCorners[0] ), + pNeighbor2->GetCornerPointIndex( iNBCorners[1] ) + }; + + Vector vAverage = pDisp->GetNormal( iMidPoint ); + vAverage += pNeighbor1->GetNormal( viNBCorners[0] ); + vAverage += pNeighbor2->GetNormal( viNBCorners[1] ); + + VectorNormalize( vAverage ); + pDisp->SetNormal( iMidPoint, vAverage ); + pNeighbor1->SetNormal( viNBCorners[0], vAverage ); + pNeighbor2->SetNormal( viNBCorners[1], vAverage ); + +#if defined( USE_SCRATCHPAD ) + ScratchPad_DrawArrowSimple( g_pPad, pDisp->GetVert( iMidPoint ), pDisp->GetNormal( iMidPoint ), Vector( 0, 1, 1 ), 25 ); +#endif + } + } + } + } +} + + +void BlendEdges( CCoreDispInfo **ppListBase, int listSize ) +{ + for ( int iDisp=0; iDisp < listSize; iDisp++ ) + { + CCoreDispInfo *pDisp = ppListBase[iDisp]; + + for ( int iEdge=0; iEdge < 4; iEdge++ ) + { + CDispNeighbor *pEdge = pDisp->GetEdgeNeighbor( iEdge ); + + for ( int iSub=0; iSub < 2; iSub++ ) + { + CDispSubNeighbor *pSub = &pEdge->m_SubNeighbors[iSub]; + if ( !pSub->IsValid() ) + continue; + + CCoreDispInfo *pNeighbor = ppListBase[ pSub->GetNeighborIndex() ]; + + int iEdgeDim = g_EdgeDims[iEdge]; + + CDispSubEdgeIterator it; + it.Start( pDisp, iEdge, iSub, true ); + + // Get setup on the first corner vert. + it.Next(); + CVertIndex viPrevPos = it.GetVertIndex(); + + while ( it.Next() ) + { + // Blend the two. + if ( !it.IsLastVert() ) + { + Vector vAverage = pDisp->GetNormal( it.GetVertIndex() ) + pNeighbor->GetNormal( it.GetNBVertIndex() ); + VectorNormalize( vAverage ); + + pDisp->SetNormal( it.GetVertIndex(), vAverage ); + pNeighbor->SetNormal( it.GetNBVertIndex(), vAverage ); + +#if defined( USE_SCRATCHPAD ) + ScratchPad_DrawArrowSimple( g_pPad, pDisp->GetVert( it.GetVertIndex() ), pDisp->GetNormal( it.GetVertIndex() ), Vector( 1, 0, 0 ), 25 ); +#endif + } + + // 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 vNormal; + VectorLerp( pDisp->GetNormal( viPrevPos ), pDisp->GetNormal( it.GetVertIndex() ), flPercent, vNormal ); + VectorNormalize( vNormal ); + + CVertIndex viTween; + viTween[iEdgeDim] = it.GetVertIndex()[ iEdgeDim ]; + viTween[!iEdgeDim] = iTween; + pDisp->SetNormal( viTween, vNormal ); + +#if defined( USE_SCRATCHPAD ) + ScratchPad_DrawArrowSimple( g_pPad, pDisp->GetVert( viTween ), pDisp->GetNormal( viTween ), Vector( 1, 0.5, 0 ), 25 ); +#endif + } + + viPrevPos = it.GetVertIndex(); + } + } + } + } +} + + +#if defined( USE_SCRATCHPAD ) + void ScratchPad_DrawOriginalNormals( const CCoreDispInfo *pListBase, int listSize ) + { + for ( int i=0; i < listSize; i++ ) + { + const CCoreDispInfo *pDisp = &pListBase[i]; + const CPowerInfo *pPowerInfo = pDisp->GetPowerInfo(); + + // Draw the triangles. + for ( int iTri=0; iTri < pPowerInfo->GetNumTriInfos(); iTri++ ) + { + const CTriInfo *pTriInfo = pPowerInfo->GetTriInfo( iTri ); + + for ( int iLine=0; iLine < 3; iLine++ ) + { + const Vector &v1 = pDisp->GetVert( pTriInfo->m_Indices[iLine] ); + const Vector &v2 = pDisp->GetVert( pTriInfo->m_Indices[(iLine+1)%3] ); + + g_pPad->DrawLine( CSPVert( v1 ), CSPVert( v2 ) ); + } + } + + // Draw the normals. + CDispCircumferenceIterator it( pPowerInfo->GetSideLength() ); + while ( it.Next() ) + { + ScratchPad_DrawArrowSimple( + g_pPad, + pDisp->GetVert( it.GetVertIndex() ), + pDisp->GetNormal( it.GetVertIndex() ), + Vector( 0, 1, 0 ), + 15 ); + } + } + } +#endif + + +void SmoothNeighboringDispSurfNormals( CCoreDispInfo **ppListBase, int listSize ) +{ +//#if defined( USE_SCRATCHPAD ) +// g_pPad = ScratchPad3D_Create(); +// ScratchPad_DrawOriginalNormals( pListBase, listSize ); +//#endif + + BlendTJuncs( ppListBase, listSize ); + + BlendCorners( ppListBase, listSize ); + + BlendEdges( ppListBase, listSize ); +} + + + diff --git a/utils/vrad/disp_vrad.h b/utils/vrad/disp_vrad.h new file mode 100644 index 0000000..0976c57 --- /dev/null +++ b/utils/vrad/disp_vrad.h @@ -0,0 +1,22 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef DISP_VRAD_H +#define DISP_VRAD_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "builddisp.h" + + +// Blend the normals of neighboring displacement surfaces so they match at edges and corners. +void SmoothNeighboringDispSurfNormals( CCoreDispInfo **ppListBase, int listSize ); + + +#endif // DISP_VRAD_H diff --git a/utils/vrad/iincremental.h b/utils/vrad/iincremental.h new file mode 100644 index 0000000..c80854f --- /dev/null +++ b/utils/vrad/iincremental.h @@ -0,0 +1,71 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef IINCREMENTAL_H +#define IINCREMENTAL_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "mathlib/vector.h" +#include "utlvector.h" + + +typedef unsigned short IncrementalLightID; + + +// Incremental lighting manager. +class IIncremental +{ +// IIncremental overrides. +public: + + virtual ~IIncremental() {} + + // Sets up for incremental mode. The BSP file (in bsplib) should be loaded + // already so it can detect if the incremental file is up to date. + virtual bool Init( char const *pBSPFilename, char const *pIncrementalFilename ) = 0; + + // Prepare to light. You must call Init once, but then you can + // do as many Prepare/AddLight/Finalize phases as you want. + virtual bool PrepareForLighting() = 0; + + // Called every time light is added to a face. + // NOTE: This is the ONLY threadsafe function in IIncremental. + virtual void AddLightToFace( + IncrementalLightID lightID, + int iFace, + int iSample, + int lmSize, + float dot, + int iThread ) = 0; + + // Called when it's done applying light from the specified light to the specified face. + virtual void FinishFace ( + IncrementalLightID lightID, + int iFace, + int iThread ) = 0; + + // For each face that was changed during the lighting process, save out + // new data for it in the incremental file. + // Returns false if the incremental lighting isn't active. + virtual bool Finalize() = 0; + + // Grows touched to a size of 'numfaces' and sets each byte to 0 or 1 telling + // if the face's lightmap was updated in Finalize. + virtual void GetFacesTouched( CUtlVector<unsigned char> &touched ) = 0; + + // This saves the .r0 file and updates the lighting in the BSP file. + virtual bool Serialize() = 0; +}; + + +extern IIncremental* GetIncremental(); + + +#endif // IINCREMENTAL_H diff --git a/utils/vrad/imagepacker.cpp b/utils/vrad/imagepacker.cpp new file mode 100644 index 0000000..f1cb5ed --- /dev/null +++ b/utils/vrad/imagepacker.cpp @@ -0,0 +1,141 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// The copyright to the contents herein is the property of Valve, L.L.C. +// The contents may be used and/or copied only with the written permission of +// Valve, L.L.C., or in accordance with the terms and conditions stipulated in +// the agreement/contract under which the contents have been supplied. +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//============================================================================= + +#include "vrad.h" +#include "imagepacker.h" + + +bool CImagePacker::Reset( int maxLightmapWidth, int maxLightmapHeight ) +{ + int i; + + Assert( maxLightmapWidth <= MAX_MAX_LIGHTMAP_WIDTH ); + + m_MaxLightmapWidth = maxLightmapWidth; + m_MaxLightmapHeight = maxLightmapHeight; + + m_MaxBlockWidth = maxLightmapWidth + 1; + m_MaxBlockHeight = maxLightmapHeight + 1; + + m_AreaUsed = 0; + m_MinimumHeight = -1; + for( i = 0; i < m_MaxLightmapWidth; i++ ) + { + m_pLightmapWavefront[i] = -1; + } + return true; +} + + +inline int CImagePacker::GetMaxYIndex( int firstX, int width ) +{ + int maxY = -1; + int maxYIndex = 0; + for( int x = firstX; x < firstX + width; ++x ) + { + // NOTE: Want the equals here since we'll never be able to fit + // in between the multiple instances of maxY + if( m_pLightmapWavefront[x] >= maxY ) + { + maxY = m_pLightmapWavefront[x]; + maxYIndex = x; + } + } + return maxYIndex; +} + + +bool CImagePacker::AddBlock( int width, int height, int *returnX, int *returnY ) +{ + // If we've already determined that a block this big couldn't fit + // then blow off checking again... + if ( ( width >= m_MaxBlockWidth ) && ( height >= m_MaxBlockHeight ) ) + return false; + + int bestX = -1; + int maxYIdx; + int outerX = 0; + int outerMinY = m_MaxLightmapHeight; + int lastX = m_MaxLightmapWidth - width; + int lastMaxYVal = -2; + while (outerX <= lastX) + { + // Skip all tiles that have the last Y value, these + // aren't going to change our min Y value + if (m_pLightmapWavefront[outerX] == lastMaxYVal) + { + ++outerX; + continue; + } + + maxYIdx = GetMaxYIndex( outerX, width ); + lastMaxYVal = m_pLightmapWavefront[maxYIdx]; + if (outerMinY > lastMaxYVal) + { + outerMinY = lastMaxYVal; + bestX = outerX; + } + outerX = maxYIdx + 1; + } + + if( bestX == -1 ) + { + // If we failed to add it, remember the block size that failed + // *only if both dimensions are smaller*!! + // Just because a 1x10 block failed, doesn't mean a 10x1 block will fail + if ( ( width <= m_MaxBlockWidth ) && ( height <= m_MaxBlockHeight ) ) + { + m_MaxBlockWidth = width; + m_MaxBlockHeight = height; + } + + return false; + } + + // Set the return positions for the block. + *returnX = bestX; + *returnY = outerMinY + 1; + + // Check if it actually fit height-wise. + // hack + // if( *returnY + height > maxLightmapHeight ) + if( *returnY + height >= m_MaxLightmapHeight - 1 ) + { + if ( ( width <= m_MaxBlockWidth ) && ( height <= m_MaxBlockHeight ) ) + { + m_MaxBlockWidth = width; + m_MaxBlockHeight = height; + } + + return false; + } + + // It fit! + // Keep up with the smallest possible size for the image so far. + if( *returnY + height > m_MinimumHeight ) + m_MinimumHeight = *returnY + height; + + // Update the wavefront info. + int x; + for( x = bestX; x < bestX + width; x++ ) + { + m_pLightmapWavefront[x] = outerMinY + height; + } + + // AddBlockToLightmapImage( *returnX, *returnY, width, height ); + m_AreaUsed += width * height; + + return true; +} + diff --git a/utils/vrad/imagepacker.h b/utils/vrad/imagepacker.h new file mode 100644 index 0000000..93a4421 --- /dev/null +++ b/utils/vrad/imagepacker.h @@ -0,0 +1,51 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// The copyright to the contents herein is the property of Valve, L.L.C. +// The contents may be used and/or copied only with the written permission of +// Valve, L.L.C., or in accordance with the terms and conditions stipulated in +// the agreement/contract under which the contents have been supplied. +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// $NoKeywords: $ +//============================================================================= + +#ifndef IMAGEPACKER_H +#define IMAGEPACKER_H + +#ifdef _WIN32 +#pragma once +#endif + +#define MAX_MAX_LIGHTMAP_WIDTH 2048 + + +//----------------------------------------------------------------------------- +// This packs a single lightmap +//----------------------------------------------------------------------------- +class CImagePacker +{ +public: + bool Reset( int maxLightmapWidth, int maxLightmapHeight ); + bool AddBlock( int width, int height, int *returnX, int *returnY ); + +protected: + int GetMaxYIndex( int firstX, int width ); + + int m_MaxLightmapWidth; + int m_MaxLightmapHeight; + int m_pLightmapWavefront[MAX_MAX_LIGHTMAP_WIDTH]; + int m_AreaUsed; + int m_MinimumHeight; + + // For optimization purposes: + // These store the width + height of the first image + // that was unable to be stored in this image + int m_MaxBlockWidth; + int m_MaxBlockHeight; +}; + + +#endif // IMAGEPACKER_H diff --git a/utils/vrad/incremental.cpp b/utils/vrad/incremental.cpp new file mode 100644 index 0000000..ad46d04 --- /dev/null +++ b/utils/vrad/incremental.cpp @@ -0,0 +1,766 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +#include "incremental.h" +#include "lightmap.h" + + + +static bool g_bFileError = false; + + +// -------------------------------------------------------------------------------- // +// Static helpers. +// -------------------------------------------------------------------------------- // + +static bool CompareLights( dworldlight_t *a, dworldlight_t *b ) +{ + static float flEpsilon = 1e-7; + + bool a1 = VectorsAreEqual( a->origin, b->origin, flEpsilon ); + bool a2 = VectorsAreEqual( a->intensity, b->intensity, 1.1f ); // intensities are huge numbers + bool a3 = VectorsAreEqual( a->normal, b->normal, flEpsilon ); + bool a4 = fabs( a->constant_attn - b->constant_attn ) < flEpsilon; + bool a5 = fabs( a->linear_attn - b->linear_attn ) < flEpsilon; + bool a6 = fabs( a->quadratic_attn - b->quadratic_attn ) < flEpsilon; + bool a7 = fabs( float( a->flags - b->flags ) ) < flEpsilon; + bool a8 = fabs( a->stopdot - b->stopdot ) < flEpsilon; + bool a9 = fabs( a->stopdot2 - b->stopdot2 ) < flEpsilon; + bool a10 = fabs( a->exponent - b->exponent ) < flEpsilon; + bool a11 = fabs( a->radius - b->radius ) < flEpsilon; + + return a1 && a2 && a3 && a4 && a5 && a6 && a7 && a8 && a9 && a10 && a11; +} + + +long FileOpen( char const *pFilename, bool bRead ) +{ + g_bFileError = false; + return (long)g_pFileSystem->Open( pFilename, bRead ? "rb" : "wb" ); +} + + +void FileClose( long fp ) +{ + if( fp ) + g_pFileSystem->Close( (FILE*)fp ); +} + + +// Returns true if there was an error reading from the file. +bool FileError() +{ + return g_bFileError; +} + +static inline void FileRead( long fp, void *pOut, int size ) +{ + if( g_bFileError || g_pFileSystem->Read( pOut, size, (FileHandle_t)fp ) != size ) + { + g_bFileError = true; + memset( pOut, 0, size ); + } +} + + +template<class T> +static inline void FileRead( long fp, T &out ) +{ + FileRead( fp, &out, sizeof(out) ); +} + + +static inline void FileWrite( long fp, void const *pData, int size ) +{ + if( g_bFileError || g_pFileSystem->Write( pData, size, (FileHandle_t)fp ) != size ) + { + g_bFileError = true; + } +} + + +template<class T> +static inline void FileWrite( long fp, T out ) +{ + FileWrite( fp, &out, sizeof(out) ); +} + + +IIncremental* GetIncremental() +{ + static CIncremental inc; + return &inc; +} + + +// -------------------------------------------------------------------------------- // +// CIncremental. +// -------------------------------------------------------------------------------- // + +CIncremental::CIncremental() +{ + m_TotalMemory = 0; + m_pIncrementalFilename = NULL; + m_pBSPFilename = NULL; + m_bSuccessfulRun = false; +} + + +CIncremental::~CIncremental() +{ +} + + +bool CIncremental::Init( char const *pBSPFilename, char const *pIncrementalFilename ) +{ + m_pBSPFilename = pBSPFilename; + m_pIncrementalFilename = pIncrementalFilename; + return true; +} + + +bool CIncremental::PrepareForLighting() +{ + if( !m_pBSPFilename ) + return false; + + // Clear the touched faces list. + m_FacesTouched.SetSize( numfaces ); + memset( m_FacesTouched.Base(), 0, numfaces ); + + // If we haven't done a complete successful run yet, then we either haven't + // loaded the lights, or a run was aborted and our lights are half-done so we + // should reload them. + if( !m_bSuccessfulRun ) + LoadIncrementalFile(); + + // unmatched = a list of the lights we have + CUtlLinkedList<int,int> unmatched; + for( int i=m_Lights.Head(); i != m_Lights.InvalidIndex(); i = m_Lights.Next(i) ) + unmatched.AddToTail( i ); + + // Match the light lists and get rid of lights that we already have all the data for. + directlight_t *pNext; + directlight_t **pPrev = &activelights; + for( directlight_t *dl=activelights; dl != NULL; dl = pNext ) + { + pNext = dl->next; + + //float flClosest = 3000000000; + //CIncLight *pClosest = 0; + + // Look for this light in our light list. + int iNextUnmatched, iUnmatched; + for( iUnmatched=unmatched.Head(); iUnmatched != unmatched.InvalidIndex(); iUnmatched = iNextUnmatched ) + { + iNextUnmatched = unmatched.Next( iUnmatched ); + + CIncLight *pLight = m_Lights[ unmatched[iUnmatched] ]; + + //float flTest = (pLight->m_Light.origin - dl->light.origin).Length(); + //if( flTest < flClosest ) + //{ + // flClosest = flTest; + // pClosest = pLight; + //} + + if( CompareLights( &dl->light, &pLight->m_Light ) ) + { + unmatched.Remove( iUnmatched ); + + // Ok, we have this light's data already, yay! + // Get rid of it from the active light list. + *pPrev = dl->next; + free( dl ); + dl = 0; + break; + } + } + + //bool bTest=false; + //if(bTest) + // CompareLights( &dl->light, &pClosest->m_Light ); + + if( iUnmatched == unmatched.InvalidIndex() ) + pPrev = &dl->next; + } + + // Remove any of our lights that were unmatched. + for( int iUnmatched=unmatched.Head(); iUnmatched != unmatched.InvalidIndex(); iUnmatched = unmatched.Next( iUnmatched ) ) + { + CIncLight *pLight = m_Lights[ unmatched[iUnmatched] ]; + + // First tag faces that it touched so they get recomposited. + for( unsigned short iFace=pLight->m_LightFaces.Head(); iFace != pLight->m_LightFaces.InvalidIndex(); iFace = pLight->m_LightFaces.Next( iFace ) ) + { + m_FacesTouched[ pLight->m_LightFaces[iFace]->m_FaceIndex ] = 1; + } + + delete pLight; + m_Lights.Remove( unmatched[iUnmatched] ); + } + + // Now add a light structure for each new light. + AddLightsForActiveLights(); + + return true; +} + + +bool CIncremental::ReadIncrementalHeader( long fp, CIncrementalHeader *pHeader ) +{ + int version; + FileRead( fp, version ); + if( version != INCREMENTALFILE_VERSION ) + return false; + + int nFaces; + FileRead( fp, nFaces ); + + pHeader->m_FaceLightmapSizes.SetSize( nFaces ); + FileRead( fp, pHeader->m_FaceLightmapSizes.Base(), sizeof(CIncrementalHeader::CLMSize) * nFaces ); + + return !FileError(); +} + + +bool CIncremental::WriteIncrementalHeader( long fp ) +{ + int version = INCREMENTALFILE_VERSION; + FileWrite( fp, version ); + + int nFaces = numfaces; + FileWrite( fp, nFaces ); + + CIncrementalHeader hdr; + hdr.m_FaceLightmapSizes.SetSize( nFaces ); + + for( int i=0; i < nFaces; i++ ) + { + hdr.m_FaceLightmapSizes[i].m_Width = g_pFaces[i].m_LightmapTextureSizeInLuxels[0]; + hdr.m_FaceLightmapSizes[i].m_Height = g_pFaces[i].m_LightmapTextureSizeInLuxels[1]; + } + + FileWrite( fp, hdr.m_FaceLightmapSizes.Base(), sizeof(CIncrementalHeader::CLMSize) * nFaces ); + + return !FileError(); +} + + +bool CIncremental::IsIncrementalFileValid() +{ + long fp = FileOpen( m_pIncrementalFilename, true ); + if( !fp ) + return false; + + bool bValid = false; + CIncrementalHeader hdr; + if( ReadIncrementalHeader( fp, &hdr ) ) + { + // If the number of faces is the same and their lightmap sizes are the same, + // then this file is considered a legitimate incremental file. + if( hdr.m_FaceLightmapSizes.Count() == numfaces ) + { + int i; + for( i=0; i < numfaces; i++ ) + { + if( hdr.m_FaceLightmapSizes[i].m_Width != g_pFaces[i].m_LightmapTextureSizeInLuxels[0] || + hdr.m_FaceLightmapSizes[i].m_Height != g_pFaces[i].m_LightmapTextureSizeInLuxels[1] ) + { + break; + } + } + + // Were all faces valid? + if( i == numfaces ) + bValid = true; + } + } + + FileClose( fp ); + return bValid && !FileError(); +} + + +void CIncremental::AddLightToFace( + IncrementalLightID lightID, + int iFace, + int iSample, + int lmSize, + float dot, + int iThread ) +{ + // If we're not being used, don't do anything. + if( !m_pIncrementalFilename ) + return; + + CIncLight *pLight = m_Lights[lightID]; + + // Check for the 99.99% case in which the face already exists. + CLightFace *pFace; + if( pLight->m_pCachedFaces[iThread] && + pLight->m_pCachedFaces[iThread]->m_FaceIndex == iFace ) + { + pFace = pLight->m_pCachedFaces[iThread]; + } + else + { + bool bNew; + + EnterCriticalSection( &pLight->m_CS ); + pFace = pLight->FindOrCreateLightFace( iFace, lmSize, &bNew ); + LeaveCriticalSection( &pLight->m_CS ); + + pLight->m_pCachedFaces[iThread] = pFace; + + if( bNew ) + m_TotalMemory += pFace->m_LightValues.Count() * sizeof( pFace->m_LightValues[0] ); + } + + // Add this into the light's data. + pFace->m_LightValues[iSample].m_Dot = dot; +} + + +unsigned short DecodeCharOrShort( CUtlBuffer *pIn ) +{ + unsigned short val = pIn->GetUnsignedChar(); + if( val & 0x80 ) + { + val = ((val & 0x7F) << 8) | pIn->GetUnsignedChar(); + } + + return val; +} + + +void EncodeCharOrShort( CUtlBuffer *pBuf, unsigned short val ) +{ + if( (val & 0xFF80) == 0 ) + { + pBuf->PutUnsignedChar( (unsigned char)val ); + } + else + { + if( val > 32767 ) + val = 32767; + + pBuf->PutUnsignedChar( (val >> 8) | 0x80 ); + pBuf->PutUnsignedChar( val & 0xFF ); + } +} + + +void DecompressLightData( CUtlBuffer *pIn, CUtlVector<CLightValue> *pOut ) +{ + int iOut = 0; + while( pIn->TellGet() < pIn->TellPut() ) + { + unsigned char runLength = pIn->GetUnsignedChar(); + unsigned short usVal = DecodeCharOrShort( pIn ); + + while( runLength > 0 ) + { + --runLength; + + pOut->Element(iOut).m_Dot = usVal; + ++iOut; + } + } +} + +#ifdef _WIN32 +#pragma warning (disable:4701) +#endif + +void CompressLightData( + CLightValue const *pValues, + int nValues, + CUtlBuffer *pBuf ) +{ + unsigned char runLength=0; + unsigned short flLastValue; + + for( int i=0; i < nValues; i++ ) + { + unsigned short flCurValue = (unsigned short)pValues[i].m_Dot; + + if( i == 0 ) + { + flLastValue = flCurValue; + runLength = 1; + } + else if( flCurValue == flLastValue && runLength < 255 ) + { + ++runLength; + } + else + { + pBuf->PutUnsignedChar( runLength ); + EncodeCharOrShort( pBuf, flLastValue ); + + flLastValue = flCurValue; + runLength = 1; + } + } + + // Write the end.. + if( runLength ) + { + pBuf->PutUnsignedChar( runLength ); + EncodeCharOrShort( pBuf, flLastValue ); + } +} + +#ifdef _WIN32 +#pragma warning (default:4701) +#endif + +void MultiplyValues( CUtlVector<CLightValue> &values, float scale ) +{ + for( int i=0; i < values.Count(); i++ ) + values[i].m_Dot *= scale; +} + + +void CIncremental::FinishFace( + IncrementalLightID lightID, + int iFace, + int iThread ) +{ + CIncLight *pLight = m_Lights[lightID]; + + // Check for the 99.99% case in which the face already exists. + CLightFace *pFace; + if( pLight->m_pCachedFaces[iThread] && pLight->m_pCachedFaces[iThread]->m_FaceIndex == iFace ) + { + pFace = pLight->m_pCachedFaces[iThread]; + + // Compress the data. + MultiplyValues( pFace->m_LightValues, pLight->m_flMaxIntensity ); + + pFace->m_CompressedData.SeekPut( CUtlBuffer::SEEK_HEAD, 0 ); + CompressLightData( + pFace->m_LightValues.Base(), + pFace->m_LightValues.Count(), + &pFace->m_CompressedData ); + +#if 0 + // test decompression + CUtlVector<CLightValue> test; + test.SetSize( 2048 ); + pFace->m_CompressedData.SeekGet( CUtlBuffer::SEEK_HEAD, 0 ); + DecompressLightData( &pFace->m_CompressedData, &test ); +#endif + + if( pFace->m_CompressedData.TellPut() == 0 ) + { + // No contribution.. delete this face from the light. + EnterCriticalSection( &pLight->m_CS ); + pLight->m_LightFaces.Remove( pFace->m_LightFacesIndex ); + delete pFace; + LeaveCriticalSection( &pLight->m_CS ); + } + else + { + // Discard the uncompressed data. + pFace->m_LightValues.Purge(); + m_FacesTouched[ pFace->m_FaceIndex ] = 1; + } + } +} + + +bool CIncremental::Finalize() +{ + // If we're not being used, don't do anything. + if( !m_pIncrementalFilename || !m_pBSPFilename ) + return false; + + CUtlVector<CFaceLightList> faceLights; + LinkLightsToFaces( faceLights ); + + Vector faceLight[(MAX_LIGHTMAP_DIM_WITHOUT_BORDER+2) * (MAX_LIGHTMAP_DIM_WITHOUT_BORDER+2)]; + CUtlVector<CLightValue> faceLightValues; + faceLightValues.SetSize( (MAX_LIGHTMAP_DIM_WITHOUT_BORDER+2) * (MAX_LIGHTMAP_DIM_WITHOUT_BORDER+2) ); + + // Only update the faces we've touched. + for( int facenum = 0; facenum < numfaces; facenum++ ) + { + if( !m_FacesTouched[facenum] || !faceLights[facenum].Count() ) + continue; + + int w = g_pFaces[facenum].m_LightmapTextureSizeInLuxels[0]+1; + int h = g_pFaces[facenum].m_LightmapTextureSizeInLuxels[1]+1; + int nLuxels = w * h; + assert( nLuxels <= sizeof(faceLight) / sizeof(faceLight[0]) ); + + // Clear the lighting for this face. + memset( faceLight, 0, nLuxels * sizeof(Vector) ); + + // Composite all the light contributions. + for( int iFace=0; iFace < faceLights[facenum].Count(); iFace++ ) + { + CLightFace *pFace = faceLights[facenum][iFace]; + + pFace->m_CompressedData.SeekGet( CUtlBuffer::SEEK_HEAD, 0 ); + DecompressLightData( &pFace->m_CompressedData, &faceLightValues ); + + for( int iSample=0; iSample < nLuxels; iSample++ ) + { + float flDot = faceLightValues[iSample].m_Dot; + if( flDot ) + { + VectorMA( + faceLight[iSample], + flDot / pFace->m_pLight->m_flMaxIntensity, + pFace->m_pLight->m_Light.intensity, + faceLight[iSample] ); + } + } + } + + // Convert to the floating-point representation in the BSP file. + Vector *pSrc = faceLight; + unsigned char *pDest = &(*pdlightdata)[ g_pFaces[facenum].lightofs ]; + + for( int iSample=0; iSample < nLuxels; iSample++ ) + { + VectorToColorRGBExp32( *pSrc, *( ColorRGBExp32 *)pDest ); + pDest += 4; + pSrc++; + } + } + + m_bSuccessfulRun = true; + return true; +} + + +void CIncremental::GetFacesTouched( CUtlVector<unsigned char> &touched ) +{ + touched.CopyArray( m_FacesTouched.Base(), m_FacesTouched.Count() ); +} + + +bool CIncremental::Serialize() +{ + if( !SaveIncrementalFile() ) + return false; + + WriteBSPFile( (char*)m_pBSPFilename ); + return true; +} + + +void CIncremental::Term() +{ + m_Lights.PurgeAndDeleteElements(); + m_TotalMemory = 0; +} + + +void CIncremental::AddLightsForActiveLights() +{ + // Create our lights. + for( directlight_t *dl=activelights; dl != NULL; dl = dl->next ) + { + CIncLight *pLight = new CIncLight; + dl->m_IncrementalID = m_Lights.AddToTail( pLight ); + + // Copy the light information. + pLight->m_Light = dl->light; + pLight->m_flMaxIntensity = max( dl->light.intensity[0], max( dl->light.intensity[1], dl->light.intensity[2] ) ); + } +} + + +bool CIncremental::LoadIncrementalFile() +{ + Term(); + + if( !IsIncrementalFileValid() ) + return false; + + long fp = FileOpen( m_pIncrementalFilename, true ); + if( !fp ) + return false; + + // Read the header. + CIncrementalHeader hdr; + if( !ReadIncrementalHeader( fp, &hdr ) ) + { + FileClose( fp ); + return false; + } + + + // Read the lights. + int nLights; + FileRead( fp, nLights ); + for( int iLight=0; iLight < nLights; iLight++ ) + { + CIncLight *pLight = new CIncLight; + m_Lights.AddToTail( pLight ); + + FileRead( fp, pLight->m_Light ); + pLight->m_flMaxIntensity = + max( pLight->m_Light.intensity.x, + max( pLight->m_Light.intensity.y, pLight->m_Light.intensity.z ) ); + + int nFaces; + FileRead( fp, nFaces ); + assert( nFaces < 70000 ); + + for( int iFace=0; iFace < nFaces; iFace++ ) + { + CLightFace *pFace = new CLightFace; + pLight->m_LightFaces.AddToTail( pFace ); + + pFace->m_pLight = pLight; + FileRead( fp, pFace->m_FaceIndex ); + + int dataSize; + FileRead( fp, dataSize ); + + pFace->m_CompressedData.SeekPut( CUtlBuffer::SEEK_HEAD, 0 ); + while( dataSize ) + { + --dataSize; + + unsigned char ucData; + FileRead( fp, ucData ); + + pFace->m_CompressedData.PutUnsignedChar( ucData ); + } + } + } + + + FileClose( fp ); + return !FileError(); +} + + +bool CIncremental::SaveIncrementalFile() +{ + long fp = FileOpen( m_pIncrementalFilename, false ); + if( !fp ) + return false; + + if( !WriteIncrementalHeader( fp ) ) + { + FileClose( fp ); + return false; + } + + // Write the lights. + int nLights = m_Lights.Count(); + FileWrite( fp, nLights ); + for( int iLight=m_Lights.Head(); iLight != m_Lights.InvalidIndex(); iLight = m_Lights.Next( iLight ) ) + { + CIncLight *pLight = m_Lights[iLight]; + + FileWrite( fp, pLight->m_Light ); + + int nFaces = pLight->m_LightFaces.Count(); + FileWrite( fp, nFaces ); + for( int iFace=pLight->m_LightFaces.Head(); iFace != pLight->m_LightFaces.InvalidIndex(); iFace = pLight->m_LightFaces.Next( iFace ) ) + { + CLightFace *pFace = pLight->m_LightFaces[iFace]; + + FileWrite( fp, pFace->m_FaceIndex ); + + int dataSize = pFace->m_CompressedData.TellPut(); + FileWrite( fp, dataSize ); + + pFace->m_CompressedData.SeekGet( CUtlBuffer::SEEK_HEAD, 0 ); + while( dataSize ) + { + --dataSize; + FileWrite( fp, pFace->m_CompressedData.GetUnsignedChar() ); + } + } + } + + + FileClose( fp ); + return !FileError(); +} + + +void CIncremental::LinkLightsToFaces( CUtlVector<CFaceLightList> &faceLights ) +{ + faceLights.SetSize( numfaces ); + + for( int iLight=m_Lights.Head(); iLight != m_Lights.InvalidIndex(); iLight = m_Lights.Next( iLight ) ) + { + CIncLight *pLight = m_Lights[iLight]; + + for( int iFace=pLight->m_LightFaces.Head(); iFace != pLight->m_LightFaces.InvalidIndex(); iFace = pLight->m_LightFaces.Next( iFace ) ) + { + CLightFace *pFace = pLight->m_LightFaces[iFace]; + + if( m_FacesTouched[pFace->m_FaceIndex] ) + faceLights[ pFace->m_FaceIndex ].AddToTail( pFace ); + } + } +} + + +// ------------------------------------------------------------------ // +// CIncLight +// ------------------------------------------------------------------ // + +CIncLight::CIncLight() +{ + memset( m_pCachedFaces, 0, sizeof(m_pCachedFaces) ); + InitializeCriticalSection( &m_CS ); +} + + +CIncLight::~CIncLight() +{ + m_LightFaces.PurgeAndDeleteElements(); + DeleteCriticalSection( &m_CS ); +} + + +CLightFace* CIncLight::FindOrCreateLightFace( int iFace, int lmSize, bool *bNew ) +{ + if( bNew ) + *bNew = false; + + + // Look for it. + for( int i=m_LightFaces.Head(); i != m_LightFaces.InvalidIndex(); i=m_LightFaces.Next(i) ) + { + CLightFace *pFace = m_LightFaces[i]; + + if( pFace->m_FaceIndex == iFace ) + { + assert( pFace->m_LightValues.Count() == lmSize ); + return pFace; + } + } + + // Ok, create one. + CLightFace *pFace = new CLightFace; + pFace->m_LightFacesIndex = m_LightFaces.AddToTail( pFace ); + pFace->m_pLight = this; + + pFace->m_FaceIndex = iFace; + pFace->m_LightValues.SetSize( lmSize ); + memset( pFace->m_LightValues.Base(), 0, sizeof( CLightValue ) * lmSize ); + + if( bNew ) + *bNew = true; + + return pFace; +} + + diff --git a/utils/vrad/incremental.h b/utils/vrad/incremental.h new file mode 100644 index 0000000..9e98054 --- /dev/null +++ b/utils/vrad/incremental.h @@ -0,0 +1,175 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef INCREMENTAL_H +#define INCREMENTAL_H +#ifdef _WIN32 +#pragma once +#endif + + + +#include "iincremental.h" +#include "utllinkedlist.h" +#include "utlvector.h" +#include "utlbuffer.h" +#include "vrad.h" + + +#define INCREMENTALFILE_VERSION 31241 + + +class CIncLight; + + +class CLightValue +{ +public: + float m_Dot; +}; + + +class CLightFace +{ +public: + unsigned short m_FaceIndex; // global face index + unsigned short m_LightFacesIndex; // index into CIncLight::m_LightFaces. + + // The lightmap grid for this face. Only used while building lighting data for a face. + // Compressed into m_CompressedData immediately afterwards. + CUtlVector<CLightValue> m_LightValues; + + CUtlBuffer m_CompressedData; + CIncLight *m_pLight; +}; + + +class CIncLight +{ +public: + CIncLight(); + ~CIncLight(); + + CLightFace* FindOrCreateLightFace( int iFace, int lmSize, bool *bNew=NULL ); + + +public: + + CRITICAL_SECTION m_CS; + + // This is the light for which m_LightFaces was built. + dworldlight_t m_Light; + + CLightFace *m_pCachedFaces[MAX_TOOL_THREADS+1]; + + // The list of faces that this light contributes to. + CUtlLinkedList<CLightFace*, unsigned short> m_LightFaces; + + // Largest value in intensity of light. Used to scale dot products up into a + // range where their values make sense. + float m_flMaxIntensity; +}; + + +class CIncrementalHeader +{ +public: + class CLMSize + { + public: + unsigned char m_Width; + unsigned char m_Height; + }; + + CUtlVector<CLMSize> m_FaceLightmapSizes; +}; + + +class CIncremental : public IIncremental +{ +public: + + CIncremental(); + ~CIncremental(); + + + +// IIncremental overrides. +public: + + virtual bool Init( char const *pBSPFilename, char const *pIncrementalFilename ); + + // Load the light definitions out of the incremental file. + // Figure out which lights have changed. + // Change 'activelights' to only consist of new or changed lights. + virtual bool PrepareForLighting(); + + virtual void AddLightToFace( + IncrementalLightID lightID, + int iFace, + int iSample, + int lmSize, + float dot, + int iThread ); + + virtual void FinishFace( + IncrementalLightID lightID, + int iFace, + int iThread ); + + // For each face that was changed during the lighting process, save out + // new data for it in the incremental file. + virtual bool Finalize(); + + virtual void GetFacesTouched( CUtlVector<unsigned char> &touched ); + + virtual bool Serialize(); + + +private: + + // Read/write the header from the file. + bool ReadIncrementalHeader( long fp, CIncrementalHeader *pHeader ); + bool WriteIncrementalHeader( long fp ); + + // Returns true if the incremental file is valid and we can use InitUpdate. + bool IsIncrementalFileValid(); + + void Term(); + + // For each light in 'activelights', add a light to m_Lights and link them together. + void AddLightsForActiveLights(); + + // Load and save the state. + bool LoadIncrementalFile(); + bool SaveIncrementalFile(); + + typedef CUtlVector<CLightFace*> CFaceLightList; + void LinkLightsToFaces( CUtlVector<CFaceLightList> &faceLights ); + + +private: + + char const *m_pIncrementalFilename; + char const *m_pBSPFilename; + + CUtlLinkedList<CIncLight*, IncrementalLightID> + m_Lights; + + // The face index is set to 1 if a face has new lighting data applied to it. + // This is used to optimize the set of lightmaps we recomposite. + CUtlVector<unsigned char> m_FacesTouched; + + int m_TotalMemory; + + // Set to true when one or more runs were completed successfully. + bool m_bSuccessfulRun; +}; + + + +#endif // INCREMENTAL_H diff --git a/utils/vrad/leaf_ambient_lighting.cpp b/utils/vrad/leaf_ambient_lighting.cpp new file mode 100644 index 0000000..3836592 --- /dev/null +++ b/utils/vrad/leaf_ambient_lighting.cpp @@ -0,0 +1,708 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "vrad.h" +#include "leaf_ambient_lighting.h" +#include "bsplib.h" +#include "vraddetailprops.h" +#include "mathlib/anorms.h" +#include "pacifier.h" +#include "coordsize.h" +#include "vstdlib/random.h" +#include "bsptreedata.h" +#include "messbuf.h" +#include "vmpi.h" +#include "vmpi_distribute_work.h" + +static TableVector g_BoxDirections[6] = +{ + { 1, 0, 0 }, + { -1, 0, 0 }, + { 0, 1, 0 }, + { 0, -1, 0 }, + { 0, 0, 1 }, + { 0, 0, -1 }, +}; + + + +static void ComputeAmbientFromSurface( dface_t *surfID, dworldlight_t* pSkylight, + Vector& radcolor ) +{ + if ( !surfID ) + return; + + texinfo_t *pTexInfo = &texinfo[surfID->texinfo]; + + // If we hit the sky, use the sky ambient + if ( pTexInfo->flags & SURF_SKY ) + { + if ( pSkylight ) + { + // add in sky ambient + VectorCopy( pSkylight->intensity, radcolor ); + } + } + else + { + Vector reflectivity = dtexdata[pTexInfo->texdata].reflectivity; + VectorMultiply( radcolor, reflectivity, radcolor ); + } +} + + +// TODO: it's CRAZY how much lighting code we share with the engine. It should all be shared code. +float Engine_WorldLightAngle( const dworldlight_t *wl, const Vector& lnormal, const Vector& snormal, const Vector& delta ) +{ + float dot, dot2; + + Assert( wl->type == emit_surface ); + + dot = DotProduct( snormal, delta ); + if (dot < 0) + return 0; + + dot2 = -DotProduct (delta, lnormal); + if (dot2 <= ON_EPSILON/10) + return 0; // behind light surface + + return dot * dot2; +} + + +// TODO: it's CRAZY how much lighting code we share with the engine. It should all be shared code. +float Engine_WorldLightDistanceFalloff( const dworldlight_t *wl, const Vector& delta ) +{ + Assert( wl->type == emit_surface ); + + // Cull out stuff that's too far + if (wl->radius != 0) + { + if ( DotProduct( delta, delta ) > (wl->radius * wl->radius)) + return 0.0f; + } + + return InvRSquared(delta); +} + + +void AddEmitSurfaceLights( const Vector &vStart, Vector lightBoxColor[6] ) +{ + fltx4 fractionVisible; + + FourVectors vStart4, wlOrigin4; + vStart4.DuplicateVector ( vStart ); + + for ( int iLight=0; iLight < *pNumworldlights; iLight++ ) + { + dworldlight_t *wl = &dworldlights[iLight]; + + // Should this light even go in the ambient cubes? + if ( !( wl->flags & DWL_FLAGS_INAMBIENTCUBE ) ) + continue; + + Assert( wl->type == emit_surface ); + + // Can this light see the point? + wlOrigin4.DuplicateVector ( wl->origin ); + TestLine ( vStart4, wlOrigin4, &fractionVisible ); + if ( !TestSignSIMD ( CmpGtSIMD ( fractionVisible, Four_Zeros ) ) ) + continue; + + // Add this light's contribution. + Vector vDelta = wl->origin - vStart; + float flDistanceScale = Engine_WorldLightDistanceFalloff( wl, vDelta ); + + Vector vDeltaNorm = vDelta; + VectorNormalize( vDeltaNorm ); + float flAngleScale = Engine_WorldLightAngle( wl, wl->normal, vDeltaNorm, vDeltaNorm ); + + float ratio = flDistanceScale * flAngleScale * SubFloat ( fractionVisible, 0 ); + if ( ratio == 0 ) + continue; + + for ( int i=0; i < 6; i++ ) + { + float t = DotProduct( g_BoxDirections[i], vDeltaNorm ); + if ( t > 0 ) + { + lightBoxColor[i] += wl->intensity * (t * ratio); + } + } + } +} + + +void ComputeAmbientFromSphericalSamples( int iThread, const Vector &vStart, Vector lightBoxColor[6] ) +{ + // Figure out the color that rays hit when shot out from this position. + Vector radcolor[NUMVERTEXNORMALS]; + float tanTheta = tan(VERTEXNORMAL_CONE_INNER_ANGLE); + + for ( int i = 0; i < NUMVERTEXNORMALS; i++ ) + { + Vector vEnd = vStart + g_anorms[i] * (COORD_EXTENT * 1.74); + + // Now that we've got a ray, see what surface we've hit + Vector lightStyleColors[MAX_LIGHTSTYLES]; + lightStyleColors[0].Init(); // We only care about light style 0 here. + CalcRayAmbientLighting( iThread, vStart, vEnd, tanTheta, lightStyleColors ); + + radcolor[i] = lightStyleColors[0]; + } + + // accumulate samples into radiant box + for ( int j = 6; --j >= 0; ) + { + float t = 0; + + lightBoxColor[j].Init(); + + for (int i = 0; i < NUMVERTEXNORMALS; i++) + { + float c = DotProduct( g_anorms[i], g_BoxDirections[j] ); + if (c > 0) + { + t += c; + lightBoxColor[j] += radcolor[i] * c; + } + } + + lightBoxColor[j] *= 1/t; + } + + // Now add direct light from the emit_surface lights. These go in the ambient cube because + // there are a ton of them and they are often so dim that they get filtered out by r_worldlightmin. + AddEmitSurfaceLights( vStart, lightBoxColor ); +} + + +bool IsLeafAmbientSurfaceLight( dworldlight_t *wl ) +{ + static const float g_flWorldLightMinEmitSurface = 0.005f; + static const float g_flWorldLightMinEmitSurfaceDistanceRatio = ( InvRSquared( Vector( 0, 0, 512 ) ) ); + + if ( wl->type != emit_surface ) + return false; + + if ( wl->style != 0 ) + return false; + + float intensity = max( wl->intensity[0], wl->intensity[1] ); + intensity = max( intensity, wl->intensity[2] ); + + return (intensity * g_flWorldLightMinEmitSurfaceDistanceRatio) < g_flWorldLightMinEmitSurface; +} + + +class CLeafSampler +{ +public: + CLeafSampler( int iThread ) : m_iThread(iThread) {} + + // Generate a random point in the leaf's bounding volume + // reject any points that aren't actually in the leaf + // do a couple of tracing heuristics to eliminate points that are inside detail brushes + // or underneath displacement surfaces in the leaf + // return once we have a valid point, use the center if one can't be computed quickly + void GenerateLeafSamplePosition( int leafIndex, const CUtlVector<dplane_t> &leafPlanes, Vector &samplePosition ) + { + dleaf_t *pLeaf = dleafs + leafIndex; + + float dx = pLeaf->maxs[0] - pLeaf->mins[0]; + float dy = pLeaf->maxs[1] - pLeaf->mins[1]; + float dz = pLeaf->maxs[2] - pLeaf->mins[2]; + bool bValid = false; + for ( int i = 0; i < 1000 && !bValid; i++ ) + { + samplePosition.x = pLeaf->mins[0] + m_random.RandomFloat(0, dx); + samplePosition.y = pLeaf->mins[1] + m_random.RandomFloat(0, dy); + samplePosition.z = pLeaf->mins[2] + m_random.RandomFloat(0, dz); + bValid = true; + + for ( int j = leafPlanes.Count(); --j >= 0 && bValid; ) + { + float d = DotProduct(leafPlanes[j].normal, samplePosition) - leafPlanes[j].dist; + if ( d < DIST_EPSILON ) + { + // not inside the leaf, try again + bValid = false; + break; + } + } + if ( !bValid ) + continue; + + for ( int j = 0; j < 6; j++ ) + { + Vector start = samplePosition; + int axis = j%3; + start[axis] = (j<3) ? pLeaf->mins[axis] : pLeaf->maxs[axis]; + float t; + Vector normal; + CastRayInLeaf( m_iThread, samplePosition, start, leafIndex, &t, &normal ); + if ( t == 0.0f ) + { + // inside a func_detail, try again. + bValid = false; + break; + } + if ( t != 1.0f ) + { + Vector delta = start - samplePosition; + if ( DotProduct(delta, normal) > 0 ) + { + // hit backside of displacement, try again. + bValid = false; + break; + } + } + } + } + if ( !bValid ) + { + // didn't generate a valid sample point, just use the center of the leaf bbox + samplePosition = ( Vector( pLeaf->mins[0], pLeaf->mins[1], pLeaf->mins[2] ) + Vector( pLeaf->maxs[0], pLeaf->maxs[1], pLeaf->maxs[2] ) ) * 0.5f; + } + } + +private: + int m_iThread; + CUniformRandomStream m_random; +}; + +// gets a list of the planes pointing into a leaf +void GetLeafBoundaryPlanes( CUtlVector<dplane_t> &list, int leafIndex ) +{ + list.RemoveAll(); + int nodeIndex = leafparents[leafIndex]; + int child = -(leafIndex + 1); + while ( nodeIndex >= 0 ) + { + dnode_t *pNode = dnodes + nodeIndex; + dplane_t *pNodePlane = dplanes + pNode->planenum; + if ( pNode->children[0] == child ) + { + // front side + list.AddToTail( *pNodePlane ); + } + else + { + // back side + int plane = list.AddToTail(); + list[plane].dist = -pNodePlane->dist; + list[plane].normal = -pNodePlane->normal; + list[plane].type = pNodePlane->type; + } + child = nodeIndex; + nodeIndex = nodeparents[child]; + } +} + +// this stores each sample of the ambient lighting +struct ambientsample_t +{ + Vector pos; + Vector cube[6]; +}; + +// add the sample to the list. If we exceed the maximum number of samples, the worst sample will +// be discarded. This has the effect of converging on the best samples when enough are added. +void AddSampleToList( CUtlVector<ambientsample_t> &list, const Vector &samplePosition, Vector *pCube ) +{ + const int MAX_SAMPLES = 16; + + int index = list.AddToTail(); + list[index].pos = samplePosition; + for ( int i = 0; i < 6; i++ ) + { + list[index].cube[i] = pCube[i]; + } + + if ( list.Count() <= MAX_SAMPLES ) + return; + + int nearestNeighborIndex = 0; + float nearestNeighborDist = FLT_MAX; + float nearestNeighborTotal = 0; + for ( int i = 0; i < list.Count(); i++ ) + { + int closestIndex = 0; + float closestDist = FLT_MAX; + float totalDC = 0; + for ( int j = 0; j < list.Count(); j++ ) + { + if ( j == i ) + continue; + float dist = (list[i].pos - list[j].pos).Length(); + float maxDC = 0; + for ( int k = 0; k < 6; k++ ) + { + // color delta is computed per-component, per cube side + for (int s = 0; s < 3; s++ ) + { + float dc = fabs(list[i].cube[k][s] - list[j].cube[k][s]); + maxDC = max(maxDC,dc); + } + totalDC += maxDC; + } + // need a measurable difference in color or we'll just rely on position + if ( maxDC < 1e-4f ) + { + maxDC = 0; + } + else if ( maxDC > 1.0f ) + { + maxDC = 1.0f; + } + // selection criteria is 10% distance, 90% color difference + // choose samples that fill the space (large distance from each other) + // and have largest color variation + float distanceFactor = 0.1f + (maxDC * 0.9f); + dist *= distanceFactor; + + // find the "closest" sample to this one + if ( dist < closestDist ) + { + closestDist = dist; + closestIndex = j; + } + } + // the sample with the "closest" neighbor is rejected + if ( closestDist < nearestNeighborDist || (closestDist == nearestNeighborDist && totalDC < nearestNeighborTotal) ) + { + nearestNeighborDist = closestDist; + nearestNeighborIndex = i; + } + } + list.FastRemove( nearestNeighborIndex ); +} + +// max number of units in gamma space of per-side delta +int CubeDeltaGammaSpace( Vector *pCube0, Vector *pCube1 ) +{ + int maxDelta = 0; + // do this comparison in gamma space to try and get a perceptual basis for the compare + for ( int i = 0; i < 6; i++ ) + { + for ( int j = 0; j < 3; j++ ) + { + int val0 = LinearToScreenGamma( pCube0[i][j] ); + int val1 = LinearToScreenGamma( pCube1[i][j] ); + int delta = abs(val0-val1); + if ( delta > maxDelta ) + maxDelta = delta; + } + } + return maxDelta; +} +// reconstruct the ambient lighting for a leaf at the given position in worldspace +// optionally skip one of the entries in the list +void Mod_LeafAmbientColorAtPos( Vector *pOut, const Vector &pos, const CUtlVector<ambientsample_t> &list, int skipIndex ) +{ + for ( int i = 0; i < 6; i++ ) + { + pOut[i].Init(); + } + float totalFactor = 0; + for ( int i = 0; i < list.Count(); i++ ) + { + if ( i == skipIndex ) + continue; + // do an inverse squared distance weighted average of the samples to reconstruct + // the original function + float dist = (list[i].pos - pos).LengthSqr(); + float factor = 1.0f / (dist + 1.0f); + totalFactor += factor; + for ( int j = 0; j < 6; j++ ) + { + pOut[j] += list[i].cube[j] * factor; + } + } + for ( int i = 0; i < 6; i++ ) + { + pOut[i] *= (1.0f / totalFactor); + } +} + +// this samples the lighting at each sample and removes any unnecessary samples +void CompressAmbientSampleList( CUtlVector<ambientsample_t> &list ) +{ + Vector testCube[6]; + for ( int i = 0; i < list.Count(); i++ ) + { + if ( list.Count() > 1 ) + { + Mod_LeafAmbientColorAtPos( testCube, list[i].pos, list, i ); + if ( CubeDeltaGammaSpace(testCube, list[i].cube) < 3 ) + { + list.FastRemove(i); + i--; + } + } + } +} + +// basically this is an intersection routine that returns a distance between the boxes +float AABBDistance( const Vector &mins0, const Vector &maxs0, const Vector &mins1, const Vector &maxs1 ) +{ + Vector delta; + for ( int i = 0; i < 3; i++ ) + { + float greatestMin = max(mins0[i], mins1[i]); + float leastMax = min(maxs0[i], maxs1[i]); + delta[i] = (greatestMin < leastMax) ? 0 : (leastMax - greatestMin); + } + return delta.Length(); +} + +// build a list of leaves from a query +class CLeafList : public ISpatialLeafEnumerator +{ +public: + virtual bool EnumerateLeaf( int leaf, int context ) + { + m_list.AddToTail(leaf); + return true; + } + + CUtlVector<int> m_list; +}; + +// conver short[3] to vector +static void LeafBounds( int leafIndex, Vector &mins, Vector &maxs ) +{ + for ( int i = 0; i < 3; i++ ) + { + mins[i] = dleafs[leafIndex].mins[i]; + maxs[i] = dleafs[leafIndex].maxs[i]; + } +} + +// returns the index of the nearest leaf with ambient samples +int NearestNeighborWithLight(int leafID) +{ + Vector mins, maxs; + LeafBounds( leafID, mins, maxs ); + Vector size = maxs - mins; + CLeafList leafList; + ToolBSPTree()->EnumerateLeavesInBox( mins-size, maxs+size, &leafList, 0 ); + float bestDist = FLT_MAX; + int bestIndex = leafID; + for ( int i = 0; i < leafList.m_list.Count(); i++ ) + { + int testIndex = leafList.m_list[i]; + if ( !g_pLeafAmbientIndex->Element(testIndex).ambientSampleCount ) + continue; + + Vector testMins, testMaxs; + LeafBounds( testIndex, testMins, testMaxs ); + float dist = AABBDistance( mins, maxs, testMins, testMaxs ); + if ( dist < bestDist ) + { + bestDist = dist; + bestIndex = testIndex; + } + } + return bestIndex; +} + +// maps a float to a byte fraction between min & max +static byte Fixed8Fraction( float t, float tMin, float tMax ) +{ + if ( tMax <= tMin ) + return 0; + + float frac = RemapValClamped( t, tMin, tMax, 0.0f, 255.0f ); + return byte(frac+0.5f); +} + +CUtlVector< CUtlVector<ambientsample_t> > g_LeafAmbientSamples; + +void ComputeAmbientForLeaf( int iThread, int leafID, CUtlVector<ambientsample_t> &list ) +{ + CUtlVector<dplane_t> leafPlanes; + CLeafSampler sampler( iThread ); + + GetLeafBoundaryPlanes( leafPlanes, leafID ); + list.RemoveAll(); + // this heuristic tries to generate at least one sample per volume (chosen to be similar to the size of a player) in the space + int xSize = (dleafs[leafID].maxs[0] - dleafs[leafID].mins[0]) / 32; + int ySize = (dleafs[leafID].maxs[1] - dleafs[leafID].mins[1]) / 32; + int zSize = (dleafs[leafID].maxs[2] - dleafs[leafID].mins[2]) / 64; + xSize = max(xSize,1); + ySize = max(xSize,1); + zSize = max(xSize,1); + // generate update 128 candidate samples, always at least one sample + int volumeCount = xSize * ySize * zSize; + if ( g_bFastAmbient ) + { + // save compute time, only do one sample + volumeCount = 1; + } + int sampleCount = clamp( volumeCount, 1, 128 ); + if ( dleafs[leafID].contents & CONTENTS_SOLID ) + { + // don't generate any samples in solid leaves + // NOTE: We copy the nearest non-solid leaf sample pointers into this leaf at the end + return; + } + Vector cube[6]; + for ( int i = 0; i < sampleCount; i++ ) + { + // compute each candidate sample and add to the list + Vector samplePosition; + sampler.GenerateLeafSamplePosition( leafID, leafPlanes, samplePosition ); + ComputeAmbientFromSphericalSamples( iThread, samplePosition, cube ); + // note this will remove the least valuable sample once the limit is reached + AddSampleToList( list, samplePosition, cube ); + } + + // remove any samples that can be reconstructed with the remaining data + CompressAmbientSampleList( list ); +} + +static void ThreadComputeLeafAmbient( int iThread, void *pUserData ) +{ + CUtlVector<ambientsample_t> list; + while (1) + { + int leafID = GetThreadWork (); + if (leafID == -1) + break; + list.RemoveAll(); + ComputeAmbientForLeaf(iThread, leafID, list); + // copy to the output array + g_LeafAmbientSamples[leafID].SetCount( list.Count() ); + for ( int i = 0; i < list.Count(); i++ ) + { + g_LeafAmbientSamples[leafID].Element(i) = list.Element(i); + } + } +} + +void VMPI_ProcessLeafAmbient( int iThread, uint64 iLeaf, MessageBuffer *pBuf ) +{ + CUtlVector<ambientsample_t> list; + ComputeAmbientForLeaf(iThread, (int)iLeaf, list); + + VMPI_SetCurrentStage( "EncodeLeafAmbientResults" ); + + // Encode the results. + int nSamples = list.Count(); + pBuf->write( &nSamples, sizeof( nSamples ) ); + if ( nSamples ) + { + pBuf->write( list.Base(), list.Count() * sizeof( ambientsample_t ) ); + } +} + +//----------------------------------------------------------------------------- +// Called on the master when a worker finishes processing a static prop. +//----------------------------------------------------------------------------- +void VMPI_ReceiveLeafAmbientResults( uint64 leafID, MessageBuffer *pBuf, int iWorker ) +{ + // Decode the results. + int nSamples; + pBuf->read( &nSamples, sizeof( nSamples ) ); + + g_LeafAmbientSamples[leafID].SetCount( nSamples ); + if ( nSamples ) + { + pBuf->read(g_LeafAmbientSamples[leafID].Base(), nSamples * sizeof(ambientsample_t) ); + } +} + + +void ComputePerLeafAmbientLighting() +{ + // Figure out which lights should go in the per-leaf ambient cubes. + int nInAmbientCube = 0; + int nSurfaceLights = 0; + for ( int i=0; i < *pNumworldlights; i++ ) + { + dworldlight_t *wl = &dworldlights[i]; + + if ( IsLeafAmbientSurfaceLight( wl ) ) + wl->flags |= DWL_FLAGS_INAMBIENTCUBE; + else + wl->flags &= ~DWL_FLAGS_INAMBIENTCUBE; + + if ( wl->type == emit_surface ) + ++nSurfaceLights; + + if ( wl->flags & DWL_FLAGS_INAMBIENTCUBE ) + ++nInAmbientCube; + } + + Msg( "%d of %d (%d%% of) surface lights went in leaf ambient cubes.\n", nInAmbientCube, nSurfaceLights, nSurfaceLights ? ((nInAmbientCube*100) / nSurfaceLights) : 0 ); + + g_LeafAmbientSamples.SetCount(numleafs); + + if ( g_bUseMPI ) + { + // Distribute the work among the workers. + VMPI_SetCurrentStage( "ComputeLeafAmbientLighting" ); + DistributeWork( numleafs, VMPI_DISTRIBUTEWORK_PACKETID, VMPI_ProcessLeafAmbient, VMPI_ReceiveLeafAmbientResults ); + } + else + { + RunThreadsOn(numleafs, true, ThreadComputeLeafAmbient); + } + + // now write out the data + Msg("Writing leaf ambient..."); + g_pLeafAmbientIndex->RemoveAll(); + g_pLeafAmbientLighting->RemoveAll(); + g_pLeafAmbientIndex->SetCount( numleafs ); + g_pLeafAmbientLighting->EnsureCapacity( numleafs*4 ); + for ( int leafID = 0; leafID < numleafs; leafID++ ) + { + const CUtlVector<ambientsample_t> &list = g_LeafAmbientSamples[leafID]; + g_pLeafAmbientIndex->Element(leafID).ambientSampleCount = list.Count(); + if ( !list.Count() ) + { + g_pLeafAmbientIndex->Element(leafID).firstAmbientSample = 0; + } + else + { + g_pLeafAmbientIndex->Element(leafID).firstAmbientSample = g_pLeafAmbientLighting->Count(); + // compute the samples in disk format. Encode the positions in 8-bits using leaf bounds fractions + for ( int i = 0; i < list.Count(); i++ ) + { + int outIndex = g_pLeafAmbientLighting->AddToTail(); + dleafambientlighting_t &light = g_pLeafAmbientLighting->Element(outIndex); + + light.x = Fixed8Fraction( list[i].pos.x, dleafs[leafID].mins[0], dleafs[leafID].maxs[0] ); + light.y = Fixed8Fraction( list[i].pos.y, dleafs[leafID].mins[1], dleafs[leafID].maxs[1] ); + light.z = Fixed8Fraction( list[i].pos.z, dleafs[leafID].mins[2], dleafs[leafID].maxs[2] ); + light.pad = 0; + for ( int side = 0; side < 6; side++ ) + { + VectorToColorRGBExp32( list[i].cube[side], light.cube.m_Color[side] ); + } + } + } + } + for ( int i = 0; i < numleafs; i++ ) + { + // UNDONE: Do this dynamically in the engine instead. This will allow us to sample across leaf + // boundaries always which should improve the quality of lighting in general + if ( g_pLeafAmbientIndex->Element(i).ambientSampleCount == 0 ) + { + if ( !(dleafs[i].contents & CONTENTS_SOLID) ) + { + Msg("Bad leaf ambient for leaf %d\n", i ); + } + + int refLeaf = NearestNeighborWithLight(i); + g_pLeafAmbientIndex->Element(i).ambientSampleCount = 0; + g_pLeafAmbientIndex->Element(i).firstAmbientSample = refLeaf; + } + } + Msg("done\n"); +} + diff --git a/utils/vrad/leaf_ambient_lighting.h b/utils/vrad/leaf_ambient_lighting.h new file mode 100644 index 0000000..6dd9410 --- /dev/null +++ b/utils/vrad/leaf_ambient_lighting.h @@ -0,0 +1,17 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef LEAF_AMBIENT_LIGHTING_H +#define LEAF_AMBIENT_LIGHTING_H +#ifdef _WIN32 +#pragma once +#endif + + +void ComputePerLeafAmbientLighting(); + + +#endif // LEAF_AMBIENT_LIGHTING_H diff --git a/utils/vrad/lightmap.cpp b/utils/vrad/lightmap.cpp new file mode 100644 index 0000000..49360ef --- /dev/null +++ b/utils/vrad/lightmap.cpp @@ -0,0 +1,3598 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#include "vrad.h" +#include "lightmap.h" +#include "radial.h" +#include "mathlib/bumpvects.h" +#include "tier1/utlvector.h" +#include "vmpi.h" +#include "mathlib/anorms.h" +#include "map_utils.h" +#include "mathlib/halton.h" +#include "imagepacker.h" +#include "tier1/utlrbtree.h" +#include "tier1/utlbuffer.h" +#include "bitmap/tgawriter.h" +#include "mathlib/quantize.h" +#include "bitmap/imageformat.h" +#include "coordsize.h" + +enum +{ + AMBIENT_ONLY = 0x1, + NON_AMBIENT_ONLY = 0x2, +}; + +#define SMOOTHING_GROUP_HARD_EDGE 0xff000000 + +//==========================================================================// +// CNormalList. +//==========================================================================// + +// This class keeps a list of unique normals and provides a fast +class CNormalList +{ +public: + CNormalList(); + + // Adds the normal if unique. Otherwise, returns the normal's index into m_Normals. + int FindOrAddNormal( Vector const &vNormal ); + + +public: + + CUtlVector<Vector> m_Normals; + + +private: + + // This represents a grid from (-1,-1,-1) to (1,1,1). + enum {NUM_SUBDIVS = 8}; + CUtlVector<int> m_NormalGrid[NUM_SUBDIVS][NUM_SUBDIVS][NUM_SUBDIVS]; +}; + + +int g_iCurFace; +edgeshare_t edgeshare[MAX_MAP_EDGES]; + +Vector face_centroids[MAX_MAP_EDGES]; + +int vertexref[MAX_MAP_VERTS]; +int *vertexface[MAX_MAP_VERTS]; +faceneighbor_t faceneighbor[MAX_MAP_FACES]; + +static directlight_t *gSkyLight = NULL; +static directlight_t *gAmbient = NULL; + +//==========================================================================// +// CNormalList implementation. +//==========================================================================// + +CNormalList::CNormalList() : m_Normals( 128 ) +{ + for( int i=0; i < sizeof(m_NormalGrid)/sizeof(m_NormalGrid[0][0][0]); i++ ) + { + (&m_NormalGrid[0][0][0] + i)->SetGrowSize( 16 ); + } +} + +int CNormalList::FindOrAddNormal( Vector const &vNormal ) +{ + int gi[3]; + + // See which grid element it's in. + for( int iDim=0; iDim < 3; iDim++ ) + { + gi[iDim] = (int)( ((vNormal[iDim] + 1.0f) * 0.5f) * NUM_SUBDIVS - 0.000001f ); + gi[iDim] = min( gi[iDim], NUM_SUBDIVS ); + gi[iDim] = max( gi[iDim], 0 ); + } + + // Look for a matching vector in there. + CUtlVector<int> *pGridElement = &m_NormalGrid[gi[0]][gi[1]][gi[2]]; + for( int i=0; i < pGridElement->Size(); i++ ) + { + int iNormal = pGridElement->Element(i); + + Vector *pVec = &m_Normals[iNormal]; + //if( pVec->DistToSqr(vNormal) < 0.00001f ) + if( *pVec == vNormal ) + return iNormal; + } + + // Ok, add a new one. + pGridElement->AddToTail( m_Normals.Size() ); + return m_Normals.AddToTail( vNormal ); +} + +// FIXME: HACK until the plane normals are made more happy +void GetBumpNormals( const float* sVect, const float* tVect, const Vector& flatNormal, + const Vector& phongNormal, Vector bumpNormals[NUM_BUMP_VECTS] ) +{ + Vector stmp( sVect[0], sVect[1], sVect[2] ); + Vector ttmp( tVect[0], tVect[1], tVect[2] ); + GetBumpNormals( stmp, ttmp, flatNormal, phongNormal, bumpNormals ); +} + +int EdgeVertex( dface_t *f, int edge ) +{ + int k; + + if (edge < 0) + edge += f->numedges; + else if (edge >= f->numedges) + edge = edge % f->numedges; + + k = dsurfedges[f->firstedge + edge]; + if (k < 0) + { + // Msg("(%d %d) ", dedges[-k].v[1], dedges[-k].v[0] ); + return dedges[-k].v[1]; + } + else + { + // Msg("(%d %d) ", dedges[k].v[0], dedges[k].v[1] ); + return dedges[k].v[0]; + } +} + + +/* + ============ + PairEdges + ============ +*/ +void PairEdges (void) +{ + int i, j, k, n, m; + dface_t *f; + int numneighbors; + int tmpneighbor[64]; + faceneighbor_t *fn; + + // count number of faces that reference each vertex + for (i=0, f = g_pFaces; i<numfaces ; i++, f++) + { + for (j=0 ; j<f->numedges ; j++) + { + // Store the count in vertexref + vertexref[EdgeVertex(f,j)]++; + } + } + + // allocate room + for (i = 0; i < numvertexes; i++) + { + // use the count from above to allocate a big enough array + vertexface[i] = ( int* )calloc( vertexref[i], sizeof( vertexface[0] ) ); + // clear the temporary data + vertexref[i] = 0; + } + + // store a list of every face that uses a particular vertex + for (i=0, f = g_pFaces ; i<numfaces ; i++, f++) + { + for (j=0 ; j<f->numedges ; j++) + { + n = EdgeVertex(f,j); + + for (k = 0; k < vertexref[n]; k++) + { + if (vertexface[n][k] == i) + break; + } + if (k >= vertexref[n]) + { + // add the face to the list + vertexface[n][k] = i; + vertexref[n]++; + } + } + } + + // calc normals and set displacement surface flag + for (i=0, f = g_pFaces; i<numfaces ; i++, f++) + { + fn = &faceneighbor[i]; + + // get face normal + VectorCopy( dplanes[f->planenum].normal, fn->facenormal ); + + // set displacement surface flag + fn->bHasDisp = false; + if( ValidDispFace( f ) ) + { + fn->bHasDisp = true; + } + } + + // find neighbors + for (i=0, f = g_pFaces ; i<numfaces ; i++, f++) + { + numneighbors = 0; + fn = &faceneighbor[i]; + + // allocate room for vertex normals + fn->normal = ( Vector* )calloc( f->numedges, sizeof( fn->normal[0] ) ); + + // look up all faces sharing vertices and add them to the list + for (j=0 ; j<f->numedges ; j++) + { + n = EdgeVertex(f,j); + + for (k = 0; k < vertexref[n]; k++) + { + double cos_normals_angle; + Vector *pNeighbornormal; + + // skip self + if (vertexface[n][k] == i) + continue; + + // if this face doens't have a displacement -- don't consider displacement neighbors + if( ( !fn->bHasDisp ) && ( faceneighbor[vertexface[n][k]].bHasDisp ) ) + continue; + + pNeighbornormal = &faceneighbor[vertexface[n][k]].facenormal; + cos_normals_angle = DotProduct( *pNeighbornormal, fn->facenormal ); + + // add normal if >= threshold or its a displacement surface (this is only if the original + // face is a displacement) + if ( fn->bHasDisp ) + { + // Always smooth with and against a displacement surface. + VectorAdd( fn->normal[j], *pNeighbornormal, fn->normal[j] ); + } + else + { + // No smoothing - use of method (backwards compatibility). + if ( ( f->smoothingGroups == 0 ) && ( g_pFaces[vertexface[n][k]].smoothingGroups == 0 ) ) + { + if ( cos_normals_angle >= smoothing_threshold ) + { + VectorAdd( fn->normal[j], *pNeighbornormal, fn->normal[j] ); + } + else + { + // not considered a neighbor + continue; + } + } + else + { + unsigned int smoothingGroup = ( f->smoothingGroups & g_pFaces[vertexface[n][k]].smoothingGroups ); + + // Hard edge. + if ( ( smoothingGroup & SMOOTHING_GROUP_HARD_EDGE ) != 0 ) + continue; + + if ( smoothingGroup != 0 ) + { + VectorAdd( fn->normal[j], *pNeighbornormal, fn->normal[j] ); + } + else + { + // not considered a neighbor + continue; + } + } + } + + // look to see if we've already added this one + for (m = 0; m < numneighbors; m++) + { + if (tmpneighbor[m] == vertexface[n][k]) + break; + } + + if (m >= numneighbors) + { + // add to neighbor list + tmpneighbor[m] = vertexface[n][k]; + numneighbors++; + if ( numneighbors > ARRAYSIZE(tmpneighbor) ) + { + Error("Stack overflow in neighbors\n"); + } + } + } + } + + if (numneighbors) + { + // copy over neighbor list + fn->numneighbors = numneighbors; + fn->neighbor = ( int* )calloc( numneighbors, sizeof( fn->neighbor[0] ) ); + for (m = 0; m < numneighbors; m++) + { + fn->neighbor[m] = tmpneighbor[m]; + } + } + + // fixup normals + for (j = 0; j < f->numedges; j++) + { + VectorAdd( fn->normal[j], fn->facenormal, fn->normal[j] ); + VectorNormalize( fn->normal[j] ); + } + } +} + + +void SaveVertexNormals( void ) +{ + faceneighbor_t *fn; + int i, j; + dface_t *f; + CNormalList normalList; + + g_numvertnormalindices = 0; + + for( i = 0 ;i<numfaces ; i++ ) + { + fn = &faceneighbor[i]; + f = &g_pFaces[i]; + + for( j = 0; j < f->numedges; j++ ) + { + Vector vNormal; + if( fn->normal ) + { + vNormal = fn->normal[j]; + } + else + { + // original faces don't have normals + vNormal.Init( 0, 0, 0 ); + } + + if( g_numvertnormalindices == MAX_MAP_VERTNORMALINDICES ) + { + Error( "g_numvertnormalindices == MAX_MAP_VERTNORMALINDICES" ); + } + + g_vertnormalindices[g_numvertnormalindices] = (unsigned short)normalList.FindOrAddNormal( vNormal ); + g_numvertnormalindices++; + } + } + + if( normalList.m_Normals.Size() > MAX_MAP_VERTNORMALS ) + { + Error( "g_numvertnormals > MAX_MAP_VERTNORMALS" ); + } + + // Copy the list of unique vert normals into g_vertnormals. + g_numvertnormals = normalList.m_Normals.Size(); + memcpy( g_vertnormals, normalList.m_Normals.Base(), sizeof(g_vertnormals[0]) * normalList.m_Normals.Size() ); +} + +/* + ================================================================= + + LIGHTMAP SAMPLE GENERATION + + ================================================================= +*/ + + +//----------------------------------------------------------------------------- +// Purpose: Spits out an error message with information about a lightinfo_t. +// Input : s - Error message string. +// l - lightmap info struct. +//----------------------------------------------------------------------------- +void ErrorLightInfo(const char *s, lightinfo_t *l) +{ + texinfo_t *tex = &texinfo[l->face->texinfo]; + winding_t *w = WindingFromFace(&g_pFaces[l->facenum], l->modelorg); + + // + // Show the face center and material name if possible. + // + if (w != NULL) + { + // Don't exit, we'll try to recover... + Vector vecCenter; + WindingCenter(w, vecCenter); +// FreeWinding(w); + + Warning("%s at (%g, %g, %g)\n\tmaterial=%s\n", s, (double)vecCenter.x, (double)vecCenter.y, (double)vecCenter.z, TexDataStringTable_GetString( dtexdata[tex->texdata].nameStringTableID ) ); + } + // + // If not, just show the material name. + // + else + { + Warning("%s at (degenerate face)\n\tmaterial=%s\n", s, TexDataStringTable_GetString( dtexdata[tex->texdata].nameStringTableID )); + } +} + + + +void CalcFaceVectors(lightinfo_t *l) +{ + texinfo_t *tex; + int i, j; + + tex = &texinfo[l->face->texinfo]; + + // move into lightinfo_t + for (i=0 ; i<2 ; i++) + { + for (j=0 ; j<3 ; j++) + { + l->worldToLuxelSpace[i][j] = tex->lightmapVecsLuxelsPerWorldUnits[i][j]; + } + } + + //Solve[ { x * w00 + y * w01 + z * w02 - s == 0, x * w10 + y * w11 + z * w12 - t == 0, A * x + B * y + C * z + D == 0 }, { x, y, z } ] + //Rule(x,( C*s*w11 - B*s*w12 + B*t*w02 - C*t*w01 + D*w02*w11 - D*w01*w12) / (+ A*w01*w12 - A*w02*w11 + B*w02*w10 - B*w00*w12 + C*w00*w11 - C*w01*w10 )), + //Rule(y,( A*s*w12 - C*s*w10 + C*t*w00 - A*t*w02 + D*w00*w12 - D*w02*w10) / (+ A*w01*w12 - A*w02*w11 + B*w02*w10 - B*w00*w12 + C*w00*w11 - C*w01*w10 )), + //Rule(z,( B*s*w10 - A*s*w11 + A*t*w01 - B*t*w00 + D*w01*w10 - D*w00*w11) / (+ A*w01*w12 - A*w02*w11 + B*w02*w10 - B*w00*w12 + C*w00*w11 - C*w01*w10 )))) + + Vector luxelSpaceCross; + + luxelSpaceCross[0] = + tex->lightmapVecsLuxelsPerWorldUnits[1][1] * tex->lightmapVecsLuxelsPerWorldUnits[0][2] - + tex->lightmapVecsLuxelsPerWorldUnits[1][2] * tex->lightmapVecsLuxelsPerWorldUnits[0][1]; + luxelSpaceCross[1] = + tex->lightmapVecsLuxelsPerWorldUnits[1][2] * tex->lightmapVecsLuxelsPerWorldUnits[0][0] - + tex->lightmapVecsLuxelsPerWorldUnits[1][0] * tex->lightmapVecsLuxelsPerWorldUnits[0][2]; + luxelSpaceCross[2] = + tex->lightmapVecsLuxelsPerWorldUnits[1][0] * tex->lightmapVecsLuxelsPerWorldUnits[0][1] - + tex->lightmapVecsLuxelsPerWorldUnits[1][1] * tex->lightmapVecsLuxelsPerWorldUnits[0][0]; + + float det = -DotProduct( l->facenormal, luxelSpaceCross ); + if ( fabs( det ) < 1.0e-20 ) + { + Warning(" warning - face vectors parallel to face normal. bad lighting will be produced\n" ); + l->luxelOrigin = vec3_origin; + } + else + { + // invert the matrix + l->luxelToWorldSpace[0][0] = (l->facenormal[2] * l->worldToLuxelSpace[1][1] - l->facenormal[1] * l->worldToLuxelSpace[1][2]) / det; + l->luxelToWorldSpace[1][0] = (l->facenormal[1] * l->worldToLuxelSpace[0][2] - l->facenormal[2] * l->worldToLuxelSpace[0][1]) / det; + l->luxelOrigin[0] = -(l->facedist * luxelSpaceCross[0]) / det; + l->luxelToWorldSpace[0][1] = (l->facenormal[0] * l->worldToLuxelSpace[1][2] - l->facenormal[2] * l->worldToLuxelSpace[1][0]) / det; + l->luxelToWorldSpace[1][1] = (l->facenormal[2] * l->worldToLuxelSpace[0][0] - l->facenormal[0] * l->worldToLuxelSpace[0][2]) / det; + l->luxelOrigin[1] = -(l->facedist * luxelSpaceCross[1]) / det; + l->luxelToWorldSpace[0][2] = (l->facenormal[1] * l->worldToLuxelSpace[1][0] - l->facenormal[0] * l->worldToLuxelSpace[1][1]) / det; + l->luxelToWorldSpace[1][2] = (l->facenormal[0] * l->worldToLuxelSpace[0][1] - l->facenormal[1] * l->worldToLuxelSpace[0][0]) / det; + l->luxelOrigin[2] = -(l->facedist * luxelSpaceCross[2]) / det; + + // adjust for luxel offset + VectorMA( l->luxelOrigin, -tex->lightmapVecsLuxelsPerWorldUnits[0][3], l->luxelToWorldSpace[0], l->luxelOrigin ); + VectorMA( l->luxelOrigin, -tex->lightmapVecsLuxelsPerWorldUnits[1][3], l->luxelToWorldSpace[1], l->luxelOrigin ); + } + // compensate for org'd bmodels + VectorAdd (l->luxelOrigin, l->modelorg, l->luxelOrigin); +} + + + +winding_t *LightmapCoordWindingForFace( lightinfo_t *l ) +{ + int i; + winding_t *w; + + w = WindingFromFace( l->face, l->modelorg ); + + for (i = 0; i < w->numpoints; i++) + { + Vector2D coord; + WorldToLuxelSpace( l, w->p[i], coord ); + w->p[i].x = coord.x; + w->p[i].y = coord.y; + w->p[i].z = 0; + } + + return w; +} + + +void WriteCoordWinding (FILE *out, lightinfo_t *l, winding_t *w, Vector& color ) +{ + int i; + Vector pos; + + fprintf (out, "%i\n", w->numpoints); + for (i=0 ; i<w->numpoints ; i++) + { + LuxelSpaceToWorld( l, w->p[i][0], w->p[i][1], pos ); + fprintf (out, "%5.2f %5.2f %5.2f %5.3f %5.3f %5.3f\n", + pos[0], + pos[1], + pos[2], + color[ 0 ] / 256, + color[ 1 ] / 256, + color[ 2 ] / 256 ); + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void DumpFaces( lightinfo_t *pLightInfo, int ndxFace ) +{ + static FileHandle_t out; + + // get face data + faceneighbor_t *fn = &faceneighbor[ndxFace]; + Vector ¢roid = face_centroids[ndxFace]; + + // disable threading (not a multi-threadable function!) + ThreadLock(); + + if( !out ) + { + // open the file + out = g_pFileSystem->Open( "face.txt", "w" ); + if( !out ) + return; + } + + // + // write out face + // + for( int ndxEdge = 0; ndxEdge < pLightInfo->face->numedges; ndxEdge++ ) + { +// int edge = dsurfedges[pLightInfo->face->firstedge+ndxEdge]; + + Vector p1, p2; + VectorAdd( dvertexes[EdgeVertex( pLightInfo->face, ndxEdge )].point, pLightInfo->modelorg, p1 ); + VectorAdd( dvertexes[EdgeVertex( pLightInfo->face, ndxEdge+1 )].point, pLightInfo->modelorg, p2 ); + + Vector &n1 = fn->normal[ndxEdge]; + Vector &n2 = fn->normal[(ndxEdge+1)%pLightInfo->face->numedges]; + + CmdLib_FPrintf( out, "3\n"); + + CmdLib_FPrintf(out, "%f %f %f %f %f %f\n", p1[0], p1[1], p1[2], n1[0] * 0.5 + 0.5, n1[1] * 0.5 + 0.5, n1[2] * 0.5 + 0.5 ); + + CmdLib_FPrintf(out, "%f %f %f %f %f %f\n", p2[0], p2[1], p2[2], n2[0] * 0.5 + 0.5, n2[1] * 0.5 + 0.5, n2[2] * 0.5 + 0.5 ); + + CmdLib_FPrintf(out, "%f %f %f %f %f %f\n", centroid[0] + pLightInfo->modelorg[0], + centroid[1] + pLightInfo->modelorg[1], + centroid[2] + pLightInfo->modelorg[2], + fn->facenormal[0] * 0.5 + 0.5, + fn->facenormal[1] * 0.5 + 0.5, + fn->facenormal[2] * 0.5 + 0.5 ); + + } + + // enable threading + ThreadUnlock(); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool BuildFacesamplesAndLuxels_DoFast( lightinfo_t *pLightInfo, facelight_t *pFaceLight ) +{ + // lightmap size + int width = pLightInfo->face->m_LightmapTextureSizeInLuxels[0]+1; + int height = pLightInfo->face->m_LightmapTextureSizeInLuxels[1]+1; + + // ratio of world area / lightmap area + texinfo_t *pTex = &texinfo[pLightInfo->face->texinfo]; + pFaceLight->worldAreaPerLuxel = 1.0 / ( sqrt( DotProduct( pTex->lightmapVecsLuxelsPerWorldUnits[0], + pTex->lightmapVecsLuxelsPerWorldUnits[0] ) ) * + sqrt( DotProduct( pTex->lightmapVecsLuxelsPerWorldUnits[1], + pTex->lightmapVecsLuxelsPerWorldUnits[1] ) ) ); + + // + // quickly create samples and luxels (copy over samples) + // + pFaceLight->numsamples = width * height; + pFaceLight->sample = ( sample_t* )calloc( pFaceLight->numsamples, sizeof( *pFaceLight->sample ) ); + if( !pFaceLight->sample ) + return false; + + pFaceLight->numluxels = width * height; + pFaceLight->luxel = ( Vector* )calloc( pFaceLight->numluxels, sizeof( *pFaceLight->luxel ) ); + if( !pFaceLight->luxel ) + return false; + + sample_t *pSamples = pFaceLight->sample; + Vector *pLuxels = pFaceLight->luxel; + + for( int t = 0; t < height; t++ ) + { + for( int s = 0; s < width; s++ ) + { + pSamples->s = s; + pSamples->t = t; + pSamples->coord[0] = s; + pSamples->coord[1] = t; + // unused but initialized anyway + pSamples->mins[0] = s - 0.5; + pSamples->mins[1] = t - 0.5; + pSamples->maxs[0] = s + 0.5; + pSamples->maxs[1] = t + 0.5; + pSamples->area = pFaceLight->worldAreaPerLuxel; + LuxelSpaceToWorld( pLightInfo, pSamples->coord[0], pSamples->coord[1], pSamples->pos ); + VectorCopy( pSamples->pos, *pLuxels ); + + pSamples++; + pLuxels++; + } + } + + return true; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool BuildSamplesAndLuxels_DoFast( lightinfo_t *pLightInfo, facelight_t *pFaceLight, int ndxFace ) +{ + // build samples for a "face" + if( pLightInfo->face->dispinfo == -1 ) + { + return BuildFacesamplesAndLuxels_DoFast( pLightInfo, pFaceLight ); + } + // build samples for a "displacement" + else + { + return StaticDispMgr()->BuildDispSamplesAndLuxels_DoFast( pLightInfo, pFaceLight, ndxFace ); + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool BuildFacesamples( lightinfo_t *pLightInfo, facelight_t *pFaceLight ) +{ + // lightmap size + int width = pLightInfo->face->m_LightmapTextureSizeInLuxels[0]+1; + int height = pLightInfo->face->m_LightmapTextureSizeInLuxels[1]+1; + + // ratio of world area / lightmap area + texinfo_t *pTex = &texinfo[pLightInfo->face->texinfo]; + pFaceLight->worldAreaPerLuxel = 1.0 / ( sqrt( DotProduct( pTex->lightmapVecsLuxelsPerWorldUnits[0], + pTex->lightmapVecsLuxelsPerWorldUnits[0] ) ) * + sqrt( DotProduct( pTex->lightmapVecsLuxelsPerWorldUnits[1], + pTex->lightmapVecsLuxelsPerWorldUnits[1] ) ) ); + + // allocate a large number of samples for creation -- get copied later! + CUtlVector<sample_t> sampleData; + sampleData.SetCount( SINGLE_BRUSH_MAP * 2 ); + sample_t *samples = sampleData.Base(); + sample_t *pSamples = samples; + + // lightmap space winding + winding_t *pLightmapWinding = LightmapCoordWindingForFace( pLightInfo ); + + // + // build vector pointing along the lightmap cutting planes + // + Vector sNorm( 1.0f, 0.0f, 0.0f ); + Vector tNorm( 0.0f, 1.0f, 0.0f ); + + // sample center offset + float sampleOffset = ( do_centersamples ) ? 0.5 : 1.0; + + // + // clip the lightmap "spaced" winding by the lightmap cutting planes + // + winding_t *pWindingT1, *pWindingT2; + winding_t *pWindingS1, *pWindingS2; + float dist; + + for( int t = 0; t < height && pLightmapWinding; t++ ) + { + dist = t + sampleOffset; + + // lop off a sample in the t dimension + // hack - need a separate epsilon for lightmap space since ON_EPSILON is for texture space + ClipWindingEpsilon( pLightmapWinding, tNorm, dist, ON_EPSILON / 16.0f, &pWindingT1, &pWindingT2 ); + + for( int s = 0; s < width && pWindingT2; s++ ) + { + dist = s + sampleOffset; + + // lop off a sample in the s dimension, and put it in ws2 + // hack - need a separate epsilon for lightmap space since ON_EPSILON is for texture space + ClipWindingEpsilon( pWindingT2, sNorm, dist, ON_EPSILON / 16.0f, &pWindingS1, &pWindingS2 ); + + // + // s2 winding is a single sample worth of winding + // + if( pWindingS2 ) + { + // save the s, t positions + pSamples->s = s; + pSamples->t = t; + + // get the lightmap space area of ws2 and convert to world area + // and find the center (then convert it to 2D) + Vector center; + pSamples->area = WindingAreaAndBalancePoint( pWindingS2, center ) * pFaceLight->worldAreaPerLuxel; + pSamples->coord[0] = center.x; + pSamples->coord[1] = center.y; + + // find winding bounds (then convert it to 2D) + Vector minbounds, maxbounds; + WindingBounds( pWindingS2, minbounds, maxbounds ); + pSamples->mins[0] = minbounds.x; + pSamples->mins[1] = minbounds.y; + pSamples->maxs[0] = maxbounds.x; + pSamples->maxs[1] = maxbounds.y; + + // convert from lightmap space to world space + LuxelSpaceToWorld( pLightInfo, pSamples->coord[0], pSamples->coord[1], pSamples->pos ); + + if (g_bDumpPatches || (do_extra && pSamples->area < pFaceLight->worldAreaPerLuxel - EQUAL_EPSILON)) + { + // + // convert the winding from lightmaps space to world for debug rendering and sub-sampling + // + Vector worldPos; + for( int ndxPt = 0; ndxPt < pWindingS2->numpoints; ndxPt++ ) + { + LuxelSpaceToWorld( pLightInfo, pWindingS2->p[ndxPt].x, pWindingS2->p[ndxPt].y, worldPos ); + VectorCopy( worldPos, pWindingS2->p[ndxPt] ); + } + pSamples->w = pWindingS2; + } + else + { + // winding isn't needed, free it. + pSamples->w = NULL; + FreeWinding( pWindingS2 ); + } + + pSamples++; + } + + // + // if winding T2 still exists free it and set it equal S1 (the rest of the row minus the sample just created) + // + if( pWindingT2 ) + { + FreeWinding( pWindingT2 ); + } + + // clip the rest of "s" + pWindingT2 = pWindingS1; + } + + // + // if the original lightmap winding exists free it and set it equal to T1 (the rest of the winding not cut into samples) + // + if( pLightmapWinding ) + { + FreeWinding( pLightmapWinding ); + } + + if( pWindingT2 ) + { + FreeWinding( pWindingT2 ); + } + + pLightmapWinding = pWindingT1; + } + + // + // copy over samples + // + pFaceLight->numsamples = pSamples - samples; + pFaceLight->sample = ( sample_t* )calloc( pFaceLight->numsamples, sizeof( *pFaceLight->sample ) ); + if( !pFaceLight->sample ) + return false; + + memcpy( pFaceLight->sample, samples, pFaceLight->numsamples * sizeof( *pFaceLight->sample ) ); + + // supply a default sample normal (face normal - assumed flat) + for( int ndxSample = 0; ndxSample < pFaceLight->numsamples; ndxSample++ ) + { + Assert ( VectorLength ( pLightInfo->facenormal ) > 1.0e-20); + pFaceLight->sample[ndxSample].normal = pLightInfo->facenormal; + } + + // statistics - warning?! + if( pFaceLight->numsamples == 0 ) + { + Msg( "no samples %d\n", pLightInfo->face - g_pFaces ); + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: Free any windings used by this facelight. It's currently assumed they're not needed again +//----------------------------------------------------------------------------- +void FreeSampleWindings( facelight_t *fl ) +{ + int i; + for (i = 0; i < fl->numsamples; i++) + { + if (fl->sample[i].w) + { + FreeWinding( fl->sample[i].w ); + fl->sample[i].w = NULL; + } + } +} + + + +//----------------------------------------------------------------------------- +// Purpose: build the sample data for each lightmapped primitive type +//----------------------------------------------------------------------------- +bool BuildSamples( lightinfo_t *pLightInfo, facelight_t *pFaceLight, int ndxFace ) +{ + // build samples for a "face" + if( pLightInfo->face->dispinfo == -1 ) + { + return BuildFacesamples( pLightInfo, pFaceLight ); + } + // build samples for a "displacement" + else + { + return StaticDispMgr()->BuildDispSamples( pLightInfo, pFaceLight, ndxFace ); + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool BuildFaceLuxels( lightinfo_t *pLightInfo, facelight_t *pFaceLight ) +{ + // lightmap size + int width = pLightInfo->face->m_LightmapTextureSizeInLuxels[0]+1; + int height = pLightInfo->face->m_LightmapTextureSizeInLuxels[1]+1; + + // calcuate actual luxel points + pFaceLight->numluxels = width * height; + pFaceLight->luxel = ( Vector* )calloc( pFaceLight->numluxels, sizeof( *pFaceLight->luxel ) ); + if( !pFaceLight->luxel ) + return false; + + for( int t = 0; t < height; t++ ) + { + for( int s = 0; s < width; s++ ) + { + LuxelSpaceToWorld( pLightInfo, s, t, pFaceLight->luxel[s+t*width] ); + } + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Purpose: build the luxels (find the luxel centers) for each lightmapped +// primitive type +//----------------------------------------------------------------------------- +bool BuildLuxels( lightinfo_t *pLightInfo, facelight_t *pFaceLight, int ndxFace ) +{ + // build luxels for a "face" + if( pLightInfo->face->dispinfo == -1 ) + { + return BuildFaceLuxels( pLightInfo, pFaceLight ); + } + // build luxels for a "displacement" + else + { + return StaticDispMgr()->BuildDispLuxels( pLightInfo, pFaceLight, ndxFace ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: for each face, find the center of each luxel; for each texture +// aligned grid point, back project onto the plane and get the world +// xyz value of the sample point +// NOTE: ndxFace = facenum +//----------------------------------------------------------------------------- +void CalcPoints( lightinfo_t *pLightInfo, facelight_t *pFaceLight, int ndxFace ) +{ + // debugging! + if( g_bDumpPatches ) + { + DumpFaces( pLightInfo, ndxFace ); + } + + // quick and dirty! + if( do_fast ) + { + if( !BuildSamplesAndLuxels_DoFast( pLightInfo, pFaceLight, ndxFace ) ) + { + Msg( "Face %d: (Fast)Error Building Samples and Luxels\n", ndxFace ); + } + return; + } + + // build the samples + if( !BuildSamples( pLightInfo, pFaceLight, ndxFace ) ) + { + Msg( "Face %d: Error Building Samples\n", ndxFace ); + } + + // build the luxels + if( !BuildLuxels( pLightInfo, pFaceLight, ndxFace ) ) + { + Msg( "Face %d: Error Building Luxels\n", ndxFace ); + } +} + + +//============================================================== + +directlight_t *activelights; +directlight_t *freelights; + +facelight_t facelight[MAX_MAP_FACES]; +int numdlights; + +/* + ================== + FindTargetEntity + ================== +*/ +entity_t *FindTargetEntity (char *target) +{ + int i; + char *n; + + for (i=0 ; i<num_entities ; i++) + { + n = ValueForKey (&entities[i], "targetname"); + if (!strcmp (n, target)) + return &entities[i]; + } + + return NULL; +} + + + +/* + ============= + AllocDLight + ============= +*/ + +int GetVisCache( int lastoffset, int cluster, byte *pvs ); +void SetDLightVis( directlight_t *dl, int cluster ); +void MergeDLightVis( directlight_t *dl, int cluster ); + +directlight_t *AllocDLight( Vector& origin, bool bAddToList ) +{ + directlight_t *dl; + + dl = ( directlight_t* )calloc(1, sizeof(directlight_t)); + dl->index = numdlights++; + + VectorCopy( origin, dl->light.origin ); + + dl->light.cluster = ClusterFromPoint(dl->light.origin); + SetDLightVis( dl, dl->light.cluster ); + + dl->facenum = -1; + + if ( bAddToList ) + { + dl->next = activelights; + activelights = dl; + } + + return dl; +} + +void AddDLightToActiveList( directlight_t *dl ) +{ + dl->next = activelights; + activelights = dl; +} + +void FreeDLights() +{ + gSkyLight = NULL; + gAmbient = NULL; + + directlight_t *pNext; + for( directlight_t *pCur=activelights; pCur; pCur=pNext ) + { + pNext = pCur->next; + free( pCur ); + } + activelights = 0; +} + + +void SetDLightVis( directlight_t *dl, int cluster ) +{ + if (dl->pvs == NULL) + { + dl->pvs = (byte *)calloc( 1, (dvis->numclusters / 8) + 1 ); + } + + GetVisCache( -1, cluster, dl->pvs ); +} + +void MergeDLightVis( directlight_t *dl, int cluster ) +{ + if (dl->pvs == NULL) + { + SetDLightVis( dl, cluster ); + } + else + { + byte pvs[MAX_MAP_CLUSTERS/8]; + GetVisCache( -1, cluster, pvs ); + + // merge both vis graphs + for (int i = 0; i < (dvis->numclusters / 8) + 1; i++) + { + dl->pvs[i] |= pvs[i]; + } + } +} + + +/* + ============= + LightForKey + ============= +*/ +int LightForKey (entity_t *ent, char *key, Vector& intensity ) +{ + char *pLight; + + pLight = ValueForKey( ent, key ); + + return LightForString( pLight, intensity ); +} + +int LightForString( char *pLight, Vector& intensity ) +{ + double r, g, b, scaler; + int argCnt; + + VectorFill( intensity, 0 ); + + // scanf into doubles, then assign, so it is vec_t size independent + r = g = b = scaler = 0; + double r_hdr,g_hdr,b_hdr,scaler_hdr; + argCnt = sscanf ( pLight, "%lf %lf %lf %lf %lf %lf %lf %lf", + &r, &g, &b, &scaler, &r_hdr,&g_hdr,&b_hdr,&scaler_hdr ); + + if (argCnt==8) // 2 4-tuples + { + if (g_bHDR) + { + r=r_hdr; + g=g_hdr; + b=b_hdr; + scaler=scaler_hdr; + } + argCnt=4; + } + + // make sure light is legal + if( r < 0.0f || g < 0.0f || b < 0.0f || scaler < 0.0f ) + { + intensity.Init( 0.0f, 0.0f, 0.0f ); + return false; + } + + intensity[0] = pow( r / 255.0, 2.2 ) * 255; // convert to linear + + switch( argCnt) + { + case 1: + // The R,G,B values are all equal. + intensity[1] = intensity[2] = intensity[0]; + break; + + case 3: + case 4: + // Save the other two G,B values. + intensity[1] = pow( g / 255.0, 2.2 ) * 255; + intensity[2] = pow( b / 255.0, 2.2 ) * 255; + + // Did we also get an "intensity" scaler value too? + if ( argCnt == 4 ) + { + // Scale the normalized 0-255 R,G,B values by the intensity scaler + VectorScale( intensity, scaler / 255.0, intensity ); + } + break; + + default: + printf("unknown light specifier type - %s\n",pLight); + return false; + } + // scale up source lights by scaling factor + VectorScale( intensity, lightscale, intensity ); + return true; +} + +//----------------------------------------------------------------------------- +// Various parsing methods +//----------------------------------------------------------------------------- + +static void ParseLightGeneric( entity_t *e, directlight_t *dl ) +{ + entity_t *e2; + char *target; + Vector dest; + + dl->light.style = (int)FloatForKey (e, "style"); + + // get intenfsity + if( g_bHDR && LightForKey( e, "_lightHDR", dl->light.intensity ) ) + { + } + else + { + LightForKey( e, "_light", dl->light.intensity ); + } + + // check angle, targets + target = ValueForKey (e, "target"); + if (target[0]) + { // point towards target + e2 = FindTargetEntity (target); + if (!e2) + Warning("WARNING: light at (%i %i %i) has missing target\n", + (int)dl->light.origin[0], (int)dl->light.origin[1], (int)dl->light.origin[2]); + else + { + GetVectorForKey (e2, "origin", dest); + VectorSubtract (dest, dl->light.origin, dl->light.normal); + VectorNormalize (dl->light.normal); + } + } + else + { + // point down angle + Vector angles; + GetVectorForKey( e, "angles", angles ); + float pitch = FloatForKey (e, "pitch"); + float angle = FloatForKey (e, "angle"); + SetupLightNormalFromProps( QAngle( angles.x, angles.y, angles.z ), angle, pitch, dl->light.normal ); + } + if ( g_bHDR ) + VectorScale( dl->light.intensity, + FloatForKeyWithDefault( e, "_lightscaleHDR", 1.0 ), + dl->light.intensity ); +} + +static void SetLightFalloffParams( entity_t * e, directlight_t * dl ) +{ + float d50=FloatForKey( e, "_fifty_percent_distance" ); + dl->m_flStartFadeDistance = 0; + dl->m_flEndFadeDistance = - 1; + dl->m_flCapDist = 1.0e22; + if ( d50 ) + { + float d0 = FloatForKey( e, "_zero_percent_distance" ); + if ( d0 < d50 ) + { + Warning( "light has _fifty_percent_distance of %f but _zero_percent_distance of %f\n", d50, d0); + d0 = 2.0 * d50; + } + float a = 0, b = 1, c = 0; + if ( ! SolveInverseQuadraticMonotonic( 0, 1.0, d50, 2.0, d0, 256.0, a, b, c )) + { + Warning( "can't solve quadratic for light %f %f\n", d50, d0 ); + } + // it it possible that the parameters couldn't be used because of enforing monoticity. If so, rescale so at + // least the 50 percent value is right +// printf("50 percent=%f 0 percent=%f\n",d50,d0); +// printf("a=%f b=%f c=%f\n",a,b,c); + float v50 = c + d50 * ( b + d50 * a ); + float scale = 2.0 / v50; + a *= scale; + b *= scale; + c *= scale; +// printf("scaled=%f a=%f b=%f c=%f\n",scale,a,b,c); +// for(float d=0;d<1000;d+=20) +// printf("at %f, %f\n",d,1.0/(c+d*(b+d*a))); + dl->light.quadratic_attn = a; + dl->light.linear_attn = b; + dl->light.constant_attn = c; + + + + if ( IntForKey(e, "_hardfalloff" ) ) + { + dl->m_flEndFadeDistance = d0; + dl->m_flStartFadeDistance = 0.75 * d0 + 0.25 * d50; // start fading 3/4 way between 50 and 0. could allow adjust + } + else + { + // now, we will find the point at which the 1/x term reaches its maximum value, and + // prevent the light from going past there. If a user specifes an extreme falloff, the + // quadratic will start making the light brighter at some distance. We handle this by + // fading it from the minimum brightess point down to zero at 10x the minimum distance + if ( fabs( a ) > 0. ) + { + float flMax = b / ( - 2.0 * a ); // where f' = 0 + if ( flMax > 0.0 ) + { + dl->m_flCapDist = flMax; + dl->m_flStartFadeDistance = flMax; + dl->m_flEndFadeDistance = 10.0 * flMax; + } + } + } + } + else + { + dl->light.constant_attn = FloatForKey (e, "_constant_attn" ); + dl->light.linear_attn = FloatForKey (e, "_linear_attn" ); + dl->light.quadratic_attn = FloatForKey (e, "_quadratic_attn" ); + + dl->light.radius = FloatForKey (e, "_distance"); + + // clamp values to >= 0 + if ( dl->light.constant_attn < EQUAL_EPSILON ) + dl->light.constant_attn = 0; + + if ( dl->light.linear_attn < EQUAL_EPSILON ) + dl->light.linear_attn = 0; + + if ( dl->light.quadratic_attn < EQUAL_EPSILON ) + dl->light.quadratic_attn = 0; + + if ( dl->light.constant_attn < EQUAL_EPSILON && dl->light.linear_attn < EQUAL_EPSILON && dl->light.quadratic_attn < EQUAL_EPSILON ) + dl->light.constant_attn = 1; + + // scale intensity for unit 100 distance + float ratio = ( dl->light.constant_attn + 100 * dl->light.linear_attn + 100 * 100 * dl->light.quadratic_attn ); + if ( ratio > 0 ) + { + VectorScale( dl->light.intensity, ratio, dl->light.intensity ); + } + } +} + +static void ParseLightSpot( entity_t* e, directlight_t* dl ) +{ + Vector dest; + GetVectorForKey (e, "origin", dest ); + dl = AllocDLight( dest, true ); + + ParseLightGeneric( e, dl ); + + dl->light.type = emit_spotlight; + + dl->light.stopdot = FloatForKey (e, "_inner_cone"); + if (!dl->light.stopdot) + dl->light.stopdot = 10; + + dl->light.stopdot2 = FloatForKey (e, "_cone"); + if (!dl->light.stopdot2) + dl->light.stopdot2 = dl->light.stopdot; + if (dl->light.stopdot2 < dl->light.stopdot) + dl->light.stopdot2 = dl->light.stopdot; + + // This is a point light if stop dots are 180... + if ((dl->light.stopdot == 180) && (dl->light.stopdot2 == 180)) + { + dl->light.stopdot = dl->light.stopdot2 = 0; + dl->light.type = emit_point; + dl->light.exponent = 0; + } + else + { + // Clamp to 90, that's all DX8 can handle! + if (dl->light.stopdot > 90) + { + Warning("WARNING: light_spot at (%i %i %i) has inner angle larger than 90 degrees! Clamping to 90...\n", + (int)dl->light.origin[0], (int)dl->light.origin[1], (int)dl->light.origin[2]); + dl->light.stopdot = 90; + } + + if (dl->light.stopdot2 > 90) + { + Warning("WARNING: light_spot at (%i %i %i) has outer angle larger than 90 degrees! Clamping to 90...\n", + (int)dl->light.origin[0], (int)dl->light.origin[1], (int)dl->light.origin[2]); + dl->light.stopdot2 = 90; + } + + dl->light.stopdot2 = (float)cos(dl->light.stopdot2/180*M_PI); + dl->light.stopdot = (float)cos(dl->light.stopdot/180*M_PI); + dl->light.exponent = FloatForKey (e, "_exponent"); + } + + SetLightFalloffParams(e,dl); +} + +// NOTE: This is just a heuristic. It traces a finite number of rays to find sky +// NOTE: Full vis is necessary to make this 100% correct. +bool CanLeafTraceToSky( int iLeaf ) +{ + // UNDONE: Really want a point inside the leaf here. Center is a guess, may not be in the leaf + // UNDONE: Clip this to each plane bounding the leaf to guarantee + Vector center = vec3_origin; + for ( int i = 0; i < 3; i++ ) + { + center[i] = ( (float)(dleafs[iLeaf].mins[i] + dleafs[iLeaf].maxs[i]) ) * 0.5f; + } + + FourVectors center4, delta; + fltx4 fractionVisible; + for ( int j = 0; j < NUMVERTEXNORMALS; j+=4 ) + { + // search back to see if we can hit a sky brush + delta.LoadAndSwizzle( g_anorms[j], g_anorms[min( j+1, NUMVERTEXNORMALS-1 )], + g_anorms[min( j+2, NUMVERTEXNORMALS-1 )], g_anorms[min( j+3, NUMVERTEXNORMALS-1 )] ); + delta *= -MAX_TRACE_LENGTH; + delta += center4; + + // return true if any hits sky + TestLine_DoesHitSky ( center4, delta, &fractionVisible ); + if ( TestSignSIMD ( CmpGtSIMD ( fractionVisible, Four_Zeros ) ) ) + return true; + } + + return false; +} + +void BuildVisForLightEnvironment( void ) +{ + // Create the vis. + for ( int iLeaf = 0; iLeaf < numleafs; ++iLeaf ) + { + dleafs[iLeaf].flags &= ~( LEAF_FLAGS_SKY | LEAF_FLAGS_SKY2D ); + unsigned int iFirstFace = dleafs[iLeaf].firstleafface; + for ( int iLeafFace = 0; iLeafFace < dleafs[iLeaf].numleaffaces; ++iLeafFace ) + { + unsigned int iFace = dleaffaces[iFirstFace+iLeafFace]; + + texinfo_t &tex = texinfo[g_pFaces[iFace].texinfo]; + if ( tex.flags & SURF_SKY ) + { + if ( tex.flags & SURF_SKY2D ) + { + dleafs[iLeaf].flags |= LEAF_FLAGS_SKY2D; + } + else + { + dleafs[iLeaf].flags |= LEAF_FLAGS_SKY; + } + MergeDLightVis( gSkyLight, dleafs[iLeaf].cluster ); + MergeDLightVis( gAmbient, dleafs[iLeaf].cluster ); + break; + } + } + } + + // Second pass to set flags on leaves that don't contain sky, but touch leaves that + // contain sky. + byte pvs[MAX_MAP_CLUSTERS / 8]; + + int nLeafBytes = (numleafs >> 3) + 1; + unsigned char *pLeafBits = (unsigned char *)stackalloc( nLeafBytes * sizeof(unsigned char) ); + unsigned char *pLeaf2DBits = (unsigned char *)stackalloc( nLeafBytes * sizeof(unsigned char) ); + memset( pLeafBits, 0, nLeafBytes ); + memset( pLeaf2DBits, 0, nLeafBytes ); + + for ( int iLeaf = 0; iLeaf < numleafs; ++iLeaf ) + { + // If this leaf has light (3d skybox) in it, then don't bother + if ( dleafs[iLeaf].flags & LEAF_FLAGS_SKY ) + continue; + + // Don't bother with this leaf if it's solid + if ( dleafs[iLeaf].contents & CONTENTS_SOLID ) + continue; + + // See what other leaves are visible from this leaf + GetVisCache( -1, dleafs[iLeaf].cluster, pvs ); + + // Now check out all other leaves + int nByte = iLeaf >> 3; + int nBit = 1 << ( iLeaf & 0x7 ); + for ( int iLeaf2 = 0; iLeaf2 < numleafs; ++iLeaf2 ) + { + if ( iLeaf2 == iLeaf ) + continue; + + if ( !(dleafs[iLeaf2].flags & ( LEAF_FLAGS_SKY | LEAF_FLAGS_SKY2D ) ) ) + continue; + + // Can this leaf see into the leaf with the sky in it? + if ( !PVSCheck( pvs, dleafs[iLeaf2].cluster ) ) + continue; + + if ( dleafs[iLeaf2].flags & LEAF_FLAGS_SKY2D ) + { + pLeaf2DBits[ nByte ] |= nBit; + } + if ( dleafs[iLeaf2].flags & LEAF_FLAGS_SKY ) + { + pLeafBits[ nByte ] |= nBit; + + // As soon as we know this leaf needs to draw the 3d skybox, we're done + break; + } + } + } + + // Must set the bits in a separate pass so as to not flood-fill LEAF_FLAGS_SKY everywhere + // pLeafbits is a bit array of all leaves that need to be marked as seeing sky + for ( int iLeaf = 0; iLeaf < numleafs; ++iLeaf ) + { + // If this leaf has light (3d skybox) in it, then don't bother + if ( dleafs[iLeaf].flags & LEAF_FLAGS_SKY ) + continue; + + // Don't bother with this leaf if it's solid + if ( dleafs[iLeaf].contents & CONTENTS_SOLID ) + continue; + + // Check to see if this is a 2D skybox leaf + if ( pLeaf2DBits[ iLeaf >> 3 ] & (1 << ( iLeaf & 0x7 )) ) + { + dleafs[iLeaf].flags |= LEAF_FLAGS_SKY2D; + } + + // If this is a 3D skybox leaf, then we don't care if it was previously a 2D skybox leaf + if ( pLeafBits[ iLeaf >> 3 ] & (1 << ( iLeaf & 0x7 )) ) + { + dleafs[iLeaf].flags |= LEAF_FLAGS_SKY; + dleafs[iLeaf].flags &= ~LEAF_FLAGS_SKY2D; + } + else + { + // if radial vis was used on this leaf some of the portals leading + // to sky may have been culled. Try tracing to find sky. + if ( dleafs[iLeaf].flags & LEAF_FLAGS_RADIAL ) + { + if ( CanLeafTraceToSky(iLeaf) ) + { + // FIXME: Should make a version that checks if we hit 2D skyboxes.. oh well. + dleafs[iLeaf].flags |= LEAF_FLAGS_SKY; + } + } + } + } +} + +static char *ValueForKeyWithDefault (entity_t *ent, char *key, char *default_value = NULL) +{ + epair_t *ep; + + for (ep=ent->epairs ; ep ; ep=ep->next) + if (!strcmp (ep->key, key) ) + return ep->value; + return default_value; +} + +static void ParseLightEnvironment( entity_t* e, directlight_t* dl ) +{ + Vector dest; + GetVectorForKey (e, "origin", dest ); + dl = AllocDLight( dest, false ); + + ParseLightGeneric( e, dl ); + + char *angle_str=ValueForKeyWithDefault( e, "SunSpreadAngle" ); + if (angle_str) + { + g_SunAngularExtent=atof(angle_str); + g_SunAngularExtent=sin((M_PI/180.0)*g_SunAngularExtent); + printf("sun extent from map=%f\n",g_SunAngularExtent); + } + if ( !gSkyLight ) + { + // Sky light. + gSkyLight = dl; + dl->light.type = emit_skylight; + + // Sky ambient light. + gAmbient = AllocDLight( dl->light.origin, false ); + gAmbient->light.type = emit_skyambient; + if( g_bHDR && LightForKey( e, "_ambientHDR", gAmbient->light.intensity ) ) + { + // we have a valid HDR ambient light value + } + else if ( !LightForKey( e, "_ambient", gAmbient->light.intensity ) ) + { + VectorScale( dl->light.intensity, 0.5, gAmbient->light.intensity ); + } + if ( g_bHDR ) + { + VectorScale( gAmbient->light.intensity, + FloatForKeyWithDefault( e, "_AmbientScaleHDR", 1.0 ), + gAmbient->light.intensity ); + } + + BuildVisForLightEnvironment(); + + // Add sky and sky ambient lights to the list. + AddDLightToActiveList( gSkyLight ); + AddDLightToActiveList( gAmbient ); + } +} + +static void ParseLightPoint( entity_t* e, directlight_t* dl ) +{ + Vector dest; + GetVectorForKey (e, "origin", dest ); + dl = AllocDLight( dest, true ); + + ParseLightGeneric( e, dl ); + + dl->light.type = emit_point; + + SetLightFalloffParams(e,dl); +} + +/* + ============= + CreateDirectLights + ============= +*/ +#define DIRECT_SCALE (100.0*100.0) +void CreateDirectLights (void) +{ + unsigned i; + CPatch *p = NULL; + directlight_t *dl = NULL; + entity_t *e = NULL; + char *name; + Vector dest; + + numdlights = 0; + + FreeDLights(); + + // + // surfaces + // + unsigned int uiPatchCount = g_Patches.Count(); + for (i=0; i< uiPatchCount; i++) + { + p = &g_Patches.Element( i ); + + // skip parent patches + if (p->child1 != g_Patches.InvalidIndex() ) + continue; + + if (p->basearea < 1e-6) + continue; + + if( VectorAvg( p->baselight ) >= dlight_threshold ) + { + dl = AllocDLight( p->origin, true ); + + dl->light.type = emit_surface; + VectorCopy (p->normal, dl->light.normal); + Assert( VectorLength( p->normal ) > 1.0e-20 ); + // scale intensity by number of texture instances + VectorScale( p->baselight, lightscale * p->area * p->scale[0] * p->scale[1] / p->basearea, dl->light.intensity ); + + // scale to a range that results in actual light + VectorScale( dl->light.intensity, DIRECT_SCALE, dl->light.intensity ); + } + } + + // + // entities + // + for (i=0 ; i<(unsigned)num_entities ; i++) + { + e = &entities[i]; + name = ValueForKey (e, "classname"); + if (strncmp (name, "light", 5)) + continue; + + // Light_dynamic is actually a real entity; not to be included here... + if (!strcmp (name, "light_dynamic")) + continue; + + if (!strcmp (name, "light_spot")) + { + ParseLightSpot( e, dl ); + } + else if (!strcmp(name, "light_environment")) + { + ParseLightEnvironment( e, dl ); + } + else if (!strcmp(name, "light")) + { + ParseLightPoint( e, dl ); + } + else + { + qprintf( "unsupported light entity: \"%s\"\n", name ); + } + } + + qprintf ("%i direct lights\n", numdlights); + // exit(1); +} + +/* + ============= + ExportDirectLightsToWorldLights + ============= +*/ + +void ExportDirectLightsToWorldLights() +{ + directlight_t *dl; + + // In case the level has already been VRADed. + *pNumworldlights = 0; + + for (dl = activelights; dl != NULL; dl = dl->next ) + { + dworldlight_t *wl = &dworldlights[(*pNumworldlights)++]; + + if (*pNumworldlights > MAX_MAP_WORLDLIGHTS) + { + Error("too many lights %d / %d\n", *pNumworldlights, MAX_MAP_WORLDLIGHTS ); + } + + wl->cluster = dl->light.cluster; + wl->type = dl->light.type; + wl->style = dl->light.style; + VectorCopy( dl->light.origin, wl->origin ); + // FIXME: why does vrad want 0 to 255 and not 0 to 1?? + VectorScale( dl->light.intensity, (1.0 / 255.0), wl->intensity ); + VectorCopy( dl->light.normal, wl->normal ); + wl->stopdot = dl->light.stopdot; + wl->stopdot2 = dl->light.stopdot2; + wl->exponent = dl->light.exponent; + wl->radius = dl->light.radius; + wl->constant_attn = dl->light.constant_attn; + wl->linear_attn = dl->light.linear_attn; + wl->quadratic_attn = dl->light.quadratic_attn; + wl->flags = 0; + } +} + +/* + ============= + GatherSampleLight + ============= +*/ +#define NORMALFORMFACTOR 40.156979 // accumuated dot products for hemisphere + +#define CONSTANT_DOT (.7/2) + +#define NSAMPLES_SUN_AREA_LIGHT 30 // number of samples to take for an + // non-point sun light + +// Helper function - gathers light from sun (emit_skylight) +void GatherSampleSkyLightSSE( SSE_sampleLightOutput_t &out, directlight_t *dl, int facenum, + FourVectors const& pos, FourVectors *pNormals, int normalCount, int iThread, + int nLFlags, int static_prop_index_to_ignore, + float flEpsilon ) +{ + bool bIgnoreNormals = ( nLFlags & GATHERLFLAGS_IGNORE_NORMALS ) != 0; + bool force_fast = ( nLFlags & GATHERLFLAGS_FORCE_FAST ) != 0; + + fltx4 dot; + + if ( bIgnoreNormals ) + dot = ReplicateX4( CONSTANT_DOT ); + else + dot = NegSIMD( pNormals[0] * dl->light.normal ); + + dot = MaxSIMD( dot, Four_Zeros ); + int zeroMask = TestSignSIMD ( CmpEqSIMD( dot, Four_Zeros ) ); + if (zeroMask == 0xF) + return; + + int nsamples = 1; + if ( g_SunAngularExtent > 0.0f ) + { + nsamples = NSAMPLES_SUN_AREA_LIGHT; + if ( do_fast || force_fast ) + nsamples /= 4; + } + + fltx4 totalFractionVisible = Four_Zeros; + fltx4 fractionVisible = Four_Zeros; + + DirectionalSampler_t sampler; + + for ( int d = 0; d < nsamples; d++ ) + { + // determine visibility of skylight + // serach back to see if we can hit a sky brush + Vector delta; + VectorScale( dl->light.normal, -MAX_TRACE_LENGTH, delta ); + if ( d ) + { + // jitter light source location + Vector ofs = sampler.NextValue(); + ofs *= MAX_TRACE_LENGTH * g_SunAngularExtent; + delta += ofs; + } + FourVectors delta4; + delta4.DuplicateVector ( delta ); + delta4 += pos; + + TestLine_DoesHitSky ( pos, delta4, &fractionVisible, true, static_prop_index_to_ignore ); + + totalFractionVisible = AddSIMD ( totalFractionVisible, fractionVisible ); + } + + fltx4 seeAmount = MulSIMD ( totalFractionVisible, ReplicateX4 ( 1.0f / nsamples ) ); + out.m_flDot[0] = MulSIMD ( dot, seeAmount ); + out.m_flFalloff = Four_Ones; + out.m_flSunAmount = MulSIMD ( seeAmount, ReplicateX4( 10000.0f ) ); + for ( int i = 1; i < normalCount; i++ ) + { + if ( bIgnoreNormals ) + out.m_flDot[i] = ReplicateX4 ( CONSTANT_DOT ); + else + { + out.m_flDot[i] = NegSIMD( pNormals[i] * dl->light.normal ); + out.m_flDot[i] = MulSIMD( out.m_flDot[i], seeAmount ); + } + } +} + +// Helper function - gathers light from ambient sky light +void GatherSampleAmbientSkySSE( SSE_sampleLightOutput_t &out, directlight_t *dl, int facenum, + FourVectors const& pos, FourVectors *pNormals, int normalCount, int iThread, + int nLFlags, int static_prop_index_to_ignore, + float flEpsilon ) +{ + + bool bIgnoreNormals = ( nLFlags & GATHERLFLAGS_IGNORE_NORMALS ) != 0; + bool force_fast = ( nLFlags & GATHERLFLAGS_FORCE_FAST ) != 0; + + fltx4 sumdot = Four_Zeros; + fltx4 ambient_intensity[NUM_BUMP_VECTS+1]; + fltx4 possibleHitCount[NUM_BUMP_VECTS+1]; + fltx4 dots[NUM_BUMP_VECTS+1]; + + for ( int i = 0; i < normalCount; i++ ) + { + ambient_intensity[i] = Four_Zeros; + possibleHitCount[i] = Four_Zeros; + } + + DirectionalSampler_t sampler; + int nsky_samples = NUMVERTEXNORMALS; + if (do_fast || force_fast ) + nsky_samples /= 4; + else + nsky_samples *= g_flSkySampleScale; + + for (int j = 0; j < nsky_samples; j++) + { + FourVectors anorm; + anorm.DuplicateVector( sampler.NextValue() ); + + if ( bIgnoreNormals ) + dots[0] = ReplicateX4( CONSTANT_DOT ); + else + dots[0] = NegSIMD( pNormals[0] * anorm ); + + fltx4 validity = CmpGtSIMD( dots[0], ReplicateX4( EQUAL_EPSILON ) ); + + // No possibility of anybody getting lit + if ( !TestSignSIMD( validity ) ) + continue; + + dots[0] = AndSIMD( validity, dots[0] ); + sumdot = AddSIMD( dots[0], sumdot ); + possibleHitCount[0] = AddSIMD( AndSIMD( validity, Four_Ones ), possibleHitCount[0] ); + + for ( int i = 1; i < normalCount; i++ ) + { + if ( bIgnoreNormals ) + dots[i] = ReplicateX4( CONSTANT_DOT ); + else + dots[i] = NegSIMD( pNormals[i] * anorm ); + fltx4 validity2 = CmpGtSIMD( dots[i], ReplicateX4 ( EQUAL_EPSILON ) ); + dots[i] = AndSIMD( validity2, dots[i] ); + possibleHitCount[i] = AddSIMD( AndSIMD( AndSIMD( validity, validity2 ), Four_Ones ), possibleHitCount[i] ); + } + + // search back to see if we can hit a sky brush + FourVectors delta = anorm; + delta *= -MAX_TRACE_LENGTH; + delta += pos; + FourVectors surfacePos = pos; + FourVectors offset = anorm; + offset *= -flEpsilon; + surfacePos -= offset; + + fltx4 fractionVisible = Four_Ones; + TestLine_DoesHitSky( surfacePos, delta, &fractionVisible, true, static_prop_index_to_ignore ); + for ( int i = 0; i < normalCount; i++ ) + { + fltx4 addedAmount = MulSIMD( fractionVisible, dots[i] ); + ambient_intensity[i] = AddSIMD( ambient_intensity[i], addedAmount ); + } + + } + + out.m_flFalloff = Four_Ones; + for ( int i = 0; i < normalCount; i++ ) + { + // now scale out the missing parts of the hemisphere of this bump basis vector + fltx4 factor = ReciprocalSIMD( possibleHitCount[0] ); + factor = MulSIMD( factor, possibleHitCount[i] ); + out.m_flDot[i] = MulSIMD( factor, sumdot ); + out.m_flDot[i] = ReciprocalSIMD( out.m_flDot[i] ); + out.m_flDot[i] = MulSIMD( ambient_intensity[i], out.m_flDot[i] ); + } + +} + +// Helper function - gathers light from area lights, spot lights, and point lights +void GatherSampleStandardLightSSE( SSE_sampleLightOutput_t &out, directlight_t *dl, int facenum, + FourVectors const& pos, FourVectors *pNormals, int normalCount, int iThread, + int nLFlags, int static_prop_index_to_ignore, + float flEpsilon ) +{ + bool bIgnoreNormals = ( nLFlags & GATHERLFLAGS_IGNORE_NORMALS ) != 0; + + FourVectors src; + src.DuplicateVector( vec3_origin ); + + if (dl->facenum == -1) + { + src.DuplicateVector( dl->light.origin ); + } + + // Find light vector + FourVectors delta; + delta = src; + delta -= pos; + fltx4 dist2 = delta.length2(); + fltx4 rpcDist = ReciprocalSqrtSIMD( dist2 ); + delta *= rpcDist; + fltx4 dist = SqrtEstSIMD( dist2 );//delta.VectorNormalize(); + + // Compute dot + fltx4 dot = ReplicateX4( (float) CONSTANT_DOT ); + if ( !bIgnoreNormals ) + dot = delta * pNormals[0]; + dot = MaxSIMD( Four_Zeros, dot ); + + // Affix dot to zero if past fade distz + bool bHasHardFalloff = ( dl->m_flEndFadeDistance > dl->m_flStartFadeDistance ); + if ( bHasHardFalloff ) + { + fltx4 notPastFadeDist = CmpLeSIMD ( dist, ReplicateX4 ( dl->m_flEndFadeDistance ) ); + dot = AndSIMD( dot, notPastFadeDist ); // dot = 0 if past fade distance + if ( !TestSignSIMD ( notPastFadeDist ) ) + return; + } + + dist = MaxSIMD( dist, Four_Ones ); + fltx4 falloffEvalDist = MinSIMD( dist, ReplicateX4( dl->m_flCapDist ) ); + + fltx4 constant, linear, quadratic; + fltx4 dot2, inCone, inFringe, mult; + FourVectors offset; + + switch (dl->light.type) + { + case emit_point: + constant = ReplicateX4( dl->light.constant_attn ); + linear = ReplicateX4( dl->light.linear_attn ); + quadratic = ReplicateX4( dl->light.quadratic_attn ); + + out.m_flFalloff = MulSIMD( falloffEvalDist, falloffEvalDist ); + out.m_flFalloff = MulSIMD( out.m_flFalloff, quadratic ); + out.m_flFalloff = AddSIMD( out.m_flFalloff, MulSIMD( linear, falloffEvalDist ) ); + out.m_flFalloff = AddSIMD( out.m_flFalloff, constant ); + out.m_flFalloff = ReciprocalSIMD( out.m_flFalloff ); + break; + + case emit_surface: + dot2 = delta * dl->light.normal; + dot2 = NegSIMD( dot2 ); + + // Light behind surface yields zero dot + dot2 = MaxSIMD( Four_Zeros, dot2 ); + if ( TestSignSIMD( CmpEqSIMD( Four_Zeros, dot ) ) == 0xF ) + return; + + out.m_flFalloff = ReciprocalSIMD ( dist2 ); + out.m_flFalloff = MulSIMD( out.m_flFalloff, dot2 ); + + // move the endpoint away from the surface by epsilon to prevent hitting the surface with the trace + offset.DuplicateVector ( dl->light.normal ); + offset *= DIST_EPSILON; + src += offset; + break; + + case emit_spotlight: + dot2 = delta * dl->light.normal; + dot2 = NegSIMD( dot2 ); + + // Affix dot2 to zero if outside light cone + inCone = CmpGtSIMD( dot2, ReplicateX4( dl->light.stopdot2 ) ); + if ( !TestSignSIMD ( inCone ) ) + return; + dot = AndSIMD( inCone, dot ); + + constant = ReplicateX4( dl->light.constant_attn ); + linear = ReplicateX4( dl->light.linear_attn ); + quadratic = ReplicateX4( dl->light.quadratic_attn ); + + out.m_flFalloff = MulSIMD( falloffEvalDist, falloffEvalDist ); + out.m_flFalloff = MulSIMD( out.m_flFalloff, quadratic ); + out.m_flFalloff = AddSIMD( out.m_flFalloff, MulSIMD( linear, falloffEvalDist ) ); + out.m_flFalloff = AddSIMD( out.m_flFalloff, constant ); + out.m_flFalloff = ReciprocalSIMD( out.m_flFalloff ); + out.m_flFalloff = MulSIMD( out.m_flFalloff, dot2 ); + + // outside the inner cone + inFringe = CmpLeSIMD( dot2, ReplicateX4( dl->light.stopdot ) ); + mult = ReplicateX4( dl->light.stopdot - dl->light.stopdot2 ); + mult = ReciprocalSIMD( mult ); + mult = MulSIMD( mult, SubSIMD( dot2, ReplicateX4( dl->light.stopdot2 ) ) ); + mult = MinSIMD( mult, Four_Ones ); + mult = MaxSIMD( mult, Four_Zeros ); + + // pow is fixed point, so this isn't the most accurate, but it doesn't need to be + if ( (dl->light.exponent != 0.0f ) && ( dl->light.exponent != 1.0f ) ) + mult = PowSIMD( mult, dl->light.exponent ); + + // if not in between inner and outer cones, mult by 1 + mult = AndSIMD( inFringe, mult ); + mult = AddSIMD( mult, AndNotSIMD( inFringe, Four_Ones ) ); + out.m_flFalloff = MulSIMD( mult, out.m_flFalloff ); + break; + + } + + // we may be in the fade region - modulate lighting by the fade curve + //float t = ( dist - dl->m_flStartFadeDistance ) / + // ( dl->m_flEndFadeDistance - dl->m_flStartFadeDistance ); + if ( bHasHardFalloff ) + { + fltx4 t = ReplicateX4( dl->m_flEndFadeDistance - dl->m_flStartFadeDistance ); + t = ReciprocalSIMD( t ); + t = MulSIMD( t, SubSIMD( dist, ReplicateX4( dl->m_flStartFadeDistance ) ) ); + + // clamp t to [0...1] + t = MinSIMD( t, Four_Ones ); + t = MaxSIMD( t, Four_Zeros ); + t = SubSIMD( Four_Ones, t ); + + // Using QuinticInterpolatingPolynomial, SSE-ified + // t * t * t *( t * ( t* 6.0 - 15.0 ) + 10.0 ) + mult = SubSIMD( MulSIMD( ReplicateX4( 6.0f ), t ), ReplicateX4( 15.0f ) ); + mult = AddSIMD( MulSIMD( mult, t ), ReplicateX4( 10.0f ) ); + mult = MulSIMD( MulSIMD( t, t), mult ); + mult = MulSIMD( t, mult ); + out.m_flFalloff = MulSIMD( mult, out.m_flFalloff ); + } + + // Raytrace for visibility function + fltx4 fractionVisible = Four_Ones; + TestLine( pos, src, &fractionVisible, static_prop_index_to_ignore); + dot = MulSIMD( fractionVisible, dot ); + out.m_flDot[0] = dot; + + for ( int i = 1; i < normalCount; i++ ) + { + if ( bIgnoreNormals ) + out.m_flDot[i] = ReplicateX4( (float) CONSTANT_DOT ); + else + { + out.m_flDot[i] = pNormals[i] * delta; + out.m_flDot[i] = MaxSIMD( Four_Zeros, out.m_flDot[i] ); + } + } +} + +// returns dot product with normal and delta +// dl - light +// pos - position of sample +// normal - surface normal of sample +// out.m_flDot[] - returned dot products with light vector and each normal +// out.m_flFalloff - amount of light falloff +void GatherSampleLightSSE( SSE_sampleLightOutput_t &out, directlight_t *dl, int facenum, + FourVectors const& pos, FourVectors *pNormals, int normalCount, int iThread, + int nLFlags, + int static_prop_index_to_ignore, + float flEpsilon ) +{ + for ( int b = 0; b < normalCount; b++ ) + out.m_flDot[b] = Four_Zeros; + out.m_flFalloff = Four_Zeros; + out.m_flSunAmount = Four_Zeros; + Assert( normalCount <= (NUM_BUMP_VECTS+1) ); + + // skylights work fundamentally differently than normal lights + switch( dl->light.type ) + { + case emit_skylight: + GatherSampleSkyLightSSE( out, dl, facenum, pos, pNormals, normalCount, + iThread, nLFlags, static_prop_index_to_ignore, flEpsilon ); + break; + case emit_skyambient: + GatherSampleAmbientSkySSE( out, dl, facenum, pos, pNormals, normalCount, + iThread, nLFlags, static_prop_index_to_ignore, flEpsilon ); + break; + case emit_point: + case emit_surface: + case emit_spotlight: + GatherSampleStandardLightSSE( out, dl, facenum, pos, pNormals, normalCount, + iThread, nLFlags, static_prop_index_to_ignore, flEpsilon ); + break; + default: + Error ("Bad dl->light.type"); + return; + } + + // NOTE: Notice here that if the light is on the back side of the face + // (tested by checking the dot product of the face normal and the light position) + // we don't want it to contribute to *any* of the bumped lightmaps. It glows + // in disturbing ways if we don't do this. + out.m_flDot[0] = MaxSIMD ( out.m_flDot[0], Four_Zeros ); + fltx4 notZero = CmpGtSIMD( out.m_flDot[0], Four_Zeros ); + for ( int n = 1; n < normalCount; n++ ) + { + out.m_flDot[n] = MaxSIMD( out.m_flDot[n], Four_Zeros ); + out.m_flDot[n] = AndSIMD( out.m_flDot[n], notZero ); + } + +} + +/* + ============= + AddSampleToPatch + + Take the sample's collected light and + add it back into the apropriate patch + for the radiosity pass. + ============= +*/ +void AddSampleToPatch (sample_t *s, LightingValue_t& light, int facenum) +{ + CPatch *patch; + Vector mins, maxs; + int i; + + if (numbounce == 0) + return; + if( VectorAvg( light.m_vecLighting ) < 1) + return; + + // + // fixed the sample position and normal -- need to find the equiv pos, etc to set up + // patches + // + if( g_FacePatches.Element( facenum ) == g_FacePatches.InvalidIndex() ) + return; + + float radius = sqrt( s->area ) / 2.0; + + CPatch *pNextPatch = NULL; + for( patch = &g_Patches.Element( g_FacePatches.Element( facenum ) ); patch; patch = pNextPatch ) + { + // next patch + pNextPatch = NULL; + if( patch->ndxNext != g_Patches.InvalidIndex() ) + { + pNextPatch = &g_Patches.Element( patch->ndxNext ); + } + + if (patch->sky) + continue; + + // skip patches with children + if ( patch->child1 != g_Patches.InvalidIndex() ) + continue; + + // see if the point is in this patch (roughly) + WindingBounds (patch->winding, mins, maxs); + + for (i=0 ; i<3 ; i++) + { + if (mins[i] > s->pos[i] + radius) + goto nextpatch; + if (maxs[i] < s->pos[i] - radius) + goto nextpatch; + } + + // add the sample to the patch + patch->samplearea += s->area; + VectorMA( patch->samplelight, s->area, light.m_vecLighting, patch->samplelight ); + + nextpatch:; + } + // don't worry if some samples don't find a patch +} + + +void GetPhongNormal( int facenum, Vector const& spot, Vector& phongnormal ) +{ + int j; + dface_t *f = &g_pFaces[facenum]; +// dplane_t *p = &dplanes[f->planenum]; + Vector facenormal, vspot; + + VectorCopy( dplanes[f->planenum].normal, facenormal ); + VectorCopy( facenormal, phongnormal ); + + if ( smoothing_threshold != 1 ) + { + faceneighbor_t *fn = &faceneighbor[facenum]; + + // Calculate modified point normal for surface + // Use the edge normals iff they are defined. Bend the surface towards the edge normal(s) + // Crude first attempt: find nearest edge normal and do a simple interpolation with facenormal. + // Second attempt: find edge points+center that bound the point and do a three-point triangulation(baricentric) + // Better third attempt: generate the point normals for all vertices and do baricentric triangulation. + + for (j=0 ; j<f->numedges ; j++) + { + Vector v1, v2; + //int e = dsurfedges[f->firstedge + j]; + //int e1 = dsurfedges[f->firstedge + ((j+f->numedges-1)%f->numedges)]; + //int e2 = dsurfedges[f->firstedge + ((j+1)%f->numedges)]; + + //edgeshare_t *es = &edgeshare[abs(e)]; + //edgeshare_t *es1 = &edgeshare[abs(e1)]; + //edgeshare_t *es2 = &edgeshare[abs(e2)]; + // dface_t *f2; + float a1, a2, aa, bb, ab; + int vert1, vert2; + + Vector& n1 = fn->normal[j]; + Vector& n2 = fn->normal[(j+1)%f->numedges]; + + /* + if (VectorCompare( n1, fn->facenormal ) + && VectorCompare( n2, fn->facenormal) ) + continue; + */ + + vert1 = EdgeVertex( f, j ); + vert2 = EdgeVertex( f, j+1 ); + + Vector& p1 = dvertexes[vert1].point; + Vector& p2 = dvertexes[vert2].point; + + // Build vectors from the middle of the face to the edge vertexes and the sample pos. + VectorSubtract( p1, face_centroids[facenum], v1 ); + VectorSubtract( p2, face_centroids[facenum], v2 ); + VectorSubtract( spot, face_centroids[facenum], vspot ); + aa = DotProduct( v1, v1 ); + bb = DotProduct( v2, v2 ); + ab = DotProduct( v1, v2 ); + a1 = (bb * DotProduct( v1, vspot ) - ab * DotProduct( vspot, v2 )) / (aa * bb - ab * ab); + a2 = (DotProduct( vspot, v2 ) - a1 * ab) / bb; + + // Test center to sample vector for inclusion between center to vertex vectors (Use dot product of vectors) + if ( a1 >= 0.0 && a2 >= 0.0) + { + // calculate distance from edge to pos + Vector temp; + float scale; + + // Interpolate between the center and edge normals based on sample position + scale = 1.0 - a1 - a2; + VectorScale( fn->facenormal, scale, phongnormal ); + VectorScale( n1, a1, temp ); + VectorAdd( phongnormal, temp, phongnormal ); + VectorScale( n2, a2, temp ); + VectorAdd( phongnormal, temp, phongnormal ); + Assert( VectorLength( phongnormal ) > 1.0e-20 ); + VectorNormalize( phongnormal ); + + /* + if (a1 > 1 || a2 > 1 || a1 + a2 > 1) + { + Msg("\n%.2f %.2f\n", a1, a2 ); + Msg("%.2f %.2f %.2f\n", v1[0], v1[1], v1[2] ); + Msg("%.2f %.2f %.2f\n", v2[0], v2[1], v2[2] ); + Msg("%.2f %.2f %.2f\n", vspot[0], vspot[1], vspot[2] ); + exit(1); + + a1 = 0; + } + */ + /* + phongnormal[0] = (((j + 1) & 4) != 0) * 255; + phongnormal[1] = (((j + 1) & 2) != 0) * 255; + phongnormal[2] = (((j + 1) & 1) != 0) * 255; + */ + return; + } + } + } +} + +void GetPhongNormal( int facenum, FourVectors const& spot, FourVectors& phongnormal ) +{ + int j; + dface_t *f = &g_pFaces[facenum]; + // dplane_t *p = &dplanes[f->planenum]; + Vector facenormal; + FourVectors vspot; + + VectorCopy( dplanes[f->planenum].normal, facenormal ); + phongnormal.DuplicateVector( facenormal ); + + FourVectors faceCentroid; + faceCentroid.DuplicateVector( face_centroids[facenum] ); + + if ( smoothing_threshold != 1 ) + { + faceneighbor_t *fn = &faceneighbor[facenum]; + + // Calculate modified point normal for surface + // Use the edge normals iff they are defined. Bend the surface towards the edge normal(s) + // Crude first attempt: find nearest edge normal and do a simple interpolation with facenormal. + // Second attempt: find edge points+center that bound the point and do a three-point triangulation(baricentric) + // Better third attempt: generate the point normals for all vertices and do baricentric triangulation. + + for ( j = 0; j < f->numedges; ++j ) + { + Vector v1, v2; + fltx4 a1, a2; + float aa, bb, ab; + int vert1, vert2; + + Vector& n1 = fn->normal[j]; + Vector& n2 = fn->normal[(j+1)%f->numedges]; + + vert1 = EdgeVertex( f, j ); + vert2 = EdgeVertex( f, j+1 ); + + Vector& p1 = dvertexes[vert1].point; + Vector& p2 = dvertexes[vert2].point; + + // Build vectors from the middle of the face to the edge vertexes and the sample pos. + VectorSubtract( p1, face_centroids[facenum], v1 ); + VectorSubtract( p2, face_centroids[facenum], v2 ); + //VectorSubtract( spot, face_centroids[facenum], vspot ); + vspot = spot; + vspot -= faceCentroid; + aa = DotProduct( v1, v1 ); + bb = DotProduct( v2, v2 ); + ab = DotProduct( v1, v2 ); + //a1 = (bb * DotProduct( v1, vspot ) - ab * DotProduct( vspot, v2 )) / (aa * bb - ab * ab); + a1 = ReciprocalSIMD( ReplicateX4( aa * bb - ab * ab ) ); + a1 = MulSIMD( a1, SubSIMD( MulSIMD( ReplicateX4( bb ), vspot * v1 ), MulSIMD( ReplicateX4( ab ), vspot * v2 ) ) ); + //a2 = (DotProduct( vspot, v2 ) - a1 * ab) / bb; + a2 = ReciprocalSIMD( ReplicateX4( bb ) ); + a2 = MulSIMD( a2, SubSIMD( vspot * v2, MulSIMD( a1, ReplicateX4( ab ) ) ) ); + + fltx4 resultMask = AndSIMD( CmpGeSIMD( a1, Four_Zeros ), CmpGeSIMD( a2, Four_Zeros ) ); + + if ( !TestSignSIMD( resultMask ) ) + continue; + + // Store the old phong normal to avoid overwriting already computed phong normals + FourVectors oldPhongNormal = phongnormal; + + // calculate distance from edge to pos + FourVectors temp; + fltx4 scale; + + // Interpolate between the center and edge normals based on sample position + scale = SubSIMD( SubSIMD( Four_Ones, a1 ), a2 ); + phongnormal.DuplicateVector( fn->facenormal ); + phongnormal *= scale; + temp.DuplicateVector( n1 ); + temp *= a1; + phongnormal += temp; + temp.DuplicateVector( n2 ); + temp *= a2; + phongnormal += temp; + + // restore the old phong normals + phongnormal.x = AddSIMD( AndSIMD( resultMask, phongnormal.x ), AndNotSIMD( resultMask, oldPhongNormal.x ) ); + phongnormal.y = AddSIMD( AndSIMD( resultMask, phongnormal.y ), AndNotSIMD( resultMask, oldPhongNormal.y ) ); + phongnormal.z = AddSIMD( AndSIMD( resultMask, phongnormal.z ), AndNotSIMD( resultMask, oldPhongNormal.z ) ); + } + + phongnormal.VectorNormalize(); + } +} + + + +int GetVisCache( int lastoffset, int cluster, byte *pvs ) +{ + // get the PVS for the pos to limit the number of checks + if ( !visdatasize ) + { + memset (pvs, 255, (dvis->numclusters+7)/8 ); + lastoffset = -1; + } + else + { + if (cluster < 0) + { + // Error, point embedded in wall + // sampled[0][1] = 255; + memset (pvs, 255, (dvis->numclusters+7)/8 ); + lastoffset = -1; + } + else + { + int thisoffset = dvis->bitofs[ cluster ][DVIS_PVS]; + if ( thisoffset != lastoffset ) + { + if ( thisoffset == -1 ) + { + Error ("visofs == -1"); + } + + DecompressVis (&dvisdata[thisoffset], pvs); + } + lastoffset = thisoffset; + } + } + return lastoffset; +} + + +void BuildPatchLights( int facenum ); + +void DumpSamples( int ndxFace, facelight_t *pFaceLight ) +{ + ThreadLock(); + + dface_t *pFace = &g_pFaces[ndxFace]; + if( pFace ) + { + bool bBumpped = ( ( texinfo[pFace->texinfo].flags & SURF_BUMPLIGHT ) != 0 ); + + for( int iStyle = 0; iStyle < 4; ++iStyle ) + { + if( pFace->styles[iStyle] != 255 ) + { + for ( int iBump = 0; iBump < 4; ++iBump ) + { + if ( iBump == 0 || ( iBump > 0 && bBumpped ) ) + { + for( int iSample = 0; iSample < pFaceLight->numsamples; ++iSample ) + { + sample_t *pSample = &pFaceLight->sample[iSample]; + WriteWinding( pFileSamples[iStyle][iBump], pSample->w, pFaceLight->light[iStyle][iBump][iSample].m_vecLighting ); + if( bDumpNormals ) + { + WriteNormal( pFileSamples[iStyle][iBump], pSample->pos, pSample->normal, 15.0f, pSample->normal * 255.0f ); + } + } + } + } + } + } + } + + ThreadUnlock(); +} + + +//----------------------------------------------------------------------------- +// Allocates light sample data +//----------------------------------------------------------------------------- +static inline void AllocateLightstyleSamples( facelight_t* fl, int styleIndex, int numnormals ) +{ + for (int n = 0; n < numnormals; ++n) + { + fl->light[styleIndex][n] = ( LightingValue_t* )calloc( fl->numsamples, sizeof(LightingValue_t ) ); + } +} + + +//----------------------------------------------------------------------------- +// Used to find an existing lightstyle on a face +//----------------------------------------------------------------------------- +static inline int FindLightstyle( dface_t* f, int lightstyle ) +{ + for (int k = 0; k < MAXLIGHTMAPS; k++) + { + if (f->styles[k] == lightstyle) + return k; + } + + return -1; +} + +static int FindOrAllocateLightstyleSamples( dface_t* f, facelight_t *fl, int lightstyle, int numnormals ) +{ + // Search the lightstyles associated with the face for a match + int k; + for (k = 0; k < MAXLIGHTMAPS; k++) + { + if (f->styles[k] == lightstyle) + break; + + // Found an empty entry, we can use it for a new lightstyle + if (f->styles[k] == 255) + { + AllocateLightstyleSamples( fl, k, numnormals ); + f->styles[k] = lightstyle; + break; + } + } + + // Check for overflow + if (k >= MAXLIGHTMAPS) + return -1; + + return k; +} + + +//----------------------------------------------------------------------------- +// Compute the illumination point + normal for the sample +//----------------------------------------------------------------------------- +static void ComputeIlluminationPointAndNormalsSSE( lightinfo_t const& l, FourVectors const &pos, FourVectors const &norm, SSE_SampleInfo_t* pInfo, int numSamples ) +{ + + Vector v[4]; + + pInfo->m_Points = pos; + bool computeNormals = ( pInfo->m_NormalCount > 1 && ( pInfo->m_IsDispFace || !l.isflat ) ); + + // FIXME: move sample point off the surface a bit, this is done so that + // light sampling will not be affected by a bug where raycasts will + // intersect with the face being lit. We really should just have that + // logic in GatherSampleLight + FourVectors faceNormal; + faceNormal.DuplicateVector( l.facenormal ); + pInfo->m_Points += faceNormal; + + if ( pInfo->m_IsDispFace ) + { + pInfo->m_PointNormals[0] = norm; + } + else if ( !l.isflat ) + { + // If the face isn't flat, use a phong-based normal instead + FourVectors modelorg; + modelorg.DuplicateVector( l.modelorg ); + FourVectors vecSample = pos; + vecSample -= modelorg; + GetPhongNormal( pInfo->m_FaceNum, vecSample, pInfo->m_PointNormals[0] ); + } + + if ( computeNormals ) + { + Vector bv[4][NUM_BUMP_VECTS]; + for ( int i = 0; i < 4; ++i ) + { + // TODO: using Vec may slow things down a bit + GetBumpNormals( pInfo->m_pTexInfo->textureVecsTexelsPerWorldUnits[0], + pInfo->m_pTexInfo->textureVecsTexelsPerWorldUnits[1], + l.facenormal, pInfo->m_PointNormals[0].Vec( i ), bv[i] ); + } + for ( int b = 0; b < NUM_BUMP_VECTS; ++b ) + { + pInfo->m_PointNormals[b+1].LoadAndSwizzle ( bv[0][b], bv[1][b], bv[2][b], bv[3][b] ); + } + } + + // TODO: this may slow things down a bit ( using Vec ) + for ( int i = 0; i < 4; ++i ) + pInfo->m_Clusters[i] = ClusterFromPoint( pos.Vec( i ) ); +} + +//----------------------------------------------------------------------------- +// Iterates over all lights and computes lighting at up to 4 sample points +//----------------------------------------------------------------------------- +static void GatherSampleLightAt4Points( SSE_SampleInfo_t& info, int sampleIdx, int numSamples ) +{ + SSE_sampleLightOutput_t out; + + // Iterate over all direct lights and add them to the particular sample + for (directlight_t *dl = activelights; dl != NULL; dl = dl->next) + { + // is this lights cluster visible? + fltx4 dotMask = Four_Zeros; + bool skipLight = true; + for( int s = 0; s < numSamples; s++ ) + { + if( PVSCheck( dl->pvs, info.m_Clusters[s] ) ) + { + dotMask = SetComponentSIMD( dotMask, s, 1.0f ); + skipLight = false; + } + } + if ( skipLight ) + continue; + + GatherSampleLightSSE( out, dl, info.m_FaceNum, info.m_Points, info.m_PointNormals, info.m_NormalCount, info.m_iThread ); + + // Apply the PVS check filter and compute falloff x dot + fltx4 fxdot[NUM_BUMP_VECTS + 1]; + skipLight = true; + for ( int b = 0; b < info.m_NormalCount; b++ ) + { + fxdot[b] = MulSIMD( out.m_flDot[b], dotMask ); + fxdot[b] = MulSIMD( fxdot[b], out.m_flFalloff ); + if ( !IsAllZeros( fxdot[b] ) ) + { + skipLight = false; + } + } + if ( skipLight ) + continue; + + // Figure out the lightstyle for this particular sample + int lightStyleIndex = FindOrAllocateLightstyleSamples( info.m_pFace, info.m_pFaceLight, + dl->light.style, info.m_NormalCount ); + if (lightStyleIndex < 0) + { + if (info.m_WarnFace != info.m_FaceNum) + { + Warning ("\nWARNING: Too many light styles on a face at (%f, %f, %f)\n", + info.m_Points.x.m128_f32[0], info.m_Points.y.m128_f32[0], info.m_Points.z.m128_f32[0] ); + info.m_WarnFace = info.m_FaceNum; + } + continue; + } + + // pLightmaps is an array of the lightmaps for each normal direction, + // here's where the result of the sample gathering goes + LightingValue_t** pLightmaps = info.m_pFaceLight->light[lightStyleIndex]; + + // Incremental lighting only cares about lightstyle zero + if( g_pIncremental && (dl->light.style == 0) ) + { + for ( int i = 0; i < numSamples; i++ ) + { + g_pIncremental->AddLightToFace( dl->m_IncrementalID, info.m_FaceNum, sampleIdx + i, + info.m_LightmapSize, SubFloat( fxdot[0], i ), info.m_iThread ); + } + } + + for( int n = 0; n < info.m_NormalCount; ++n ) + { + for ( int i = 0; i < numSamples; i++ ) + { + pLightmaps[n][sampleIdx + i].AddLight( SubFloat( fxdot[n], i ), dl->light.intensity, SubFloat( out.m_flSunAmount, i ) ); + } + } + } +} + + + +//----------------------------------------------------------------------------- +// Iterates over all lights and computes lighting at a sample point +//----------------------------------------------------------------------------- +static void ResampleLightAt4Points( SSE_SampleInfo_t& info, int lightStyleIndex, int flags, LightingValue_t pLightmap[4][NUM_BUMP_VECTS+1] ) +{ + SSE_sampleLightOutput_t out; + + // Clear result + for ( int i = 0; i < 4; ++i ) + { + for ( int n = 0; n < info.m_NormalCount; ++n ) + { + pLightmap[i][n].Zero(); + } + } + + // Iterate over all direct lights and add them to the particular sample + for (directlight_t *dl = activelights; dl != NULL; dl = dl->next) + { + if ((flags & AMBIENT_ONLY) && (dl->light.type != emit_skyambient)) + continue; + + if ((flags & NON_AMBIENT_ONLY) && (dl->light.type == emit_skyambient)) + continue; + + // Only add contributions that match the lightstyle + Assert( lightStyleIndex <= MAXLIGHTMAPS ); + Assert( info.m_pFace->styles[lightStyleIndex] != 255 ); + if (dl->light.style != info.m_pFace->styles[lightStyleIndex]) + continue; + + // is this lights cluster visible? + fltx4 dotMask = Four_Zeros; + bool skipLight = true; + for( int s = 0; s < 4; s++ ) + { + if( PVSCheck( dl->pvs, info.m_Clusters[s] ) ) + { + dotMask = SetComponentSIMD( dotMask, s, 1.0f ); + skipLight = false; + } + } + if ( skipLight ) + continue; + + // NOTE: Notice here that if the light is on the back side of the face + // (tested by checking the dot product of the face normal and the light position) + // we don't want it to contribute to *any* of the bumped lightmaps. It glows + // in disturbing ways if we don't do this. + GatherSampleLightSSE( out, dl, info.m_FaceNum, info.m_Points, info.m_PointNormals, info.m_NormalCount, info.m_iThread ); + + // Apply the PVS check filter and compute falloff x dot + fltx4 fxdot[NUM_BUMP_VECTS + 1]; + for ( int b = 0; b < info.m_NormalCount; b++ ) + { + fxdot[b] = MulSIMD( out.m_flFalloff, out.m_flDot[b] ); + fxdot[b] = MulSIMD( fxdot[b], dotMask ); + } + + // Compute the contributions to each of the bumped lightmaps + // The first sample is for non-bumped lighting. + // The other sample are for bumpmapping. + for( int i = 0; i < 4; ++i ) + { + for( int n = 0; n < info.m_NormalCount; ++n ) + { + pLightmap[i][n].AddLight( SubFloat( fxdot[n], i ), dl->light.intensity, SubFloat( out.m_flSunAmount, i ) ); + } + } + } +} + +bool PointsInWinding ( FourVectors const & point, winding_t *w, int &invalidBits ) +{ + FourVectors edge, toPt, cross, testCross, p0, p1; + fltx4 invalidMask; + + // + // get the first normal to test + // + p0.DuplicateVector( w->p[0] ); + p1.DuplicateVector( w->p[1] ); + toPt = point; + toPt -= p0; + edge = p1; + edge -= p0; + testCross = edge ^ toPt; + testCross.VectorNormalizeFast(); + + for( int ndxPt = 1; ndxPt < w->numpoints; ndxPt++ ) + { + p0.DuplicateVector( w->p[ndxPt] ); + p1.DuplicateVector( w->p[(ndxPt+1)%w->numpoints] ); + toPt = point; + toPt -= p0; + edge = p1; + edge -= p0; + cross = edge ^ toPt; + cross.VectorNormalizeFast(); + + fltx4 dot = cross * testCross; + invalidMask = OrSIMD( invalidMask, CmpLtSIMD( dot, Four_Zeros ) ); + + invalidBits = TestSignSIMD ( invalidMask ); + if ( invalidBits == 0xF ) + return false; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Perform supersampling at a particular point +//----------------------------------------------------------------------------- +static int SupersampleLightAtPoint( lightinfo_t& l, SSE_SampleInfo_t& info, + int sampleIndex, int lightStyleIndex, LightingValue_t *pLight, int flags ) +{ + sample_t& sample = info.m_pFaceLight->sample[sampleIndex]; + + // Get the position of the original sample in lightmapspace + Vector2D temp; + WorldToLuxelSpace( &l, sample.pos, temp ); + Vector sampleLightOrigin( temp[0], temp[1], 0.0f ); + + // Some parameters related to supersampling + float sampleWidth = ( flags & NON_AMBIENT_ONLY ) ? 4 : 2; + float cscale = 1.0f / sampleWidth; + float csshift = -((sampleWidth - 1) * cscale) / 2.0; + + // Clear out the light values + for (int i = 0; i < info.m_NormalCount; ++i ) + pLight[i].Zero(); + + int subsampleCount = 0; + + FourVectors superSampleNormal; + superSampleNormal.DuplicateVector( sample.normal ); + + FourVectors superSampleLightCoord; + FourVectors superSamplePosition; + + if ( flags & NON_AMBIENT_ONLY ) + { + float aRow[4]; + for ( int coord = 0; coord < 4; ++coord ) + aRow[coord] = csshift + coord * cscale; + fltx4 sseRow = LoadUnalignedSIMD( aRow ); + + for (int s = 0; s < 4; ++s) + { + // make sure the coordinate is inside of the sample's winding and when normalizing + // below use the number of samples used, not just numsamples and some of them + // will be skipped if they are not inside of the winding + superSampleLightCoord.DuplicateVector( sampleLightOrigin ); + superSampleLightCoord.x = AddSIMD( superSampleLightCoord.x, ReplicateX4( aRow[s] ) ); + superSampleLightCoord.y = AddSIMD( superSampleLightCoord.y, sseRow ); + + // Figure out where the supersample exists in the world, and make sure + // it lies within the sample winding + LuxelSpaceToWorld( &l, superSampleLightCoord[0], superSampleLightCoord[1], superSamplePosition ); + + // A winding should exist only if the sample wasn't a uniform luxel, or if g_bDumpPatches is true. + int invalidBits = 0; + if ( sample.w && !PointsInWinding( superSamplePosition, sample.w, invalidBits ) ) + continue; + + // Compute the super-sample illumination point and normal + // We're assuming the flat normal is the same for all supersamples + ComputeIlluminationPointAndNormalsSSE( l, superSamplePosition, superSampleNormal, &info, 4 ); + + // Resample the non-ambient light at this point... + LightingValue_t result[4][NUM_BUMP_VECTS+1]; + ResampleLightAt4Points( info, lightStyleIndex, NON_AMBIENT_ONLY, result ); + + // Got more subsamples + for ( int i = 0; i < 4; i++ ) + { + if ( !( ( invalidBits >> i ) & 0x1 ) ) + { + for ( int n = 0; n < info.m_NormalCount; ++n ) + { + pLight[n].AddLight( result[i][n] ); + } + ++subsampleCount; + } + } + } + } + else + { + FourVectors superSampleOffsets; + superSampleOffsets.LoadAndSwizzle( Vector( csshift, csshift, 0 ), Vector( csshift, csshift + cscale, 0), + Vector( csshift + cscale, csshift, 0 ), Vector( csshift + cscale, csshift + cscale, 0 ) ); + superSampleLightCoord.DuplicateVector( sampleLightOrigin ); + superSampleLightCoord += superSampleOffsets; + + LuxelSpaceToWorld( &l, superSampleLightCoord[0], superSampleLightCoord[1], superSamplePosition ); + + int invalidBits = 0; + if ( sample.w && !PointsInWinding( superSamplePosition, sample.w, invalidBits ) ) + return 0; + + ComputeIlluminationPointAndNormalsSSE( l, superSamplePosition, superSampleNormal, &info, 4 ); + + LightingValue_t result[4][NUM_BUMP_VECTS+1]; + ResampleLightAt4Points( info, lightStyleIndex, AMBIENT_ONLY, result ); + + // Got more subsamples + for ( int i = 0; i < 4; i++ ) + { + if ( !( ( invalidBits >> i ) & 0x1 ) ) + { + for ( int n = 0; n < info.m_NormalCount; ++n ) + { + pLight[n].AddLight( result[i][n] ); + } + ++subsampleCount; + } + } + } + + return subsampleCount; +} + + +//----------------------------------------------------------------------------- +// Compute gradients of a lightmap +//----------------------------------------------------------------------------- +static void ComputeLightmapGradients( SSE_SampleInfo_t& info, bool const* pHasProcessedSample, + float* pIntensity, float* gradient ) +{ + int w = info.m_LightmapWidth; + int h = info.m_LightmapHeight; + facelight_t* fl = info.m_pFaceLight; + + for (int i=0 ; i<fl->numsamples ; i++) + { + // Don't supersample the same sample twice + if (pHasProcessedSample[i]) + continue; + + gradient[i] = 0.0f; + sample_t& sample = fl->sample[i]; + + // Choose the maximum gradient of all bumped lightmap intensities + for ( int n = 0; n < info.m_NormalCount; ++n ) + { + int j = n * info.m_LightmapSize + sample.s + sample.t * w; + + if (sample.t > 0) + { + if (sample.s > 0) gradient[i] = max( gradient[i], fabs( pIntensity[j] - pIntensity[j-1-w] ) ); + gradient[i] = max( gradient[i], fabs( pIntensity[j] - pIntensity[j-w] ) ); + if (sample.s < w-1) gradient[i] = max( gradient[i], fabs( pIntensity[j] - pIntensity[j+1-w] ) ); + } + if (sample.t < h-1) + { + if (sample.s > 0) gradient[i] = max( gradient[i], fabs( pIntensity[j] - pIntensity[j-1+w] ) ); + gradient[i] = max( gradient[i], fabs( pIntensity[j] - pIntensity[j+w] ) ); + if (sample.s < w-1) gradient[i] = max( gradient[i], fabs( pIntensity[j] - pIntensity[j+1+w] ) ); + } + if (sample.s > 0) gradient[i] = max( gradient[i], fabs( pIntensity[j] - pIntensity[j-1] ) ); + if (sample.s < w-1) gradient[i] = max( gradient[i], fabs( pIntensity[j] - pIntensity[j+1] ) ); + } + } +} + +//----------------------------------------------------------------------------- +// ComputeLuxelIntensity... +//----------------------------------------------------------------------------- +static inline void ComputeLuxelIntensity( SSE_SampleInfo_t& info, int sampleIdx, + LightingValue_t **ppLightSamples, float* pSampleIntensity ) +{ + // Compute a separate intensity for each + sample_t& sample = info.m_pFaceLight->sample[sampleIdx]; + int destIdx = sample.s + sample.t * info.m_LightmapWidth; + for (int n = 0; n < info.m_NormalCount; ++n) + { + float intensity = ppLightSamples[n][sampleIdx].Intensity(); + + // convert to a linear perception space + pSampleIntensity[n * info.m_LightmapSize + destIdx] = pow( intensity / 256.0, 1.0 / 2.2 ); + } +} + +//----------------------------------------------------------------------------- +// Compute the maximum intensity based on all bumped lighting +//----------------------------------------------------------------------------- +static void ComputeSampleIntensities( SSE_SampleInfo_t& info, LightingValue_t **ppLightSamples, float* pSampleIntensity ) +{ + for (int i=0; i<info.m_pFaceLight->numsamples; i++) + { + ComputeLuxelIntensity( info, i, ppLightSamples, pSampleIntensity ); + } +} + +//----------------------------------------------------------------------------- +// Perform supersampling on a particular lightstyle +//----------------------------------------------------------------------------- +static void BuildSupersampleFaceLights( lightinfo_t& l, SSE_SampleInfo_t& info, int lightstyleIndex ) +{ + LightingValue_t pAmbientLight[NUM_BUMP_VECTS+1]; + LightingValue_t pDirectLight[NUM_BUMP_VECTS+1]; + + // This is used to make sure we don't supersample a light sample more than once + int processedSampleSize = info.m_LightmapSize * sizeof(bool); + bool* pHasProcessedSample = (bool*)stackalloc( processedSampleSize ); + memset( pHasProcessedSample, 0, processedSampleSize ); + + // This is used to compute a simple gradient computation of the light samples + // We're going to store the maximum intensity of all bumped samples at each sample location + float* pGradient = (float*)stackalloc( info.m_pFaceLight->numsamples * sizeof(float) ); + float* pSampleIntensity = (float*)stackalloc( info.m_NormalCount * info.m_LightmapSize * sizeof(float) ); + + // Compute the maximum intensity of all lighting associated with this lightstyle + // for all bumped lighting + LightingValue_t **ppLightSamples = info.m_pFaceLight->light[lightstyleIndex]; + ComputeSampleIntensities( info, ppLightSamples, pSampleIntensity ); + + Vector *pVisualizePass = NULL; + if (debug_extra) + { + int visualizationSize = info.m_pFaceLight->numsamples * sizeof(Vector); + pVisualizePass = (Vector*)stackalloc( visualizationSize ); + memset( pVisualizePass, 0, visualizationSize ); + } + + // What's going on here is that we're looking for large lighting discontinuities + // (large light intensity gradients) as a clue that we should probably be supersampling + // in that area. Because the supersampling operation will cause lighting changes, + // we've found that it's good to re-check the gradients again and see if any other + // areas should be supersampled as a result of the previous pass. Keep going + // until all the gradients are reasonable or until we hit a max number of passes + bool do_anotherpass = true; + int pass = 1; + while (do_anotherpass && pass <= extrapasses) + { + // Look for lighting discontinuities to see what we should be supersampling + ComputeLightmapGradients( info, pHasProcessedSample, pSampleIntensity, pGradient ); + + do_anotherpass = false; + + // Now check all of the samples and supersample those which we have + // marked as having high gradients + for (int i=0 ; i<info.m_pFaceLight->numsamples; ++i) + { + // Don't supersample the same sample twice + if (pHasProcessedSample[i]) + continue; + + // Don't supersample if the lighting is pretty uniform near the sample + if (pGradient[i] < 0.0625) + continue; + + // Joy! We're supersampling now, and we therefore must do another pass + // Also, we need never bother with this sample again + pHasProcessedSample[i] = true; + do_anotherpass = true; + + if (debug_extra) + { + // Mark the little visualization bitmap with a color indicating + // which pass it was updated on. + pVisualizePass[i][0] = (pass & 1) * 255; + pVisualizePass[i][1] = (pass & 2) * 128; + pVisualizePass[i][2] = (pass & 4) * 64; + } + + // Supersample the ambient light for each bump direction vector + int ambientSupersampleCount = SupersampleLightAtPoint( l, info, i, lightstyleIndex, pAmbientLight, AMBIENT_ONLY ); + + // Supersample the non-ambient light for each bump direction vector + int directSupersampleCount = SupersampleLightAtPoint( l, info, i, lightstyleIndex, pDirectLight, NON_AMBIENT_ONLY ); + + // Because of sampling problems, small area triangles may have no samples. + // In this case, just use what we already have + if ( ambientSupersampleCount > 0 && directSupersampleCount > 0 ) + { + // Add the ambient + directional terms together, stick it back into the lightmap + for (int n = 0; n < info.m_NormalCount; ++n) + { + ppLightSamples[n][i].Zero(); + ppLightSamples[n][i].AddWeighted( pDirectLight[n],1.0f / directSupersampleCount ); + ppLightSamples[n][i].AddWeighted( pAmbientLight[n], 1.0f / ambientSupersampleCount ); + } + + // Recompute the luxel intensity based on the supersampling + ComputeLuxelIntensity( info, i, ppLightSamples, pSampleIntensity ); + } + + } + + // We've finished another pass + pass++; + } + + if (debug_extra) + { + // Copy colors representing which supersample pass the sample was messed with + // into the actual lighting values so we can visualize it + for (int i=0 ; i<info.m_pFaceLight->numsamples ; ++i) + { + for (int j = 0; j <info.m_NormalCount; ++j) + { + VectorCopy( pVisualizePass[i], ppLightSamples[j][i].m_vecLighting ); + } + } + } +} + +void InitLightinfo( lightinfo_t *pl, int facenum ) +{ + dface_t *f; + + f = &g_pFaces[facenum]; + + memset (pl, 0, sizeof(*pl)); + pl->facenum = facenum; + + pl->face = f; + + // + // rotate plane + // + VectorCopy (dplanes[f->planenum].normal, pl->facenormal); + pl->facedist = dplanes[f->planenum].dist; + + // get the origin offset for rotating bmodels + VectorCopy (face_offset[facenum], pl->modelorg); + + CalcFaceVectors( pl ); + + // figure out if the surface is flat + pl->isflat = true; + if (smoothing_threshold != 1) + { + faceneighbor_t *fn = &faceneighbor[facenum]; + + for (int j=0 ; j<f->numedges ; j++) + { + float dot = DotProduct( pl->facenormal, fn->normal[j] ); + if (dot < 1.0 - EQUAL_EPSILON) + { + pl->isflat = false; + break; + } + } + } +} + +static void InitSampleInfo( lightinfo_t const& l, int iThread, SSE_SampleInfo_t& info ) +{ + info.m_LightmapWidth = l.face->m_LightmapTextureSizeInLuxels[0]+1; + info.m_LightmapHeight = l.face->m_LightmapTextureSizeInLuxels[1]+1; + info.m_LightmapSize = info.m_LightmapWidth * info.m_LightmapHeight; + + // How many lightmaps are we going to need? + info.m_pTexInfo = &texinfo[l.face->texinfo]; + info.m_NormalCount = (info.m_pTexInfo->flags & SURF_BUMPLIGHT) ? NUM_BUMP_VECTS + 1 : 1; + info.m_FaceNum = l.facenum; + info.m_pFace = l.face; + info.m_pFaceLight = &facelight[info.m_FaceNum]; + info.m_IsDispFace = ValidDispFace( info.m_pFace ); + info.m_iThread = iThread; + info.m_WarnFace = -1; + + info.m_NumSamples = info.m_pFaceLight->numsamples; + info.m_NumSampleGroups = ( info.m_NumSamples & 0x3) ? ( info.m_NumSamples / 4 ) + 1 : ( info.m_NumSamples / 4 ); + + // initialize normals if the surface is flat + if (l.isflat) + { + info.m_PointNormals[0].DuplicateVector( l.facenormal ); + + // use facenormal along with the smooth normal to build the three bump map vectors + if( info.m_NormalCount > 1 ) + { + Vector bumpVects[NUM_BUMP_VECTS]; + GetBumpNormals( info.m_pTexInfo->textureVecsTexelsPerWorldUnits[0], + info.m_pTexInfo->textureVecsTexelsPerWorldUnits[1], l.facenormal, + l.facenormal, bumpVects );//&info.m_PointNormal[1] ); + + for ( int b = 0; b < NUM_BUMP_VECTS; ++b ) + { + info.m_PointNormals[b + 1].DuplicateVector( bumpVects[b] ); + } + } + } +} + +void BuildFacelights (int iThread, int facenum) +{ + int i, j; + + lightinfo_t l; + dface_t *f; + facelight_t *fl; + SSE_SampleInfo_t sampleInfo; + directlight_t *dl; + Vector spot; + Vector v[4], n[4]; + + if( g_bInterrupt ) + return; + + // FIXME: Is there a better way to do this? Like, in RunThreadsOn, for instance? + // Don't pay this cost unless we have to; this is super perf-critical code. + if (g_pIncremental) + { + // Both threads will be accessing this so it needs to be protected or else thread A + // will load it in and thread B will increment it but its increment will be + // overwritten by thread A when thread A writes it back. + ThreadLock(); + ++g_iCurFace; + ThreadUnlock(); + } + + // some surfaces don't need lightmaps + f = &g_pFaces[facenum]; + f->lightofs = -1; + for (j=0 ; j<MAXLIGHTMAPS ; j++) + f->styles[j] = 255; + + // Trivial-reject the whole face? + if( !( g_FacesVisibleToLights[facenum>>3] & (1 << (facenum & 7)) ) ) + return; + + if ( texinfo[f->texinfo].flags & TEX_SPECIAL) + return; // non-lit texture + + // check for patches for this face. If none it must be degenerate. Ignore. + if( g_FacePatches.Element( facenum ) == g_FacePatches.InvalidIndex() ) + return; + + fl = &facelight[facenum]; + + InitLightinfo( &l, facenum ); + CalcPoints( &l, fl, facenum ); + InitSampleInfo( l, iThread, sampleInfo ); + + // Allocate sample positions/normals to SSE + int numGroups = ( fl->numsamples & 0x3) ? ( fl->numsamples / 4 ) + 1 : ( fl->numsamples / 4 ); + + // always allocate style 0 lightmap + f->styles[0] = 0; + AllocateLightstyleSamples( fl, 0, sampleInfo.m_NormalCount ); + + // sample the lights at each sample location + for ( int grp = 0; grp < numGroups; ++grp ) + { + int nSample = 4 * grp; + + sample_t *sample = sampleInfo.m_pFaceLight->sample + nSample; + int numSamples = min ( 4, sampleInfo.m_pFaceLight->numsamples - nSample ); + + FourVectors positions; + FourVectors normals; + + for ( int i = 0; i < 4; i++ ) + { + v[i] = ( i < numSamples ) ? sample[i].pos : sample[numSamples - 1].pos; + n[i] = ( i < numSamples ) ? sample[i].normal : sample[numSamples - 1].normal; + } + positions.LoadAndSwizzle( v[0], v[1], v[2], v[3] ); + normals.LoadAndSwizzle( n[0], n[1], n[2], n[3] ); + + ComputeIlluminationPointAndNormalsSSE( l, positions, normals, &sampleInfo, numSamples ); + + // Fixup sample normals in case of smooth faces + if ( !l.isflat ) + { + for ( int i = 0; i < numSamples; i++ ) + sample[i].normal = sampleInfo.m_PointNormals[0].Vec( i ); + } + + // Iterate over all the lights and add their contribution to this group of spots + GatherSampleLightAt4Points( sampleInfo, nSample, numSamples ); + } + + // Tell the incremental light manager that we're done with this face. + if( g_pIncremental ) + { + for (dl = activelights; dl != NULL; dl = dl->next) + { + // Only deal with lightstyle 0 for incremental lighting + if (dl->light.style == 0) + g_pIncremental->FinishFace( dl->m_IncrementalID, facenum, iThread ); + } + + // Don't have to deal with patch lights (only direct lighting is used) + // or supersampling + return; + } + + // get rid of the -extra functionality on displacement surfaces + if (do_extra && !sampleInfo.m_IsDispFace) + { + // For each lightstyle, perform a supersampling pass + for ( i = 0; i < MAXLIGHTMAPS; ++i ) + { + // Stop when we run out of lightstyles + if (f->styles[i] == 255) + break; + + BuildSupersampleFaceLights( l, sampleInfo, i ); + } + } + + if (!g_bUseMPI) + { + // + // This is done on the master node when MPI is used + // + BuildPatchLights( facenum ); + } + + if( g_bDumpPatches ) + { + DumpSamples( facenum, fl ); + } + else + { + FreeSampleWindings( fl ); + } + +} + +void BuildPatchLights( int facenum ) +{ + int i, k; + + CPatch *patch; + + dface_t *f = &g_pFaces[facenum]; + facelight_t *fl = &facelight[facenum]; + + for( k = 0; k < MAXLIGHTMAPS; k++ ) + { + if (f->styles[k] == 0) + break; + } + + if (k >= MAXLIGHTMAPS) + return; + + for (i = 0; i < fl->numsamples; i++) + { + AddSampleToPatch( &fl->sample[i], fl->light[k][0][i], facenum); + } + + // check for a valid face + if( g_FacePatches.Element( facenum ) == g_FacePatches.InvalidIndex() ) + return; + + // push up sampled light to parents (children always exist first in the list) + CPatch *pNextPatch; + for( patch = &g_Patches.Element( g_FacePatches.Element( facenum ) ); patch; patch = pNextPatch ) + { + // next patch + pNextPatch = NULL; + if( patch->ndxNext != g_Patches.InvalidIndex() ) + { + pNextPatch = &g_Patches.Element( patch->ndxNext ); + } + + // skip patches without parents + if( patch->parent == g_Patches.InvalidIndex() ) +// if (patch->parent == -1) + continue; + + CPatch *parent = &g_Patches.Element( patch->parent ); + + parent->samplearea += patch->samplearea; + VectorAdd( parent->samplelight, patch->samplelight, parent->samplelight ); + } + + // average up the direct light on each patch for radiosity + if (numbounce > 0) + { + for( patch = &g_Patches.Element( g_FacePatches.Element( facenum ) ); patch; patch = pNextPatch ) + { + // next patch + pNextPatch = NULL; + if( patch->ndxNext != g_Patches.InvalidIndex() ) + { + pNextPatch = &g_Patches.Element( patch->ndxNext ); + } + + if (patch->samplearea) + { + float scale; + Vector v; + scale = 1.0 / patch->samplearea; + + VectorScale( patch->samplelight, scale, v ); + VectorAdd( patch->totallight.light[0], v, patch->totallight.light[0] ); + VectorAdd( patch->directlight, v, patch->directlight ); + } + } + } + + // pull totallight from children (children always exist first in the list) + for( patch = &g_Patches.Element( g_FacePatches.Element( facenum ) ); patch; patch = pNextPatch ) + { + // next patch + pNextPatch = NULL; + if( patch->ndxNext != g_Patches.InvalidIndex() ) + { + pNextPatch = &g_Patches.Element( patch->ndxNext ); + } + + if ( patch->child1 != g_Patches.InvalidIndex() ) + { + float s1, s2; + CPatch *child1; + CPatch *child2; + + child1 = &g_Patches.Element( patch->child1 ); + child2 = &g_Patches.Element( patch->child2 ); + + s1 = child1->area / (child1->area + child2->area); + s2 = child2->area / (child1->area + child2->area); + + VectorScale( child1->totallight.light[0], s1, patch->totallight.light[0] ); + VectorMA( patch->totallight.light[0], s2, child2->totallight.light[0], patch->totallight.light[0] ); + + VectorCopy( patch->totallight.light[0], patch->directlight ); + } + } + + bool needsBumpmap = false; + if( texinfo[f->texinfo].flags & SURF_BUMPLIGHT ) + { + needsBumpmap = true; + } + + // add an ambient term if desired + if (ambient[0] || ambient[1] || ambient[2]) + { + for( int j=0; j < MAXLIGHTMAPS && f->styles[j] != 255; j++ ) + { + if ( f->styles[j] == 0 ) + { + for (i = 0; i < fl->numsamples; i++) + { + fl->light[j][0][i].m_vecLighting += ambient; + if( needsBumpmap ) + { + fl->light[j][1][i].m_vecLighting += ambient; + fl->light[j][2][i].m_vecLighting += ambient; + fl->light[j][3][i].m_vecLighting += ambient; + } + } + break; + } + } + } + + // light from dlight_threshold and above is sent out, but the + // texture itself should still be full bright + +#if 0 + // if( VectorAvg( g_FacePatches[facenum]->baselight ) >= dlight_threshold) // Now all lighted surfaces glow + { + for( j=0; j < MAXLIGHTMAPS && f->styles[j] != 255; j++ ) + { + if ( f->styles[j] == 0 ) + { + // BUG: shouldn't this be done for all patches on the face? + for (i=0 ; i<fl->numsamples ; i++) + { + // garymctchange + VectorAdd( fl->light[j][0][i], g_FacePatches[facenum]->baselight, fl->light[j][0][i] ); + if( needsBumpmap ) + { + for( bumpSample = 1; bumpSample < NUM_BUMP_VECTS + 1; bumpSample++ ) + { + VectorAdd( fl->light[j][bumpSample][i], g_FacePatches[facenum]->baselight, fl->light[j][bumpSample][i] ); + } + } + } + break; + } + } + } +#endif +} + + +/* + ============= + PrecompLightmapOffsets + ============= +*/ + +void PrecompLightmapOffsets() +{ + int facenum; + dface_t *f; + int lightstyles; + int lightdatasize = 0; + + // NOTE: We store avg face light data in this lump *before* the lightmap data itself + // in *reverse order* of the way the lightstyles appear in the styles array. + for( facenum = 0; facenum < numfaces; facenum++ ) + { + f = &g_pFaces[facenum]; + + if ( texinfo[f->texinfo].flags & TEX_SPECIAL) + continue; // non-lit texture + + if ( dlight_map != 0 ) + f->styles[1] = 0; + + for (lightstyles=0; lightstyles < MAXLIGHTMAPS; lightstyles++ ) + { + if ( f->styles[lightstyles] == 255 ) + break; + } + + if ( !lightstyles ) + continue; + + // Reserve room for the avg light color data + lightdatasize += lightstyles * 4; + + f->lightofs = lightdatasize; + + bool needsBumpmap = false; + if( texinfo[f->texinfo].flags & SURF_BUMPLIGHT ) + { + needsBumpmap = true; + } + + int nLuxels = (f->m_LightmapTextureSizeInLuxels[0]+1) * (f->m_LightmapTextureSizeInLuxels[1]+1); + if( needsBumpmap ) + { + lightdatasize += nLuxels * 4 * lightstyles * ( NUM_BUMP_VECTS + 1 ); + } + else + { + lightdatasize += nLuxels * 4 * lightstyles; + } + } + + // The incremental lighting code needs us to preserve the contents of dlightdata + // since it only recomposites lighting for faces that have lights that touch them. + if( g_pIncremental && pdlightdata->Count() ) + return; + + pdlightdata->SetSize( lightdatasize ); +} + +// Clamp the three values for bumped lighting such that we trade off directionality for brightness. +static void ColorClampBumped( Vector& color1, Vector& color2, Vector& color3 ) +{ + Vector maxs; + Vector *colors[3] = { &color1, &color2, &color3 }; + maxs[0] = VectorMaximum( color1 ); + maxs[1] = VectorMaximum( color2 ); + maxs[2] = VectorMaximum( color3 ); + + // HACK! Clean this up, and add some else statements +#define CONDITION(a,b,c) do { if( maxs[a] >= maxs[b] && maxs[b] >= maxs[c] ) { order[0] = a; order[1] = b; order[2] = c; } } while( 0 ) + + int order[3]; + CONDITION(0,1,2); + CONDITION(0,2,1); + CONDITION(1,0,2); + CONDITION(1,2,0); + CONDITION(2,0,1); + CONDITION(2,1,0); + + int i; + for( i = 0; i < 3; i++ ) + { + float max = VectorMaximum( *colors[order[i]] ); + if( max <= 1.0f ) + { + continue; + } + // This channel is too bright. . take half of the amount that we are over and + // add it to the other two channel. + float factorToRedist = ( max - 1.0f ) / max; + Vector colorToRedist = factorToRedist * *colors[order[i]]; + *colors[order[i]] -= colorToRedist; + colorToRedist *= 0.5f; + *colors[order[(i+1)%3]] += colorToRedist; + *colors[order[(i+2)%3]] += colorToRedist; + } + + ColorClamp( color1 ); + ColorClamp( color2 ); + ColorClamp( color3 ); + + if( color1[0] < 0.f ) color1[0] = 0.f; + if( color1[1] < 0.f ) color1[1] = 0.f; + if( color1[2] < 0.f ) color1[2] = 0.f; + if( color2[0] < 0.f ) color2[0] = 0.f; + if( color2[1] < 0.f ) color2[1] = 0.f; + if( color2[2] < 0.f ) color2[2] = 0.f; + if( color3[0] < 0.f ) color3[0] = 0.f; + if( color3[1] < 0.f ) color3[1] = 0.f; + if( color3[2] < 0.f ) color3[2] = 0.f; +} + +static void LinearToBumpedLightmap( + const float *linearColor, + const float *linearBumpColor1, + const float *linearBumpColor2, + const float *linearBumpColor3, + unsigned char *ret, + unsigned char *retBump1, + unsigned char *retBump2, + unsigned char *retBump3 ) +{ + const Vector &linearBump1 = *( ( const Vector * )linearBumpColor1 ); + const Vector &linearBump2 = *( ( const Vector * )linearBumpColor2 ); + const Vector &linearBump3 = *( ( const Vector * )linearBumpColor3 ); + + Vector gammaGoal; + // gammaGoal is premultiplied by 1/overbright, which we want + gammaGoal[0] = LinearToVertexLight( linearColor[0] ); + gammaGoal[1] = LinearToVertexLight( linearColor[1] ); + gammaGoal[2] = LinearToVertexLight( linearColor[2] ); + Vector bumpAverage = linearBump1; + bumpAverage += linearBump2; + bumpAverage += linearBump3; + bumpAverage *= ( 1.0f / 3.0f ); + + Vector correctionScale; + if( *( int * )&bumpAverage[0] != 0 && *( int * )&bumpAverage[1] != 0 && *( int * )&bumpAverage[2] != 0 ) + { + // fast path when we know that we don't have to worry about divide by zero. + VectorDivide( gammaGoal, bumpAverage, correctionScale ); +// correctionScale = gammaGoal / bumpSum; + } + else + { + correctionScale.Init( 0.0f, 0.0f, 0.0f ); + if( bumpAverage[0] != 0.0f ) + { + correctionScale[0] = gammaGoal[0] / bumpAverage[0]; + } + if( bumpAverage[1] != 0.0f ) + { + correctionScale[1] = gammaGoal[1] / bumpAverage[1]; + } + if( bumpAverage[2] != 0.0f ) + { + correctionScale[2] = gammaGoal[2] / bumpAverage[2]; + } + } + Vector correctedBumpColor1; + Vector correctedBumpColor2; + Vector correctedBumpColor3; + VectorMultiply( linearBump1, correctionScale, correctedBumpColor1 ); + VectorMultiply( linearBump2, correctionScale, correctedBumpColor2 ); + VectorMultiply( linearBump3, correctionScale, correctedBumpColor3 ); + + Vector check = ( correctedBumpColor1 + correctedBumpColor2 + correctedBumpColor3 ) / 3.0f; + + ColorClampBumped( correctedBumpColor1, correctedBumpColor2, correctedBumpColor3 ); + + ret[0] = RoundFloatToByte( gammaGoal[0] * 255.0f ); + ret[1] = RoundFloatToByte( gammaGoal[1] * 255.0f ); + ret[2] = RoundFloatToByte( gammaGoal[2] * 255.0f ); + retBump1[0] = RoundFloatToByte( correctedBumpColor1[0] * 255.0f ); + retBump1[1] = RoundFloatToByte( correctedBumpColor1[1] * 255.0f ); + retBump1[2] = RoundFloatToByte( correctedBumpColor1[2] * 255.0f ); + retBump2[0] = RoundFloatToByte( correctedBumpColor2[0] * 255.0f ); + retBump2[1] = RoundFloatToByte( correctedBumpColor2[1] * 255.0f ); + retBump2[2] = RoundFloatToByte( correctedBumpColor2[2] * 255.0f ); + retBump3[0] = RoundFloatToByte( correctedBumpColor3[0] * 255.0f ); + retBump3[1] = RoundFloatToByte( correctedBumpColor3[1] * 255.0f ); + retBump3[2] = RoundFloatToByte( correctedBumpColor3[2] * 255.0f ); +} + +//----------------------------------------------------------------------------- +// Convert a RGBExp32 to a RGBA8888 +// This matches the engine's conversion, so the lighting result is consistent. +//----------------------------------------------------------------------------- +void ConvertRGBExp32ToRGBA8888( const ColorRGBExp32 *pSrc, unsigned char *pDst, Vector* _optOutLinear ) +{ + Vector linearColor; + + // convert from ColorRGBExp32 to linear space + linearColor[0] = TexLightToLinear( ((ColorRGBExp32 *)pSrc)->r, ((ColorRGBExp32 *)pSrc)->exponent ); + linearColor[1] = TexLightToLinear( ((ColorRGBExp32 *)pSrc)->g, ((ColorRGBExp32 *)pSrc)->exponent ); + linearColor[2] = TexLightToLinear( ((ColorRGBExp32 *)pSrc)->b, ((ColorRGBExp32 *)pSrc)->exponent ); + + ConvertLinearToRGBA8888( &linearColor, pDst ); + if ( _optOutLinear ) + *_optOutLinear = linearColor; +} + +//----------------------------------------------------------------------------- +// Converts a RGBExp32 to a linear color value. +//----------------------------------------------------------------------------- +void ConvertRGBExp32ToLinear(const ColorRGBExp32 *pSrc, Vector* pDst) +{ + + (*pDst)[0] = TexLightToLinear(((ColorRGBExp32 *)pSrc)->r, ((ColorRGBExp32 *)pSrc)->exponent); + (*pDst)[1] = TexLightToLinear(((ColorRGBExp32 *)pSrc)->g, ((ColorRGBExp32 *)pSrc)->exponent); + (*pDst)[2] = TexLightToLinear(((ColorRGBExp32 *)pSrc)->b, ((ColorRGBExp32 *)pSrc)->exponent); +} + +//----------------------------------------------------------------------------- +// Converts a linear color value (suitable for combining linearly) to an RBGA8888 value expected by the engine. +//----------------------------------------------------------------------------- +void ConvertLinearToRGBA8888(const Vector *pSrcLinear, unsigned char *pDst) +{ + Vector vertexColor; + + // convert from linear space to lightmap space + // cannot use mathlib routine directly because it doesn't match + // the colorspace version found in the engine, which *is* the same sequence here + vertexColor[0] = LinearToVertexLight((*pSrcLinear)[0]); + vertexColor[1] = LinearToVertexLight((*pSrcLinear)[1]); + vertexColor[2] = LinearToVertexLight((*pSrcLinear)[2]); + + // this is really a color normalization with a floor + ColorClamp(vertexColor); + + // final [0..255] scale + pDst[0] = RoundFloatToByte(vertexColor[0] * 255.0f); + pDst[1] = RoundFloatToByte(vertexColor[1] * 255.0f); + pDst[2] = RoundFloatToByte(vertexColor[2] * 255.0f); + pDst[3] = 255; +} diff --git a/utils/vrad/lightmap.h b/utils/vrad/lightmap.h new file mode 100644 index 0000000..a4c698d --- /dev/null +++ b/utils/vrad/lightmap.h @@ -0,0 +1,141 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef LIGHTMAP_H +#define LIGHTMAP_H +#pragma once + +#include "mathlib/bumpvects.h" +#include "bsplib.h" + +typedef struct +{ + dface_t *faces[2]; + Vector interface_normal; + qboolean coplanar; +} edgeshare_t; + +extern edgeshare_t edgeshare[MAX_MAP_EDGES]; + + +//============================================== + +// This is incremented each time BuildFaceLights and FinalLightFace +// are called. It's used for a status bar in WorldCraft. +extern int g_iCurFace; + +extern int vertexref[MAX_MAP_VERTS]; +extern int *vertexface[MAX_MAP_VERTS]; + +struct faceneighbor_t +{ + int numneighbors; // neighboring faces that share vertices + int *neighbor; // neighboring face list (max of 64) + + Vector *normal; // adjusted normal per vertex + Vector facenormal; // face normal + + bool bHasDisp; // is this surface a displacement surface??? +}; + +extern faceneighbor_t faceneighbor[MAX_MAP_FACES]; + +//============================================== + + +struct sample_t +{ + // in local luxel space + winding_t *w; + int s, t; + Vector2D coord; + Vector2D mins; + Vector2D maxs; + // in world units + Vector pos; + Vector normal; + float area; +}; + +struct facelight_t +{ + // irregularly shaped light sample data, clipped by face and luxel grid + int numsamples; + sample_t *sample; + LightingValue_t *light[MAXLIGHTMAPS][NUM_BUMP_VECTS+1]; // result of direct illumination, indexed by sample + + // regularly spaced lightmap grid + int numluxels; + Vector *luxel; // world space position of luxel + Vector *luxelNormals; // world space normal of luxel + float worldAreaPerLuxel; +}; + +extern directlight_t *activelights; +extern directlight_t *freelights; + +extern facelight_t facelight[MAX_MAP_FACES]; +extern int numdlights; + + +//============================================== + +struct lightinfo_t +{ + vec_t facedist; + Vector facenormal; + + Vector facemid; // world coordinates of center + + Vector modelorg; // for origined bmodels + + Vector luxelOrigin; + Vector worldToLuxelSpace[2]; // s = (world - luxelOrigin) . worldToLuxelSpace[0], t = (world - luxelOrigin) . worldToLuxelSpace[1] + Vector luxelToWorldSpace[2]; // world = luxelOrigin + s * luxelToWorldSpace[0] + t * luxelToWorldSpace[1] + + int facenum; + dface_t *face; + + int isflat; + int hasbumpmap; +}; + +struct SSE_SampleInfo_t +{ + int m_FaceNum; + int m_WarnFace; + dface_t *m_pFace; + facelight_t *m_pFaceLight; + int m_LightmapWidth; + int m_LightmapHeight; + int m_LightmapSize; + int m_NormalCount; + int m_iThread; + texinfo_t *m_pTexInfo; + bool m_IsDispFace; + + int m_NumSamples; + int m_NumSampleGroups; + int m_Clusters[4]; + FourVectors m_Points; + FourVectors m_PointNormals[ NUM_BUMP_VECTS + 1 ]; +}; + +extern void InitLightinfo( lightinfo_t *l, int facenum ); + +void FreeDLights(); + +void ExportDirectLightsToWorldLights(); + + +#endif // LIGHTMAP_H diff --git a/utils/vrad/macro_texture.cpp b/utils/vrad/macro_texture.cpp new file mode 100644 index 0000000..cd0eac1 --- /dev/null +++ b/utils/vrad/macro_texture.cpp @@ -0,0 +1,166 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#include "tier1/strtools.h" +#include "macro_texture.h" +#include "bsplib.h" +#include "cmdlib.h" +#include "vtf/vtf.h" +#include "tier1/utldict.h" +#include "tier1/utlbuffer.h" +#include "bitmap/imageformat.h" + + +class CMacroTextureData +{ +public: + int m_Width, m_Height; + CUtlMemory<unsigned char> m_ImageData; +}; + + +CMacroTextureData *g_pGlobalMacroTextureData = NULL; + +// Which macro texture each map face uses. +static CUtlDict<CMacroTextureData*, int> g_MacroTextureLookup; // Stores a list of unique macro textures. +static CUtlVector<CMacroTextureData*> g_FaceMacroTextures; // Which macro texture each face wants to use. +static Vector g_MacroWorldMins, g_MacroWorldMaxs; + + +CMacroTextureData* FindMacroTexture( const char *pFilename ) +{ + int index = g_MacroTextureLookup.Find( pFilename ); + if ( g_MacroTextureLookup.IsValidIndex( index ) ) + return g_MacroTextureLookup[index]; + else + return NULL; +} + + +CMacroTextureData* LoadMacroTextureFile( const char *pFilename ) +{ + FileHandle_t hFile = g_pFileSystem->Open( pFilename, "rb" ); + if ( hFile == FILESYSTEM_INVALID_HANDLE ) + return NULL; + + // Read the file in. + CUtlVector<char> tempData; + tempData.SetSize( g_pFileSystem->Size( hFile ) ); + g_pFileSystem->Read( tempData.Base(), tempData.Count(), hFile ); + g_pFileSystem->Close( hFile ); + + + // Now feed the data into a CUtlBuffer (great...) + CUtlBuffer buf; + buf.Put( tempData.Base(), tempData.Count() ); + + + // Now make a texture out of it. + IVTFTexture *pTex = CreateVTFTexture(); + if ( !pTex->Unserialize( buf ) ) + Error( "IVTFTexture::Unserialize( %s ) failed.", pFilename ); + + pTex->ConvertImageFormat( IMAGE_FORMAT_RGBA8888, false ); // Get it in a format we like. + + + // Now convert to a CMacroTextureData. + CMacroTextureData *pData = new CMacroTextureData; + pData->m_Width = pTex->Width(); + pData->m_Height = pTex->Height(); + pData->m_ImageData.EnsureCapacity( pData->m_Width * pData->m_Height * 4 ); + memcpy( pData->m_ImageData.Base(), pTex->ImageData(), pData->m_Width * pData->m_Height * 4 ); + + DestroyVTFTexture( pTex ); + + Msg( "-- LoadMacroTextureFile: %s\n", pFilename ); + return pData; +} + + +void InitMacroTexture( const char *pBSPFilename ) +{ + // Get the world bounds (same ones used by minimaps and level designers know how to use). + int i = 0; + for (i; i < num_entities; ++i) + { + char* pEntity = ValueForKey(&entities[i], "classname"); + if( !strcmp(pEntity, "worldspawn") ) + { + GetVectorForKey( &entities[i], "world_mins", g_MacroWorldMins ); + GetVectorForKey( &entities[i], "world_maxs", g_MacroWorldMaxs ); + break; + } + } + + if ( i == num_entities ) + { + Warning( "MaskOnMacroTexture: can't find worldspawn" ); + return; + } + + + // Load the macro texture that is mapped onto everything. + char mapName[512], vtfFilename[512]; + Q_FileBase( pBSPFilename, mapName, sizeof( mapName ) ); + Q_snprintf( vtfFilename, sizeof( vtfFilename ), "materials/macro/%s/base.vtf", mapName ); + g_pGlobalMacroTextureData = LoadMacroTextureFile( vtfFilename ); + + + // Now load the macro texture for each face. + g_FaceMacroTextures.SetSize( numfaces ); + for ( int iFace=0; iFace < numfaces; iFace++ ) + { + g_FaceMacroTextures[iFace] = NULL; + + if ( iFace < g_FaceMacroTextureInfos.Count() ) + { + unsigned short stringID = g_FaceMacroTextureInfos[iFace].m_MacroTextureNameID; + if ( stringID != 0xFFFF ) + { + const char *pMacroTextureName = &g_TexDataStringData[ g_TexDataStringTable[stringID] ]; + Q_snprintf( vtfFilename, sizeof( vtfFilename ), "%smaterials/%s.vtf", gamedir, pMacroTextureName ); + + g_FaceMacroTextures[iFace] = FindMacroTexture( vtfFilename ); + if ( !g_FaceMacroTextures[iFace] ) + { + g_FaceMacroTextures[iFace] = LoadMacroTextureFile( vtfFilename ); + if ( g_FaceMacroTextures[iFace] ) + { + g_MacroTextureLookup.Insert( vtfFilename, g_FaceMacroTextures[iFace] ); + } + } + } + } + } +} + + +inline Vector SampleMacroTexture( const CMacroTextureData *t, const Vector &vWorldPos ) +{ + int ix = (int)RemapVal( vWorldPos.x, g_MacroWorldMins.x, g_MacroWorldMaxs.x, 0, t->m_Width-0.00001 ); + int iy = (int)RemapVal( vWorldPos.y, g_MacroWorldMins.y, g_MacroWorldMaxs.y, 0, t->m_Height-0.00001 ); + ix = clamp( ix, 0, t->m_Width-1 ); + iy = t->m_Height - 1 - clamp( iy, 0, t->m_Height-1 ); + + const unsigned char *pInputColor = &t->m_ImageData[(iy*t->m_Width + ix) * 4]; + return Vector( pInputColor[0] / 255.0, pInputColor[1] / 255.0, pInputColor[2] / 255.0 ); +} + + +void ApplyMacroTextures( int iFace, const Vector &vWorldPos, Vector &outLuxel ) +{ + // Add the global macro texture. + Vector vGlobal; + if ( g_pGlobalMacroTextureData ) + outLuxel *= SampleMacroTexture( g_pGlobalMacroTextureData, vWorldPos ); + + // Now add the per-material macro texture. + if ( g_FaceMacroTextures[iFace] ) + outLuxel *= SampleMacroTexture( g_FaceMacroTextures[iFace], vWorldPos ); +} + + + diff --git a/utils/vrad/macro_texture.h b/utils/vrad/macro_texture.h new file mode 100644 index 0000000..bfae258 --- /dev/null +++ b/utils/vrad/macro_texture.h @@ -0,0 +1,24 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef MACRO_TEXTURE_H +#define MACRO_TEXTURE_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "mathlib/vector.h" + + +// The macro texture looks for a TGA file with the same name as the BSP file and in +// the same directory. If it finds one, it maps this texture onto the world dimensions +// (in the worldspawn entity) and masks all lightmaps with it. +void InitMacroTexture( const char *pBSPFilename ); +void ApplyMacroTextures( int iFace, const Vector &vWorldPos, Vector &outLuxel ); + + +#endif // MACRO_TEXTURE_H diff --git a/utils/vrad/mpivrad.cpp b/utils/vrad/mpivrad.cpp new file mode 100644 index 0000000..5b7bfc0 --- /dev/null +++ b/utils/vrad/mpivrad.cpp @@ -0,0 +1,496 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// +// +// mpivrad.cpp +// + +#include <windows.h> +#include <conio.h> +#include "vrad.h" +#include "physdll.h" +#include "lightmap.h" +#include "tier1/strtools.h" +#include "radial.h" +#include "utlbuffer.h" +#include "pacifier.h" +#include "messbuf.h" +#include "bsplib.h" +#include "consolewnd.h" +#include "vismat.h" +#include "vmpi_filesystem.h" +#include "vmpi_dispatch.h" +#include "utllinkedlist.h" +#include "vmpi.h" +#include "mpi_stats.h" +#include "vmpi_distribute_work.h" +#include "vmpi_tools_shared.h" + + + + +CUtlVector<char> g_LightResultsFilename; + + +extern int total_transfer; +extern int max_transfer; + +extern void BuildVisLeafs(int); +extern void BuildPatchLights( int facenum ); + + +// Handle VRAD packets. +bool VRAD_DispatchFn( MessageBuffer *pBuf, int iSource, int iPacketID ) +{ + switch( pBuf->data[1] ) + { + case VMPI_SUBPACKETID_PLIGHTDATA_RESULTS: + { + const char *pFilename = &pBuf->data[2]; + g_LightResultsFilename.CopyArray( pFilename, strlen( pFilename ) + 1 ); + return true; + } + + default: + return false; + } +} +CDispatchReg g_VRADDispatchReg( VMPI_VRAD_PACKET_ID, VRAD_DispatchFn ); // register to handle the messages we want +CDispatchReg g_DistributeWorkReg( VMPI_DISTRIBUTEWORK_PACKETID, DistributeWorkDispatch ); + + + +void VRAD_SetupMPI( int &argc, char **&argv ) +{ + CmdLib_AtCleanup( VMPI_Stats_Term ); + + // + // Preliminary check -mpi flag + // + if ( !VMPI_FindArg( argc, argv, "-mpi", "" ) && !VMPI_FindArg( argc, argv, VMPI_GetParamString( mpi_Worker ), "" ) ) + return; + + // Force local mode? + VMPIRunMode mode; + if ( VMPI_FindArg( argc, argv, VMPI_GetParamString( mpi_Local ), "" ) ) + mode = VMPI_RUN_LOCAL; + else + mode = VMPI_RUN_NETWORKED; + + VMPI_Stats_InstallSpewHook(); + + // + // Extract mpi specific arguments + // + Msg( "Initializing VMPI...\n" ); + if ( !VMPI_Init( + argc, + argv, + "dependency_info_vrad.txt", + HandleMPIDisconnect, + mode + ) ) + { + Error( "MPI_Init failed." ); + } + + StatsDB_InitStatsDatabase( argc, argv, "dbinfo_vrad.txt" ); +} + + +//----------------------------------------- +// +// Run BuildFaceLights across all available processing nodes +// and collect the results. +// + +CCycleCount g_CPUTime; + + +template<class T> void WriteValues( MessageBuffer *pmb, T const *pSrc, int nNumValues) +{ + pmb->write(pSrc, sizeof( pSrc[0]) * nNumValues ); +} + +template<class T> int ReadValues( MessageBuffer *pmb, T *pDest, int nNumValues) +{ + return pmb->read( pDest, sizeof( pDest[0]) * nNumValues ); +} + + +//-------------------------------------------------- +// Serialize face data +void SerializeFace( MessageBuffer * pmb, int facenum ) +{ + int i, n; + + dface_t * f = &g_pFaces[facenum]; + facelight_t * fl = &facelight[facenum]; + + pmb->write(f, sizeof(dface_t)); + pmb->write(fl, sizeof(facelight_t)); + + WriteValues( pmb, fl->sample, fl->numsamples); + + // + // Write the light information + // + for (i=0; i<MAXLIGHTMAPS; ++i) { + for (n=0; n<NUM_BUMP_VECTS+1; ++n) { + if (fl->light[i][n]) + { + WriteValues( pmb, fl->light[i][n], fl->numsamples); + } + } + } + + if (fl->luxel) + WriteValues( pmb, fl->luxel, fl->numluxels); + + if (fl->luxelNormals) + WriteValues( pmb, fl->luxelNormals, fl->numluxels); +} + +//-------------------------------------------------- +// UnSerialize face data +// +void UnSerializeFace( MessageBuffer * pmb, int facenum, int iSource ) +{ + int i, n; + + dface_t * f = &g_pFaces[facenum]; + facelight_t * fl = &facelight[facenum]; + + if (pmb->read(f, sizeof(dface_t)) < 0) + Error("UnSerializeFace - invalid dface_t from %s (mb len: %d, offset: %d)", VMPI_GetMachineName( iSource ), pmb->getLen(), pmb->getOffset() ); + + if (pmb->read(fl, sizeof(facelight_t)) < 0) + Error("UnSerializeFace - invalid facelight_t from %s (mb len: %d, offset: %d)", VMPI_GetMachineName( iSource ), pmb->getLen(), pmb->getOffset() ); + + fl->sample = (sample_t *) calloc(fl->numsamples, sizeof(sample_t)); + if (pmb->read(fl->sample, sizeof(sample_t) * fl->numsamples) < 0) + Error("UnSerializeFace - invalid sample_t from %s (mb len: %d, offset: %d, fl->numsamples: %d)", VMPI_GetMachineName( iSource ), pmb->getLen(), pmb->getOffset(), fl->numsamples ); + + // + // Read the light information + // + for (i=0; i<MAXLIGHTMAPS; ++i) { + for (n=0; n<NUM_BUMP_VECTS+1; ++n) { + if (fl->light[i][n]) + { + fl->light[i][n] = (LightingValue_t *) calloc( fl->numsamples, sizeof(LightingValue_t ) ); + if ( ReadValues( pmb, fl->light[i][n], fl->numsamples) < 0) + Error("UnSerializeFace - invalid fl->light from %s (mb len: %d, offset: %d)", VMPI_GetMachineName( iSource ), pmb->getLen(), pmb->getOffset() ); + } + } + } + + if (fl->luxel) { + fl->luxel = (Vector *) calloc(fl->numluxels, sizeof(Vector)); + if (ReadValues( pmb, fl->luxel, fl->numluxels) < 0) + Error("UnSerializeFace - invalid fl->luxel from %s (mb len: %d, offset: %d)", VMPI_GetMachineName( iSource ), pmb->getLen(), pmb->getOffset() ); + } + + if (fl->luxelNormals) { + fl->luxelNormals = (Vector *) calloc(fl->numluxels, sizeof( Vector )); + if ( ReadValues( pmb, fl->luxelNormals, fl->numluxels) < 0) + Error("UnSerializeFace - invalid fl->luxelNormals from %s (mb len: %d, offset: %d)", VMPI_GetMachineName( iSource ), pmb->getLen(), pmb->getOffset() ); + } + +} + + +void MPI_ReceiveFaceResults( uint64 iWorkUnit, MessageBuffer *pBuf, int iWorker ) +{ + UnSerializeFace( pBuf, iWorkUnit, iWorker ); +} + + +void MPI_ProcessFaces( int iThread, uint64 iWorkUnit, MessageBuffer *pBuf ) +{ + // Do BuildFacelights on the face. + CTimeAdder adder( &g_CPUTime ); + + BuildFacelights( iThread, iWorkUnit ); + + // Send the results. + if ( pBuf ) + { + SerializeFace( pBuf, iWorkUnit ); + } +} + + +void RunMPIBuildFacelights() +{ + g_CPUTime.Init(); + + Msg( "%-20s ", "BuildFaceLights:" ); + if ( g_bMPIMaster ) + { + StartPacifier(""); + } + + VMPI_SetCurrentStage( "RunMPIBuildFaceLights" ); + double elapsed = DistributeWork( + numfaces, + VMPI_DISTRIBUTEWORK_PACKETID, + MPI_ProcessFaces, + MPI_ReceiveFaceResults ); + + if ( g_bMPIMaster ) + { + EndPacifier(false); + Msg( " (%d)\n", (int)elapsed ); + } + + if ( g_bMPIMaster ) + { + // + // BuildPatchLights is normally called from BuildFacelights(), + // but in MPI mode we have the master do the calculation + // We might be able to speed this up by doing while the master + // is idling in the above loop. Wouldn't want to slow down the + // handing out of work - maybe another thread? + // + for ( int i=0; i < numfaces; ++i ) + { + BuildPatchLights(i); + } + } + else + { + if ( g_iVMPIVerboseLevel >= 1 ) + Msg( "\n\n%.1f%% CPU utilization during BuildFaceLights\n\n", ( g_CPUTime.GetSeconds() * 100 / elapsed ) ); + } +} + + +//----------------------------------------- +// +// Run BuildVisLeafs across all available processing nodes +// and collect the results. +// + +// This function is called when the master receives results back from a worker. +void MPI_ReceiveVisLeafsResults( uint64 iWorkUnit, MessageBuffer *pBuf, int iWorker ) +{ + int patchesInCluster = 0; + + pBuf->read(&patchesInCluster, sizeof(patchesInCluster)); + + for ( int k=0; k < patchesInCluster; ++k ) + { + int patchnum = 0; + pBuf->read(&patchnum, sizeof(patchnum)); + + CPatch * patch = &g_Patches[patchnum]; + int numtransfers; + pBuf->read( &numtransfers, sizeof(numtransfers) ); + patch->numtransfers = numtransfers; + if (numtransfers) + { + patch->transfers = new transfer_t[numtransfers]; + pBuf->read(patch->transfers, numtransfers * sizeof(transfer_t)); + } + + total_transfer += numtransfers; + if (max_transfer < numtransfers) + max_transfer = numtransfers; + } +} + + +// Temporary variables used during callbacks. If we're going to be threadsafe, these +// should go in a structure and get passed around. +class CVMPIVisLeafsData +{ +public: + MessageBuffer *m_pVisLeafsMB; + int m_nPatchesInCluster; + transfer_t *m_pBuildVisLeafsTransfers; +}; + +CVMPIVisLeafsData g_VMPIVisLeafsData[MAX_TOOL_THREADS+1]; + + + +// This is called by BuildVisLeafs_Cluster every time it finishes a patch. +// The results are appended to g_VisLeafsMB and sent back to the master when all clusters are done. +void MPI_AddPatchData( int iThread, int patchnum, CPatch *patch ) +{ + CVMPIVisLeafsData *pData = &g_VMPIVisLeafsData[iThread]; + if ( pData->m_pVisLeafsMB ) + { + // Add in results for this patch + ++pData->m_nPatchesInCluster; + pData->m_pVisLeafsMB->write(&patchnum, sizeof(patchnum)); + pData->m_pVisLeafsMB->write(&patch->numtransfers, sizeof(patch->numtransfers)); + pData->m_pVisLeafsMB->write( patch->transfers, patch->numtransfers * sizeof(transfer_t) ); + } +} + + +// This handles a work unit sent by the master. Each work unit here is a +// list of clusters. +void MPI_ProcessVisLeafs( int iThread, uint64 iWorkUnit, MessageBuffer *pBuf ) +{ + CTimeAdder adder( &g_CPUTime ); + + CVMPIVisLeafsData *pData = &g_VMPIVisLeafsData[iThread]; + int iCluster = iWorkUnit; + + // Start this cluster. + pData->m_nPatchesInCluster = 0; + pData->m_pVisLeafsMB = pBuf; + + // Write a temp value in there. We overwrite it later. + int iSavePos = 0; + if ( pBuf ) + { + iSavePos = pBuf->getLen(); + pBuf->write( &pData->m_nPatchesInCluster, sizeof(pData->m_nPatchesInCluster) ); + } + + // Collect the results in MPI_AddPatchData. + BuildVisLeafs_Cluster( iThread, pData->m_pBuildVisLeafsTransfers, iCluster, MPI_AddPatchData ); + + // Now send the results back.. + if ( pBuf ) + { + pBuf->update( iSavePos, &pData->m_nPatchesInCluster, sizeof(pData->m_nPatchesInCluster) ); + pData->m_pVisLeafsMB = NULL; + } +} + + +void RunMPIBuildVisLeafs() +{ + g_CPUTime.Init(); + + Msg( "%-20s ", "BuildVisLeafs :" ); + if ( g_bMPIMaster ) + { + StartPacifier(""); + } + + memset( g_VMPIVisLeafsData, 0, sizeof( g_VMPIVisLeafsData ) ); + if ( !g_bMPIMaster || VMPI_GetActiveWorkUnitDistributor() == k_eWorkUnitDistributor_SDK ) + { + // Allocate space for the transfers for each thread. + for ( int i=0; i < numthreads; i++ ) + { + g_VMPIVisLeafsData[i].m_pBuildVisLeafsTransfers = BuildVisLeafs_Start(); + } + } + + // + // Slaves ask for work via GetMPIBuildVisLeafWork() + // Results are returned in BuildVisRow() + // + VMPI_SetCurrentStage( "RunMPIBuildVisLeafs" ); + + double elapsed = DistributeWork( + dvis->numclusters, + VMPI_DISTRIBUTEWORK_PACKETID, + MPI_ProcessVisLeafs, + MPI_ReceiveVisLeafsResults ); + + // Free the transfers from each thread. + for ( int i=0; i < numthreads; i++ ) + { + if ( g_VMPIVisLeafsData[i].m_pBuildVisLeafsTransfers ) + BuildVisLeafs_End( g_VMPIVisLeafsData[i].m_pBuildVisLeafsTransfers ); + } + + if ( g_bMPIMaster ) + { + EndPacifier(false); + Msg( " (%d)\n", (int)elapsed ); + } + else + { + if ( g_iVMPIVerboseLevel >= 1 ) + Msg( "%.1f%% CPU utilization during PortalFlow\n", (g_CPUTime.GetSeconds() * 100.0f / elapsed) / numthreads ); + } +} + +void VMPI_DistributeLightData() +{ + if ( !g_bUseMPI ) + return; + + if ( g_bMPIMaster ) + { + const char *pVirtualFilename = "--plightdata--"; + + CUtlBuffer lightFaceData; + + // write out the light data + lightFaceData.EnsureCapacity( pdlightdata->Count() + (numfaces * (MAXLIGHTMAPS+sizeof(int))) ); + Q_memcpy( lightFaceData.PeekPut(), pdlightdata->Base(), pdlightdata->Count() ); + lightFaceData.SeekPut( CUtlBuffer::SEEK_HEAD, pdlightdata->Count() ); + + // write out the relevant face info into the stream + for ( int i = 0; i < numfaces; i++ ) + { + for ( int j = 0; j < MAXLIGHTMAPS; j++ ) + { + lightFaceData.PutChar(g_pFaces[i].styles[j]); + } + lightFaceData.PutInt(g_pFaces[i].lightofs); + } + VMPI_FileSystem_CreateVirtualFile( pVirtualFilename, lightFaceData.Base(), lightFaceData.TellMaxPut() ); + + char cPacketID[2] = { VMPI_VRAD_PACKET_ID, VMPI_SUBPACKETID_PLIGHTDATA_RESULTS }; + VMPI_Send2Chunks( cPacketID, sizeof( cPacketID ), pVirtualFilename, strlen( pVirtualFilename ) + 1, VMPI_PERSISTENT ); + } + else + { + VMPI_SetCurrentStage( "VMPI_DistributeLightData" ); + + // Wait until we've received the filename from the master. + while ( g_LightResultsFilename.Count() == 0 ) + { + VMPI_DispatchNextMessage(); + } + + // Open + FileHandle_t fp = g_pFileSystem->Open( g_LightResultsFilename.Base(), "rb", VMPI_VIRTUAL_FILES_PATH_ID ); + if ( !fp ) + Error( "Can't open '%s' to read lighting info.", g_LightResultsFilename.Base() ); + + int size = g_pFileSystem->Size( fp ); + int faceSize = (numfaces*(MAXLIGHTMAPS+sizeof(int))); + + if ( size > faceSize ) + { + int lightSize = size - faceSize; + CUtlBuffer faceData; + pdlightdata->EnsureCount( lightSize ); + faceData.EnsureCapacity( faceSize ); + + g_pFileSystem->Read( pdlightdata->Base(), lightSize, fp ); + g_pFileSystem->Read( faceData.Base(), faceSize, fp ); + g_pFileSystem->Close( fp ); + + faceData.SeekPut( CUtlBuffer::SEEK_HEAD, faceSize ); + + // write out the face data + for ( int i = 0; i < numfaces; i++ ) + { + for ( int j = 0; j < MAXLIGHTMAPS; j++ ) + { + g_pFaces[i].styles[j] = faceData.GetChar(); + } + g_pFaces[i].lightofs = faceData.GetInt(); + } + } + } +} + + diff --git a/utils/vrad/mpivrad.h b/utils/vrad/mpivrad.h new file mode 100644 index 0000000..01c841b --- /dev/null +++ b/utils/vrad/mpivrad.h @@ -0,0 +1,36 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef MPIVRAD_H +#define MPIVRAD_H +#ifdef _WIN32 +#pragma once +#endif + + +#define VMPI_VRAD_PACKET_ID 1 + // Sub packet IDs. + #define VMPI_SUBPACKETID_VIS_LEAFS 0 + #define VMPI_SUBPACKETID_BUILDFACELIGHTS 1 + #define VMPI_SUBPACKETID_PLIGHTDATA_RESULTS 2 + +// DistributeWork owns this packet ID. +#define VMPI_DISTRIBUTEWORK_PACKETID 2 + + +// Called first thing in the exe. +void VRAD_SetupMPI( int &argc, char **&argv ); + +void RunMPIBuildFacelights(void); +void RunMPIBuildVisLeafs(void); +void VMPI_DistributeLightData(); + +// This handles disconnections. They're usually not fatal for the master. +void HandleMPIDisconnect( int procID ); + + +#endif // MPIVRAD_H diff --git a/utils/vrad/notes.txt b/utils/vrad/notes.txt new file mode 100644 index 0000000..8c21cc6 --- /dev/null +++ b/utils/vrad/notes.txt @@ -0,0 +1,22 @@ +//============================================================================= +// CHARLIE: + +DONE: just rename files from .c to .cpp in source safe so history is kept +converting files of to .cpp files -- just get stuff recompiling first +make sure all common files and external dependencies handle .cpp files +add the dispmap class +add the dispface class +get rid of patches.c or patches.cpp as the case may be + +DONE: create a parallel array for visited displacement faces +change the visited array to a flag in the "bsp" ddispface_t structure +DONE: add reset visited displacement face functionality +DONE: visited neighbor flags +DONE: reset visited neighbor flags +DONE: move the radial_t structure and some of its functionality to a more global scope +DONE: create a finallightdispface +DONE: generate luxels for displacement face +DONE: fix precomplightmapoffsets to take displacement faces into account +fill in luxel data from sample data +modify the GetPhongNormal and GatherSampleLight functions to use displacement face normals at "spot" +make a lightinfo list? -- so we don't regenerate initlightinfo so many times? diff --git a/utils/vrad/origface.cpp b/utils/vrad/origface.cpp new file mode 100644 index 0000000..fbbe0e8 --- /dev/null +++ b/utils/vrad/origface.cpp @@ -0,0 +1,51 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// + +#include "vrad.h" + +bool bOrigFacesTouched[MAX_MAP_FACES]; + + +//----------------------------------------------------------------------------- +// Pupose: clear (reset) the bOrigFacesTouched list -- parellels the original +// face list allowing an original face to only be processed once in +// pairing edges! +//----------------------------------------------------------------------------- +void ResetOrigFacesTouched( void ) +{ + for( int i = 0; i < MAX_MAP_FACES; i++ ) + { + bOrigFacesTouched[i] = false; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: mark an original faces as touched (dirty) +// Input: index - index of the original face touched +//----------------------------------------------------------------------------- +void SetOrigFaceTouched( int index ) +{ + bOrigFacesTouched[index] = true; +} + + +//----------------------------------------------------------------------------- +// Purpose: return whether or not an original face has been touched +// Input: index - index of the original face touched +// Output: true/false +//----------------------------------------------------------------------------- +bool IsOrigFaceTouched( int index ) +{ + return bOrigFacesTouched[index]; +} diff --git a/utils/vrad/radial.cpp b/utils/vrad/radial.cpp new file mode 100644 index 0000000..5dc99b5 --- /dev/null +++ b/utils/vrad/radial.cpp @@ -0,0 +1,882 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#include "vrad.h" +#include "lightmap.h" +#include "radial.h" +#include "mathlib/bumpvects.h" +#include "utlrbtree.h" +#include "mathlib/VMatrix.h" +#include "macro_texture.h" + + +void WorldToLuxelSpace( lightinfo_t const *l, Vector const &world, Vector2D &coord ) +{ + Vector pos; + + VectorSubtract( world, l->luxelOrigin, pos ); + coord[0] = DotProduct( pos, l->worldToLuxelSpace[0] ) - l->face->m_LightmapTextureMinsInLuxels[0]; + coord[1] = DotProduct( pos, l->worldToLuxelSpace[1] ) - l->face->m_LightmapTextureMinsInLuxels[1]; +} + +void LuxelSpaceToWorld( lightinfo_t const *l, float s, float t, Vector &world ) +{ + Vector pos; + + s += l->face->m_LightmapTextureMinsInLuxels[0]; + t += l->face->m_LightmapTextureMinsInLuxels[1]; + VectorMA( l->luxelOrigin, s, l->luxelToWorldSpace[0], pos ); + VectorMA( pos, t, l->luxelToWorldSpace[1], world ); +} + +void WorldToLuxelSpace( lightinfo_t const *l, FourVectors const &world, FourVectors &coord ) +{ + FourVectors luxelOrigin; + luxelOrigin.DuplicateVector ( l->luxelOrigin ); + + FourVectors pos = world; + pos -= luxelOrigin; + + coord.x = pos * l->worldToLuxelSpace[0]; + coord.x = SubSIMD ( coord.x, ReplicateX4 ( l->face->m_LightmapTextureMinsInLuxels[0] ) ); + coord.y = pos * l->worldToLuxelSpace[1]; + coord.y = SubSIMD ( coord.y, ReplicateX4 ( l->face->m_LightmapTextureMinsInLuxels[1] ) ); + coord.z = Four_Zeros; +} + +void LuxelSpaceToWorld( lightinfo_t const *l, fltx4 s, fltx4 t, FourVectors &world ) +{ + world.DuplicateVector ( l->luxelOrigin ); + FourVectors st; + + s = AddSIMD ( s, ReplicateX4 ( l->face->m_LightmapTextureMinsInLuxels[0] ) ); + st.DuplicateVector ( l->luxelToWorldSpace[0] ); + st *= s; + world += st; + + t = AddSIMD ( t, ReplicateX4 ( l->face->m_LightmapTextureMinsInLuxels[1] ) ); + st.DuplicateVector ( l->luxelToWorldSpace[1] ); + st *= t; + world += st; +} + + + +void AddDirectToRadial( radial_t *rad, + Vector const &pnt, + Vector2D const &coordmins, Vector2D const &coordmaxs, + LightingValue_t const light[NUM_BUMP_VECTS+1], + bool hasBumpmap, bool neighborHasBumpmap ) +{ + int s_min, s_max, t_min, t_max; + Vector2D coord; + int s, t; + float ds, dt; + float r; + float area; + int bumpSample; + + // convert world pos into local lightmap texture coord + WorldToLuxelSpace( &rad->l, pnt, coord ); + + s_min = ( int )( coordmins[0] ); + t_min = ( int )( coordmins[1] ); + s_max = ( int )( coordmaxs[0] + 0.9999f ) + 1; // ???? + t_max = ( int )( coordmaxs[1] + 0.9999f ) + 1; + + s_min = max( s_min, 0 ); + t_min = max( t_min, 0 ); + s_max = min( s_max, rad->w ); + t_max = min( t_max, rad->h ); + + for( s = s_min; s < s_max; s++ ) + { + for( t = t_min; t < t_max; t++ ) + { + float s0 = max( coordmins[0] - s, -1.0 ); + float t0 = max( coordmins[1] - t, -1.0 ); + float s1 = min( coordmaxs[0] - s, 1.0 ); + float t1 = min( coordmaxs[1] - t, 1.0 ); + + area = (s1 - s0) * (t1 - t0); + + if (area > EQUAL_EPSILON) + { + ds = fabs( coord[0] - s ); + dt = fabs( coord[1] - t ); + + r = max( ds, dt ); + + if (r < 0.1) + { + r = area / 0.1; + } + else + { + r = area / r; + } + + int i = s+t*rad->w; + + if( hasBumpmap ) + { + if( neighborHasBumpmap ) + { + for( bumpSample = 0; bumpSample < NUM_BUMP_VECTS + 1; bumpSample++ ) + { + rad->light[bumpSample][i].AddWeighted( light[bumpSample], r ); + } + } + else + { + rad->light[0][i].AddWeighted(light[0],r ); + for( bumpSample = 1; bumpSample < NUM_BUMP_VECTS + 1; bumpSample++ ) + { + rad->light[bumpSample][i].AddWeighted( light[0], r * OO_SQRT_3 ); + } + } + } + else + { + rad->light[0][i].AddWeighted( light[0], r ); + } + + rad->weight[i] += r; + } + } + } +} + + + +void AddBouncedToRadial( radial_t *rad, + Vector const &pnt, + Vector2D const &coordmins, Vector2D const &coordmaxs, + Vector const light[NUM_BUMP_VECTS+1], + bool hasBumpmap, bool neighborHasBumpmap ) +{ + int s_min, s_max, t_min, t_max; + Vector2D coord; + int s, t; + float ds, dt; + float r; + int bumpSample; + + // convert world pos into local lightmap texture coord + WorldToLuxelSpace( &rad->l, pnt, coord ); + + float dists, distt; + + dists = (coordmaxs[0] - coordmins[0]); + distt = (coordmaxs[1] - coordmins[1]); + + // patches less than a luxel in size could be mistakeningly filtered, so clamp. + dists = max( 1.0, dists ); + distt = max( 1.0, distt ); + + // find possible domain of patch influence + s_min = ( int )( coord[0] - dists * RADIALDIST ); + t_min = ( int )( coord[1] - distt * RADIALDIST ); + s_max = ( int )( coord[0] + dists * RADIALDIST + 1.0f ); + t_max = ( int )( coord[1] + distt * RADIALDIST + 1.0f ); + + // clamp to valid luxel + s_min = max( s_min, 0 ); + t_min = max( t_min, 0 ); + s_max = min( s_max, rad->w ); + t_max = min( t_max, rad->h ); + + for( s = s_min; s < s_max; s++ ) + { + for( t = t_min; t < t_max; t++ ) + { + // patch influence is based on patch size + ds = ( coord[0] - s ) / dists; + dt = ( coord[1] - t ) / distt; + + r = RADIALDIST2 - (ds * ds + dt * dt); + + int i = s+t*rad->w; + + if (r > 0) + { + if( hasBumpmap ) + { + if( neighborHasBumpmap ) + { + for( bumpSample = 0; bumpSample < NUM_BUMP_VECTS + 1; bumpSample++ ) + { + rad->light[bumpSample][i].AddWeighted( light[bumpSample], r ); + } + } + else + { + rad->light[0][i].AddWeighted( light[0], r ); + for( bumpSample = 1; bumpSample < NUM_BUMP_VECTS + 1; bumpSample++ ) + { + rad->light[bumpSample][i].AddWeighted( light[0], r * OO_SQRT_3 ); + } + } + } + else + { + rad->light[0][i].AddWeighted( light[0], r ); + } + + rad->weight[i] += r; + } + } + } +} + +void PatchLightmapCoordRange( radial_t *rad, int ndxPatch, Vector2D &mins, Vector2D &maxs ) +{ + winding_t *w; + int i; + Vector2D coord; + + mins.Init( 1E30, 1E30 ); + maxs.Init( -1E30, -1E30 ); + + CPatch *patch = &g_Patches.Element( ndxPatch ); + w = patch->winding; + + for (i = 0; i < w->numpoints; i++) + { + WorldToLuxelSpace( &rad->l, w->p[i], coord ); + mins[0] = min( mins[0], coord[0] ); + maxs[0] = max( maxs[0], coord[0] ); + mins[1] = min( mins[1], coord[1] ); + maxs[1] = max( maxs[1], coord[1] ); + } +} + +radial_t *AllocateRadial( int facenum ) +{ + radial_t *rad; + + rad = ( radial_t* )calloc( 1, sizeof( *rad ) ); + + rad->facenum = facenum; + InitLightinfo( &rad->l, facenum ); + + rad->w = rad->l.face->m_LightmapTextureSizeInLuxels[0]+1; + rad->h = rad->l.face->m_LightmapTextureSizeInLuxels[1]+1; + + return rad; +} + +void FreeRadial( radial_t *rad ) +{ + if (rad) + free( rad ); +} + + +radial_t *BuildPatchRadial( int facenum ) +{ + int j; + radial_t *rad; + CPatch *patch; + faceneighbor_t *fn; + Vector2D mins, maxs; + bool needsBumpmap, neighborNeedsBumpmap; + + needsBumpmap = texinfo[g_pFaces[facenum].texinfo].flags & SURF_BUMPLIGHT ? true : false; + + rad = AllocateRadial( facenum ); + + fn = &faceneighbor[ rad->facenum ]; + + CPatch *pNextPatch; + + if( g_FacePatches.Element( rad->facenum ) != g_FacePatches.InvalidIndex() ) + { + for( patch = &g_Patches.Element( g_FacePatches.Element( rad->facenum ) ); patch; patch = pNextPatch ) + { + // next patch + pNextPatch = NULL; + if( patch->ndxNext != g_Patches.InvalidIndex() ) + { + pNextPatch = &g_Patches.Element( patch->ndxNext ); + } + + // skip patches with children + if (patch->child1 != g_Patches.InvalidIndex() ) + continue; + + // get the range of patch lightmap texture coords + int ndxPatch = patch - g_Patches.Base(); + PatchLightmapCoordRange( rad, ndxPatch, mins, maxs ); + + if (patch->numtransfers == 0) + { + // Error, using patch that was never evaluated or has no samples + // patch->totallight[1] = 255; + } + + // + // displacement surface patch origin position and normal vectors have been changed to + // represent the displacement surface position and normal -- for radial "blending" + // we need to get the base surface patch origin! + // + if( ValidDispFace( &g_pFaces[facenum] ) ) + { + Vector patchOrigin; + WindingCenter (patch->winding, patchOrigin ); + AddBouncedToRadial( rad, patchOrigin, mins, maxs, patch->totallight.light, + needsBumpmap, needsBumpmap ); + } + else + { + AddBouncedToRadial( rad, patch->origin, mins, maxs, patch->totallight.light, + needsBumpmap, needsBumpmap ); + } + } + } + + for (j=0 ; j<fn->numneighbors; j++) + { + if( g_FacePatches.Element( fn->neighbor[j] ) != g_FacePatches.InvalidIndex() ) + { + for( patch = &g_Patches.Element( g_FacePatches.Element( fn->neighbor[j] ) ); patch; patch = pNextPatch ) + { + // next patch + pNextPatch = NULL; + if( patch->ndxNext != g_Patches.InvalidIndex() ) + { + pNextPatch = &g_Patches.Element( patch->ndxNext ); + } + + // skip patches with children + if (patch->child1 != g_Patches.InvalidIndex() ) + continue; + + // get the range of patch lightmap texture coords + int ndxPatch = patch - g_Patches.Base(); + PatchLightmapCoordRange( rad, ndxPatch, mins, maxs ); + + neighborNeedsBumpmap = texinfo[g_pFaces[facenum].texinfo].flags & SURF_BUMPLIGHT ? true : false; + + // + // displacement surface patch origin position and normal vectors have been changed to + // represent the displacement surface position and normal -- for radial "blending" + // we need to get the base surface patch origin! + // + if( ValidDispFace( &g_pFaces[fn->neighbor[j]] ) ) + { + Vector patchOrigin; + WindingCenter (patch->winding, patchOrigin ); + AddBouncedToRadial( rad, patchOrigin, mins, maxs, patch->totallight.light, + needsBumpmap, needsBumpmap ); + } + else + { + AddBouncedToRadial( rad, patch->origin, mins, maxs, patch->totallight.light, + needsBumpmap, needsBumpmap ); + } + } + } + } + + return rad; +} + + +radial_t *BuildLuxelRadial( int facenum, int style ) +{ + LightingValue_t light[NUM_BUMP_VECTS + 1]; + + facelight_t *fl = &facelight[facenum]; + faceneighbor_t *fn = &faceneighbor[facenum]; + + radial_t *rad = AllocateRadial( facenum ); + + bool needsBumpmap = texinfo[g_pFaces[facenum].texinfo].flags & SURF_BUMPLIGHT ? true : false; + + for (int k=0 ; k<fl->numsamples ; k++) + { + if( needsBumpmap ) + { + for( int bumpSample = 0; bumpSample < NUM_BUMP_VECTS + 1; bumpSample++ ) + { + light[bumpSample] = fl->light[style][bumpSample][k]; + } + } + else + { + light[0] = fl->light[style][0][k]; + } + + AddDirectToRadial( rad, fl->sample[k].pos, fl->sample[k].mins, fl->sample[k].maxs, light, needsBumpmap, needsBumpmap ); + } + + for (int j = 0; j < fn->numneighbors; j++) + { + fl = &facelight[fn->neighbor[j]]; + + bool neighborHasBumpmap = false; + + if( texinfo[g_pFaces[fn->neighbor[j]].texinfo].flags & SURF_BUMPLIGHT ) + { + neighborHasBumpmap = true; + } + + int nstyle = 0; + + // look for style that matches + if (g_pFaces[fn->neighbor[j]].styles[nstyle] != g_pFaces[facenum].styles[style]) + { + for (nstyle = 1; nstyle < MAXLIGHTMAPS; nstyle++ ) + if ( g_pFaces[fn->neighbor[j]].styles[nstyle] == g_pFaces[facenum].styles[style] ) + break; + + // if not found, skip this neighbor + if (nstyle >= MAXLIGHTMAPS) + continue; + } + + lightinfo_t l; + + InitLightinfo( &l, fn->neighbor[j] ); + + for (int k=0 ; k<fl->numsamples ; k++) + { + if( neighborHasBumpmap ) + { + for( int bumpSample = 0; bumpSample < NUM_BUMP_VECTS + 1; bumpSample++ ) + { + light[bumpSample] = fl->light[nstyle][bumpSample][k]; + } + } + else + { + light[0]=fl->light[nstyle][0][k]; + } + + Vector tmp; + Vector2D mins, maxs; + + LuxelSpaceToWorld( &l, fl->sample[k].mins[0], fl->sample[k].mins[1], tmp ); + WorldToLuxelSpace( &rad->l, tmp, mins ); + LuxelSpaceToWorld( &l, fl->sample[k].maxs[0], fl->sample[k].maxs[1], tmp ); + WorldToLuxelSpace( &rad->l, tmp, maxs ); + + AddDirectToRadial( rad, fl->sample[k].pos, mins, maxs, light, + needsBumpmap, neighborHasBumpmap ); + } + } + + return rad; +} + + +//----------------------------------------------------------------------------- +// Purpose: returns the closest light value for a given point on the surface +// this is normally a 1:1 mapping +//----------------------------------------------------------------------------- +bool SampleRadial( radial_t *rad, Vector& pnt, LightingValue_t light[NUM_BUMP_VECTS + 1], int bumpSampleCount ) +{ + int bumpSample; + Vector2D coord; + + WorldToLuxelSpace( &rad->l, pnt, coord ); + int u = ( int )( coord[0] + 0.5f ); + int v = ( int )( coord[1] + 0.5f ); + int i = u + v * rad->w; + + if (u < 0 || u > rad->w || v < 0 || v > rad->h) + { + static bool warning = false; + if ( !warning ) + { + // punting over to KenB + // 2d coord indexes off of lightmap, generation of pnt seems suspect + Warning( "SampleRadial: Punting, Waiting for fix\n" ); + warning = true; + } + for( bumpSample = 0; bumpSample < bumpSampleCount; bumpSample++ ) + { + light[bumpSample].m_vecLighting.Init( 2550, 0, 0 ); + } + return false; + } + + bool baseSampleOk = true; + for( bumpSample = 0; bumpSample < bumpSampleCount; bumpSample++ ) + { + light[bumpSample].Zero(); + + if (rad->weight[i] > WEIGHT_EPS) + { + light[bumpSample]= rad->light[bumpSample][i]; + light[bumpSample].Scale( 1.0 / rad->weight[i] ); + } + else + { + if ( bRed2Black ) + { + // Error, luxel has no samples + light[bumpSample].m_vecLighting.Init( 0, 0, 0 ); + } + else + { + // Error, luxel has no samples + // Yes, it actually should be 2550 + light[bumpSample].m_vecLighting.Init( 2550, 0, 0 ); + } + + if (bumpSample == 0) + baseSampleOk = false; + } + } + + return baseSampleOk; +} + +bool FloatLess( float const& src1, float const& src2 ) +{ + return src1 < src2; +} + + +//----------------------------------------------------------------------------- +// Debugging! +//----------------------------------------------------------------------------- +void GetRandomColor( unsigned char *color ) +{ + static bool firstTime = true; + + if( firstTime ) + { + firstTime = false; + srand( 0 ); + } + + color[0] = ( unsigned char )( rand() * ( 255.0f / VALVE_RAND_MAX ) ); + color[1] = ( unsigned char )( rand() * ( 255.0f / VALVE_RAND_MAX ) ); + color[2] = ( unsigned char )( rand() * ( 255.0f / VALVE_RAND_MAX ) ); +} + + +#if 0 +// debugging! -- not accurate! +void DumpLuxels( facelight_t *pFaceLight, Vector *luxelColors, int ndxFace ) +{ + static FileHandle_t pFpLuxels = NULL; + + ThreadLock(); + + if( !pFpLuxels ) + { + pFpLuxels = g_pFileSystem->Open( "luxels.txt", "w" ); + } + + dface_t *pFace = &g_pFaces[ndxFace]; + bool bDisp = ( pFace->dispinfo != -1 ); + + for( int ndx = 0; ndx < pFaceLight->numluxels; ndx++ ) + { + WriteWinding( pFpLuxels, pFaceLight->sample[ndx].w, luxelColors[ndx] ); + if( bDumpNormals && bDisp ) + { + WriteNormal( pFpLuxels, pFaceLight->luxel[ndx], pFaceLight->luxelNormals[ndx], 15.0f, Vector( 255, 255, 0 ) ); + } + } + + ThreadUnlock(); +} +#endif + + +static FileHandle_t pFileLuxels[4] = { NULL, NULL, NULL, NULL }; + +void DumpDispLuxels( int iFace, Vector &color, int iLuxel, int nBump ) +{ + // Lock the thread and dump the luxel data. + ThreadLock(); + + // Get the face and facelight data. + facelight_t *pFaceLight = &facelight[iFace]; + + // Open the luxel files. + char szFileName[512]; + for ( int iBump = 0; iBump < ( NUM_BUMP_VECTS+1 ); ++iBump ) + { + if ( pFileLuxels[iBump] == NULL ) + { + sprintf( szFileName, "luxels_bump%d.txt", iBump ); + pFileLuxels[iBump] = g_pFileSystem->Open( szFileName, "w" ); + } + } + + WriteWinding( pFileLuxels[nBump], pFaceLight->sample[iLuxel].w, color ); + + ThreadUnlock(); +} + +void CloseDispLuxels() +{ + for ( int iBump = 0; iBump < ( NUM_BUMP_VECTS+1 ); ++iBump ) + { + if ( pFileLuxels[iBump] ) + { + g_pFileSystem->Close( pFileLuxels[iBump] ); + } + } +} + +/* +============= +FinalLightFace + +Add the indirect lighting on top of the direct +lighting and save into final map format +============= +*/ +void FinalLightFace( int iThread, int facenum ) +{ + dface_t *f; + int i, j, k; + facelight_t *fl; + float minlight; + int lightstyles; + LightingValue_t lb[NUM_BUMP_VECTS + 1], v[NUM_BUMP_VECTS + 1]; + unsigned char *pdata[NUM_BUMP_VECTS + 1]; + int bumpSample; + radial_t *rad = NULL; + radial_t *prad = NULL; + + f = &g_pFaces[facenum]; + + // test for non-lit texture + if ( texinfo[f->texinfo].flags & TEX_SPECIAL) + return; + + fl = &facelight[facenum]; + + + for (lightstyles=0; lightstyles < MAXLIGHTMAPS; lightstyles++ ) + { + if ( f->styles[lightstyles] == 255 ) + break; + } + if ( !lightstyles ) + return; + + + // + // sample the triangulation + // + minlight = FloatForKey (face_entity[facenum], "_minlight") * 128; + + bool needsBumpmap = ( texinfo[f->texinfo].flags & SURF_BUMPLIGHT ) ? true : false; + int bumpSampleCount = needsBumpmap ? NUM_BUMP_VECTS + 1 : 1; + + bool bDisp = ( f->dispinfo != -1 ); + +//#define RANDOM_COLOR + +#ifdef RANDOM_COLOR + unsigned char randomColor[3]; + GetRandomColor( randomColor ); +#endif + + + // NOTE: I'm using these RB trees to sort all the illumination values + // to compute median colors. Turns out that this is a somewhat better + // method that using the average; usually if there are surfaces + // with a large light intensity variation, the extremely bright regions + // have a very small area and tend to influence the average too much. + CUtlRBTree< float, int > m_Red( 0, 256, FloatLess ); + CUtlRBTree< float, int > m_Green( 0, 256, FloatLess ); + CUtlRBTree< float, int > m_Blue( 0, 256, FloatLess ); + + for (k=0 ; k < lightstyles; k++ ) + { + m_Red.RemoveAll(); + m_Green.RemoveAll(); + m_Blue.RemoveAll(); + + if (!do_fast) + { + if( !bDisp ) + { + rad = BuildLuxelRadial( facenum, k ); + } + else + { + rad = StaticDispMgr()->BuildLuxelRadial( facenum, k, needsBumpmap ); + } + } + + if (numbounce > 0 && k == 0) + { + // currently only radiosity light non-displacement surfaces! + if( !bDisp ) + { + prad = BuildPatchRadial( facenum ); + } + else + { + prad = StaticDispMgr()->BuildPatchRadial( facenum, needsBumpmap ); + } + } + + // pack the nonbump texture and the three bump texture for the given + // lightstyle right next to each other. + // NOTE: Even though it's building positions for all bump-mapped data, + // it isn't going to use those positions (see loop over bumpSample below) + // The file offset is correctly computed to only store space for 1 set + // of light data if we don't have bumped lighting. + for( bumpSample = 0; bumpSample < bumpSampleCount; ++bumpSample ) + { + pdata[bumpSample] = &(*pdlightdata)[f->lightofs + (k * bumpSampleCount + bumpSample) * fl->numluxels*4]; + } + + // Compute the average luxel color, but not for the bump samples + Vector avg( 0.0f, 0.0f, 0.0f ); + int avgCount = 0; + + for (j=0 ; j<fl->numluxels; j++) + { + // garymct - direct lighting + bool baseSampleOk = true; + + if (!do_fast) + { + if( !bDisp ) + { + baseSampleOk = SampleRadial( rad, fl->luxel[j], lb, bumpSampleCount ); + } + else + { + baseSampleOk = StaticDispMgr()->SampleRadial( facenum, rad, fl->luxel[j], j, lb, bumpSampleCount, false ); + } + } + else + { + for ( int iBump = 0 ; iBump < bumpSampleCount; iBump++ ) + { + lb[iBump] = fl->light[0][iBump][j]; + } + } + + if (prad) + { + // garymct - bounced light + // v is indirect light that is received on the luxel. + if( !bDisp ) + { + SampleRadial( prad, fl->luxel[j], v, bumpSampleCount ); + } + else + { + StaticDispMgr()->SampleRadial( facenum, prad, fl->luxel[j], j, v, bumpSampleCount, true ); + } + + for( bumpSample = 0; bumpSample < bumpSampleCount; ++bumpSample ) + { + lb[bumpSample].AddLight( v[bumpSample] ); + } + } + + if ( bDisp && g_bDumpPatches ) + { + for( bumpSample = 0; bumpSample < bumpSampleCount; ++bumpSample ) + { + DumpDispLuxels( facenum, lb[bumpSample].m_vecLighting, j, bumpSample ); + } + } + + if (fl->numsamples == 0) + { + for( i = 0; i < bumpSampleCount; i++ ) + { + lb[i].Init( 255, 0, 0 ); + } + baseSampleOk = false; + } + + int bumpSample; + for( bumpSample = 0; bumpSample < bumpSampleCount; bumpSample++ ) + { + // clip from the bottom first + // garymct: minlight is a per entity minimum light value? + for( i=0; i<3; i++ ) + { + lb[bumpSample].m_vecLighting[i] = max( lb[bumpSample].m_vecLighting[i], minlight ); + } + + // Do the average light computation, I'm assuming (perhaps incorrectly?) + // that all luxels in a particular lightmap have the same area here. + // Also, don't bother doing averages for the bump samples. Doing it here + // because of the minlight clamp above + the random color testy thingy. + // Also have to do it before Vec3toColorRGBExp32 because it + // destructively modifies lb[bumpSample] (Feh!) + if ((bumpSample == 0) && baseSampleOk) + { + ++avgCount; + + ApplyMacroTextures( facenum, fl->luxel[j], lb[0].m_vecLighting ); + + // For median computation + m_Red.Insert( lb[bumpSample].m_vecLighting[0] ); + m_Green.Insert( lb[bumpSample].m_vecLighting[1] ); + m_Blue.Insert( lb[bumpSample].m_vecLighting[2] ); + } + +#ifdef RANDOM_COLOR + pdata[bumpSample][0] = randomColor[0] / ( bumpSample + 1 ); + pdata[bumpSample][1] = randomColor[1] / ( bumpSample + 1 ); + pdata[bumpSample][2] = randomColor[2] / ( bumpSample + 1 ); + pdata[bumpSample][3] = 0; +#else + // convert to a 4 byte r,g,b,signed exponent format + VectorToColorRGBExp32( Vector( lb[bumpSample].m_vecLighting.x, lb[bumpSample].m_vecLighting.y, + lb[bumpSample].m_vecLighting.z ), *( ColorRGBExp32 *)pdata[bumpSample] ); +#endif + + pdata[bumpSample] += 4; + } + } + FreeRadial( rad ); + if (prad) + { + FreeRadial( prad ); + prad = NULL; + } + + // Compute the median color for this lightstyle + // Remember, the data goes *before* the specified light_ofs, in *reverse order* + ColorRGBExp32 *pAvgColor = dface_AvgLightColor( f, k ); + if (avgCount == 0) + { + Vector median( 0, 0, 0 ); + VectorToColorRGBExp32( median, *pAvgColor ); + } + else + { + unsigned int r, g, b; + r = m_Red.FirstInorder(); + g = m_Green.FirstInorder(); + b = m_Blue.FirstInorder(); + avgCount >>= 1; + while (avgCount > 0) + { + r = m_Red.NextInorder(r); + g = m_Green.NextInorder(g); + b = m_Blue.NextInorder(b); + --avgCount; + } + + Vector median( m_Red[r], m_Green[g], m_Blue[b] ); + VectorToColorRGBExp32( median, *pAvgColor ); + } + } +} diff --git a/utils/vrad/radial.h b/utils/vrad/radial.h new file mode 100644 index 0000000..ebf8289 --- /dev/null +++ b/utils/vrad/radial.h @@ -0,0 +1,76 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef RADIAL_H +#define RADIAL_H +#pragma once + +#include "mathlib/bumpvects.h" +#include "mathlib/ssemath.h" +#include "lightmap.h" + +#define RADIALDIST2 2 // (1.25*1.25+1.25*1.25) +#define RADIALDIST 1.42 // 1.77 // sqrt( RADIALDIST2 ) + +#define WEIGHT_EPS 0.00001f + +//----------------------------------------------------------------------------- +// The radial_t data structure is used to accumulate irregularly spaced and irregularly +// shaped direct and indirect lighting samples into a uniformly spaced and shaped luxel grid. +// +// The name "radial" is more historical than discriptive; it stems from the filtering method, +// one of several methods initially tried. Since all the other methods have since been deleted, +// it would probably be more accurate to rename it something like "LuxelAccumulationBucket" or +// something similar, but since "radial" is fairly meaningless it's not like it's actually confusing +// the issue. +//----------------------------------------------------------------------------- +typedef struct radial_s +{ + int facenum; + lightinfo_t l; + int w, h; + float weight[SINGLEMAP]; + LightingValue_t light[NUM_BUMP_VECTS + 1][SINGLEMAP]; +} radial_t; + + +void WorldToLuxelSpace( lightinfo_t const *l, Vector const &world, Vector2D &coord ); +void LuxelSpaceToWorld( lightinfo_t const *l, float s, float t, Vector &world ); + +void WorldToLuxelSpace( lightinfo_t const *l, FourVectors const &world, FourVectors &coord ); +void LuxelSpaceToWorld( lightinfo_t const *l, fltx4 s, fltx4 t, FourVectors &world ); + +void AddDirectToRadial( radial_t *rad, + Vector const &pnt, + Vector2D const &coordmins, Vector2D const &coordmaxs, + Vector const light[NUM_BUMP_VECTS+1], + bool hasBumpmap, bool neighborHasBumpmap ); + +void AddBounceToRadial( radial_t *rad, + Vector const &pnt, + Vector2D const &coordmins, Vector2D const &coordmaxs, + Vector const light[NUM_BUMP_VECTS+1], + bool hasBumpmap, bool neighborHasBumpmap ); + +bool SampleRadial( radial_t *rad, Vector& pnt, Vector light[NUM_BUMP_VECTS+1], int bumpSampleCount ); + +radial_t *AllocateRadial( int facenum ); +void FreeRadial( radial_t *rad ); + +bool SampleRadial( radial_t *rad, Vector& pnt, Vector light[NUM_BUMP_VECTS + 1], int bumpSampleCount ); +radial_t *BuildPatchRadial( int facenum ); + +// utilities +bool FloatLess( float const& src1, float const& src2 ); + +#endif diff --git a/utils/vrad/samplehash.cpp b/utils/vrad/samplehash.cpp new file mode 100644 index 0000000..09bc94e --- /dev/null +++ b/utils/vrad/samplehash.cpp @@ -0,0 +1,230 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#include "vrad.h" +#include "lightmap.h" + +#define SAMPLEHASH_NUM_BUCKETS 65536 +#define SAMPLEHASH_GROW_SIZE 0 +#define SAMPLEHASH_INIT_SIZE 0 + +int samplesAdded = 0; +int patchSamplesAdded = 0; +static unsigned short g_PatchIterationKey = 0; + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool SampleData_CompareFunc( SampleData_t const &src1, SampleData_t const &src2 ) +{ + return ( ( src1.x == src2.x ) && + ( src1.y == src2.y ) && + ( src1.z == src2.z ) ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +unsigned int SampleData_KeyFunc( SampleData_t const &src ) +{ + return ( src.x + src.y + src.z ); +} + + +CUtlHash<SampleData_t> g_SampleHashTable( SAMPLEHASH_NUM_BUCKETS, + SAMPLEHASH_GROW_SIZE, + SAMPLEHASH_INIT_SIZE, + SampleData_CompareFunc, SampleData_KeyFunc ); + + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +UtlHashHandle_t SampleData_Find( sample_t *pSample ) +{ + SampleData_t sampleData; + sampleData.x = ( int )( pSample->pos.x / SAMPLEHASH_VOXEL_SIZE ) * 100; + sampleData.y = ( int )( pSample->pos.y / SAMPLEHASH_VOXEL_SIZE ) * 10; + sampleData.z = ( int )( pSample->pos.z / SAMPLEHASH_VOXEL_SIZE ); + + return g_SampleHashTable.Find( sampleData ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +UtlHashHandle_t SampleData_InsertIntoHashTable( sample_t *pSample, SampleHandle_t sampleHandle ) +{ + SampleData_t sampleData; + sampleData.x = ( int )( pSample->pos.x / SAMPLEHASH_VOXEL_SIZE ) * 100; + sampleData.y = ( int )( pSample->pos.y / SAMPLEHASH_VOXEL_SIZE ) * 10; + sampleData.z = ( int )( pSample->pos.z / SAMPLEHASH_VOXEL_SIZE ); + + UtlHashHandle_t handle = g_SampleHashTable.AllocEntryFromKey( sampleData ); + + SampleData_t *pSampleData = &g_SampleHashTable.Element( handle ); + pSampleData->x = sampleData.x; + pSampleData->y = sampleData.y; + pSampleData->z = sampleData.z; + pSampleData->m_Samples.AddToTail( sampleHandle ); + + samplesAdded++; + + return handle; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +UtlHashHandle_t SampleData_AddSample( sample_t *pSample, SampleHandle_t sampleHandle ) +{ + + // find the key -- if it doesn't exist add new sample data to the + // hash table + UtlHashHandle_t handle = SampleData_Find( pSample ); + if( handle == g_SampleHashTable.InvalidHandle() ) + { + handle = SampleData_InsertIntoHashTable( pSample, sampleHandle ); + } + else + { + SampleData_t *pSampleData = &g_SampleHashTable.Element( handle ); + pSampleData->m_Samples.AddToTail( sampleHandle ); + + samplesAdded++; + } + + return handle; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void SampleData_Log( void ) +{ + if( g_bLogHashData ) + { + g_SampleHashTable.Log( "samplehash.txt" ); + } +} + + +//============================================================================= +//============================================================================= +// +// PatchSample Functions +// +//============================================================================= +//============================================================================= + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool PatchSampleData_CompareFunc( PatchSampleData_t const &src1, PatchSampleData_t const &src2 ) +{ + return ( ( src1.x == src2.x ) && + ( src1.y == src2.y ) && + ( src1.z == src2.z ) ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +unsigned int PatchSampleData_KeyFunc( PatchSampleData_t const &src ) +{ + return ( src.x + src.y + src.z ); +} + + +CUtlHash<PatchSampleData_t> g_PatchSampleHashTable( SAMPLEHASH_NUM_BUCKETS, + SAMPLEHASH_GROW_SIZE, + SAMPLEHASH_INIT_SIZE, + PatchSampleData_CompareFunc, PatchSampleData_KeyFunc ); + +void GetPatchSampleHashXYZ( const Vector &vOrigin, int &x, int &y, int &z ) +{ + x = ( int )( vOrigin.x / SAMPLEHASH_VOXEL_SIZE ); + y = ( int )( vOrigin.y / SAMPLEHASH_VOXEL_SIZE ); + z = ( int )( vOrigin.z / SAMPLEHASH_VOXEL_SIZE ); +} + + +unsigned short IncrementPatchIterationKey() +{ + if ( g_PatchIterationKey == 0xFFFF ) + { + g_PatchIterationKey = 1; + for ( int i=0; i < g_Patches.Count(); i++ ) + g_Patches[i].m_IterationKey = 0; + } + else + { + g_PatchIterationKey++; + } + return g_PatchIterationKey; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void PatchSampleData_AddSample( CPatch *pPatch, int ndxPatch ) +{ + int patchSampleMins[3], patchSampleMaxs[3]; + +#if defined( SAMPLEHASH_USE_AREA_PATCHES ) + GetPatchSampleHashXYZ( pPatch->mins, patchSampleMins[0], patchSampleMins[1], patchSampleMins[2] ); + GetPatchSampleHashXYZ( pPatch->maxs, patchSampleMaxs[0], patchSampleMaxs[1], patchSampleMaxs[2] ); +#else + // If not using area patches, just use the patch's origin to add it to the voxels. + GetPatchSampleHashXYZ( pPatch->origin, patchSampleMins[0], patchSampleMins[1], patchSampleMins[2] ); + memcpy( patchSampleMaxs, patchSampleMins, sizeof( patchSampleMaxs ) ); +#endif + + // Make sure mins are smaller than maxs so we don't iterate for 4 bil. + Assert( patchSampleMins[0] <= patchSampleMaxs[0] && patchSampleMins[1] <= patchSampleMaxs[1] && patchSampleMins[2] <= patchSampleMaxs[2] ); + patchSampleMins[0] = min( patchSampleMins[0], patchSampleMaxs[0] ); + patchSampleMins[1] = min( patchSampleMins[1], patchSampleMaxs[1] ); + patchSampleMins[2] = min( patchSampleMins[2], patchSampleMaxs[2] ); + + int iterateCoords[3]; + for ( iterateCoords[0]=patchSampleMins[0]; iterateCoords[0] <= patchSampleMaxs[0]; iterateCoords[0]++ ) + { + for ( iterateCoords[1]=patchSampleMins[1]; iterateCoords[1] <= patchSampleMaxs[1]; iterateCoords[1]++ ) + { + for ( iterateCoords[2]=patchSampleMins[2]; iterateCoords[2] <= patchSampleMaxs[2]; iterateCoords[2]++ ) + { + // find the key -- if it doesn't exist add new sample data to the + // hash table + PatchSampleData_t iteratePatch; + iteratePatch.x = iterateCoords[0] * 100; + iteratePatch.y = iterateCoords[1] * 10; + iteratePatch.z = iterateCoords[2]; + + UtlHashHandle_t handle = g_PatchSampleHashTable.Find( iteratePatch ); + if( handle == g_PatchSampleHashTable.InvalidHandle() ) + { + UtlHashHandle_t handle = g_PatchSampleHashTable.AllocEntryFromKey( iteratePatch ); + + PatchSampleData_t *pPatchData = &g_PatchSampleHashTable.Element( handle ); + pPatchData->x = iteratePatch.x; + pPatchData->y = iteratePatch.y; + pPatchData->z = iteratePatch.z; + pPatchData->m_ndxPatches.AddToTail( ndxPatch ); + + patchSamplesAdded++; + } + else + { + PatchSampleData_t *pPatchData = &g_PatchSampleHashTable.Element( handle ); + pPatchData->m_ndxPatches.AddToTail( ndxPatch ); + + patchSamplesAdded++; + } + } + } + } +} + diff --git a/utils/vrad/trace.cpp b/utils/vrad/trace.cpp new file mode 100644 index 0000000..e0926e2 --- /dev/null +++ b/utils/vrad/trace.cpp @@ -0,0 +1,653 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//===========================================================================// +// trace.c + +//============================================================================= + +#include "vrad.h" +#include "trace.h" +#include "Cmodel.h" +#include "mathlib/vmatrix.h" + + +//============================================================================= + +class CToolTrace : public CBaseTrace +{ +public: + CToolTrace() {} + + Vector mins; + Vector maxs; + Vector extents; + + texinfo_t *surface; + + qboolean ispoint; + +private: + CToolTrace( const CToolTrace& ); +}; + + +// 1/32 epsilon to keep floating point happy +#define DIST_EPSILON (0.03125) + +// JAYHL2: This used to be -1, but that caused lots of epsilon issues +// around slow sloping planes. Perhaps Quake2 limited maps to a certain +// slope / angle on walkable ground. It has to be a negative number +// so that the tests work out. +#define NEVER_UPDATED -9999 + +//============================================================================= + +bool DM_RayDispIntersectTest( CVRADDispColl *pTree, Vector& rayStart, Vector& rayEnd, CToolTrace *pTrace ); +void DM_ClipBoxToBrush( CToolTrace *trace, const Vector & mins, const Vector & maxs, const Vector& p1, const Vector& p2, dbrush_t *brush ); + +//============================================================================= + +float TraceLeafBrushes( int leafIndex, const Vector &start, const Vector &end, CBaseTrace &traceOut ) +{ + dleaf_t *pLeaf = dleafs + leafIndex; + CToolTrace trace; + memset( &trace, 0, sizeof(trace) ); + trace.ispoint = true; + trace.startsolid = false; + trace.fraction = 1.0; + + for ( int i = 0; i < pLeaf->numleafbrushes; i++ ) + { + int brushnum = dleafbrushes[pLeaf->firstleafbrush+i]; + dbrush_t *b = &dbrushes[brushnum]; + if ( !(b->contents & MASK_OPAQUE)) + continue; + + Vector zeroExtents = vec3_origin; + DM_ClipBoxToBrush( &trace, zeroExtents, zeroExtents, start, end, b); + if ( trace.fraction != 1.0 || trace.startsolid ) + { + if ( trace.startsolid ) + trace.fraction = 0.0f; + traceOut = trace; + return trace.fraction; + } + } + traceOut = trace; + return 1.0f; +} + +DispTested_t s_DispTested[MAX_TOOL_THREADS+1]; + +// this just uses the average coverage for the triangle +class CCoverageCount : public ITransparentTriangleCallback +{ +public: + CCoverageCount() + { + m_coverage = Four_Zeros; + } + + virtual bool VisitTriangle_ShouldContinue( const TriIntersectData_t &triangle, const FourRays &rays, fltx4 *pHitMask, fltx4 *b0, fltx4 *b1, fltx4 *b2, int32 hitID ) + { + float color = g_RtEnv.GetTriangleColor( hitID ).x; + m_coverage = AddSIMD( m_coverage, AndSIMD ( *pHitMask, ReplicateX4 ( color ) ) ); + m_coverage = MinSIMD( m_coverage, Four_Ones ); + + fltx4 onesMask = CmpEqSIMD( m_coverage, Four_Ones ); + + // we should continue if the ones that hit the triangle have onesMask set to zero + // so hitMask & onesMask != hitMask + // so hitMask & onesMask == hitMask means we're done + // so ts(hitMask & onesMask == hitMask) != 0xF says go on + return 0xF != TestSignSIMD ( CmpEqSIMD ( AndSIMD( *pHitMask, onesMask ), *pHitMask ) ); + } + + fltx4 GetCoverage() + { + return m_coverage; + } + + fltx4 GetFractionVisible() + { + return SubSIMD ( Four_Ones, m_coverage ); + } + + fltx4 m_coverage; +}; + +// this will sample the texture to get a coverage at the ray intersection point +class CCoverageCountTexture : public CCoverageCount +{ +public: + virtual bool VisitTriangle_ShouldContinue( const TriIntersectData_t &triangle, const FourRays &rays, fltx4 *pHitMask, fltx4 *b0, fltx4 *b1, fltx4 *b2, int32 hitID ) + { + int sign = TestSignSIMD( *pHitMask ); + float addedCoverage[4]; + for ( int s = 0; s < 4; s++) + { + addedCoverage[s] = 0.0f; + if ( ( sign >> s) & 0x1 ) + { + addedCoverage[s] = ComputeCoverageFromTexture( b0->m128_f32[s], b1->m128_f32[s], b2->m128_f32[s], hitID ); + } + } + m_coverage = AddSIMD( m_coverage, LoadUnalignedSIMD( addedCoverage ) ); + m_coverage = MinSIMD( m_coverage, Four_Ones ); + fltx4 onesMask = CmpEqSIMD( m_coverage, Four_Ones ); + + // we should continue if the ones that hit the triangle have onesMask set to zero + // so hitMask & onesMask != hitMask + // so hitMask & onesMask == hitMask means we're done + // so ts(hitMask & onesMask == hitMask) != 0xF says go on + return 0xF != TestSignSIMD ( CmpEqSIMD ( AndSIMD( *pHitMask, onesMask ), *pHitMask ) ); + } +}; + +void TestLine( const FourVectors& start, const FourVectors& stop, + fltx4 *pFractionVisible, int static_prop_index_to_ignore ) +{ + FourRays myrays; + myrays.origin = start; + myrays.direction = stop; + myrays.direction -= myrays.origin; + fltx4 len = myrays.direction.length(); + myrays.direction *= ReciprocalSIMD( len ); + + RayTracingResult rt_result; + CCoverageCountTexture coverageCallback; + + g_RtEnv.Trace4Rays(myrays, Four_Zeros, len, &rt_result, TRACE_ID_STATICPROP | static_prop_index_to_ignore, g_bTextureShadows ? &coverageCallback : 0 ); + + // Assume we can see the targets unless we get hits + float visibility[4]; + for ( int i = 0; i < 4; i++ ) + { + visibility[i] = 1.0f; + if ( ( rt_result.HitIds[i] != -1 ) && + ( rt_result.HitDistance.m128_f32[i] < len.m128_f32[i] ) ) + { + visibility[i] = 0.0f; + } + } + *pFractionVisible = LoadUnalignedSIMD( visibility ); + if ( g_bTextureShadows ) + *pFractionVisible = MinSIMD( *pFractionVisible, coverageCallback.GetFractionVisible() ); +} + + + +/* +================ +DM_ClipBoxToBrush +================ +*/ +void DM_ClipBoxToBrush( CToolTrace *trace, const Vector& mins, const Vector& maxs, const Vector& p1, const Vector& p2, + dbrush_t *brush) +{ + dplane_t *plane, *clipplane; + float dist; + Vector ofs; + float d1, d2; + float f; + dbrushside_t *side, *leadside; + + if (!brush->numsides) + return; + + float enterfrac = NEVER_UPDATED; + float leavefrac = 1.f; + clipplane = NULL; + + bool getout = false; + bool startout = false; + leadside = NULL; + + // Loop interchanged, so we don't have to check trace->ispoint every side. + if ( !trace->ispoint ) + { + for (int i=0 ; i<brush->numsides ; ++i) + { + side = &dbrushsides[brush->firstside+i]; + plane = dplanes + side->planenum; + + // FIXME: special case for axial + + // general box case + // push the plane out apropriately for mins/maxs + + // FIXME: use signbits into 8 way lookup for each mins/maxs + ofs.x = (plane->normal.x < 0) ? maxs.x : mins.x; + ofs.y = (plane->normal.y < 0) ? maxs.y : mins.y; + ofs.z = (plane->normal.z < 0) ? maxs.z : mins.z; +// for (j=0 ; j<3 ; j++) +// { + // Set signmask to either 0 if the sign is negative, or 0xFFFFFFFF is the sign is positive: + //int signmask = (((*(int *)&(plane->normal[j]))&0x80000000) >> 31) - 1; + + //float temp = maxs[j]; + //*(int *)&(ofs[j]) = (~signmask) & (*(int *)&temp); + //float temp1 = mins[j]; + //*(int *)&(ofs[j]) |= (signmask) & (*(int *)&temp1); +// } + dist = DotProduct (ofs, plane->normal); + dist = plane->dist - dist; + + d1 = DotProduct (p1, plane->normal) - dist; + d2 = DotProduct (p2, plane->normal) - dist; + + // if completely in front of face, no intersection + if (d1 > 0 && d2 > 0) + return; + + if (d2 > 0) + getout = true; // endpoint is not in solid + if (d1 > 0) + startout = true; + + if (d1 <= 0 && d2 <= 0) + continue; + + // crosses face + if (d1 > d2) + { // enter + f = (d1-DIST_EPSILON) / (d1-d2); + if (f > enterfrac) + { + enterfrac = f; + clipplane = plane; + leadside = side; + } + } + else + { // leave + f = (d1+DIST_EPSILON) / (d1-d2); + if (f < leavefrac) + leavefrac = f; + } + } + } + else + { + for (int i=0 ; i<brush->numsides ; ++i) + { + side = &dbrushsides[brush->firstside+i]; + plane = dplanes + side->planenum; + + // FIXME: special case for axial + + // special point case + // don't ray trace against bevel planes + if( side->bevel == 1 ) + continue; + + dist = plane->dist; + d1 = DotProduct (p1, plane->normal) - dist; + d2 = DotProduct (p2, plane->normal) - dist; + + // if completely in front of face, no intersection + if (d1 > 0 && d2 > 0) + return; + + if (d2 > 0) + getout = true; // endpoint is not in solid + if (d1 > 0) + startout = true; + + if (d1 <= 0 && d2 <= 0) + continue; + + // crosses face + if (d1 > d2) + { // enter + f = (d1-DIST_EPSILON) / (d1-d2); + if (f > enterfrac) + { + enterfrac = f; + clipplane = plane; + leadside = side; + } + } + else + { // leave + f = (d1+DIST_EPSILON) / (d1-d2); + if (f < leavefrac) + leavefrac = f; + } + } + } + + + + if (!startout) + { // original point was inside brush + trace->startsolid = true; + if (!getout) + trace->allsolid = true; + return; + } + if (enterfrac < leavefrac) + { + if (enterfrac > NEVER_UPDATED && enterfrac < trace->fraction) + { + if (enterfrac < 0) + enterfrac = 0; + trace->fraction = enterfrac; + trace->plane.dist = clipplane->dist; + trace->plane.normal = clipplane->normal; + trace->plane.type = clipplane->type; + if (leadside->texinfo!=-1) + trace->surface = &texinfo[leadside->texinfo]; + else + trace->surface = 0; + trace->contents = brush->contents; + } + } +} + +void TestLine_DoesHitSky( FourVectors const& start, FourVectors const& stop, + fltx4 *pFractionVisible, bool canRecurse, int static_prop_to_skip, bool bDoDebug ) +{ + FourRays myrays; + myrays.origin = start; + myrays.direction = stop; + myrays.direction -= myrays.origin; + fltx4 len = myrays.direction.length(); + myrays.direction *= ReciprocalSIMD( len ); + RayTracingResult rt_result; + CCoverageCountTexture coverageCallback; + + g_RtEnv.Trace4Rays(myrays, Four_Zeros, len, &rt_result, TRACE_ID_STATICPROP | static_prop_to_skip, g_bTextureShadows? &coverageCallback : 0); + + if ( bDoDebug ) + { + WriteTrace( "trace.txt", myrays, rt_result ); + } + + float aOcclusion[4]; + for ( int i = 0; i < 4; i++ ) + { + aOcclusion[i] = 0.0f; + if ( ( rt_result.HitIds[i] != -1 ) && + ( rt_result.HitDistance.m128_f32[i] < len.m128_f32[i] ) ) + { + int id = g_RtEnv.OptimizedTriangleList[rt_result.HitIds[i]].m_Data.m_IntersectData.m_nTriangleID; + if ( !( id & TRACE_ID_SKY ) ) + aOcclusion[i] = 1.0f; + } + } + fltx4 occlusion = LoadUnalignedSIMD( aOcclusion ); + if (g_bTextureShadows) + occlusion = MaxSIMD ( occlusion, coverageCallback.GetCoverage() ); + + bool fullyOccluded = ( TestSignSIMD( CmpGeSIMD( occlusion, Four_Ones ) ) == 0xF ); + + // if we hit sky, and we're not in a sky camera's area, try clipping into the 3D sky boxes + if ( (! fullyOccluded) && canRecurse && (! g_bNoSkyRecurse ) ) + { + FourVectors dir = stop; + dir -= start; + dir.VectorNormalize(); + + int leafIndex = -1; + leafIndex = PointLeafnum( start.Vec( 0 ) ); + if ( leafIndex >= 0 ) + { + int area = dleafs[leafIndex].area; + if (area >= 0 && area < numareas) + { + if (area_sky_cameras[area] < 0) + { + int cam; + for (cam = 0; cam < num_sky_cameras; ++cam) + { + FourVectors skystart, skytrans, skystop; + skystart.DuplicateVector( sky_cameras[cam].origin ); + skystop = start; + skystop *= sky_cameras[cam].world_to_sky; + skystart += skystop; + + skystop = dir; + skystop *= MAX_TRACE_LENGTH; + skystop += skystart; + TestLine_DoesHitSky ( skystart, skystop, pFractionVisible, false, static_prop_to_skip, bDoDebug ); + occlusion = AddSIMD ( occlusion, Four_Ones ); + occlusion = SubSIMD ( occlusion, *pFractionVisible ); + } + } + } + } + } + + occlusion = MaxSIMD( occlusion, Four_Zeros ); + occlusion = MinSIMD( occlusion, Four_Ones ); + *pFractionVisible = SubSIMD( Four_Ones, occlusion ); +} + + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +int PointLeafnum_r( const Vector &point, int ndxNode ) +{ + // while loop here is to avoid recursion overhead + while( ndxNode >= 0 ) + { + dnode_t *pNode = dnodes + ndxNode; + dplane_t *pPlane = dplanes + pNode->planenum; + + float dist; + if( pPlane->type < 3 ) + { + dist = point[pPlane->type] - pPlane->dist; + } + else + { + dist = DotProduct( pPlane->normal, point ) - pPlane->dist; + } + + if( dist < 0.0f ) + { + ndxNode = pNode->children[1]; + } + else + { + ndxNode = pNode->children[0]; + } + } + + return ( -1 - ndxNode ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +int PointLeafnum( const Vector &point ) +{ + return PointLeafnum_r( point, 0 ); +} + +// this iterates the list of entities looking for _vradshadows 1 +// each brush entity containing this key is added to the raytracing environment +// as a triangle soup model. + +dmodel_t *BrushmodelForEntity( entity_t *pEntity ) +{ + const char *pModelname = ValueForKey( pEntity, "model" ); + if ( Q_strlen(pModelname) > 1 ) + { + int modelIndex = atol( pModelname + 1 ); + if ( modelIndex > 0 && modelIndex < nummodels ) + { + return &dmodels[modelIndex]; + } + } + return NULL; +} + +void AddBrushToRaytraceEnvironment( dbrush_t *pBrush, const VMatrix &xform ) +{ + if ( !( pBrush->contents & MASK_OPAQUE ) ) + return; + + Vector v0, v1, v2; + for (int i = 0; i < pBrush->numsides; i++ ) + { + dbrushside_t *side = &dbrushsides[pBrush->firstside + i]; + dplane_t *plane = &dplanes[side->planenum]; + texinfo_t *tx = &texinfo[side->texinfo]; + winding_t *w = BaseWindingForPlane (plane->normal, plane->dist); + + if ( tx->flags & SURF_SKY || side->dispinfo ) + continue; + + for (int j=0 ; j<pBrush->numsides && w; j++) + { + if (i == j) + continue; + dbrushside_t *pOtherSide = &dbrushsides[pBrush->firstside + j]; + if (pOtherSide->bevel) + continue; + plane = &dplanes[pOtherSide->planenum^1]; + ChopWindingInPlace (&w, plane->normal, plane->dist, 0); + } + if ( w ) + { + for ( int j = 2; j < w->numpoints; j++ ) + { + v0 = xform.VMul4x3(w->p[0]); + v1 = xform.VMul4x3(w->p[j-1]); + v2 = xform.VMul4x3(w->p[j]); + Vector fullCoverage; + fullCoverage.x = 1.0f; + g_RtEnv.AddTriangle(TRACE_ID_OPAQUE, v0, v1, v2, fullCoverage); + } + FreeWinding( w ); + } + } +} + + +// recurse the bsp and build a list of brushes at the leaves under this node +void GetBrushes_r( int node, CUtlVector<int> &list ) +{ + if ( node < 0 ) + { + int leafIndex = -1 - node; + // Add the solids in the leaf + for ( int i = 0; i < dleafs[leafIndex].numleafbrushes; i++ ) + { + int brushIndex = dleafbrushes[dleafs[leafIndex].firstleafbrush + i]; + if ( list.Find(brushIndex) < 0 ) + { + list.AddToTail( brushIndex ); + } + } + } + else + { + // recurse + dnode_t *pnode = dnodes + node; + + GetBrushes_r( pnode->children[0], list ); + GetBrushes_r( pnode->children[1], list ); + } +} + + +void AddBrushes( dmodel_t *pModel, const VMatrix &xform ) +{ + if ( pModel ) + { + CUtlVector<int> brushList; + GetBrushes_r( pModel->headnode, brushList ); + for ( int i = 0; i < brushList.Count(); i++ ) + { + int ndxBrush = brushList[i]; + AddBrushToRaytraceEnvironment( &dbrushes[ndxBrush], xform ); + } + } +} + + +// Adds the brush entities that cast shadows to the raytrace environment +void ExtractBrushEntityShadowCasters() +{ + for ( int i = 0; i < num_entities; i++ ) + { + if ( IntForKey( &entities[i], "vrad_brush_cast_shadows" ) != 0 ) + { + Vector origin; + QAngle angles; + GetVectorForKey( &entities[i], "origin", origin ); + GetAnglesForKey( &entities[i], "angles", angles ); + VMatrix xform; + xform.SetupMatrixOrgAngles( origin, angles ); + AddBrushes( BrushmodelForEntity( &entities[i] ), xform ); + } + } +} + +void AddBrushesForRayTrace( void ) +{ + if ( !nummodels ) + return; + + VMatrix identity; + identity.Identity(); + + CUtlVector<int> brushList; + GetBrushes_r ( dmodels[0].headnode, brushList ); + + for ( int i = 0; i < brushList.Size(); i++ ) + { + dbrush_t *brush = &dbrushes[brushList[i]]; + AddBrushToRaytraceEnvironment ( brush, identity ); + } + + for ( int i = 0; i < dmodels[0].numfaces; i++ ) + { + int ndxFace = dmodels[0].firstface + i; + dface_t *face = &g_pFaces[ndxFace]; + + texinfo_t *tx = &texinfo[face->texinfo]; + if ( !( tx->flags & SURF_SKY ) ) + continue; + + Vector points[MAX_POINTS_ON_WINDING]; + + for ( int j = 0; j < face->numedges; j++ ) + { + if ( j >= MAX_POINTS_ON_WINDING ) + Error( "***** ERROR! MAX_POINTS_ON_WINDING reached!" ); + + if ( face->firstedge + j >= ARRAYSIZE( dsurfedges ) ) + Error( "***** ERROR! face->firstedge + j >= ARRAYSIZE( dsurfedges )!" ); + + int surfEdge = dsurfedges[face->firstedge + j]; + unsigned short v; + + if (surfEdge < 0) + v = dedges[-surfEdge].v[1]; + else + v = dedges[surfEdge].v[0]; + + if ( v >= ARRAYSIZE( dvertexes ) ) + Error( "***** ERROR! v(%u) >= ARRAYSIZE( dvertexes(%d) )!", ( unsigned int )v, ARRAYSIZE( dvertexes ) ); + + dvertex_t *dv = &dvertexes[v]; + points[j] = dv->point; + } + + for ( int j = 2; j < face->numedges; j++ ) + { + Vector fullCoverage; + fullCoverage.x = 1.0f; + g_RtEnv.AddTriangle ( TRACE_ID_SKY, points[0], points[j - 1], points[j], fullCoverage ); + } + } +} diff --git a/utils/vrad/vismat.cpp b/utils/vrad/vismat.cpp new file mode 100644 index 0000000..ca9398e --- /dev/null +++ b/utils/vrad/vismat.cpp @@ -0,0 +1,483 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +#include "vrad.h" +#include "vmpi.h" +#ifdef MPI +#include "messbuf.h" +static MessageBuffer mb; +#endif + +#define HALFBIT + +extern char source[MAX_PATH]; +extern char vismatfile[_MAX_PATH]; +extern char incrementfile[_MAX_PATH]; +extern qboolean incremental; + +/* +=================================================================== + +VISIBILITY MATRIX + +Determine which patches can see each other +Use the PVS to accelerate if available +=================================================================== +*/ + +#define TEST_EPSILON 0.1 +#define PLANE_TEST_EPSILON 0.01 // patch must be this much in front of the plane to be considered "in front" +#define PATCH_FACE_OFFSET 0.1 // push patch origins off from the face by this amount to avoid self collisions + +#define STREAM_SIZE 512 + +class CTransferMaker +{ +public: + + CTransferMaker( transfer_t *all_transfers ); + ~CTransferMaker(); + + FORCEINLINE void TestMakeTransfer( Vector start, Vector stop, int ndxShooter, int ndxReciever ) + { + g_RtEnv.AddToRayStream( m_RayStream, start, stop, &m_pResults[m_nTests] ); + m_pShooterPatches[m_nTests] = ndxShooter; + m_pRecieverPatches[m_nTests] = ndxReciever; + ++m_nTests; + } + + void Finish(); + +private: + + int m_nTests; + RayTracingSingleResult *m_pResults; + int *m_pShooterPatches; + int *m_pRecieverPatches; + RayStream m_RayStream; + transfer_t *m_AllTransfers; +}; + +CTransferMaker::CTransferMaker( transfer_t *all_transfers ) : + m_AllTransfers( all_transfers ), m_nTests( 0 ) +{ + m_pResults = (RayTracingSingleResult *)calloc( 1, MAX_PATCHES * sizeof ( RayTracingSingleResult ) ); + m_pShooterPatches = (int *)calloc( 1, MAX_PATCHES * sizeof( int ) ); + m_pRecieverPatches = (int *)calloc( 1, MAX_PATCHES * sizeof( int ) ); +} + +CTransferMaker::~CTransferMaker() +{ + free ( m_pResults ); + free ( m_pShooterPatches ); + free (m_pRecieverPatches ); +} + +void CTransferMaker::Finish() +{ + g_RtEnv.FinishRayStream( m_RayStream ); + for ( int i = 0; i < m_nTests; ++i ) + { + if ( m_pResults[i].HitID == -1 || m_pResults[i].HitDistance >= m_pResults[i].ray_length ) + { + MakeTransfer( m_pShooterPatches[i], m_pRecieverPatches[i], m_AllTransfers ); + } + } + m_nTests = 0; +} + + +dleaf_t* PointInLeaf (int iNode, Vector const& point) +{ + if ( iNode < 0 ) + return &dleafs[ (-1-iNode) ]; + + dnode_t *node = &dnodes[iNode]; + dplane_t *plane = &dplanes[ node->planenum ]; + + float dist = DotProduct (point, plane->normal) - plane->dist; + if ( dist > TEST_EPSILON ) + { + return PointInLeaf( node->children[0], point ); + } + else if ( dist < -TEST_EPSILON ) + { + return PointInLeaf( node->children[1], point ); + } + else + { + dleaf_t *pTest = PointInLeaf( node->children[0], point ); + if ( pTest->cluster != -1 ) + return pTest; + + return PointInLeaf( node->children[1], point ); + } +} + + +int ClusterFromPoint( Vector const& point ) +{ + dleaf_t *leaf = PointInLeaf( 0, point ); + + return leaf->cluster; +} + +void PvsForOrigin (Vector& org, byte *pvs) +{ + int visofs; + int cluster; + + if (!visdatasize) + { + memset (pvs, 255, (dvis->numclusters+7)/8 ); + return; + } + + cluster = ClusterFromPoint( org ); + if ( cluster < 0 ) + { + visofs = -1; + } + else + { + visofs = dvis->bitofs[ cluster ][DVIS_PVS]; + } + + if (visofs == -1) + Error ("visofs == -1"); + + DecompressVis (&dvisdata[visofs], pvs); +} + + +void TestPatchToPatch( int ndxPatch1, int ndxPatch2, int head, transfer_t *transfers, CTransferMaker &transferMaker, int iThread ) +{ + Vector tmp; + + // + // get patches + // + if( ndxPatch1 == g_Patches.InvalidIndex() || ndxPatch2 == g_Patches.InvalidIndex() ) + return; + + CPatch *patch = &g_Patches.Element( ndxPatch1 ); + CPatch *patch2 = &g_Patches.Element( ndxPatch2 ); + + if (patch2->child1 != g_Patches.InvalidIndex() ) + { + // check to see if we should use a child node instead + + VectorSubtract( patch->origin, patch2->origin, tmp ); + // SQRT( 1/4 ) + // FIXME: should be based on form-factor (ie. include visible angle, etc) + if ( DotProduct(tmp, tmp) * 0.0625 < patch2->area ) + { + TestPatchToPatch( ndxPatch1, patch2->child1, head, transfers, transferMaker, iThread ); + TestPatchToPatch( ndxPatch1, patch2->child2, head, transfers, transferMaker, iThread ); + return; + } + } + + // check vis between patch and patch2 + // if bit has not already been set + // && v2 is not behind light plane + // && v2 is visible from v1 + if ( DotProduct( patch2->origin, patch->normal ) > patch->planeDist + PLANE_TEST_EPSILON ) + { + // push out origins from face so that don't intersect their owners + Vector p1, p2; + VectorAdd( patch->origin, patch->normal, p1 ); + VectorAdd( patch2->origin, patch2->normal, p2 ); + transferMaker.TestMakeTransfer( p1, p2, ndxPatch1, ndxPatch2 ); + } +} + + +/* +============== +TestPatchToFace + +Sets vis bits for all patches in the face +============== +*/ +void TestPatchToFace (unsigned patchnum, int facenum, int head, transfer_t *transfers, CTransferMaker &transferMaker, int iThread ) +{ + if( faceParents.Element( facenum ) == g_Patches.InvalidIndex() || patchnum == g_Patches.InvalidIndex() ) + return; + + CPatch *patch = &g_Patches.Element( patchnum ); + CPatch *patch2 = &g_Patches.Element( faceParents.Element( facenum ) ); + + // if emitter is behind that face plane, skip all patches + + CPatch *pNextPatch; + + if ( patch2 && DotProduct(patch->origin, patch2->normal) > patch2->planeDist + PLANE_TEST_EPSILON ) + { + // we need to do a real test + for( ; patch2; patch2 = pNextPatch ) + { + // next patch + pNextPatch = NULL; + if( patch2->ndxNextParent != g_Patches.InvalidIndex() ) + { + pNextPatch = &g_Patches.Element( patch2->ndxNextParent ); + } + + /* + // skip patches too far away + VectorSubtract( patch->origin, patch2->origin, tmp ); + if (DotProduct( tmp, tmp ) > 512 * 512) + continue; + */ + + int ndxPatch2 = patch2 - g_Patches.Base(); + TestPatchToPatch( patchnum, ndxPatch2, head, transfers, transferMaker, iThread ); + } + } +} + + +struct ClusterDispList_t +{ + CUtlVector<int> dispFaces; +}; + +static CUtlVector<ClusterDispList_t> g_ClusterDispFaces; + +//----------------------------------------------------------------------------- +// Helps us find all displacements associated with a particular cluster +//----------------------------------------------------------------------------- +void AddDispsToClusterTable( void ) +{ + g_ClusterDispFaces.SetCount( g_ClusterLeaves.Count() ); + + // + // add displacement faces to the cluster table + // + for( int ndxFace = 0; ndxFace < numfaces; ndxFace++ ) + { + // search for displacement faces + if( g_pFaces[ndxFace].dispinfo == -1 ) + continue; + + // + // get the clusters associated with the face + // + if( g_FacePatches.Element( ndxFace ) != g_FacePatches.InvalidIndex() ) + { + CPatch *pNextPatch = NULL; + for( CPatch *pPatch = &g_Patches.Element( g_FacePatches.Element( ndxFace ) ); pPatch; pPatch = pNextPatch ) + { + // next patch + pNextPatch = NULL; + if( pPatch->ndxNext != g_Patches.InvalidIndex() ) + { + pNextPatch = &g_Patches.Element( pPatch->ndxNext ); + } + + if( pPatch->clusterNumber != g_Patches.InvalidIndex() ) + { + int ndxDisp = g_ClusterDispFaces[pPatch->clusterNumber].dispFaces.Find( ndxFace ); + if( ndxDisp == -1 ) + { + ndxDisp = g_ClusterDispFaces[pPatch->clusterNumber].dispFaces.AddToTail(); + g_ClusterDispFaces[pPatch->clusterNumber].dispFaces[ndxDisp] = ndxFace; + } + } + } + } + } +} + + +/* +============== +BuildVisRow + +Calc vis bits from a single patch +============== +*/ +void BuildVisRow (int patchnum, byte *pvs, int head, transfer_t *transfers, CTransferMaker &transferMaker, int iThread ) +{ + int j, k, l, leafIndex; + CPatch *patch; + dleaf_t *leaf; + byte face_tested[MAX_MAP_FACES]; + byte disp_tested[MAX_MAP_FACES]; + + patch = &g_Patches.Element( patchnum ); + + memset( face_tested, 0, numfaces ) ; + memset( disp_tested, 0, numfaces ); + + for (j=0; j<dvis->numclusters; j++) + { + if ( ! ( pvs[(j)>>3] & (1<<((j)&7)) ) ) + { + continue; // not in pvs + } + + for ( leafIndex = 0; leafIndex < g_ClusterLeaves[j].leafCount; leafIndex++ ) + { + leaf = dleafs + g_ClusterLeaves[j].leafs[leafIndex]; + + for (k=0 ; k<leaf->numleaffaces; k++) + { + l = dleaffaces[leaf->firstleafface + k]; + // faces can be marksurfed by multiple leaves, but + // don't bother testing again + if (face_tested[l]) + { + continue; + } + face_tested[l] = 1; + + // don't check patches on the same face + if (patch->faceNumber == l) + continue; + TestPatchToFace (patchnum, l, head, transfers, transferMaker, iThread ); + } + } + + int dispCount = g_ClusterDispFaces[j].dispFaces.Size(); + for( int ndxDisp = 0; ndxDisp < dispCount; ndxDisp++ ) + { + int ndxFace = g_ClusterDispFaces[j].dispFaces[ndxDisp]; + if( disp_tested[ndxFace] ) + continue; + + disp_tested[ndxFace] = 1; + + // don't check patches on the same face + if( patch->faceNumber == ndxFace ) + continue; + + TestPatchToFace( patchnum, ndxFace, head, transfers, transferMaker, iThread ); + } + } + + + // Msg("%d) Transfers: %5d\n", patchnum, patch->numtransfers); +} + + + +/* +=========== +BuildVisLeafs + + This is run by multiple threads +=========== +*/ + +transfer_t* BuildVisLeafs_Start() +{ + return (transfer_t *)calloc( 1, MAX_PATCHES * sizeof( transfer_t ) ); +} + + +// If PatchCB is non-null, it is called after each row is generated (used by MPI). +void BuildVisLeafs_Cluster( + int threadnum, + transfer_t *transfers, + int iCluster, + void (*PatchCB)(int iThread, int patchnum, CPatch *patch) + ) +{ + byte pvs[(MAX_MAP_CLUSTERS+7)/8]; + CPatch *patch; + int head; + unsigned patchnum; + + DecompressVis( &dvisdata[ dvis->bitofs[ iCluster ][DVIS_PVS] ], pvs); + head = 0; + + CTransferMaker transferMaker( transfers ); + + // light every patch in the cluster + if( clusterChildren.Element( iCluster ) != clusterChildren.InvalidIndex() ) + { + CPatch *pNextPatch; + for( patch = &g_Patches.Element( clusterChildren.Element( iCluster ) ); patch; patch = pNextPatch ) + { + // + // next patch + // + pNextPatch = NULL; + if( patch->ndxNextClusterChild != g_Patches.InvalidIndex() ) + { + pNextPatch = &g_Patches.Element( patch->ndxNextClusterChild ); + } + + patchnum = patch - g_Patches.Base(); + + // build to all other world clusters + BuildVisRow (patchnum, pvs, head, transfers, transferMaker, threadnum ); + transferMaker.Finish(); + + // do the transfers + MakeScales( patchnum, transfers ); + + // Let MPI aggregate the data if it's being used. + if ( PatchCB ) + PatchCB( threadnum, patchnum, patch ); + } + } +} + + +void BuildVisLeafs_End( transfer_t *transfers ) +{ + free( transfers ); +} + + +void BuildVisLeafs( int threadnum, void *pUserData ) +{ + transfer_t *transfers = BuildVisLeafs_Start(); + + while ( 1 ) + { + // + // build a minimal BSP tree that only + // covers areas relevent to the PVS + // + // JAY: Now this returns a cluster index + int iCluster = GetThreadWork(); + if ( iCluster == -1 ) + break; + + BuildVisLeafs_Cluster( threadnum, transfers, iCluster, NULL ); + } + + BuildVisLeafs_End( transfers ); +} + + +/* +============== +BuildVisMatrix +============== +*/ +void BuildVisMatrix (void) +{ + if ( g_bUseMPI ) + { + RunMPIBuildVisLeafs(); + } + else + { + RunThreadsOn (dvis->numclusters, true, BuildVisLeafs); + } +} + +void FreeVisMatrix (void) +{ + +} diff --git a/utils/vrad/vismat.h b/utils/vrad/vismat.h new file mode 100644 index 0000000..927314a --- /dev/null +++ b/utils/vrad/vismat.h @@ -0,0 +1,34 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef VISMAT_H +#define VISMAT_H +#ifdef _WIN32 +#pragma once +#endif + + + +void BuildVisLeafs( int threadnum ); + + +// MPI uses these. +struct transfer_t; +transfer_t* BuildVisLeafs_Start(); + +// If PatchCB is non-null, it is called after each row is generated (used by MPI). +void BuildVisLeafs_Cluster( + int threadnum, + transfer_t *transfers, + int iCluster, + void (*PatchCB)(int iThread, int patchnum, CPatch *patch) ); + +void BuildVisLeafs_End( transfer_t *transfers ); + + + +#endif // VISMAT_H diff --git a/utils/vrad/vrad.cpp b/utils/vrad/vrad.cpp new file mode 100644 index 0000000..c77409a --- /dev/null +++ b/utils/vrad/vrad.cpp @@ -0,0 +1,2960 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +// +//=============================================================================// + +// vrad.c + +#include "vrad.h" +#include "physdll.h" +#include "lightmap.h" +#include "tier1/strtools.h" +#include "vmpi.h" +#include "macro_texture.h" +#include "vmpi_tools_shared.h" +#include "leaf_ambient_lighting.h" +#include "tools_minidump.h" +#include "loadcmdline.h" +#include "byteswap.h" + +#define ALLOWDEBUGOPTIONS (0 || _DEBUG) + +static FileHandle_t pFpTrans = NULL; + +/* + +NOTES +----- + +every surface must be divided into at least two patches each axis + +*/ + +CUtlVector<CPatch> g_Patches; +CUtlVector<int> g_FacePatches; // contains all patches, children first +CUtlVector<int> faceParents; // contains only root patches, use next parent to iterate +CUtlVector<int> clusterChildren; +CUtlVector<Vector> emitlight; +CUtlVector<bumplights_t> addlight; + +int num_sky_cameras; +sky_camera_t sky_cameras[MAX_MAP_AREAS]; +int area_sky_cameras[MAX_MAP_AREAS]; + +entity_t *face_entity[MAX_MAP_FACES]; +Vector face_offset[MAX_MAP_FACES]; // for rotating bmodels +int fakeplanes; + +unsigned numbounce = 100; // 25; /* Originally this was 8 */ + +float maxchop = 4; // coarsest allowed number of luxel widths for a patch +float minchop = 4; // "-chop" tightest number of luxel widths for a patch, used on edges +float dispchop = 8.0f; // number of luxel widths for a patch +float g_MaxDispPatchRadius = 1500.0f; // Maximum radius allowed for displacement patches +qboolean g_bDumpPatches; +bool bDumpNormals = false; +bool g_bDumpRtEnv = false; +bool bRed2Black = true; +bool g_bFastAmbient = false; +bool g_bNoSkyRecurse = false; +bool g_bDumpPropLightmaps = false; + + +int junk; + +Vector ambient( 0, 0, 0 ); + +float lightscale = 1.0; +float dlight_threshold = 0.1; // was DIRECT_LIGHT constant + +char source[MAX_PATH] = ""; + +char level_name[MAX_PATH] = ""; // map filename, without extension or path info + +char global_lights[MAX_PATH] = ""; +char designer_lights[MAX_PATH] = ""; +char level_lights[MAX_PATH] = ""; + +char vismatfile[_MAX_PATH] = ""; +char incrementfile[_MAX_PATH] = ""; + +IIncremental *g_pIncremental = 0; +bool g_bInterrupt = false; // Wsed with background lighting in WC. Tells VRAD + // to stop lighting. +float g_SunAngularExtent=0.0; + +float g_flSkySampleScale = 1.0; + +bool g_bLargeDispSampleRadius = false; + +bool g_bOnlyStaticProps = false; +bool g_bShowStaticPropNormals = false; + + +float gamma = 0.5; +float indirect_sun = 1.0; +float reflectivityScale = 1.0; +qboolean do_extra = true; +bool debug_extra = false; +qboolean do_fast = false; +qboolean do_centersamples = false; +int extrapasses = 4; +float smoothing_threshold = 0.7071067; // cos(45.0*(M_PI/180)) +// Cosine of smoothing angle(in radians) +float coring = 1.0; // Light threshold to force to blackness(minimizes lightmaps) +qboolean texscale = true; +int dlight_map = 0; // Setting to 1 forces direct lighting into different lightmap than radiosity + +float luxeldensity = 1.0; +unsigned num_degenerate_faces; + +qboolean g_bLowPriority = false; +qboolean g_bLogHashData = false; +bool g_bNoDetailLighting = false; +double g_flStartTime; +bool g_bStaticPropLighting = false; +bool g_bStaticPropPolys = false; +bool g_bTextureShadows = false; +bool g_bDisablePropSelfShadowing = false; + + +CUtlVector<byte> g_FacesVisibleToLights; + +RayTracingEnvironment g_RtEnv; + +dface_t *g_pFaces=0; + +// this is a list of material names used on static props which shouldn't cast shadows. a +// sequential search is used since we allow substring matches. its not time critical, and this +// functionality is a stopgap until vrad starts reading .vmt files. +CUtlVector<char const *> g_NonShadowCastingMaterialStrings; +/* +=================================================================== + +MISC + +=================================================================== +*/ + + +int leafparents[MAX_MAP_LEAFS]; +int nodeparents[MAX_MAP_NODES]; + +void MakeParents (int nodenum, int parent) +{ + int i, j; + dnode_t *node; + + nodeparents[nodenum] = parent; + node = &dnodes[nodenum]; + + for (i=0 ; i<2 ; i++) + { + j = node->children[i]; + if (j < 0) + leafparents[-j - 1] = nodenum; + else + MakeParents (j, nodenum); + } +} + + +/* +=================================================================== + + TEXTURE LIGHT VALUES + +=================================================================== +*/ + +typedef struct +{ + char name[256]; + Vector value; + char *filename; +} texlight_t; + +#define MAX_TEXLIGHTS 128 + +texlight_t texlights[MAX_TEXLIGHTS]; +int num_texlights; + +/* +============ +ReadLightFile +============ +*/ +void ReadLightFile (char *filename) +{ + char buf[1024]; + int file_texlights = 0; + + FileHandle_t f = g_pFileSystem->Open( filename, "r" ); + if (!f) + { + Warning("Warning: Couldn't open texlight file %s.\n", filename); + return; + } + + Msg("[Reading texlights from '%s']\n", filename); + while ( CmdLib_FGets( buf, sizeof( buf ), f ) ) + { + // check ldr/hdr + char * scan = buf; + if ( !strnicmp( "hdr:", scan, 4) ) + { + scan += 4; + if ( ! g_bHDR ) + { + continue; + } + } + if ( !strnicmp( "ldr:", scan, 4) ) + { + scan += 4; + if ( g_bHDR ) + { + continue; + } + } + + scan += strspn( scan, " \t" ); + char NoShadName[1024]; + if ( sscanf(scan,"noshadow %s",NoShadName)==1) + { + char * dot = strchr( NoShadName, '.' ); + if ( dot ) // if they specify .vmt, kill it + * dot = 0; + //printf("add %s as a non shadow casting material\n",NoShadName); + g_NonShadowCastingMaterialStrings.AddToTail( strdup( NoShadName )); + } + else if ( sscanf( scan, "forcetextureshadow %s", NoShadName ) == 1 ) + { + //printf("add %s as a non shadow casting material\n",NoShadName); + ForceTextureShadowsOnModel( NoShadName ); + } + else + { + char szTexlight[256]; + Vector value; + if ( num_texlights == MAX_TEXLIGHTS ) + Error ("Too many texlights, max = %d", MAX_TEXLIGHTS); + + int argCnt = sscanf (scan, "%s ",szTexlight ); + + if( argCnt != 1 ) + { + if ( strlen( scan ) > 4 ) + Msg( "ignoring bad texlight '%s' in %s", scan, filename ); + continue; + } + + LightForString( scan + strlen( szTexlight ) + 1, value ); + + int j = 0; + for( j; j < num_texlights; j ++ ) + { + if ( strcmp( texlights[j].name, szTexlight ) == 0 ) + { + if ( strcmp( texlights[j].filename, filename ) == 0 ) + { + Msg( "ERROR\a: Duplication of '%s' in file '%s'!\n", + texlights[j].name, texlights[j].filename ); + } + else if ( texlights[j].value[0] != value[0] + || texlights[j].value[1] != value[1] + || texlights[j].value[2] != value[2] ) + { + Warning( "Warning: Overriding '%s' from '%s' with '%s'!\n", + texlights[j].name, texlights[j].filename, filename ); + } + else + { + Warning( "Warning: Redundant '%s' def in '%s' AND '%s'!\n", + texlights[j].name, texlights[j].filename, filename ); + } + break; + } + } + strcpy( texlights[j].name, szTexlight ); + VectorCopy( value, texlights[j].value ); + texlights[j].filename = filename; + file_texlights ++; + + num_texlights = max( num_texlights, j + 1 ); + } + } + qprintf ( "[%i texlights parsed from '%s']\n\n", file_texlights, filename); + g_pFileSystem->Close( f ); +} + + +/* +============ +LightForTexture +============ +*/ +void LightForTexture( const char *name, Vector& result ) +{ + int i; + + result[ 0 ] = result[ 1 ] = result[ 2 ] = 0; + + char baseFilename[ MAX_PATH ]; + + if ( Q_strncmp( "maps/", name, 5 ) == 0 ) + { + // this might be a patch texture for cubemaps. try to parse out the original filename. + if ( Q_strncmp( level_name, name + 5, Q_strlen( level_name ) ) == 0 ) + { + const char *base = name + 5 + Q_strlen( level_name ); + if ( *base == '/' ) + { + ++base; // step past the path separator + + // now we've gotten rid of the 'maps/level_name/' part, so we're left with + // 'originalName_%d_%d_%d'. + strcpy( baseFilename, base ); + bool foundSeparators = true; + for ( int i=0; i<3; ++i ) + { + char *underscore = Q_strrchr( baseFilename, '_' ); + if ( underscore && *underscore ) + { + *underscore = '\0'; + } + else + { + foundSeparators = false; + } + } + + if ( foundSeparators ) + { + name = baseFilename; + } + } + } + } + + for (i=0 ; i<num_texlights ; i++) + { + if (!Q_strcasecmp (name, texlights[i].name)) + { + VectorCopy( texlights[i].value, result ); + return; + } + } +} + +/* +======================================================================= + +MAKE FACES + +======================================================================= +*/ + +/* +============= +WindingFromFace +============= +*/ +winding_t *WindingFromFace (dface_t *f, Vector& origin ) +{ + int i; + int se; + dvertex_t *dv; + int v; + winding_t *w; + + w = AllocWinding (f->numedges); + w->numpoints = f->numedges; + + for (i=0 ; i<f->numedges ; i++) + { + se = dsurfedges[f->firstedge + i]; + if (se < 0) + v = dedges[-se].v[1]; + else + v = dedges[se].v[0]; + + dv = &dvertexes[v]; + VectorAdd (dv->point, origin, w->p[i]); + } + + RemoveColinearPoints (w); + + return w; +} + +/* +============= +BaseLightForFace +============= +*/ +void BaseLightForFace( dface_t *f, Vector& light, float *parea, Vector& reflectivity ) +{ + texinfo_t *tx; + dtexdata_t *texdata; + + // + // check for light emited by texture + // + tx = &texinfo[f->texinfo]; + texdata = &dtexdata[tx->texdata]; + + LightForTexture (TexDataStringTable_GetString( texdata->nameStringTableID ), light); + + + *parea = texdata->height * texdata->width; + + VectorScale( texdata->reflectivity, reflectivityScale, reflectivity ); + + // always keep this less than 1 or the solution will not converge + for ( int i = 0; i < 3; i++ ) + { + if ( reflectivity[i] > 0.99 ) + reflectivity[i] = 0.99; + } +} + +qboolean IsSky (dface_t *f) +{ + texinfo_t *tx; + + tx = &texinfo[f->texinfo]; + if (tx->flags & SURF_SKY) + return true; + return false; +} + +#ifdef STATIC_FOG +/*============= +IsFog +=============*/ +qboolean IsFog( dface_t *f ) +{ + texinfo_t *tx; + + tx = &texinfo[f->texinfo]; + + // % denotes a fog texture + if( tx->texture[0] == '%' ) + return true; + + return false; +} +#endif + + +void ProcessSkyCameras() +{ + int i; + num_sky_cameras = 0; + for (i = 0; i < numareas; ++i) + { + area_sky_cameras[i] = -1; + } + + for (i = 0; i < num_entities; ++i) + { + entity_t *e = &entities[i]; + const char *name = ValueForKey (e, "classname"); + if (stricmp (name, "sky_camera")) + continue; + + Vector origin; + GetVectorForKey( e, "origin", origin ); + int node = PointLeafnum( origin ); + int area = -1; + if (node >= 0 && node < numleafs) area = dleafs[node].area; + float scale = FloatForKey( e, "scale" ); + + if (scale > 0.0f) + { + sky_cameras[num_sky_cameras].origin = origin; + sky_cameras[num_sky_cameras].sky_to_world = scale; + sky_cameras[num_sky_cameras].world_to_sky = 1.0f / scale; + sky_cameras[num_sky_cameras].area = area; + + if (area >= 0 && area < numareas) + { + area_sky_cameras[area] = num_sky_cameras; + } + + ++num_sky_cameras; + } + } + +} + + +/* +============= +MakePatchForFace +============= +*/ +float totalarea; +void MakePatchForFace (int fn, winding_t *w) +{ + dface_t *f = g_pFaces + fn; + float area; + CPatch *patch; + Vector centroid(0,0,0); + int i, j; + texinfo_t *tx; + + // get texture info + tx = &texinfo[f->texinfo]; + + // No patches at all for fog! +#ifdef STATIC_FOG + if ( IsFog( f ) ) + return; +#endif + + // the sky needs patches or the form factors don't work out correctly + // if (IsSky( f ) ) + // return; + + area = WindingArea (w); + if (area <= 0) + { + num_degenerate_faces++; + // Msg("degenerate face\n"); + return; + } + + totalarea += area; + + // get a patch + int ndxPatch = g_Patches.AddToTail(); + patch = &g_Patches[ndxPatch]; + memset( patch, 0, sizeof( CPatch ) ); + patch->ndxNext = g_Patches.InvalidIndex(); + patch->ndxNextParent = g_Patches.InvalidIndex(); + patch->ndxNextClusterChild = g_Patches.InvalidIndex(); + patch->child1 = g_Patches.InvalidIndex(); + patch->child2 = g_Patches.InvalidIndex(); + patch->parent = g_Patches.InvalidIndex(); + patch->needsBumpmap = tx->flags & SURF_BUMPLIGHT ? true : false; + + // link and save patch data + patch->ndxNext = g_FacePatches.Element( fn ); + g_FacePatches[fn] = ndxPatch; +// patch->next = face_g_Patches[fn]; +// face_g_Patches[fn] = patch; + + // compute a separate scale for chop - since the patch "scale" is the texture scale + // we want textures with higher resolution lighting to be chopped up more + float chopscale[2]; + chopscale[0] = chopscale[1] = 16.0f; + if ( texscale ) + { + // Compute the texture "scale" in s,t + for( i=0; i<2; i++ ) + { + patch->scale[i] = 0.0f; + chopscale[i] = 0.0f; + for( j=0; j<3; j++ ) + { + patch->scale[i] += + tx->textureVecsTexelsPerWorldUnits[i][j] * + tx->textureVecsTexelsPerWorldUnits[i][j]; + chopscale[i] += + tx->lightmapVecsLuxelsPerWorldUnits[i][j] * + tx->lightmapVecsLuxelsPerWorldUnits[i][j]; + } + patch->scale[i] = sqrt( patch->scale[i] ); + chopscale[i] = sqrt( chopscale[i] ); + } + } + else + { + patch->scale[0] = patch->scale[1] = 1.0f; + } + + patch->area = area; + + patch->sky = IsSky( f ); + + // chop scaled up lightmaps coarser + patch->luxscale = ((chopscale[0]+chopscale[1])/2); + patch->chop = maxchop; + + +#ifdef STATIC_FOG + patch->fog = FALSE; +#endif + + patch->winding = w; + + patch->plane = &dplanes[f->planenum]; + + // make a new plane to adjust for origined bmodels + if (face_offset[fn][0] || face_offset[fn][1] || face_offset[fn][2] ) + { + dplane_t *pl; + + // origin offset faces must create new planes + if (numplanes + fakeplanes >= MAX_MAP_PLANES) + { + Error ("numplanes + fakeplanes >= MAX_MAP_PLANES"); + } + pl = &dplanes[numplanes + fakeplanes]; + fakeplanes++; + + *pl = *(patch->plane); + pl->dist += DotProduct (face_offset[fn], pl->normal); + patch->plane = pl; + } + + patch->faceNumber = fn; + WindingCenter (w, patch->origin); + + // Save "center" for generating the face normals later. + VectorSubtract( patch->origin, face_offset[fn], face_centroids[fn] ); + + VectorCopy( patch->plane->normal, patch->normal ); + + WindingBounds (w, patch->face_mins, patch->face_maxs); + VectorCopy( patch->face_mins, patch->mins ); + VectorCopy( patch->face_maxs, patch->maxs ); + + BaseLightForFace( f, patch->baselight, &patch->basearea, patch->reflectivity ); + + // Chop all texlights very fine. + if ( !VectorCompare( patch->baselight, vec3_origin ) ) + { + // patch->chop = do_extra ? maxchop / 2 : maxchop; + tx->flags |= SURF_LIGHT; + } + + // get rid of do extra functionality on displacement surfaces + if( ValidDispFace( f ) ) + { + patch->chop = maxchop; + } + + // FIXME: If we wanted to add a dependency from vrad to the material system, + // we could do this. It would add a bunch of file accesses, though: + + /* + // Check for a material var which would override the patch chop + bool bFound; + const char *pMaterialName = TexDataStringTable_GetString( dtexdata[ tx->texdata ].nameStringTableID ); + MaterialSystemMaterial_t hMaterial = FindMaterial( pMaterialName, &bFound, false ); + if ( bFound ) + { + const char *pChopValue = GetMaterialVar( hMaterial, "%chop" ); + if ( pChopValue ) + { + float flChopValue; + if ( sscanf( pChopValue, "%f", &flChopValue ) > 0 ) + { + patch->chop = flChopValue; + } + } + } + */ +} + + +entity_t *EntityForModel (int modnum) +{ + int i; + char *s; + char name[16]; + + sprintf (name, "*%i", modnum); + // search the entities for one using modnum + for (i=0 ; i<num_entities ; i++) + { + s = ValueForKey (&entities[i], "model"); + if (!strcmp (s, name)) + return &entities[i]; + } + + return &entities[0]; +} + +/* +============= +MakePatches +============= +*/ +void MakePatches (void) +{ + int i, j; + dface_t *f; + int fn; + winding_t *w; + dmodel_t *mod; + Vector origin; + entity_t *ent; + + ParseEntities (); + qprintf ("%i faces\n", numfaces); + + for (i=0 ; i<nummodels ; i++) + { + mod = dmodels+i; + ent = EntityForModel (i); + VectorCopy (vec3_origin, origin); + + // bmodels with origin brushes need to be offset into their + // in-use position + GetVectorForKey (ent, "origin", origin); + + for (j=0 ; j<mod->numfaces ; j++) + { + fn = mod->firstface + j; + face_entity[fn] = ent; + VectorCopy (origin, face_offset[fn]); + f = &g_pFaces[fn]; + if( f->dispinfo == -1 ) + { + w = WindingFromFace (f, origin ); + MakePatchForFace( fn, w ); + } + } + } + + if (num_degenerate_faces > 0) + { + qprintf("%d degenerate faces\n", num_degenerate_faces ); + } + + qprintf ("%i square feet [%.2f square inches]\n", (int)(totalarea/144), totalarea ); + + // make the displacement surface patches + StaticDispMgr()->MakePatches(); +} + +/* +======================================================================= + +SUBDIVIDE + +======================================================================= +*/ + + +//----------------------------------------------------------------------------- +// Purpose: does this surface take/emit light +//----------------------------------------------------------------------------- +bool PreventSubdivision( CPatch *patch ) +{ + dface_t *f = g_pFaces + patch->faceNumber; + texinfo_t *tx = &texinfo[f->texinfo]; + + if (tx->flags & SURF_NOCHOP) + return true; + + if (tx->flags & SURF_NOLIGHT && !(tx->flags & SURF_LIGHT)) + return true; + + return false; +} + + +//----------------------------------------------------------------------------- +// Purpose: subdivide the "parent" patch +//----------------------------------------------------------------------------- +int CreateChildPatch( int nParentIndex, winding_t *pWinding, float flArea, const Vector &vecCenter ) +{ + int nChildIndex = g_Patches.AddToTail(); + + CPatch *child = &g_Patches[nChildIndex]; + CPatch *parent = &g_Patches[nParentIndex]; + + // copy all elements of parent patch to children + *child = *parent; + + // Set up links + child->ndxNext = g_Patches.InvalidIndex(); + child->ndxNextParent = g_Patches.InvalidIndex(); + child->ndxNextClusterChild = g_Patches.InvalidIndex(); + child->child1 = g_Patches.InvalidIndex(); + child->child2 = g_Patches.InvalidIndex(); + child->parent = nParentIndex; + child->m_IterationKey = 0; + + child->winding = pWinding; + child->area = flArea; + + VectorCopy( vecCenter, child->origin ); + if ( ValidDispFace( g_pFaces + child->faceNumber ) ) + { + // shouldn't get here anymore!! + Msg( "SubdividePatch: Error - Should not be here!\n" ); + StaticDispMgr()->GetDispSurfNormal( child->faceNumber, child->origin, child->normal, true ); + } + else + { + GetPhongNormal( child->faceNumber, child->origin, child->normal ); + } + + child->planeDist = child->plane->dist; + WindingBounds(child->winding, child->mins, child->maxs); + + if ( !VectorCompare( child->baselight, vec3_origin ) ) + { + // don't check edges on surf lights + return nChildIndex; + } + + // Subdivide patch towards minchop if on the edge of the face + Vector total; + VectorSubtract( child->maxs, child->mins, total ); + VectorScale( total, child->luxscale, total ); + if ( child->chop > minchop && (total[0] < child->chop) && (total[1] < child->chop) && (total[2] < child->chop) ) + { + for ( int i=0; i<3; ++i ) + { + if ( (child->face_maxs[i] == child->maxs[i] || child->face_mins[i] == child->mins[i] ) + && total[i] > minchop ) + { + child->chop = max( minchop, child->chop / 2 ); + break; + } + } + } + + return nChildIndex; +} + + +//----------------------------------------------------------------------------- +// Purpose: subdivide the "parent" patch +//----------------------------------------------------------------------------- +void SubdividePatch( int ndxPatch ) +{ + winding_t *w, *o1, *o2; + Vector total; + Vector split; + vec_t dist; + vec_t widest = -1; + int i, widest_axis = -1; + bool bSubdivide = false; + + // get the current patch + CPatch *patch = &g_Patches.Element( ndxPatch ); + if ( !patch ) + return; + + // never subdivide sky patches + if ( patch->sky ) + return; + + // get the patch winding + w = patch->winding; + + // subdivide along the widest axis + VectorSubtract (patch->maxs, patch->mins, total); + VectorScale( total, patch->luxscale, total ); + for (i=0 ; i<3 ; i++) + { + if ( total[i] > widest ) + { + widest_axis = i; + widest = total[i]; + } + + if ( (total[i] >= patch->chop) && (total[i] >= minchop) ) + { + bSubdivide = true; + } + } + + if ((!bSubdivide) && widest_axis != -1) + { + // make more square + if (total[widest_axis] > total[(widest_axis + 1) % 3] * 2 && total[widest_axis] > total[(widest_axis + 2) % 3] * 2) + { + if (patch->chop > minchop) + { + bSubdivide = true; + patch->chop = max( minchop, patch->chop / 2 ); + } + } + } + + if ( !bSubdivide ) + return; + + // split the winding + VectorCopy (vec3_origin, split); + split[widest_axis] = 1; + dist = (patch->mins[widest_axis] + patch->maxs[widest_axis])*0.5f; + ClipWindingEpsilon (w, split, dist, ON_EPSILON, &o1, &o2); + + // calculate the area of the patches to see if they are "significant" + Vector center1, center2; + float area1 = WindingAreaAndBalancePoint( o1, center1 ); + float area2 = WindingAreaAndBalancePoint( o2, center2 ); + + if( area1 == 0 || area2 == 0 ) + { + Msg( "zero area child patch\n" ); + return; + } + + // create new child patches + int ndxChild1Patch = CreateChildPatch( ndxPatch, o1, area1, center1 ); + int ndxChild2Patch = CreateChildPatch( ndxPatch, o2, area2, center2 ); + + // FIXME: This could go into CreateChildPatch if child1, child2 were stored in the patch as child[0], child[1] + patch = &g_Patches.Element( ndxPatch ); + patch->child1 = ndxChild1Patch; + patch->child2 = ndxChild2Patch; + + SubdividePatch( ndxChild1Patch ); + SubdividePatch( ndxChild2Patch ); +} + + +/* +============= +SubdividePatches +============= +*/ +void SubdividePatches (void) +{ + unsigned i, num; + + if (numbounce == 0) + return; + + unsigned int uiPatchCount = g_Patches.Size(); + qprintf ("%i patches before subdivision\n", uiPatchCount); + + for (i = 0; i < uiPatchCount; i++) + { + CPatch *pCur = &g_Patches.Element( i ); + pCur->planeDist = pCur->plane->dist; + + pCur->ndxNextParent = faceParents.Element( pCur->faceNumber ); + faceParents[pCur->faceNumber] = pCur - g_Patches.Base(); + } + + for (i=0 ; i< uiPatchCount; i++) + { + CPatch *patch = &g_Patches.Element( i ); + patch->parent = -1; + if ( PreventSubdivision(patch) ) + continue; + + if (!do_fast) + { + if( g_pFaces[patch->faceNumber].dispinfo == -1 ) + { + SubdividePatch( i ); + } + else + { + StaticDispMgr()->SubdividePatch( i ); + } + } + } + + // fixup next pointers + for (i = 0; i < (unsigned)numfaces; i++) + { + g_FacePatches[i] = g_FacePatches.InvalidIndex(); + } + + uiPatchCount = g_Patches.Size(); + for (i = 0; i < uiPatchCount; i++) + { + CPatch *pCur = &g_Patches.Element( i ); + pCur->ndxNext = g_FacePatches.Element( pCur->faceNumber ); + g_FacePatches[pCur->faceNumber] = pCur - g_Patches.Base(); + +#if 0 + CPatch *prev; + prev = face_g_Patches[g_Patches[i].faceNumber]; + g_Patches[i].next = prev; + face_g_Patches[g_Patches[i].faceNumber] = &g_Patches[i]; +#endif + } + + // Cache off the leaf number: + // We have to do this after subdivision because some patches span leaves. + // (only the faces for model #0 are split by it's BSP which is what governs the PVS, and the leaves we're interested in) + // Sub models (1-255) are only split for the BSP that their model forms. + // When those patches are subdivided their origins can end up in a different leaf. + // The engine will split (clip) those faces at run time to the world BSP because the models + // are dynamic and can be moved. In the software renderer, they must be split exactly in order + // to sort per polygon. + for ( i = 0; i < uiPatchCount; i++ ) + { + g_Patches[i].clusterNumber = ClusterFromPoint( g_Patches[i].origin ); + + // + // test for point in solid space (can happen with detail and displacement surfaces) + // + if( g_Patches[i].clusterNumber == -1 ) + { + for( int j = 0; j < g_Patches[i].winding->numpoints; j++ ) + { + int clusterNumber = ClusterFromPoint( g_Patches[i].winding->p[j] ); + if( clusterNumber != -1 ) + { + g_Patches[i].clusterNumber = clusterNumber; + break; + } + } + } + } + + // build the list of patches that need to be lit + for ( num = 0; num < uiPatchCount; num++ ) + { + // do them in reverse order + i = uiPatchCount - num - 1; + + // skip patches with children + CPatch *pCur = &g_Patches.Element( i ); + if( pCur->child1 == g_Patches.InvalidIndex() ) + { + if( pCur->clusterNumber != - 1 ) + { + pCur->ndxNextClusterChild = clusterChildren.Element( pCur->clusterNumber ); + clusterChildren[pCur->clusterNumber] = pCur - g_Patches.Base(); + } + } + +#if 0 + if (g_Patches[i].child1 == g_Patches.InvalidIndex() ) + { + if( g_Patches[i].clusterNumber != -1 ) + { + g_Patches[i].nextclusterchild = cluster_children[g_Patches[i].clusterNumber]; + cluster_children[g_Patches[i].clusterNumber] = &g_Patches[i]; + } + } +#endif + } + + qprintf ("%i patches after subdivision\n", uiPatchCount); +} + + +//===================================================================== + +/* +============= +MakeScales + + This is the primary time sink. + It can be run multi threaded. +============= +*/ +int total_transfer; +int max_transfer; + + +//----------------------------------------------------------------------------- +// Purpose: Computes the form factor from a polygon patch to a differential patch +// using formula 81 of Philip Dutre's Global Illumination Compendium, +// [email protected], http://www.graphics.cornell.edu/~phil/GI/ +//----------------------------------------------------------------------------- +float FormFactorPolyToDiff ( CPatch *pPolygon, CPatch* pDifferential ) +{ + winding_t *pWinding = pPolygon->winding; + + float flFormFactor = 0.0f; + + for ( int iPoint = 0; iPoint < pWinding->numpoints; iPoint++ ) + { + int iNextPoint = ( iPoint < pWinding->numpoints - 1 ) ? iPoint + 1 : 0; + + Vector vGammaVector, vVector1, vVector2; + VectorSubtract( pWinding->p[ iPoint ], pDifferential->origin, vVector1 ); + VectorSubtract( pWinding->p[ iNextPoint ], pDifferential->origin, vVector2 ); + VectorNormalize( vVector1 ); + VectorNormalize( vVector2 ); + CrossProduct( vVector1, vVector2, vGammaVector ); + float flSinAlpha = VectorNormalize( vGammaVector ); + if (flSinAlpha < -1.0f || flSinAlpha > 1.0f) + return 0.0f; + vGammaVector *= asin( flSinAlpha ); + + flFormFactor += DotProduct( vGammaVector, pDifferential->normal ); + } + + flFormFactor *= ( 0.5f / pPolygon->area ); // divide by pi later, multiply by area later + + return flFormFactor; +} + + +//----------------------------------------------------------------------------- +// Purpose: Computes the form factor from a differential element to a differential +// element. This is okay when the distance between patches is 5 times +// greater than patch size. Lecture slides by Pat Hanrahan, +// http://graphics.stanford.edu/courses/cs348b-00/lectures/lecture17/radiosity.2.pdf +//----------------------------------------------------------------------------- +float FormFactorDiffToDiff ( CPatch *pDiff1, CPatch* pDiff2 ) +{ + Vector vDelta; + VectorSubtract( pDiff1->origin, pDiff2->origin, vDelta ); + float flLength = VectorNormalize( vDelta ); + + return -DotProduct( vDelta, pDiff1->normal ) * DotProduct( vDelta, pDiff2->normal ) / ( flLength * flLength ); +} + + + +void MakeTransfer( int ndxPatch1, int ndxPatch2, transfer_t *all_transfers ) +//void MakeTransfer (CPatch *patch, CPatch *patch2, transfer_t *all_transfers ) +{ + Vector delta; + vec_t scale; + float trans; + transfer_t *transfer; + + // + // get patches + // + if( ndxPatch1 == g_Patches.InvalidIndex() || ndxPatch2 == g_Patches.InvalidIndex() ) + return; + + CPatch *pPatch1 = &g_Patches.Element( ndxPatch1 ); + CPatch *pPatch2 = &g_Patches.Element( ndxPatch2 ); + + if (IsSky( &g_pFaces[ pPatch2->faceNumber ] ) ) + return; + + // overflow check! + if ( pPatch1->numtransfers >= MAX_PATCHES) + { + return; + } + + // hack for patch areas that area <= 0 (degenerate) + if ( pPatch2->area <= 0) + { + return; + } + + transfer = &all_transfers[pPatch1->numtransfers]; + + scale = FormFactorDiffToDiff( pPatch2, pPatch1 ); + + // patch normals may be > 90 due to smoothing groups + if (scale <= 0) + { + //Msg("scale <= 0\n"); + return; + } + + // Test 5 times rule + Vector vDelta; + VectorSubtract( pPatch1->origin, pPatch2->origin, vDelta ); + float flThreshold = ( M_PI * 0.04 ) * DotProduct( vDelta, vDelta ); + + if (flThreshold < pPatch2->area) + { + scale = FormFactorPolyToDiff( pPatch2, pPatch1 ); + if (scale <= 0.0) + return; + } + + trans = (pPatch2->area*scale); + + if (trans <= TRANSFER_EPSILON) + { + return; + } + + transfer->patch = pPatch2 - g_Patches.Base(); + + // FIXME: why is this not trans? + transfer->transfer = trans; + +#if 0 + // DEBUG! Dump patches and transfer connection for displacements. This creates a lot of data, so only + // use it when you really want it - that is why it is #if-ed out. + if ( g_bDumpPatches ) + { + if ( !pFpTrans ) + { + pFpTrans = g_pFileSystem->Open( "trans.txt", "w" ); + } + Vector light = pPatch1->totallight.light[0] + pPatch1->directlight; + WriteWinding( pFpTrans, pPatch1->winding, light ); + light = pPatch2->totallight.light[0] + pPatch2->directlight; + WriteWinding( pFpTrans, pPatch2->winding, light ); + WriteLine( pFpTrans, pPatch1->origin, pPatch2->origin, Vector( 255, 0, 255 ) ); + } +#endif + + pPatch1->numtransfers++; +} + + +void MakeScales ( int ndxPatch, transfer_t *all_transfers ) +{ + int j; + float total; + transfer_t *t, *t2; + total = 0; + + if( ndxPatch == g_Patches.InvalidIndex() ) + return; + CPatch *patch = &g_Patches.Element( ndxPatch ); + + // copy the transfers out + if (patch->numtransfers) + { + if (patch->numtransfers > max_transfer) + { + max_transfer = patch->numtransfers; + } + + + patch->transfers = ( transfer_t* )calloc (1, patch->numtransfers * sizeof(transfer_t)); + if (!patch->transfers) + Error ("Memory allocation failure"); + + // get total transfer energy + t2 = all_transfers; + + // overflow check! + for (j=0 ; j<patch->numtransfers ; j++, t2++) + { + total += t2->transfer; + } + + // the total transfer should be PI, but we need to correct errors due to overlaping surfaces + if (total > M_PI) + total = 1.0f/total; + else + total = 1.0f/M_PI; + + t = patch->transfers; + t2 = all_transfers; + for (j=0 ; j<patch->numtransfers ; j++, t++, t2++) + { + t->transfer = t2->transfer*total; + t->patch = t2->patch; + } + if (patch->numtransfers > max_transfer) + { + max_transfer = patch->numtransfers; + } + } + else + { + // Error - patch has no transfers + // patch->totallight[2] = 255; + } + + ThreadLock (); + total_transfer += patch->numtransfers; + ThreadUnlock (); +} + +/* +============= +WriteWorld +============= +*/ +void WriteWorld (char *name, int iBump) +{ + unsigned j; + FileHandle_t out; + CPatch *patch; + + out = g_pFileSystem->Open( name, "w" ); + if (!out) + Error ("Couldn't open %s", name); + + unsigned int uiPatchCount = g_Patches.Size(); + for (j=0; j<uiPatchCount; j++) + { + patch = &g_Patches.Element( j ); + + // skip parent patches + if (patch->child1 != g_Patches.InvalidIndex() ) + continue; + + if( patch->clusterNumber == -1 ) + { + Vector vGreen; + VectorClear( vGreen ); + vGreen[1] = 256.0f; + WriteWinding( out, patch->winding, vGreen ); + } + else + { + Vector light = patch->totallight.light[iBump] + patch->directlight; + WriteWinding( out, patch->winding, light ); + if( bDumpNormals ) + { + WriteNormal( out, patch->origin, patch->plane->normal, 15.0f, patch->plane->normal * 255.0f ); + } + } + } + + g_pFileSystem->Close( out ); +} + +void WriteRTEnv (char *name) +{ + FileHandle_t out; + + out = g_pFileSystem->Open( name, "w" ); + if (!out) + Error ("Couldn't open %s", name); + + winding_t *triw = AllocWinding( 3 ); + triw->numpoints = 3; + + for( int i = 0; i < g_RtEnv.OptimizedTriangleList.Size(); i++ ) + { + triw->p[0] = g_RtEnv.OptimizedTriangleList[i].Vertex( 0); + triw->p[1] = g_RtEnv.OptimizedTriangleList[i].Vertex( 1); + triw->p[2] = g_RtEnv.OptimizedTriangleList[i].Vertex( 2); + int id = g_RtEnv.OptimizedTriangleList[i].m_Data.m_GeometryData.m_nTriangleID; + Vector color(0, 0, 0); + if (id & TRACE_ID_OPAQUE) color.Init(0, 255, 0); + if (id & TRACE_ID_SKY) color.Init(0, 0, 255); + if (id & TRACE_ID_STATICPROP) color.Init(255, 0, 0); + WriteWinding(out, triw, color); + } + FreeWinding(triw); + + g_pFileSystem->Close( out ); +} + +void WriteWinding (FileHandle_t out, winding_t *w, Vector& color ) +{ + int i; + + CmdLib_FPrintf (out, "%i\n", w->numpoints); + for (i=0 ; i<w->numpoints ; i++) + { + CmdLib_FPrintf (out, "%5.2f %5.2f %5.2f %5.3f %5.3f %5.3f\n", + w->p[i][0], + w->p[i][1], + w->p[i][2], + color[ 0 ] / 256, + color[ 1 ] / 256, + color[ 2 ] / 256 ); + } +} + + +void WriteNormal( FileHandle_t out, Vector const &nPos, Vector const &nDir, + float length, Vector const &color ) +{ + CmdLib_FPrintf( out, "2\n" ); + CmdLib_FPrintf( out, "%5.2f %5.2f %5.2f %5.3f %5.3f %5.3f\n", + nPos.x, nPos.y, nPos.z, + color.x / 256, color.y / 256, color.z / 256 ); + CmdLib_FPrintf( out, "%5.2f %5.2f %5.2f %5.3f %5.3f %5.3f\n", + nPos.x + ( nDir.x * length ), + nPos.y + ( nDir.y * length ), + nPos.z + ( nDir.z * length ), + color.x / 256, color.y / 256, color.z / 256 ); +} + +void WriteLine( FileHandle_t out, const Vector &vecPos1, const Vector &vecPos2, const Vector &color ) +{ + CmdLib_FPrintf( out, "2\n" ); + CmdLib_FPrintf( out, "%5.2f %5.2f %5.2f %5.3f %5.3f %5.3f\n", + vecPos1.x, vecPos1.y, vecPos1.z, + color.x / 256, color.y / 256, color.z / 256 ); + CmdLib_FPrintf( out, "%5.2f %5.2f %5.2f %5.3f %5.3f %5.3f\n", + vecPos2.x, vecPos2.y, vecPos2.z, + color.x / 256, color.y / 256, color.z / 256 ); +} + +void WriteTrace( const char *pFileName, const FourRays &rays, const RayTracingResult& result ) +{ + FileHandle_t out; + + out = g_pFileSystem->Open( pFileName, "a" ); + if (!out) + Error ("Couldn't open %s", pFileName); + + // Draws rays + for ( int i = 0; i < 4; ++i ) + { + Vector vecOrigin = rays.origin.Vec(i); + Vector vecEnd = rays.direction.Vec(i); + VectorNormalize( vecEnd ); + vecEnd *= SubFloat( result.HitDistance, i ); + vecEnd += vecOrigin; + WriteLine( out, vecOrigin, vecEnd, Vector( 256, 0, 0 ) ); + WriteNormal( out, vecEnd, result.surface_normal.Vec(i), 10.0f, Vector( 256, 265, 0 ) ); + } + + g_pFileSystem->Close( out ); +} + + +/* +============= +CollectLight +============= +*/ +// patch's totallight += new light received to each patch +// patch's emitlight = addlight (newly received light from GatherLight) +// patch's addlight = 0 +// pull received light from children. +void CollectLight( Vector& total ) +{ + int i, j; + CPatch *patch; + + VectorFill( total, 0 ); + + // process patches in reverse order so that children are processed before their parents + unsigned int uiPatchCount = g_Patches.Size(); + for( i = uiPatchCount - 1; i >= 0; i-- ) + { + patch = &g_Patches.Element( i ); + int normalCount = patch->needsBumpmap ? NUM_BUMP_VECTS+1 : 1; + // sky's never collect light, it is just dropped + if (patch->sky) + { + VectorFill( emitlight[ i ], 0 ); + } + else if ( patch->child1 == g_Patches.InvalidIndex() ) + { + // This is a leaf node. + for ( j = 0; j < normalCount; j++ ) + { + VectorAdd( patch->totallight.light[j], addlight[i].light[j], patch->totallight.light[j] ); + } + VectorCopy( addlight[i].light[0], emitlight[i] ); + VectorAdd( total, emitlight[i], total ); + } + else + { + // This is an interior node. + // Pull received light from children. + float s1, s2; + CPatch *child1; + CPatch *child2; + + child1 = &g_Patches[patch->child1]; + child2 = &g_Patches[patch->child2]; + + // BUG: This doesn't do anything? + if ((int)patch->area != (int)(child1->area + child2->area)) + s1 = 0; + + s1 = child1->area / (child1->area + child2->area); + s2 = child2->area / (child1->area + child2->area); + + // patch->totallight = s1 * child1->totallight + s2 * child2->totallight + for ( j = 0; j < normalCount; j++ ) + { + VectorScale( child1->totallight.light[j], s1, patch->totallight.light[j] ); + VectorMA( patch->totallight.light[j], s2, child2->totallight.light[j], patch->totallight.light[j] ); + } + + // patch->emitlight = s1 * child1->emitlight + s2 * child2->emitlight + VectorScale( emitlight[patch->child1], s1, emitlight[i] ); + VectorMA( emitlight[i], s2, emitlight[patch->child2], emitlight[i] ); + } + for ( j = 0; j < NUM_BUMP_VECTS+1; j++ ) + { + VectorFill( addlight[ i ].light[j], 0 ); + } + } +} + +/* +============= +GatherLight + +Get light from other patches + Run multi-threaded +============= +*/ + +#ifdef _WIN32 +#pragma warning (disable:4701) +#endif + +extern void GetBumpNormals( const float* sVect, const float* tVect, const Vector& flatNormal, + const Vector& phongNormal, Vector bumpNormals[NUM_BUMP_VECTS] ); + + +void PreGetBumpNormalsForDisp( texinfo_t *pTexinfo, Vector &vecU, Vector &vecV, Vector &vecNormal ) +{ + Vector vecTexU( pTexinfo->textureVecsTexelsPerWorldUnits[0][0], pTexinfo->textureVecsTexelsPerWorldUnits[0][1], pTexinfo->textureVecsTexelsPerWorldUnits[0][2] ); + Vector vecTexV( pTexinfo->textureVecsTexelsPerWorldUnits[1][0], pTexinfo->textureVecsTexelsPerWorldUnits[1][1], pTexinfo->textureVecsTexelsPerWorldUnits[1][2] ); + Vector vecLightU( pTexinfo->lightmapVecsLuxelsPerWorldUnits[0][0], pTexinfo->lightmapVecsLuxelsPerWorldUnits[0][1], pTexinfo->lightmapVecsLuxelsPerWorldUnits[0][2] ); + Vector vecLightV( pTexinfo->lightmapVecsLuxelsPerWorldUnits[1][0], pTexinfo->lightmapVecsLuxelsPerWorldUnits[1][1], pTexinfo->lightmapVecsLuxelsPerWorldUnits[1][2] ); + + VectorNormalize( vecTexU ); + VectorNormalize( vecTexV ); + VectorNormalize( vecLightU ); + VectorNormalize( vecLightV ); + + bool bDoConversion = false; + if ( fabs( vecTexU.Dot( vecLightU ) ) < 0.999f ) + { + bDoConversion = true; + } + + if ( fabs( vecTexV.Dot( vecLightV ) ) < 0.999f ) + { + bDoConversion = true; + } + + if ( bDoConversion ) + { + matrix3x4_t matTex( vecTexU, vecTexV, vecNormal, vec3_origin ); + matrix3x4_t matLight( vecLightU, vecLightV, vecNormal, vec3_origin ); + matrix3x4_t matTmp; + ConcatTransforms ( matLight, matTex, matTmp ); + MatrixGetColumn( matTmp, 0, vecU ); + MatrixGetColumn( matTmp, 1, vecV ); + MatrixGetColumn( matTmp, 2, vecNormal ); + + Assert( fabs( vecTexU.Dot( vecTexV ) ) <= 0.001f ); + return; + } + + vecU = vecTexU; + vecV = vecTexV; +} + +void GatherLight (int threadnum, void *pUserData) +{ + int i, j, k; + transfer_t *trans; + int num; + CPatch *patch; + Vector sum, v; + + while (1) + { + j = GetThreadWork (); + if (j == -1) + break; + + patch = &g_Patches[j]; + + trans = patch->transfers; + num = patch->numtransfers; + if ( patch->needsBumpmap ) + { + Vector delta; + Vector bumpSum[NUM_BUMP_VECTS+1]; + Vector normals[NUM_BUMP_VECTS+1]; + + // Disps + bool bDisp = ( g_pFaces[patch->faceNumber].dispinfo != -1 ); + if ( bDisp ) + { + normals[0] = patch->normal; + texinfo_t *pTexinfo = &texinfo[g_pFaces[patch->faceNumber].texinfo]; + Vector vecTexU, vecTexV; + PreGetBumpNormalsForDisp( pTexinfo, vecTexU, vecTexV, normals[0] ); + + // use facenormal along with the smooth normal to build the three bump map vectors + GetBumpNormals( vecTexU, vecTexV, normals[0], normals[0], &normals[1] ); + } + else + { + GetPhongNormal( patch->faceNumber, patch->origin, normals[0] ); + + texinfo_t *pTexinfo = &texinfo[g_pFaces[patch->faceNumber].texinfo]; + // use facenormal along with the smooth normal to build the three bump map vectors + GetBumpNormals( pTexinfo->textureVecsTexelsPerWorldUnits[0], + pTexinfo->textureVecsTexelsPerWorldUnits[1], patch->normal, + normals[0], &normals[1] ); + } + + // force the base lightmap to use the flat normal instead of the phong normal + // FIXME: why does the patch not use the phong normal? + normals[0] = patch->normal; + + for ( i = 0; i < NUM_BUMP_VECTS+1; i++ ) + { + VectorFill( bumpSum[i], 0 ); + } + + float dot; + for (k=0 ; k<num ; k++, trans++) + { + CPatch *patch2 = &g_Patches[trans->patch]; + + // get vector to other patch + VectorSubtract (patch2->origin, patch->origin, delta); + VectorNormalize (delta); + // find light emitted from other patch + for(i=0; i<3; i++) + { + v[i] = emitlight[trans->patch][i] * patch2->reflectivity[i]; + } + // remove normal already factored into transfer steradian + float scale = 1.0f / DotProduct (delta, patch->normal); + VectorScale( v, trans->transfer * scale, v ); + + Vector bumpTransfer; + for ( i = 0; i < NUM_BUMP_VECTS+1; i++ ) + { + dot = DotProduct( delta, normals[i] ); + if ( dot <= 0 ) + { +// Assert( i > 0 ); // if this hits, then the transfer shouldn't be here. It doesn't face the flat normal of this face! + continue; + } + bumpTransfer = v * dot; + VectorAdd( bumpSum[i], bumpTransfer, bumpSum[i] ); + } + } + for ( i = 0; i < NUM_BUMP_VECTS+1; i++ ) + { + VectorCopy( bumpSum[i], addlight[j].light[i] ); + } + } + else + { + VectorFill( sum, 0 ); + for (k=0 ; k<num ; k++, trans++) + { + for(i=0; i<3; i++) + { + v[i] = emitlight[trans->patch][i] * g_Patches[trans->patch].reflectivity[i]; + } + VectorScale( v, trans->transfer, v ); + VectorAdd( sum, v, sum ); + } + VectorCopy( sum, addlight[j].light[0] ); + } + } +} + +#ifdef _WIN32 +#pragma warning (default:4701) +#endif + + +/* +============= +BounceLight +============= +*/ +void BounceLight (void) +{ + unsigned i; + Vector added; + char name[64]; + qboolean bouncing = numbounce > 0; + + unsigned int uiPatchCount = g_Patches.Size(); + for (i=0 ; i<uiPatchCount; i++) + { + // totallight has a copy of the direct lighting. Move it to the emitted light and zero it out (to integrate bounces only) + VectorCopy( g_Patches[i].totallight.light[0], emitlight[i] ); + + // NOTE: This means that only the bounced light is integrated into totallight! + VectorFill( g_Patches[i].totallight.light[0], 0 ); + } + +#if 0 + FileHandle_t dFp = g_pFileSystem->Open( "lightemit.txt", "w" ); + + unsigned int uiPatchCount = g_Patches.Size(); + for (i=0 ; i<uiPatchCount; i++) + { + CmdLib_FPrintf( dFp, "Emit %d: %f %f %f\n", i, emitlight[i].x, emitlight[i].y, emitlight[i].z ); + } + + g_pFileSystem->Close( dFp ); + + for (i=0; i<num_patches ; i++) + { + Vector total; + + VectorSubtract (g_Patches[i].maxs, g_Patches[i].mins, total); + Msg("%4d %4d %4d %4d (%d) %.0f", i, g_Patches[i].parent, g_Patches[i].child1, g_Patches[i].child2, g_Patches[i].samples, g_Patches[i].area ); + Msg(" [%.0f %.0f %.0f]", total[0], total[1], total[2] ); + if (g_Patches[i].child1 != g_Patches.InvalidIndex() ) + { + Vector tmp; + VectorScale( g_Patches[i].totallight.light[0], g_Patches[i].area, tmp ); + + VectorMA( tmp, -g_Patches[g_Patches[i].child1].area, g_Patches[g_Patches[i].child1].totallight.light[0], tmp ); + VectorMA( tmp, -g_Patches[g_Patches[i].child2].area, g_Patches[g_Patches[i].child2].totallight.light[0], tmp ); + // Msg("%.0f ", VectorLength( tmp ) ); + // Msg("%d ", g_Patches[i].samples - g_Patches[g_Patches[i].child1].samples - g_Patches[g_Patches[i].child2].samples ); + // Msg("%d ", g_Patches[i].samples ); + } + Msg("\n"); + } +#endif + + i = 0; + while ( bouncing ) + { + // transfer light from to the leaf patches from other patches via transfers + // this moves shooter->emitlight to receiver->addlight + unsigned int uiPatchCount = g_Patches.Size(); + RunThreadsOn (uiPatchCount, true, GatherLight); + // move newly received light (addlight) to light to be sent out (emitlight) + // start at children and pull light up to parents + // light is always received to leaf patches + CollectLight( added ); + + qprintf ("\tBounce #%i added RGB(%.0f, %.0f, %.0f)\n", i+1, added[0], added[1], added[2] ); + + if ( i+1 == numbounce || (added[0] < 1.0 && added[1] < 1.0 && added[2] < 1.0) ) + bouncing = false; + + i++; + if ( g_bDumpPatches && !bouncing && i != 1) + { + sprintf (name, "bounce%i.txt", i); + WriteWorld (name, 0); + } + } +} + + + +//----------------------------------------------------------------------------- +// Purpose: Counts the number of clusters in a map with no visibility +// Output : int +//----------------------------------------------------------------------------- +int CountClusters( void ) +{ + int clusterCount = 0; + + for ( int i = 0; i < numleafs; i++ ) + { + if ( dleafs[i].cluster > clusterCount ) + clusterCount = dleafs[i].cluster; + } + + return clusterCount + 1; +} + + +/* +============= +RadWorld +============= +*/ +void RadWorld_Start() +{ + unsigned i; + + if (luxeldensity < 1.0) + { + // Remember the old lightmap vectors. + float oldLightmapVecs[MAX_MAP_TEXINFO][2][4]; + for (i = 0; i < texinfo.Count(); i++) + { + for( int j=0; j < 2; j++ ) + { + for( int k=0; k < 3; k++ ) + { + oldLightmapVecs[i][j][k] = texinfo[i].lightmapVecsLuxelsPerWorldUnits[j][k]; + } + } + } + + // rescale luxels to be no denser than "luxeldensity" + for (i = 0; i < texinfo.Count(); i++) + { + texinfo_t *tx = &texinfo[i]; + + for (int j = 0; j < 2; j++ ) + { + Vector tmp( tx->lightmapVecsLuxelsPerWorldUnits[j][0], tx->lightmapVecsLuxelsPerWorldUnits[j][1], tx->lightmapVecsLuxelsPerWorldUnits[j][2] ); + float scale = VectorNormalize( tmp ); + // only rescale them if the current scale is "tighter" than the desired scale + // FIXME: since this writes out to the BSP file every run, once it's set high it can't be reset + // to a lower value. + if (fabs( scale ) > luxeldensity) + { + if (scale < 0) + { + scale = -luxeldensity; + } + else + { + scale = luxeldensity; + } + VectorScale( tmp, scale, tmp ); + tx->lightmapVecsLuxelsPerWorldUnits[j][0] = tmp.x; + tx->lightmapVecsLuxelsPerWorldUnits[j][1] = tmp.y; + tx->lightmapVecsLuxelsPerWorldUnits[j][2] = tmp.z; + } + } + } + + UpdateAllFaceLightmapExtents(); + } + + MakeParents (0, -1); + + BuildClusterTable(); + + // turn each face into a single patch + MakePatches (); + PairEdges (); + + // store the vertex normals calculated in PairEdges + // so that the can be written to the bsp file for + // use in the engine + SaveVertexNormals(); + + // subdivide patches to a maximum dimension + SubdividePatches (); + + // add displacement faces to cluster table + AddDispsToClusterTable(); + + // create directlights out of patches and lights + CreateDirectLights (); + + // set up sky cameras + ProcessSkyCameras(); +} + + +// This function should fill in the indices into g_pFaces[] for the faces +// with displacements that touch the specified leaf. +void STUB_GetDisplacementsTouchingLeaf( int iLeaf, CUtlVector<int> &dispFaces ) +{ +} + + +void BuildFacesVisibleToLights( bool bAllVisible ) +{ + g_FacesVisibleToLights.SetSize( numfaces/8 + 1 ); + + if( bAllVisible ) + { + memset( g_FacesVisibleToLights.Base(), 0xFF, g_FacesVisibleToLights.Count() ); + return; + } + + // First merge all the light PVSes. + CUtlVector<byte> aggregate; + aggregate.SetSize( (dvis->numclusters/8) + 1 ); + memset( aggregate.Base(), 0, aggregate.Count() ); + + int nDWords = aggregate.Count() / 4; + int nBytes = aggregate.Count() - nDWords*4; + + for( directlight_t *dl = activelights; dl != NULL; dl = dl->next ) + { + byte *pIn = dl->pvs; + byte *pOut = aggregate.Base(); + for( int iDWord=0; iDWord < nDWords; iDWord++ ) + { + *((unsigned long*)pOut) |= *((unsigned long*)pIn); + pIn += 4; + pOut += 4; + } + + for( int iByte=0; iByte < nBytes; iByte++ ) + { + *pOut |= *pIn; + ++pOut; + ++pIn; + } + } + + + // Now tag any faces that are visible to this monster PVS. + for( int iCluster=0; iCluster < dvis->numclusters; iCluster++ ) + { + if( g_ClusterLeaves[iCluster].leafCount ) + { + if( aggregate[iCluster>>3] & (1 << (iCluster & 7)) ) + { + for ( int i = 0; i < g_ClusterLeaves[iCluster].leafCount; i++ ) + { + int iLeaf = g_ClusterLeaves[iCluster].leafs[i]; + + // Tag all the faces. + int iFace; + for( iFace=0; iFace < dleafs[iLeaf].numleaffaces; iFace++ ) + { + int index = dleafs[iLeaf].firstleafface + iFace; + index = dleaffaces[index]; + + assert( index < numfaces ); + g_FacesVisibleToLights[index >> 3] |= (1 << (index & 7)); + } + + // Fill in STUB_GetDisplacementsTouchingLeaf when it's available + // so displacements get relit. + CUtlVector<int> dispFaces; + STUB_GetDisplacementsTouchingLeaf( iLeaf, dispFaces ); + for( iFace=0; iFace < dispFaces.Count(); iFace++ ) + { + int index = dispFaces[iFace]; + g_FacesVisibleToLights[index >> 3] |= (1 << (index & 7)); + } + } + } + } + } + + // For stats.. figure out how many faces it's going to touch. + int nFacesToProcess = 0; + for( int i=0; i < numfaces; i++ ) + { + if( g_FacesVisibleToLights[i>>3] & (1 << (i & 7)) ) + ++nFacesToProcess; + } +} + + + +void MakeAllScales (void) +{ + // determine visibility between patches + BuildVisMatrix (); + + // release visibility matrix + FreeVisMatrix (); + + Msg("transfers %d, max %d\n", total_transfer, max_transfer ); + + qprintf ("transfer lists: %5.1f megs\n" + , (float)total_transfer * sizeof(transfer_t) / (1024*1024)); +} + + +// Helper function. This can be useful to visualize the world and faces and see which face +// corresponds to which dface. +#if 0 + #include "iscratchpad3d.h" + void ScratchPad_DrawWorld() + { + IScratchPad3D *pPad = ScratchPad3D_Create(); + pPad->SetAutoFlush( false ); + + for ( int i=0; i < numfaces; i++ ) + { + dface_t *f = &g_pFaces[i]; + + // Draw the face's outline, then put text for its face index on it too. + CUtlVector<Vector> points; + for ( int iEdge = 0; iEdge < f->numedges; iEdge++ ) + { + int v; + int se = dsurfedges[f->firstedge + iEdge]; + if ( se < 0 ) + v = dedges[-se].v[1]; + else + v = dedges[se].v[0]; + + dvertex_t *dv = &dvertexes[v]; + points.AddToTail( dv->point ); + } + + // Draw the outline. + Vector vCenter( 0, 0, 0 ); + for ( iEdge=0; iEdge < points.Count(); iEdge++ ) + { + pPad->DrawLine( CSPVert( points[iEdge] ), CSPVert( points[(iEdge+1)%points.Count()] ) ); + vCenter += points[iEdge]; + } + vCenter /= points.Count(); + + // Draw the text. + char str[512]; + Q_snprintf( str, sizeof( str ), "%d", i ); + + CTextParams params; + + params.m_bCentered = true; + params.m_bOutline = true; + params.m_flLetterWidth = 2; + params.m_vColor.Init( 1, 0, 0 ); + + VectorAngles( dplanes[f->planenum].normal, params.m_vAngles ); + params.m_bTwoSided = true; + + params.m_vPos = vCenter; + + pPad->DrawText( str, params ); + } + + pPad->Release(); + } +#endif + + +bool RadWorld_Go() +{ + g_iCurFace = 0; + + InitMacroTexture( source ); + + if( g_pIncremental ) + { + g_pIncremental->PrepareForLighting(); + + // Cull out faces that aren't visible to any of the lights that we're updating with. + BuildFacesVisibleToLights( false ); + } + else + { + // Mark all faces visible.. when not doing incremental lighting, it's highly + // likely that all faces are going to be touched by at least one light so don't + // waste time here. + BuildFacesVisibleToLights( true ); + } + + // build initial facelights + if (g_bUseMPI) + { + // RunThreadsOnIndividual (numfaces, true, BuildFacelights); + RunMPIBuildFacelights(); + } + else + { + RunThreadsOnIndividual (numfaces, true, BuildFacelights); + } + + // Was the process interrupted? + if( g_pIncremental && (g_iCurFace != numfaces) ) + return false; + + // Figure out the offset into lightmap data for each face. + PrecompLightmapOffsets(); + + // If we're doing incremental lighting, stop here. + if( g_pIncremental ) + { + g_pIncremental->Finalize(); + } + else + { + // free up the direct lights now that we have facelights + ExportDirectLightsToWorldLights(); + + if ( g_bDumpPatches ) + { + for( int iBump = 0; iBump < 4; ++iBump ) + { + char szName[64]; + sprintf ( szName, "bounce0_%d.txt", iBump ); + WriteWorld( szName, iBump ); + } + } + + if (numbounce > 0) + { + // allocate memory for emitlight/addlight + emitlight.SetSize( g_Patches.Size() ); + memset( emitlight.Base(), 0, g_Patches.Size() * sizeof( Vector ) ); + addlight.SetSize( g_Patches.Size() ); + memset( addlight.Base(), 0, g_Patches.Size() * sizeof( bumplights_t ) ); + + MakeAllScales (); + + // spread light around + BounceLight (); + } + + // + // displacement surface luxel accumulation (make threaded!!!) + // + StaticDispMgr()->StartTimer( "Build Patch/Sample Hash Table(s)....." ); + StaticDispMgr()->InsertSamplesDataIntoHashTable(); + StaticDispMgr()->InsertPatchSampleDataIntoHashTable(); + StaticDispMgr()->EndTimer(); + + // blend bounced light into direct light and save + VMPI_SetCurrentStage( "FinalLightFace" ); + if ( !g_bUseMPI || g_bMPIMaster ) + RunThreadsOnIndividual (numfaces, true, FinalLightFace); + + // Distribute the lighting data to workers. + VMPI_DistributeLightData(); + + Msg("FinalLightFace Done\n"); fflush(stdout); + } + + return true; +} + +// declare the sample file pointer -- the whole debug print system should +// be reworked at some point!! +FileHandle_t pFileSamples[4][4]; + +void LoadPhysicsDLL( void ) +{ + PhysicsDLLPath( "VPHYSICS.DLL" ); +} + + +void InitDumpPatchesFiles() +{ + for( int iStyle = 0; iStyle < 4; ++iStyle ) + { + for ( int iBump = 0; iBump < 4; ++iBump ) + { + char szFilename[MAX_PATH]; + sprintf( szFilename, "samples_style%d_bump%d.txt", iStyle, iBump ); + pFileSamples[iStyle][iBump] = g_pFileSystem->Open( szFilename, "w" ); + if( !pFileSamples[iStyle][iBump] ) + { + Error( "Can't open %s for -dump.\n", szFilename ); + } + } + } +} + +extern IFileSystem *g_pOriginalPassThruFileSystem; + +void VRAD_LoadBSP( char const *pFilename ) +{ + ThreadSetDefault (); + + g_flStartTime = Plat_FloatTime(); + + if( g_bLowPriority ) + { + SetLowPriority(); + } + + strcpy( level_name, source ); + + // This must come after InitFileSystem because the file system pointer might change. + if ( g_bDumpPatches ) + InitDumpPatchesFiles(); + + // This part is just for VMPI. VMPI's file system needs the basedir in front of all filenames, + // so we prepend qdir here. + strcpy( source, ExpandPath( source ) ); + + if ( !g_bUseMPI ) + { + // Setup the logfile. + char logFile[512]; + _snprintf( logFile, sizeof(logFile), "%s.log", source ); + SetSpewFunctionLogFile( logFile ); + } + + LoadPhysicsDLL(); + + // Set the required global lights filename and try looking in qproject + strcpy( global_lights, "lights.rad" ); + if ( !g_pFileSystem->FileExists( global_lights ) ) + { + // Otherwise, try looking in the BIN directory from which we were run from + Msg( "Could not find lights.rad in %s.\nTrying VRAD BIN directory instead...\n", + global_lights ); + GetModuleFileName( NULL, global_lights, sizeof( global_lights ) ); + Q_ExtractFilePath( global_lights, global_lights, sizeof( global_lights ) ); + strcat( global_lights, "lights.rad" ); + } + + // Set the optional level specific lights filename + strcpy( level_lights, source ); + + Q_DefaultExtension( level_lights, ".rad", sizeof( level_lights ) ); + if ( !g_pFileSystem->FileExists( level_lights ) ) + *level_lights = 0; + + ReadLightFile(global_lights); // Required + if ( *designer_lights ) ReadLightFile(designer_lights); // Command-line + if ( *level_lights ) ReadLightFile(level_lights); // Optional & implied + + strcpy(incrementfile, source); + Q_DefaultExtension(incrementfile, ".r0", sizeof(incrementfile)); + Q_DefaultExtension(source, ".bsp", sizeof( source )); + + Msg( "Loading %s\n", source ); + VMPI_SetCurrentStage( "LoadBSPFile" ); + LoadBSPFile (source); + + // Add this bsp to our search path so embedded resources can be found + if ( g_bUseMPI && g_bMPIMaster ) + { + // MPI Master, MPI workers don't need to do anything + g_pOriginalPassThruFileSystem->AddSearchPath(source, "GAME", PATH_ADD_TO_HEAD); + g_pOriginalPassThruFileSystem->AddSearchPath(source, "MOD", PATH_ADD_TO_HEAD); + } + else if ( !g_bUseMPI ) + { + // Non-MPI + g_pFullFileSystem->AddSearchPath(source, "GAME", PATH_ADD_TO_HEAD); + g_pFullFileSystem->AddSearchPath(source, "MOD", PATH_ADD_TO_HEAD); + } + + // now, set whether or not static prop lighting is present + if (g_bStaticPropLighting) + g_LevelFlags |= g_bHDR? LVLFLAGS_BAKED_STATIC_PROP_LIGHTING_HDR : LVLFLAGS_BAKED_STATIC_PROP_LIGHTING_NONHDR; + else + { + g_LevelFlags &= ~( LVLFLAGS_BAKED_STATIC_PROP_LIGHTING_HDR | LVLFLAGS_BAKED_STATIC_PROP_LIGHTING_NONHDR ); + } + + // now, we need to set our face ptr depending upon hdr, and if hdr, init it + if (g_bHDR) + { + g_pFaces = dfaces_hdr; + if (numfaces_hdr==0) + { + numfaces_hdr = numfaces; + memcpy( dfaces_hdr, dfaces, numfaces*sizeof(dfaces[0]) ); + } + } + else + { + g_pFaces = dfaces; + } + + + ParseEntities (); + ExtractBrushEntityShadowCasters(); + + StaticPropMgr()->Init(); + StaticDispMgr()->Init(); + + if (!visdatasize) + { + Msg("No vis information, direct lighting only.\n"); + numbounce = 0; + ambient[0] = ambient[1] = ambient[2] = 0.1f; + dvis->numclusters = CountClusters(); + } + + // + // patches and referencing data (ensure capacity) + // + // TODO: change the maxes to the amount from the bsp!! + // +// g_Patches.EnsureCapacity( MAX_PATCHES ); + + g_FacePatches.SetSize( MAX_MAP_FACES ); + faceParents.SetSize( MAX_MAP_FACES ); + clusterChildren.SetSize( MAX_MAP_CLUSTERS ); + + int ndx; + for ( ndx = 0; ndx < MAX_MAP_FACES; ndx++ ) + { + g_FacePatches[ndx] = g_FacePatches.InvalidIndex(); + faceParents[ndx] = faceParents.InvalidIndex(); + } + + for ( ndx = 0; ndx < MAX_MAP_CLUSTERS; ndx++ ) + { + clusterChildren[ndx] = clusterChildren.InvalidIndex(); + } + + // Setup ray tracer + AddBrushesForRayTrace(); + StaticDispMgr()->AddPolysForRayTrace(); + StaticPropMgr()->AddPolysForRayTrace(); + + // Dump raytracer for glview + if ( g_bDumpRtEnv ) + WriteRTEnv("trace.txt"); + + // Build acceleration structure + printf ( "Setting up ray-trace acceleration structure... "); + float start = Plat_FloatTime(); + g_RtEnv.SetupAccelerationStructure(); + float end = Plat_FloatTime(); + printf ( "Done (%.2f seconds)\n", end-start ); + +#if 0 // To test only k-d build + exit(0); +#endif + + RadWorld_Start(); + + // Setup incremental lighting. + if( g_pIncremental ) + { + if( !g_pIncremental->Init( source, incrementfile ) ) + { + Error( "Unable to load incremental lighting file in %s.\n", incrementfile ); + return; + } + } +} + + +void VRAD_ComputeOtherLighting() +{ + // Compute lighting for the bsp file + if ( !g_bNoDetailLighting ) + { + ComputeDetailPropLighting( THREADINDEX_MAIN ); + } + + ComputePerLeafAmbientLighting(); + + // bake the static props high quality vertex lighting into the bsp + if ( !do_fast && g_bStaticPropLighting ) + { + StaticPropMgr()->ComputeLighting( THREADINDEX_MAIN ); + } +} + +extern void CloseDispLuxels(); + +void VRAD_Finish() +{ + Msg( "Ready to Finish\n" ); + fflush( stdout ); + + if ( verbose ) + { + PrintBSPFileSizes(); + } + + Msg( "Writing %s\n", source ); + VMPI_SetCurrentStage( "WriteBSPFile" ); + WriteBSPFile(source); + + if ( g_bDumpPatches ) + { + for ( int iStyle = 0; iStyle < 4; ++iStyle ) + { + for ( int iBump = 0; iBump < 4; ++iBump ) + { + g_pFileSystem->Close( pFileSamples[iStyle][iBump] ); + } + } + } + + CloseDispLuxels(); + + StaticPropMgr()->Shutdown(); + + double end = Plat_FloatTime(); + + char str[512]; + GetHourMinuteSecondsString( (int)( end - g_flStartTime ), str, sizeof( str ) ); + Msg( "%s elapsed\n", str ); + + ReleasePakFileLumps(); +} + + +// Run startup code like initialize mathlib (called from main() and from the +// WorldCraft interface into vrad). +void VRAD_Init() +{ + MathLib_Init( 2.2f, 2.2f, 0.0f, 2.0f, false, false, false, false ); + InstallAllocationFunctions(); + InstallSpewFunction(); +} + + +int ParseCommandLine( int argc, char **argv, bool *onlydetail ) +{ + *onlydetail = false; + + int mapArg = -1; + + // default to LDR + SetHDRMode( false ); + int i; + for( i=1 ; i<argc ; i++ ) + { + if ( !Q_stricmp( argv[i], "-StaticPropLighting" ) ) + { + g_bStaticPropLighting = true; + } + else if ( !stricmp( argv[i], "-StaticPropNormals" ) ) + { + g_bShowStaticPropNormals = true; + } + else if ( !stricmp( argv[i], "-OnlyStaticProps" ) ) + { + g_bOnlyStaticProps = true; + } + else if ( !Q_stricmp( argv[i], "-StaticPropPolys" ) ) + { + g_bStaticPropPolys = true; + } + else if ( !Q_stricmp( argv[i], "-nossprops" ) ) + { + g_bDisablePropSelfShadowing = true; + } + else if ( !Q_stricmp( argv[i], "-textureshadows" ) ) + { + g_bTextureShadows = true; + } + else if ( !strcmp(argv[i], "-dump") ) + { + g_bDumpPatches = true; + } + else if ( !Q_stricmp( argv[i], "-nodetaillight" ) ) + { + g_bNoDetailLighting = true; + } + else if ( !Q_stricmp( argv[i], "-rederrors" ) ) + { + bRed2Black = false; + } + else if ( !Q_stricmp( argv[i], "-dumpnormals" ) ) + { + bDumpNormals = true; + } + else if ( !Q_stricmp( argv[i], "-dumptrace" ) ) + { + g_bDumpRtEnv = true; + } + else if ( !Q_stricmp( argv[i], "-LargeDispSampleRadius" ) ) + { + g_bLargeDispSampleRadius = true; + } + else if (!Q_stricmp( argv[i], "-dumppropmaps")) + { + g_bDumpPropLightmaps = true; + } + else if (!Q_stricmp(argv[i],"-bounce")) + { + if ( ++i < argc ) + { + int bounceParam = atoi (argv[i]); + if ( bounceParam < 0 ) + { + Warning("Error: expected non-negative value after '-bounce'\n" ); + return -1; + } + numbounce = (unsigned)bounceParam; + } + else + { + Warning("Error: expected a value after '-bounce'\n" ); + return -1; + } + } + else if (!Q_stricmp(argv[i],"-verbose") || !Q_stricmp(argv[i],"-v")) + { + verbose = true; + } + else if (!Q_stricmp(argv[i],"-threads")) + { + if ( ++i < argc ) + { + numthreads = atoi (argv[i]); + if ( numthreads <= 0 ) + { + Warning("Error: expected positive value after '-threads'\n" ); + return -1; + } + } + else + { + Warning("Error: expected a value after '-threads'\n" ); + return -1; + } + } + else if ( !Q_stricmp(argv[i], "-lights" ) ) + { + if ( ++i < argc && *argv[i] ) + { + strcpy( designer_lights, argv[i] ); + } + else + { + Warning("Error: expected a filepath after '-lights'\n" ); + return -1; + } + } + else if (!Q_stricmp(argv[i],"-noextra")) + { + do_extra = false; + } + else if (!Q_stricmp(argv[i],"-debugextra")) + { + debug_extra = true; + } + else if ( !Q_stricmp(argv[i], "-fastambient") ) + { + g_bFastAmbient = true; + } + else if (!Q_stricmp(argv[i],"-fast")) + { + do_fast = true; + } + else if (!Q_stricmp(argv[i],"-noskyboxrecurse")) + { + g_bNoSkyRecurse = true; + } + else if (!Q_stricmp(argv[i],"-final")) + { + g_flSkySampleScale = 16.0; + } + else if (!Q_stricmp(argv[i],"-extrasky")) + { + if ( ++i < argc && *argv[i] ) + { + g_flSkySampleScale = atof( argv[i] ); + } + else + { + Warning("Error: expected a scale factor after '-extrasky'\n" ); + return -1; + } + } + else if (!Q_stricmp(argv[i],"-centersamples")) + { + do_centersamples = true; + } + else if (!Q_stricmp(argv[i],"-smooth")) + { + if ( ++i < argc ) + { + smoothing_threshold = (float)cos(atof(argv[i])*(M_PI/180.0)); + } + else + { + Warning("Error: expected an angle after '-smooth'\n" ); + return -1; + } + } + else if (!Q_stricmp(argv[i],"-dlightmap")) + { + dlight_map = 1; + } + else if (!Q_stricmp(argv[i],"-luxeldensity")) + { + if ( ++i < argc ) + { + luxeldensity = (float)atof (argv[i]); + if (luxeldensity > 1.0) + luxeldensity = 1.0 / luxeldensity; + } + else + { + Warning("Error: expected a value after '-luxeldensity'\n" ); + return -1; + } + } + else if( !Q_stricmp( argv[i], "-low" ) ) + { + g_bLowPriority = true; + } + else if( !Q_stricmp( argv[i], "-loghash" ) ) + { + g_bLogHashData = true; + } + else if( !Q_stricmp( argv[i], "-onlydetail" ) ) + { + *onlydetail = true; + } + else if (!Q_stricmp(argv[i],"-softsun")) + { + if ( ++i < argc ) + { + g_SunAngularExtent=atof(argv[i]); + g_SunAngularExtent=sin((M_PI/180.0)*g_SunAngularExtent); + printf("sun extent=%f\n",g_SunAngularExtent); + } + else + { + Warning("Error: expected an angular extent value (0..180) '-softsun'\n" ); + return -1; + } + } + else if ( !Q_stricmp( argv[i], "-maxdispsamplesize" ) ) + { + if ( ++i < argc ) + { + g_flMaxDispSampleSize = ( float )atof( argv[i] ); + } + else + { + Warning( "Error: expected a sample size after '-maxdispsamplesize'\n" ); + return -1; + } + } + else if ( stricmp( argv[i], "-StopOnExit" ) == 0 ) + { + g_bStopOnExit = true; + } + else if ( stricmp( argv[i], "-steam" ) == 0 ) + { + } + else if ( stricmp( argv[i], "-allowdebug" ) == 0 ) + { + // Don't need to do anything, just don't error out. + } + else if ( !Q_stricmp( argv[i], CMDLINEOPTION_NOVCONFIG ) ) + { + } + else if ( !Q_stricmp( argv[i], "-vproject" ) || !Q_stricmp( argv[i], "-game" ) || !Q_stricmp( argv[i], "-insert_search_path" ) ) + { + ++i; + } + else if ( !Q_stricmp( argv[i], "-FullMinidumps" ) ) + { + EnableFullMinidumps( true ); + } + else if ( !Q_stricmp( argv[i], "-hdr" ) ) + { + SetHDRMode( true ); + } + else if ( !Q_stricmp( argv[i], "-ldr" ) ) + { + SetHDRMode( false ); + } + else if (!Q_stricmp(argv[i],"-maxchop")) + { + if ( ++i < argc ) + { + maxchop = (float)atof (argv[i]); + if ( maxchop < 1 ) + { + Warning("Error: expected positive value after '-maxchop'\n" ); + return -1; + } + } + else + { + Warning("Error: expected a value after '-maxchop'\n" ); + return -1; + } + } + else if (!Q_stricmp(argv[i],"-chop")) + { + if ( ++i < argc ) + { + minchop = (float)atof (argv[i]); + if ( minchop < 1 ) + { + Warning("Error: expected positive value after '-chop'\n" ); + return -1; + } + minchop = min( minchop, maxchop ); + } + else + { + Warning("Error: expected a value after '-chop'\n" ); + return -1; + } + } + else if ( !Q_stricmp( argv[i], "-dispchop" ) ) + { + if ( ++i < argc ) + { + dispchop = ( float )atof( argv[i] ); + if ( dispchop < 1.0f ) + { + Warning( "Error: expected positive value after '-dipschop'\n" ); + return -1; + } + } + else + { + Warning( "Error: expected a value after '-dispchop'\n" ); + return -1; + } + } + else if ( !Q_stricmp( argv[i], "-disppatchradius" ) ) + { + if ( ++i < argc ) + { + g_MaxDispPatchRadius = ( float )atof( argv[i] ); + if ( g_MaxDispPatchRadius < 10.0f ) + { + Warning( "Error: g_MaxDispPatchRadius < 10.0\n" ); + return -1; + } + } + else + { + Warning( "Error: expected a value after '-disppatchradius'\n" ); + return -1; + } + } + +#if ALLOWDEBUGOPTIONS + else if (!Q_stricmp(argv[i],"-scale")) + { + if ( ++i < argc ) + { + lightscale = (float)atof (argv[i]); + } + else + { + Warning("Error: expected a value after '-scale'\n" ); + return -1; + } + } + else if (!Q_stricmp(argv[i],"-ambient")) + { + if ( i+3 < argc ) + { + ambient[0] = (float)atof (argv[++i]) * 128; + ambient[1] = (float)atof (argv[++i]) * 128; + ambient[2] = (float)atof (argv[++i]) * 128; + } + else + { + Warning("Error: expected three color values after '-ambient'\n" ); + return -1; + } + } + else if (!Q_stricmp(argv[i],"-dlight")) + { + if ( ++i < argc ) + { + dlight_threshold = (float)atof (argv[i]); + } + else + { + Warning("Error: expected a value after '-dlight'\n" ); + return -1; + } + } + else if (!Q_stricmp(argv[i],"-sky")) + { + if ( ++i < argc ) + { + indirect_sun = (float)atof (argv[i]); + } + else + { + Warning("Error: expected a value after '-sky'\n" ); + return -1; + } + } + else if (!Q_stricmp(argv[i],"-notexscale")) + { + texscale = false; + } + else if (!Q_stricmp(argv[i],"-coring")) + { + if ( ++i < argc ) + { + coring = (float)atof( argv[i] ); + } + else + { + Warning("Error: expected a light threshold after '-coring'\n" ); + return -1; + } + } +#endif + // NOTE: the -mpi checks must come last here because they allow the previous argument + // to be -mpi as well. If it game before something else like -game, then if the previous + // argument was -mpi and the current argument was something valid like -game, it would skip it. + else if ( !Q_strncasecmp( argv[i], "-mpi", 4 ) || !Q_strncasecmp( argv[i-1], "-mpi", 4 ) ) + { + if ( stricmp( argv[i], "-mpi" ) == 0 ) + g_bUseMPI = true; + + // Any other args that start with -mpi are ok too. + if ( i == argc - 1 && V_stricmp( argv[i], "-mpi_ListParams" ) != 0 ) + break; + } + else if ( mapArg == -1 ) + { + mapArg = i; + } + else + { + return -1; + } + } + + return mapArg; +} + + +void PrintCommandLine( int argc, char **argv ) +{ + Warning( "Command line: " ); + for ( int z=0; z < argc; z++ ) + { + Warning( "\"%s\" ", argv[z] ); + } + Warning( "\n\n" ); +} + + +void PrintUsage( int argc, char **argv ) +{ + PrintCommandLine( argc, argv ); + + Warning( + "usage : vrad [options...] bspfile\n" + "example: vrad c:\\hl2\\hl2\\maps\\test\n" + "\n" + "Common options:\n" + "\n" + " -v (or -verbose): Turn on verbose output (also shows more command\n" + " -bounce # : Set max number of bounces (default: 100).\n" + " -fast : Quick and dirty lighting.\n" + " -fastambient : Per-leaf ambient sampling is lower quality to save compute time.\n" + " -final : High quality processing. equivalent to -extrasky 16.\n" + " -extrasky n : trace N times as many rays for indirect light and sky ambient.\n" + " -low : Run as an idle-priority process.\n" + " -mpi : Use VMPI to distribute computations.\n" + " -rederror : Show errors in red.\n" + "\n" + " -vproject <directory> : Override the VPROJECT environment variable.\n" + " -game <directory> : Same as -vproject.\n" + "\n" + "Other options:\n" + " -novconfig : Don't bring up graphical UI on vproject errors.\n" + " -dump : Write debugging .txt files.\n" + " -dumpnormals : Write normals to debug files.\n" + " -dumptrace : Write ray-tracing environment to debug files.\n" + " -threads : Control the number of threads vbsp uses (defaults to the #\n" + " or processors on your machine).\n" + " -lights <file> : Load a lights file in addition to lights.rad and the\n" + " level lights file.\n" + " -noextra : Disable supersampling.\n" + " -debugextra : Places debugging data in lightmaps to visualize\n" + " supersampling.\n" + " -smooth # : Set the threshold for smoothing groups, in degrees\n" + " (default 45).\n" + " -dlightmap : Force direct lighting into different lightmap than\n" + " radiosity.\n" + " -stoponexit : Wait for a keypress on exit.\n" + " -mpi_pw <pw> : Use a password to choose a specific set of VMPI workers.\n" + " -nodetaillight : Don't light detail props.\n" + " -centersamples : Move sample centers.\n" + " -luxeldensity # : Rescale all luxels by the specified amount (default: 1.0).\n" + " The number specified must be less than 1.0 or it will be\n" + " ignored.\n" + " -loghash : Log the sample hash table to samplehash.txt.\n" + " -onlydetail : Only light detail props and per-leaf lighting.\n" + " -maxdispsamplesize #: Set max displacement sample size (default: 512).\n" + " -softsun <n> : Treat the sun as an area light source of size <n> degrees." + " Produces soft shadows.\n" + " Recommended values are between 0 and 5. Default is 0.\n" + " -FullMinidumps : Write large minidumps on crash.\n" + " -chop : Smallest number of luxel widths for a bounce patch, used on edges\n" + " -maxchop : Coarsest allowed number of luxel widths for a patch, used in face interiors\n" + "\n" + " -LargeDispSampleRadius: This can be used if there are splotches of bounced light\n" + " on terrain. The compile will take longer, but it will gather\n" + " light across a wider area.\n" + " -StaticPropLighting : generate backed static prop vertex lighting\n" + " -StaticPropPolys : Perform shadow tests of static props at polygon precision\n" + " -OnlyStaticProps : Only perform direct static prop lighting (vrad debug option)\n" + " -StaticPropNormals : when lighting static props, just show their normal vector\n" + " -textureshadows : Allows texture alpha channels to block light - rays intersecting alpha surfaces will sample the texture\n" + " -noskyboxrecurse : Turn off recursion into 3d skybox (skybox shadows on world)\n" + " -nossprops : Globally disable self-shadowing on static props\n" + "\n" +#if 1 // Disabled for the initial SDK release with VMPI so we can get feedback from selected users. + ); +#else + " -mpi_ListParams : Show a list of VMPI parameters.\n" + "\n" + ); + + // Show VMPI parameters? + for ( int i=1; i < argc; i++ ) + { + if ( V_stricmp( argv[i], "-mpi_ListParams" ) == 0 ) + { + Warning( "VMPI-specific options:\n\n" ); + + bool bIsSDKMode = VMPI_IsSDKMode(); + for ( int i=k_eVMPICmdLineParam_FirstParam+1; i < k_eVMPICmdLineParam_LastParam; i++ ) + { + if ( (VMPI_GetParamFlags( (EVMPICmdLineParam)i ) & VMPI_PARAM_SDK_HIDDEN) && bIsSDKMode ) + continue; + + Warning( "[%s]\n", VMPI_GetParamString( (EVMPICmdLineParam)i ) ); + Warning( VMPI_GetParamHelpString( (EVMPICmdLineParam)i ) ); + Warning( "\n\n" ); + } + break; + } + } +#endif +} + +int RunVRAD( int argc, char **argv ) +{ +#if defined(_MSC_VER) && ( _MSC_VER >= 1310 ) + Msg("Valve Software - vrad.exe SSE (" __DATE__ ")\n" ); +#else + Msg("Valve Software - vrad.exe (" __DATE__ ")\n" ); +#endif + + Msg("\n Valve Radiosity Simulator \n"); + + verbose = true; // Originally FALSE + + bool onlydetail; + int i = ParseCommandLine( argc, argv, &onlydetail ); + if (i == -1) + { + PrintUsage( argc, argv ); + DeleteCmdLine( argc, argv ); + CmdLib_Exit( 1 ); + } + + // Initialize the filesystem, so additional commandline options can be loaded + Q_StripExtension( argv[ i ], source, sizeof( source ) ); + CmdLib_InitFileSystem( argv[ i ] ); + Q_FileBase( source, source, sizeof( source ) ); + + VRAD_LoadBSP( argv[i] ); + + if ( (! onlydetail) && (! g_bOnlyStaticProps ) ) + { + RadWorld_Go(); + } + + VRAD_ComputeOtherLighting(); + + VRAD_Finish(); + + VMPI_SetCurrentStage( "master done" ); + + DeleteCmdLine( argc, argv ); + CmdLib_Cleanup(); + return 0; +} + + +int VRAD_Main(int argc, char **argv) +{ + g_pFileSystem = NULL; // Safeguard against using it before it's properly initialized. + + VRAD_Init(); + + // This must come first. + VRAD_SetupMPI( argc, argv ); + +#if !defined( _DEBUG ) + if ( g_bUseMPI && !g_bMPIMaster ) + { + SetupToolsMinidumpHandler( VMPI_ExceptionFilter ); + } + else +#endif + { + LoadCmdLineFromFile( argc, argv, source, "vrad" ); // Don't do this if we're a VMPI worker.. + SetupDefaultToolsMinidumpHandler(); + } + + return RunVRAD( argc, argv ); +} + + + + + diff --git a/utils/vrad/vrad.h b/utils/vrad/vrad.h new file mode 100644 index 0000000..e741ee6 --- /dev/null +++ b/utils/vrad/vrad.h @@ -0,0 +1,614 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Workfile: $ +// $Date: $ +// +//----------------------------------------------------------------------------- +// $Log: $ +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef VRAD_H +#define VRAD_H +#pragma once + + +#include "commonmacros.h" +#include "worldsize.h" +#include "cmdlib.h" +#include "mathlib/mathlib.h" +#include "bsplib.h" +#include "polylib.h" +#include "threads.h" +#include "builddisp.h" +#include "VRAD_DispColl.h" +#include "UtlMemory.h" +#include "UtlHash.h" +#include "utlvector.h" +#include "iincremental.h" +#include "raytrace.h" + + +#ifdef _WIN32 +#include <windows.h> +#endif + +#include <sys/types.h> +#include <sys/stat.h> + +#pragma warning(disable: 4142 4028) +#include <io.h> +#pragma warning(default: 4142 4028) + +#include <fcntl.h> +#include <direct.h> +#include <ctype.h> + + +// Can remove these options if they don't generate problems. +//#define SAMPLEHASH_USE_AREA_PATCHES // Add patches to sample hash based on their AABB instead of as a single point. +#define SAMPLEHASH_QUERY_ONCE // Big optimization - causes way less sample hash queries. + +extern float dispchop; // "-dispchop" tightest number of luxel widths for a patch, used on edges +extern float g_MaxDispPatchRadius; + +//----------------------------------------------------------------------------- +// forward declarations +//----------------------------------------------------------------------------- + +struct Ray_t; + +#define TRANSFER_EPSILON 0.0000001 + +struct directlight_t +{ + int index; + + directlight_t *next; + dworldlight_t light; + + byte *pvs; // accumulated domain of the light + int facenum; // domain of attached lights + int texdata; // texture source of traced lights + + Vector snormal; + Vector tnormal; + float sscale; + float tscale; + float soffset; + float toffset; + + int dorecalc; // position, vector, spot angle, etc. + IncrementalLightID m_IncrementalID; + + // hard-falloff lights (lights that fade to an actual zero). between m_flStartFadeDistance and + // m_flEndFadeDistance, a smoothstep to zero will be done, so that the light goes to zero at + // the end. + float m_flStartFadeDistance; + float m_flEndFadeDistance; + float m_flCapDist; // max distance to feed in + + directlight_t(void) + { + m_flEndFadeDistance = -1.0; // end<start indicates not set + m_flStartFadeDistance= 0.0; + m_flCapDist = 1.0e22; + + } +}; + +struct bumplights_t +{ + Vector light[NUM_BUMP_VECTS+1]; +}; + + +struct transfer_t +{ + int patch; + float transfer; +}; + + +struct LightingValue_t +{ + Vector m_vecLighting; + float m_flDirectSunAmount; + + FORCEINLINE bool IsValid( void ) const + { + return ( m_vecLighting.x >= 0 && + m_vecLighting.y >= 0 && + m_vecLighting.z >= 0 && + m_vecLighting.x < 1e10 && + m_vecLighting.y < 1e10 && + m_vecLighting.z < 1e10 ); + } + + FORCEINLINE void Zero( void ) + { + m_vecLighting.Init( 0, 0, 0 ); + m_flDirectSunAmount = 0.0; + } + + FORCEINLINE void Scale( float m_flScale ) + { + m_vecLighting *= m_flScale; + m_flDirectSunAmount *= m_flScale; + } + + FORCEINLINE void AddWeighted( LightingValue_t const &src, float flWeight ) + { + m_vecLighting += flWeight * src.m_vecLighting; + m_flDirectSunAmount += flWeight * src.m_flDirectSunAmount; + } + + FORCEINLINE void AddWeighted( Vector const &src, float flWeight ) + { + m_vecLighting += flWeight * src; + } + + FORCEINLINE float Intensity( void ) const + { + return m_vecLighting.x + m_vecLighting.y + m_vecLighting.z; + } + + FORCEINLINE void AddLight( float flAmount, Vector const &vecColor, float flSunAmount = 0.0 ) + { + VectorMA( m_vecLighting, flAmount, vecColor, m_vecLighting ); + m_flDirectSunAmount += flSunAmount; + Assert( this->IsValid() ); + } + + + FORCEINLINE void AddLight( LightingValue_t const &src ) + { + m_vecLighting += src.m_vecLighting; + m_flDirectSunAmount += src.m_flDirectSunAmount; + Assert( this->IsValid() ); + } + + FORCEINLINE void Init( float x, float y, float z ) + { + m_vecLighting.Init( x, y, z ); + m_flDirectSunAmount = 0.0; + } + + +}; + + +#define MAX_PATCHES (4*65536) + +struct CPatch +{ + winding_t *winding; + Vector mins, maxs, face_mins, face_maxs; + + Vector origin; // adjusted off face by face normal + + dplane_t *plane; // plane (corrected for facing) + + unsigned short m_IterationKey; // Used to prevent touching the same patch multiple times in the same query. + // See IncrementPatchIterationKey(). + + // these are packed into one dword + unsigned int normalMajorAxis : 2; // the major axis of base face normal + unsigned int sky : 1; + unsigned int needsBumpmap : 1; + unsigned int pad : 28; + + Vector normal; // adjusted for phong shading + + float planeDist; // Fixes up patch planes for brush models with an origin brush + + float chop; // smallest acceptable width of patch face + float luxscale; // average luxels per world coord + float scale[2]; // Scaling of texture in s & t + + bumplights_t totallight; // accumulated by radiosity + // does NOT include light + // accounted for by direct lighting + Vector baselight; // emissivity only + float basearea; // surface per area per baselight instance + + Vector directlight; // direct light value + float area; + + Vector reflectivity; // Average RGB of texture, modified by material type. + + Vector samplelight; + float samplearea; // for averaging direct light + int faceNumber; + int clusterNumber; + + int parent; // patch index of parent + int child1; // patch index for children + int child2; + + int ndxNext; // next patch index in face + int ndxNextParent; // next parent patch index in face + int ndxNextClusterChild; // next terminal child index in cluster +// struct patch_s *next; // next in face +// struct patch_s *nextparent; // next in face +// struct patch_s *nextclusterchild; // next terminal child in cluster + + int numtransfers; + transfer_t *transfers; + + short indices[3]; // displacement use these for subdivision +}; + + +extern CUtlVector<CPatch> g_Patches; +extern CUtlVector<int> g_FacePatches; // constains all patches, children first +extern CUtlVector<int> faceParents; // contains only root patches, use next parent to iterate +extern CUtlVector<int> clusterChildren; + + +struct sky_camera_t +{ + Vector origin; + float world_to_sky; + float sky_to_world; + int area; +}; + +extern int num_sky_cameras; +extern sky_camera_t sky_cameras[MAX_MAP_AREAS]; +extern int area_sky_cameras[MAX_MAP_AREAS]; +void ProcessSkyCameras(); + +extern entity_t *face_entity[MAX_MAP_FACES]; +extern Vector face_offset[MAX_MAP_FACES]; // for rotating bmodels +extern Vector face_centroids[MAX_MAP_EDGES]; +extern int leafparents[MAX_MAP_LEAFS]; +extern int nodeparents[MAX_MAP_NODES]; +extern float lightscale; +extern float dlight_threshold; +extern float coring; +extern qboolean g_bDumpPatches; +extern bool bRed2Black; +extern bool g_bNoSkyRecurse; +extern bool bDumpNormals; +extern bool g_bFastAmbient; +extern float maxchop; +extern FileHandle_t pFileSamples[4][4]; +extern qboolean g_bLowPriority; +extern qboolean do_fast; +extern bool g_bInterrupt; // Was used with background lighting in WC. Tells VRAD to stop lighting. +extern IIncremental *g_pIncremental; // null if not doing incremental lighting +extern bool g_bDumpPropLightmaps; + +extern float g_flSkySampleScale; // extra sampling factor for indirect light + +extern bool g_bLargeDispSampleRadius; +extern bool g_bStaticPropPolys; +extern bool g_bTextureShadows; +extern bool g_bShowStaticPropNormals; +extern bool g_bDisablePropSelfShadowing; + +extern CUtlVector<char const *> g_NonShadowCastingMaterialStrings; +extern void ForceTextureShadowsOnModel( const char *pModelName ); +extern bool IsModelTextureShadowsForced( const char *pModelName ); + +// Raytracing + +#define TRACE_ID_SKY 0x01000000 // sky face ray blocker +#define TRACE_ID_OPAQUE 0x02000000 // everyday light blocking face +#define TRACE_ID_STATICPROP 0x04000000 // static prop - lower bits are prop ID +extern RayTracingEnvironment g_RtEnv; + +#include "mpivrad.h" + +void MakeShadowSplits (void); + +//============================================== + +void BuildVisMatrix (void); +void BuildClusterTable( void ); +void AddDispsToClusterTable( void ); +void FreeVisMatrix (void); +// qboolean CheckVisBit (unsigned int p1, unsigned int p2); +void TouchVMFFile (void); + +//============================================== + +extern qboolean do_extra; +extern qboolean do_fast; +extern qboolean do_centersamples; +extern int extrapasses; +extern Vector ambient; +extern float maxlight; +extern unsigned numbounce; +extern qboolean g_bLogHashData; +extern bool debug_extra; +extern directlight_t *activelights; +extern directlight_t *freelights; + +// because of hdr having two face lumps (light styles can cause them to be different, among other +// things), we need to always access (r/w) face data though this pointer +extern dface_t *g_pFaces; + + +extern bool g_bMPIProps; + +extern byte nodehit[MAX_MAP_NODES]; +extern float gamma; +extern float indirect_sun; +extern float smoothing_threshold; +extern int dlight_map; + +extern float g_flMaxDispSampleSize; +extern float g_SunAngularExtent; + +extern char source[MAX_PATH]; + +// Used by incremental lighting to trivial-reject faces. +// There is a bit in here for each face telling whether or not any of the +// active lights can see the face. +extern CUtlVector<byte> g_FacesVisibleToLights; + +void MakeTnodes (dmodel_t *bm); +void PairEdges (void); + +void SaveVertexNormals( void ); + +qboolean IsIncremental(char *filename); +int SaveIncremental(char *filename); +int PartialHead (void); +void BuildFacelights (int facenum, int threadnum); +void PrecompLightmapOffsets(); +void FinalLightFace (int threadnum, int facenum); +void PvsForOrigin (Vector& org, byte *pvs); +void ConvertRGBExp32ToRGBA8888( const ColorRGBExp32 *pSrc, unsigned char *pDst, Vector* _optOutLinear = NULL ); +void ConvertRGBExp32ToLinear(const ColorRGBExp32 *pSrc, Vector* pDst); +void ConvertLinearToRGBA8888( const Vector *pSrc, unsigned char *pDst ); + + +inline byte PVSCheck( const byte *pvs, int iCluster ) +{ + if ( iCluster >= 0 ) + { + return pvs[iCluster >> 3] & ( 1 << ( iCluster & 7 ) ); + } + else + { + // PointInLeaf still returns -1 for valid points sometimes and rather than + // have black samples, we assume the sample is in the PVS. + return 1; + } +} + +// outputs 1 in fractionVisible if no occlusion, 0 if full occlusion, and in-between values +void TestLine( FourVectors const& start, FourVectors const& stop, fltx4 *pFractionVisible, int static_prop_index_to_ignore=-1); + +// returns 1 if the ray sees the sky, 0 if it doesn't, and in-between values for partial coverage +void TestLine_DoesHitSky( FourVectors const& start, FourVectors const& stop, + fltx4 *pFractionVisible, bool canRecurse = true, int static_prop_to_skip=-1, bool bDoDebug = false ); + +// converts any marked brush entities to triangles for shadow casting +void ExtractBrushEntityShadowCasters ( void ); +void AddBrushesForRayTrace ( void ); + +void BaseLightForFace( dface_t *f, Vector& light, float *parea, Vector& reflectivity ); +void CreateDirectLights (void); +void GetPhongNormal( int facenum, Vector const& spot, Vector& phongnormal ); +int LightForString( char *pLight, Vector& intensity ); +void MakeTransfer( int ndxPatch1, int ndxPatch2, transfer_t *all_transfers ); +void MakeScales( int ndxPatch, transfer_t *all_transfers ); + +// Run startup code like initialize mathlib. +void VRAD_Init(); + +// Load the BSP file and prepare to do the lighting. +// This is called after any command-line parameters have been set. +void VRAD_LoadBSP( char const *pFilename ); + +int VRAD_Main(int argc, char **argv); + +// This performs an actual lighting pass. +// Returns true if the process was interrupted (with g_bInterrupt). +bool RadWorld_Go(); + +dleaf_t *PointInLeaf (Vector const& point); +int ClusterFromPoint( Vector const& point ); +winding_t *WindingFromFace (dface_t *f, Vector& origin ); + +void WriteWinding (FileHandle_t out, winding_t *w, Vector& color ); +void WriteNormal( FileHandle_t out, Vector const &nPos, Vector const &nDir, + float length, Vector const &color ); +void WriteLine( FileHandle_t out, const Vector &vecPos1, const Vector &vecPos2, const Vector &color ); +void WriteTrace( const char *pFileName, const FourRays &rays, const RayTracingResult& result ); + +#ifdef STATIC_FOG +qboolean IsFog( dface_t * f ); +#endif + +#define CONTENTS_EMPTY 0 +#define TEX_SPECIAL (SURF_SKY|SURF_NOLIGHT) + +//============================================================================= + +// trace.cpp + +bool AddDispCollTreesToWorld( void ); +int PointLeafnum( Vector const &point ); +float TraceLeafBrushes( int leafIndex, const Vector &start, const Vector &end, CBaseTrace &traceOut ); + +//============================================================================= + +// dispinfo.cpp + +struct SSE_sampleLightOutput_t +{ + fltx4 m_flDot[NUM_BUMP_VECTS+1]; + fltx4 m_flFalloff; + fltx4 m_flSunAmount; +}; + +#define GATHERLFLAGS_FORCE_FAST 1 +#define GATHERLFLAGS_IGNORE_NORMALS 2 + +// SSE Gather light stuff +void GatherSampleLightSSE( SSE_sampleLightOutput_t &out, directlight_t *dl, int facenum, + FourVectors const& pos, FourVectors *pNormals, int normalCount, int iThread, + int nLFlags = 0, // GATHERLFLAGS_xxx + int static_prop_to_skip=-1, + float flEpsilon = 0.0 ); +//void GatherSampleSkyLightSSE( SSE_sampleLightOutput_t &out, directlight_t *dl, int facenum, +// FourVectors const& pos, FourVectors *pNormals, int normalCount, int iThread, +// int nLFlags = 0, +// int static_prop_to_skip=-1, +// float flEpsilon = 0.0 ); +//void GatherSampleAmbientSkySSE( SSE_sampleLightOutput_t &out, directlight_t *dl, int facenum, +// FourVectors const& pos, FourVectors *pNormals, int normalCount, int iThread, +// int nLFlags = 0, // GATHERLFLAGS_xxx +// int static_prop_to_skip=-1, +// float flEpsilon = 0.0 ); +//void GatherSampleStandardLightSSE( SSE_sampleLightOutput_t &out, directlight_t *dl, int facenum, +// FourVectors const& pos, FourVectors *pNormals, int normalCount, int iThread, +// int nLFlags = 0, // GATHERLFLAGS_xxx +// int static_prop_to_skip=-1, +// float flEpsilon = 0.0 ); + +//----------------------------------------------------------------------------- +// VRad Displacements +//----------------------------------------------------------------------------- + +struct facelight_t; +typedef struct radial_s radial_t; +struct lightinfo_t; + +// NOTE: should probably come up with a bsptreetested_t struct or something, +// see below (PropTested_t) +struct DispTested_t +{ + int m_Enum; + int *m_pTested; +}; + +class IVRadDispMgr +{ +public: + // creation/destruction + virtual void Init( void ) = 0; + virtual void Shutdown( void ) = 0; + + // "CalcPoints" + virtual bool BuildDispSamples( lightinfo_t *pLightInfo, facelight_t *pFaceLight, int ndxFace ) = 0; + virtual bool BuildDispLuxels( lightinfo_t *pLightInfo, facelight_t *pFaceLight, int ndxFace ) = 0; + virtual bool BuildDispSamplesAndLuxels_DoFast( lightinfo_t *pLightInfo, facelight_t *pFaceLight, int ndxFace ) = 0; + + // patching functions + virtual void MakePatches( void ) = 0; + virtual void SubdividePatch( int iPatch ) = 0; + + // pre "FinalLightFace" + virtual void InsertSamplesDataIntoHashTable( void ) = 0; + virtual void InsertPatchSampleDataIntoHashTable( void ) = 0; + + // "FinalLightFace" + virtual radial_t *BuildLuxelRadial( int ndxFace, int ndxStyle, bool bBump ) = 0; + virtual bool SampleRadial( int ndxFace, radial_t *pRadial, Vector const &vPos, int ndxLxl, LightingValue_t *pLightSample, int sampleCount, bool bPatch ) = 0; + virtual radial_t *BuildPatchRadial( int ndxFace, bool bBump ) = 0; + + // utility + virtual void GetDispSurfNormal( int ndxFace, Vector &pt, Vector &ptNormal, bool bInside ) = 0; + virtual void GetDispSurf( int ndxFace, CVRADDispColl **ppDispTree ) = 0; + + // bsp tree functions + virtual bool ClipRayToDisp( DispTested_t &dispTested, Ray_t const &ray ) = 0; + virtual bool ClipRayToDispInLeaf( DispTested_t &dispTested, Ray_t const &ray, int ndxLeaf ) = 0; + virtual void ClipRayToDispInLeaf( DispTested_t &dispTested, Ray_t const &ray, + int ndxLeaf, float& dist, dface_t*& pFace, Vector2D& luxelCoord ) = 0; + virtual void ClipRayToDispInLeaf( DispTested_t &dispTested, Ray_t const &ray, + int ndxLeaf, float& dist, Vector *pNormal ) = 0; + virtual void StartRayTest( DispTested_t &dispTested ) = 0; + virtual void AddPolysForRayTrace() = 0; + + // general timing -- should be moved!! + virtual void StartTimer( const char *name ) = 0; + virtual void EndTimer( void ) = 0; +}; + +IVRadDispMgr *StaticDispMgr( void ); + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline bool ValidDispFace( dface_t *pFace ) +{ + if( !pFace ) { return false; } + if( pFace->dispinfo == -1 ) { return false; } + if( pFace->numedges != 4 ) { return false; } + + return true; +} + +#define SAMPLEHASH_VOXEL_SIZE 64.0f +typedef unsigned int SampleHandle_t; // the upper 16 bits = facelight index (works because max face are 65536) + // the lower 16 bits = sample index inside of facelight +struct sample_t; +struct SampleData_t +{ + unsigned short x, y, z; + CUtlVector<SampleHandle_t> m_Samples; +}; + +struct PatchSampleData_t +{ + unsigned short x, y, z; + CUtlVector<int> m_ndxPatches; +}; + +UtlHashHandle_t SampleData_AddSample( sample_t *pSample, SampleHandle_t sampleHandle ); +void PatchSampleData_AddSample( CPatch *pPatch, int ndxPatch ); +unsigned short IncrementPatchIterationKey(); +void SampleData_Log( void ); + +extern CUtlHash<SampleData_t> g_SampleHashTable; +extern CUtlHash<PatchSampleData_t> g_PatchSampleHashTable; + +extern int samplesAdded; +extern int patchSamplesAdded; + +//----------------------------------------------------------------------------- +// Computes lighting for the detail props +//----------------------------------------------------------------------------- + +void ComputeDetailPropLighting( int iThread ); +void ComputeIndirectLightingAtPoint( Vector &position, Vector &normal, Vector &outColor, + int iThread, bool force_fast = false, bool bIgnoreNormals = false ); + +//----------------------------------------------------------------------------- +// VRad static props +//----------------------------------------------------------------------------- +class IPhysicsCollision; +struct PropTested_t +{ + int m_Enum; + int* m_pTested; + IPhysicsCollision *pThreadedCollision; +}; + +class IVradStaticPropMgr +{ +public: + // methods of IStaticPropMgr + virtual void Init() = 0; + virtual void Shutdown() = 0; + virtual void ComputeLighting( int iThread ) = 0; + virtual void AddPolysForRayTrace() = 0; +}; + +//extern PropTested_t s_PropTested[MAX_TOOL_THREADS+1]; +extern DispTested_t s_DispTested[MAX_TOOL_THREADS+1]; + +IVradStaticPropMgr* StaticPropMgr(); + +extern float ComputeCoverageFromTexture( float b0, float b1, float b2, int32 hitID ); + +#endif // VRAD_H diff --git a/utils/vrad/vrad_dispcoll.cpp b/utils/vrad/vrad_dispcoll.cpp new file mode 100644 index 0000000..2edd1ca --- /dev/null +++ b/utils/vrad/vrad_dispcoll.cpp @@ -0,0 +1,1080 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "vrad.h" +#include "VRAD_DispColl.h" +#include "DispColl_Common.h" +#include "radial.h" +#include "CollisionUtils.h" +#include "tier0\dbg.h" + +#define SAMPLE_BBOX_SLOP 5.0f +#define TRIEDGE_EPSILON 0.001f + +float g_flMaxDispSampleSize = 512.0f; + +static FileHandle_t pDispFile = FILESYSTEM_INVALID_HANDLE; + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +CVRADDispColl::CVRADDispColl() +{ + m_iParent = -1; + + m_flSampleRadius2 = 0.0f; + m_flPatchSampleRadius2 = 0.0f; + + m_flSampleWidth = 0.0f; + m_flSampleHeight = 0.0f; + + m_aLuxelCoords.Purge(); + m_aVertNormals.Purge(); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +CVRADDispColl::~CVRADDispColl() +{ + m_aLuxelCoords.Purge(); + m_aVertNormals.Purge(); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CVRADDispColl::Create( CCoreDispInfo *pDisp ) +{ + // Base class create. + if( !CDispCollTree::Create( pDisp ) ) + return false; + + // Allocate VRad specific memory. + m_aLuxelCoords.SetSize( GetSize() ); + m_aVertNormals.SetSize( GetSize() ); + + // VRad specific base surface data. + CCoreDispSurface *pSurf = pDisp->GetSurface(); + m_iParent = pSurf->GetHandle(); + + // VRad specific displacement surface data. + for ( int iVert = 0; iVert < m_aVerts.Count(); ++iVert ) + { + pDisp->GetNormal( iVert, m_aVertNormals[iVert] ); + pDisp->GetLuxelCoord( 0, iVert, m_aLuxelCoords[iVert] ); + } + + // Re-calculate the lightmap size (in uv) so that the luxels give + // a better world-space uniform approx. due to the non-linear nature + // of the displacement surface in uv-space + dface_t *pFace = &g_pFaces[m_iParent]; + if( pFace ) + { + CalcSampleRadius2AndBox( pFace ); + } + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CVRADDispColl::CalcSampleRadius2AndBox( dface_t *pFace ) +{ + // Get the luxel sample size. + texinfo_t *pTexInfo = &texinfo[pFace->texinfo]; + Assert ( pTexInfo ); + if ( !pTexInfo ) + return; + + // Todo: Width = Height now, should change all the code to look at one value. + Vector vecTmp( pTexInfo->lightmapVecsLuxelsPerWorldUnits[0][0], + pTexInfo->lightmapVecsLuxelsPerWorldUnits[0][1], + pTexInfo->lightmapVecsLuxelsPerWorldUnits[0][2] ); + float flWidth = 1.0f / VectorLength( vecTmp ); + float flHeight = flWidth; + + // Save off the sample width and height. + m_flSampleWidth = flWidth; + m_flSampleHeight = flHeight; + + // Calculate the sample radius squared. + float flSampleRadius = sqrt( ( ( flWidth * flWidth ) + ( flHeight * flHeight ) ) ) * 2.2f;//RADIALDIST2; + if ( flSampleRadius > g_flMaxDispSampleSize ) + { + flSampleRadius = g_flMaxDispSampleSize; + } + m_flSampleRadius2 = flSampleRadius * flSampleRadius; + + // Calculate the patch radius - the max sample edge length * the number of luxels per edge "chop." + float flSampleSize = max( m_flSampleWidth, m_flSampleHeight ); + float flPatchSampleRadius = flSampleSize * dispchop * 2.2f; + if ( flPatchSampleRadius > g_MaxDispPatchRadius ) + { + flPatchSampleRadius = g_MaxDispPatchRadius; + Warning( "Patch Sample Radius Clamped!\n" ); + } + m_flPatchSampleRadius2 = flPatchSampleRadius * flPatchSampleRadius; +} + +//----------------------------------------------------------------------------- +// Purpose: Get the min/max of the displacement surface. +//----------------------------------------------------------------------------- +void CVRADDispColl::GetSurfaceMinMax( Vector &boxMin, Vector &boxMax ) +{ + // Initialize the minimum and maximum box + boxMin = m_aVerts[0]; + boxMax = m_aVerts[0]; + + for( int i = 1; i < m_aVerts.Count(); i++ ) + { + if( m_aVerts[i].x < boxMin.x ) { boxMin.x = m_aVerts[i].x; } + if( m_aVerts[i].y < boxMin.y ) { boxMin.y = m_aVerts[i].y; } + if( m_aVerts[i].z < boxMin.z ) { boxMin.z = m_aVerts[i].z; } + + if( m_aVerts[i].x > boxMax.x ) { boxMax.x = m_aVerts[i].x; } + if( m_aVerts[i].y > boxMax.y ) { boxMax.y = m_aVerts[i].y; } + if( m_aVerts[i].z > boxMax.z ) { boxMax.z = m_aVerts[i].z; } + } +} + +//----------------------------------------------------------------------------- +// Purpose: Find the minor projection axes based on the given normal. +//----------------------------------------------------------------------------- +void CVRADDispColl::GetMinorAxes( Vector const &vecNormal, int &nAxis0, int &nAxis1 ) +{ + nAxis0 = 0; + nAxis1 = 1; + + if( FloatMakePositive( vecNormal.x ) > FloatMakePositive( vecNormal.y ) ) + { + if( FloatMakePositive( vecNormal.x ) > FloatMakePositive( vecNormal.z ) ) + { + nAxis0 = 1; + nAxis1 = 2; + } + } + else + { + if( FloatMakePositive( vecNormal.y ) > FloatMakePositive( vecNormal.z ) ) + { + nAxis0 = 0; + nAxis1 = 2; + } + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVRADDispColl::BaseFacePlaneToDispUV( Vector const &vecPlanePt, Vector2D &dispUV ) +{ + PointInQuadToBarycentric( m_vecSurfPoints[0], m_vecSurfPoints[3], m_vecSurfPoints[2], m_vecSurfPoints[1], vecPlanePt, dispUV ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVRADDispColl::DispUVToSurfPoint( Vector2D const &dispUV, Vector &vecPoint, float flPushEps ) +{ + // Check to see that the point is on the surface. + if ( dispUV.x < 0.0f || dispUV.x > 1.0f || dispUV.y < 0.0f || dispUV.y > 1.0f ) + return; + + // Get the displacement power. + int nWidth = ( ( 1 << m_nPower ) + 1 ); + int nHeight = nWidth; + + // Scale the U, V coordinates to the displacement grid size. + float flU = dispUV.x * static_cast<float>( nWidth - 1.000001f ); + float flV = dispUV.y * static_cast<float>( nHeight - 1.000001f ); + + // Find the base U, V. + int nSnapU = static_cast<int>( flU ); + int nSnapV = static_cast<int>( flV ); + + // Use this to get the triangle orientation. + bool bOdd = ( ( ( nSnapV * nWidth ) + nSnapU ) % 2 == 1 ); + + // Top Left to Bottom Right + if( bOdd ) + { + DispUVToSurf_TriTLToBR( vecPoint, flPushEps, flU, flV, nSnapU, nSnapV, nWidth, nHeight ); + } + // Bottom Left to Top Right + else + { + DispUVToSurf_TriBLToTR( vecPoint, flPushEps, flU, flV, nSnapU, nSnapV, nWidth, nHeight ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CVRADDispColl::DispUVToSurf_TriTLToBR( Vector &vecPoint, float flPushEps, + float flU, float flV, int nSnapU, int nSnapV, + int nWidth, int nHeight ) +{ + int nNextU = nSnapU + 1; + int nNextV = nSnapV + 1; + if ( nNextU == nWidth) { --nNextU; } + if ( nNextV == nHeight ) { --nNextV; } + + float flFracU = flU - static_cast<float>( nSnapU ); + float flFracV = flV - static_cast<float>( nSnapV ); + + if( ( flFracU + flFracV ) >= ( 1.0f + TRIEDGE_EPSILON ) ) + { + int nIndices[3]; + nIndices[0] = nNextV * nWidth + nSnapU; + nIndices[1] = nNextV * nWidth + nNextU; + nIndices[2] = nSnapV * nWidth + nNextU; + + Vector edgeU = m_aVerts[nIndices[0]] - m_aVerts[nIndices[1]]; + Vector edgeV = m_aVerts[nIndices[2]] - m_aVerts[nIndices[1]]; + vecPoint = m_aVerts[nIndices[1]] + edgeU * ( 1.0f - flFracU ) + edgeV * ( 1.0f - flFracV ); + + if ( flPushEps != 0.0f ) + { + Vector vecNormal; + vecNormal = CrossProduct( edgeU, edgeV ); + VectorNormalize( vecNormal ); + vecPoint += ( vecNormal * flPushEps ); + } + } + else + { + int nIndices[3]; + nIndices[0] = nSnapV * nWidth + nSnapU; + nIndices[1] = nNextV * nWidth + nSnapU; + nIndices[2] = nSnapV * nWidth + nNextU; + + Vector edgeU = m_aVerts[nIndices[2]] - m_aVerts[nIndices[0]]; + Vector edgeV = m_aVerts[nIndices[1]] - m_aVerts[nIndices[0]]; + vecPoint = m_aVerts[nIndices[0]] + edgeU * flFracU + edgeV * flFracV; + + if ( flPushEps != 0.0f ) + { + Vector vecNormal; + vecNormal = CrossProduct( edgeU, edgeV ); + VectorNormalize( vecNormal ); + vecPoint += ( vecNormal * flPushEps ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CVRADDispColl::DispUVToSurf_TriBLToTR( Vector &vecPoint, float flPushEps, + float flU, float flV, int nSnapU, int nSnapV, + int nWidth, int nHeight ) +{ + int nNextU = nSnapU + 1; + int nNextV = nSnapV + 1; + if ( nNextU == nWidth) { --nNextU; } + if ( nNextV == nHeight ) { --nNextV; } + + float flFracU = flU - static_cast<float>( nSnapU ); + float flFracV = flV - static_cast<float>( nSnapV ); + + if( flFracU < flFracV ) + { + int nIndices[3]; + nIndices[0] = nSnapV * nWidth + nSnapU; + nIndices[1] = nNextV * nWidth + nSnapU; + nIndices[2] = nNextV * nWidth + nNextU; + + Vector edgeU = m_aVerts[nIndices[2]] - m_aVerts[nIndices[1]]; + Vector edgeV = m_aVerts[nIndices[0]] - m_aVerts[nIndices[1]]; + vecPoint = m_aVerts[nIndices[1]] + edgeU * flFracU + edgeV * ( 1.0f - flFracV ); + + if ( flPushEps != 0.0f ) + { + Vector vecNormal; + vecNormal = CrossProduct( edgeV, edgeU ); + VectorNormalize( vecNormal ); + vecPoint += ( vecNormal * flPushEps ); + } + } + else + { + int nIndices[3]; + nIndices[0] = nSnapV * nWidth + nSnapU; + nIndices[1] = nNextV * nWidth + nNextU; + nIndices[2] = nSnapV * nWidth + nNextU; + + Vector edgeU = m_aVerts[nIndices[0]] - m_aVerts[nIndices[2]]; + Vector edgeV = m_aVerts[nIndices[1]] - m_aVerts[nIndices[2]]; + vecPoint = m_aVerts[nIndices[2]] + edgeU * ( 1.0f - flFracU ) + edgeV * flFracV; + + if ( flPushEps != 0.0f ) + { + Vector vecNormal; + vecNormal = CrossProduct( edgeV, edgeU ); + VectorNormalize( vecNormal ); + vecPoint += ( vecNormal * flPushEps ); + } + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVRADDispColl::DispUVToSurfNormal( Vector2D const &dispUV, Vector &vecNormal ) +{ + // Check to see that the point is on the surface. + if ( dispUV.x < 0.0f || dispUV.x > 1.0f || dispUV.y < 0.0f || dispUV.y > 1.0f ) + return; + + // Get the displacement power. + int nWidth = ( ( 1 << m_nPower ) + 1 ); + int nHeight = nWidth; + + // Scale the U, V coordinates to the displacement grid size. + float flU = dispUV.x * static_cast<float>( nWidth - 1.000001f ); + float flV = dispUV.y * static_cast<float>( nHeight - 1.000001f ); + + // Find the base U, V. + int nSnapU = static_cast<int>( flU ); + int nSnapV = static_cast<int>( flV ); + + int nNextU = nSnapU + 1; + int nNextV = nSnapV + 1; + if ( nNextU == nWidth) { --nNextU; } + if ( nNextV == nHeight ) { --nNextV; } + + float flFracU = flU - static_cast<float>( nSnapU ); + float flFracV = flV - static_cast<float>( nSnapV ); + + // Get the four normals "around" the "spot" + int iQuad[VRAD_QUAD_SIZE]; + iQuad[0] = ( nSnapV * nWidth ) + nSnapU; + iQuad[1] = ( nNextV * nWidth ) + nSnapU; + iQuad[2] = ( nNextV * nWidth ) + nNextU; + iQuad[3] = ( nSnapV * nWidth ) + nNextU; + + // Find the blended normal (bi-linear). + Vector vecTmpNormals[2], vecBlendedNormals[2], vecDispNormals[4]; + + for ( int iVert = 0; iVert < VRAD_QUAD_SIZE; ++iVert ) + { + GetVertNormal( iQuad[iVert], vecDispNormals[iVert] ); + } + + vecTmpNormals[0] = vecDispNormals[0] * ( 1.0f - flFracU ); + vecTmpNormals[1] = vecDispNormals[3] * flFracU; + vecBlendedNormals[0] = vecTmpNormals[0] + vecTmpNormals[1]; + VectorNormalize( vecBlendedNormals[0] ); + + vecTmpNormals[0] = vecDispNormals[1] * ( 1.0f - flFracU ); + vecTmpNormals[1] = vecDispNormals[2] * flFracU; + vecBlendedNormals[1] = vecTmpNormals[0] + vecTmpNormals[1]; + VectorNormalize( vecBlendedNormals[1] ); + + vecTmpNormals[0] = vecBlendedNormals[0] * ( 1.0f - flFracV ); + vecTmpNormals[1] = vecBlendedNormals[1] * flFracV; + + vecNormal = vecTmpNormals[0] + vecTmpNormals[1]; + VectorNormalize( vecNormal ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : float +//----------------------------------------------------------------------------- +float CVRADDispColl::CreateParentPatches( void ) +{ + // Save the total surface area of the displacement. + float flTotalArea = 0.0f; + + // Get the number of displacement subdivisions. + int nInterval = GetWidth(); + + Vector vecPoints[4]; + vecPoints[0].Init( m_aVerts[0].x, m_aVerts[0].y, m_aVerts[0].z ); + vecPoints[1].Init( m_aVerts[(nInterval*(nInterval-1))].x, m_aVerts[(nInterval*(nInterval-1))].y, m_aVerts[(nInterval*(nInterval-1))].z ); + vecPoints[2].Init( m_aVerts[((nInterval*nInterval)-1)].x, m_aVerts[((nInterval*nInterval)-1)].y, m_aVerts[((nInterval*nInterval)-1)].z ); + vecPoints[3].Init( m_aVerts[(nInterval-1)].x, m_aVerts[(nInterval-1)].y, m_aVerts[(nInterval-1)].z ); + + // Create and initialize the patch. + int iPatch = g_Patches.AddToTail(); + if ( iPatch == g_Patches.InvalidIndex() ) + return flTotalArea; + + // Keep track of the area of the patches. + float flArea = 0.0f; + if ( !InitParentPatch( iPatch, vecPoints, flArea ) ) + { + g_Patches.Remove( iPatch ); + flArea = 0.0f; + } + + // Return the displacement area. + return flArea; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : iParentPatch - +// nLevel - +//----------------------------------------------------------------------------- +void CVRADDispColl::CreateChildPatchesFromRoot( int iParentPatch, int *pChildPatch ) +{ + // Initialize the child patch indices. + pChildPatch[0] = g_Patches.InvalidIndex(); + pChildPatch[1] = g_Patches.InvalidIndex(); + + // Get the number of displacement subdivisions. + int nInterval = GetWidth(); + + // Get the parent patch. + CPatch *pParentPatch = &g_Patches[iParentPatch]; + if ( !pParentPatch ) + return; + + // Split along the longest edge. + Vector vecEdges[4]; + vecEdges[0] = pParentPatch->winding->p[1] - pParentPatch->winding->p[0]; + vecEdges[1] = pParentPatch->winding->p[2] - pParentPatch->winding->p[1]; + vecEdges[2] = pParentPatch->winding->p[3] - pParentPatch->winding->p[2]; + vecEdges[3] = pParentPatch->winding->p[3] - pParentPatch->winding->p[0]; + + // Should the patch be subdivided - check the area. + float flMaxLength = max( m_flSampleWidth, m_flSampleHeight ); + float flMinEdgeLength = flMaxLength * dispchop; + + // Find the longest edge. + float flEdgeLength = 0.0f; + int iLongEdge = -1; + for ( int iEdge = 0; iEdge < 4; ++iEdge ) + { + float flLength = vecEdges[iEdge].Length(); + if ( flEdgeLength < flLength ) + { + flEdgeLength = vecEdges[iEdge].Length(); + iLongEdge = iEdge; + } + } + + // Small enough already, return. + if ( flEdgeLength < flMinEdgeLength ) + return; + + // Test area as well so we don't allow slivers. + float flMinArea = ( dispchop * flMaxLength ) * ( dispchop * flMaxLength ); + Vector vecNormal = vecEdges[3].Cross( vecEdges[0] ); + float flTestArea = VectorNormalize( vecNormal ); + if ( flTestArea < flMinArea ) + return; + + // Get the points for the first triangle. + int iPoints[3]; + Vector vecPoints[3]; + float flArea; + + iPoints[0] = ( nInterval * nInterval ) - 1; + iPoints[1] = 0; + iPoints[2] = nInterval * ( nInterval - 1 ); + for ( int iPoint = 0; iPoint < 3; ++iPoint ) + { + VectorCopy( m_aVerts[iPoints[iPoint]], vecPoints[iPoint] ); + } + + // Create and initialize the patch. + pChildPatch[0] = g_Patches.AddToTail(); + if ( pChildPatch[0] == g_Patches.InvalidIndex() ) + return; + + if ( !InitPatch( pChildPatch[0], iParentPatch, 0, vecPoints, iPoints, flArea ) ) + { + g_Patches.Remove( pChildPatch[0] ); + pChildPatch[0] = g_Patches.InvalidIndex(); + return; + } + + // Get the points for the second triangle. + iPoints[0] = 0; + iPoints[1] = ( nInterval * nInterval ) - 1; + iPoints[2] = nInterval - 1; + for ( int iPoint = 0; iPoint < 3; ++iPoint ) + { + VectorCopy( m_aVerts[iPoints[iPoint]], vecPoints[iPoint] ); + } + + // Create and initialize the patch. + pChildPatch[1] = g_Patches.AddToTail(); + if ( pChildPatch[1] == g_Patches.InvalidIndex() ) + { + g_Patches.Remove( pChildPatch[0] ); + pChildPatch[0] = g_Patches.InvalidIndex(); + return; + } + + if ( !InitPatch( pChildPatch[1], iParentPatch, 1, vecPoints, iPoints, flArea ) ) + { + g_Patches.Remove( pChildPatch[0] ); + pChildPatch[0] = g_Patches.InvalidIndex(); + g_Patches.Remove( pChildPatch[1] ); + pChildPatch[1] = g_Patches.InvalidIndex(); + return; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : flMinArea - +// Output : float +//----------------------------------------------------------------------------- +void CVRADDispColl::CreateChildPatches( int iParentPatch, int nLevel ) +{ + // Get the parent patch. + CPatch *pParentPatch = &g_Patches[iParentPatch]; + if ( !pParentPatch ) + return; + + // The root face is a quad - special case. + if ( pParentPatch->winding->numpoints == 4 ) + { + int iChildPatch[2]; + CreateChildPatchesFromRoot( iParentPatch, iChildPatch ); + if ( iChildPatch[0] != g_Patches.InvalidIndex() && iChildPatch[1] != g_Patches.InvalidIndex() ) + { + CreateChildPatches( iChildPatch[0], 0 ); + CreateChildPatches( iChildPatch[1], 0 ); + } + return; + } + + // Calculate the the area of the patch (triangle!). + Assert( pParentPatch->winding->numpoints == 3 ); + if ( pParentPatch->winding->numpoints != 3 ) + return; + + // Should the patch be subdivided - check the area. + float flMaxLength = max( m_flSampleWidth, m_flSampleHeight ); + float flMinEdgeLength = flMaxLength * dispchop; + + // Split along the longest edge. + Vector vecEdges[3]; + vecEdges[0] = pParentPatch->winding->p[1] - pParentPatch->winding->p[0]; + vecEdges[1] = pParentPatch->winding->p[2] - pParentPatch->winding->p[0]; + vecEdges[2] = pParentPatch->winding->p[2] - pParentPatch->winding->p[1]; + + // Find the longest edge. + float flEdgeLength = 0.0f; + int iLongEdge = -1; + for ( int iEdge = 0; iEdge < 3; ++iEdge ) + { + if ( flEdgeLength < vecEdges[iEdge].Length() ) + { + flEdgeLength = vecEdges[iEdge].Length(); + iLongEdge = iEdge; + } + } + + // Small enough already, return. + if ( flEdgeLength < flMinEdgeLength ) + return; + + // Test area as well so we don't allow slivers. + float flMinArea = ( dispchop * flMaxLength ) * ( dispchop * flMaxLength ) * 0.5f; + Vector vecNormal = vecEdges[1].Cross( vecEdges[0] ); + float flTestArea = VectorNormalize( vecNormal ); + flTestArea *= 0.5f; + if ( flTestArea < flMinArea ) + return; + + // Check to see if any more displacement verts exist - go to subdivision if not. + if ( nLevel >= ( m_nPower * 2 ) ) + { + CreateChildPatchesSub( iParentPatch ); + return; + } + + int nChildIndices[2][3]; + int nNewIndex = ( pParentPatch->indices[1] + pParentPatch->indices[0] ) / 2; + nChildIndices[0][0] = pParentPatch->indices[2]; + nChildIndices[0][1] = pParentPatch->indices[0]; + nChildIndices[0][2] = nNewIndex; + + nChildIndices[1][0] = pParentPatch->indices[1]; + nChildIndices[1][1] = pParentPatch->indices[2]; + nChildIndices[1][2] = nNewIndex; + + Vector vecChildPoints[2][3]; + for ( int iTri = 0; iTri < 2; ++iTri ) + { + for ( int iPoint = 0; iPoint < 3; ++iPoint ) + { + VectorCopy( m_aVerts[nChildIndices[iTri][iPoint]], vecChildPoints[iTri][iPoint] ); + } + } + + // Create and initialize the children patches. + int iChildPatch[2] = { -1, -1 }; + for ( int iChild = 0; iChild < 2; ++iChild ) + { + iChildPatch[iChild] = g_Patches.AddToTail(); + + float flArea = 0.0f; + if ( !InitPatch( iChildPatch[iChild], iParentPatch, iChild, vecChildPoints[iChild], nChildIndices[iChild], flArea ) ) + { + if ( iChild == 0 ) + { + pParentPatch->child1 = g_Patches.InvalidIndex(); + g_Patches.Remove( iChildPatch[iChild] ); + break; + } + else + { + pParentPatch->child1 = g_Patches.InvalidIndex(); + pParentPatch->child2 = g_Patches.InvalidIndex(); + g_Patches.Remove( iChildPatch[iChild] ); + g_Patches.Remove( iChildPatch[0] ); + } + } + } + + // Continue creating children patches. + int nNewLevel = ++nLevel; + CreateChildPatches( iChildPatch[0], nNewLevel ); + CreateChildPatches( iChildPatch[1], nNewLevel ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : flMinArea - +// Output : float +//----------------------------------------------------------------------------- +void CVRADDispColl::CreateChildPatchesSub( int iParentPatch ) +{ + // Get the parent patch. + CPatch *pParentPatch = &g_Patches[iParentPatch]; + if ( !pParentPatch ) + return; + + // Calculate the the area of the patch (triangle!). + Assert( pParentPatch->winding->numpoints == 3 ); + if ( pParentPatch->winding->numpoints != 3 ) + return; + + // Should the patch be subdivided - check the area. + float flMaxLength = max( m_flSampleWidth, m_flSampleHeight ); + float flMinEdgeLength = flMaxLength * dispchop; + + // Split along the longest edge. + Vector vecEdges[3]; + vecEdges[0] = pParentPatch->winding->p[1] - pParentPatch->winding->p[0]; + vecEdges[1] = pParentPatch->winding->p[2] - pParentPatch->winding->p[1]; + vecEdges[2] = pParentPatch->winding->p[0] - pParentPatch->winding->p[2]; + + // Find the longest edge. + float flEdgeLength = 0.0f; + int iLongEdge = -1; + for ( int iEdge = 0; iEdge < 3; ++iEdge ) + { + if ( flEdgeLength < vecEdges[iEdge].Length() ) + { + flEdgeLength = vecEdges[iEdge].Length(); + iLongEdge = iEdge; + } + } + + // Small enough already, return. + if ( flEdgeLength < flMinEdgeLength ) + return; + + // Test area as well so we don't allow slivers. + float flMinArea = ( dispchop * flMaxLength ) * ( dispchop * flMaxLength ) * 0.5f; + Vector vecNormal = vecEdges[1].Cross( vecEdges[0] ); + float flTestArea = VectorNormalize( vecNormal ); + flTestArea *= 0.5f; + if ( flTestArea < flMinArea ) + return; + + // Create children patchs - 2 of them. + Vector vecChildPoints[2][3]; + switch ( iLongEdge ) + { + case 0: + { + vecChildPoints[0][0] = pParentPatch->winding->p[0]; + vecChildPoints[0][1] = ( pParentPatch->winding->p[0] + pParentPatch->winding->p[1] ) * 0.5f; + vecChildPoints[0][2] = pParentPatch->winding->p[2]; + + vecChildPoints[1][0] = ( pParentPatch->winding->p[0] + pParentPatch->winding->p[1] ) * 0.5f; + vecChildPoints[1][1] = pParentPatch->winding->p[1]; + vecChildPoints[1][2] = pParentPatch->winding->p[2]; + break; + } + case 1: + { + vecChildPoints[0][0] = pParentPatch->winding->p[0]; + vecChildPoints[0][1] = pParentPatch->winding->p[1]; + vecChildPoints[0][2] = ( pParentPatch->winding->p[1] + pParentPatch->winding->p[2] ) * 0.5f; + + vecChildPoints[1][0] = ( pParentPatch->winding->p[1] + pParentPatch->winding->p[2] ) * 0.5f; + vecChildPoints[1][1] = pParentPatch->winding->p[2]; + vecChildPoints[1][2] = pParentPatch->winding->p[0]; + break; + } + case 2: + { + vecChildPoints[0][0] = pParentPatch->winding->p[0]; + vecChildPoints[0][1] = pParentPatch->winding->p[1]; + vecChildPoints[0][2] = ( pParentPatch->winding->p[0] + pParentPatch->winding->p[2] ) * 0.5f; + + vecChildPoints[1][0] = ( pParentPatch->winding->p[0] + pParentPatch->winding->p[2] ) * 0.5f; + vecChildPoints[1][1] = pParentPatch->winding->p[1]; + vecChildPoints[1][2] = pParentPatch->winding->p[2]; + break; + } + } + + + // Create and initialize the children patches. + int iChildPatch[2] = { 0, 0 }; + int nChildIndices[3] = { -1, -1, -1 }; + for ( int iChild = 0; iChild < 2; ++iChild ) + { + iChildPatch[iChild] = g_Patches.AddToTail(); + + float flArea = 0.0f; + if ( !InitPatch( iChildPatch[iChild], iParentPatch, iChild, vecChildPoints[iChild], nChildIndices, flArea ) ) + { + if ( iChild == 0 ) + { + pParentPatch->child1 = g_Patches.InvalidIndex(); + g_Patches.Remove( iChildPatch[iChild] ); + break; + } + else + { + pParentPatch->child1 = g_Patches.InvalidIndex(); + pParentPatch->child2 = g_Patches.InvalidIndex(); + g_Patches.Remove( iChildPatch[iChild] ); + g_Patches.Remove( iChildPatch[0] ); + } + } + } + + // Continue creating children patches. + CreateChildPatchesSub( iChildPatch[0] ); + CreateChildPatchesSub( iChildPatch[1] ); +} + +int PlaneTypeForNormal (Vector& normal) +{ + vec_t ax, ay, az; + + // NOTE: should these have an epsilon around 1.0? + if (normal[0] == 1.0 || normal[0] == -1.0) + return PLANE_X; + if (normal[1] == 1.0 || normal[1] == -1.0) + return PLANE_Y; + if (normal[2] == 1.0 || normal[2] == -1.0) + return PLANE_Z; + + ax = fabs(normal[0]); + ay = fabs(normal[1]); + az = fabs(normal[2]); + + if (ax >= ay && ax >= az) + return PLANE_ANYX; + if (ay >= ax && ay >= az) + return PLANE_ANYY; + return PLANE_ANYZ; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : iPatch - +// iParentPatch - +// iChild - +// *pPoints - +// *pIndices - +// &flArea - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CVRADDispColl::InitParentPatch( int iPatch, Vector *pPoints, float &flArea ) +{ + // Get the current patch. + CPatch *pPatch = &g_Patches[iPatch]; + if ( !pPatch ) + return false; + + // Clear the patch data. + memset( pPatch, 0, sizeof( CPatch ) ); + + // This is a parent. + pPatch->ndxNext = g_FacePatches.Element( GetParentIndex() ); + g_FacePatches[GetParentIndex()] = iPatch; + pPatch->faceNumber = GetParentIndex(); + + // Initialize parent and children indices. + pPatch->child1 = g_Patches.InvalidIndex(); + pPatch->child2 = g_Patches.InvalidIndex(); + pPatch->parent = g_Patches.InvalidIndex(); + pPatch->ndxNextClusterChild = g_Patches.InvalidIndex(); + pPatch->ndxNextParent = g_Patches.InvalidIndex(); + + Vector vecEdges[2]; + vecEdges[0] = pPoints[1] - pPoints[0]; + vecEdges[1] = pPoints[3] - pPoints[0]; + + // Calculate the triangle normal and area. + Vector vecNormal = vecEdges[1].Cross( vecEdges[0] ); + flArea = VectorNormalize( vecNormal ); + + // Initialize the patch scale. + pPatch->scale[0] = pPatch->scale[1] = 1.0f; + + // Set the patch chop - minchop (that is what the minimum area is based on). + pPatch->chop = dispchop; + + // Displacements are not sky! + pPatch->sky = false; + + // Copy the winding. + Vector vecCenter( 0.0f, 0.0f, 0.0f ); + pPatch->winding = AllocWinding( 4 ); + pPatch->winding->numpoints = 4; + for ( int iPoint = 0; iPoint < 4; ++iPoint ) + { + VectorCopy( pPoints[iPoint], pPatch->winding->p[iPoint] ); + VectorAdd( pPoints[iPoint], vecCenter, vecCenter ); + } + + // Set the origin and normal. + VectorScale( vecCenter, ( 1.0f / 4.0f ), vecCenter ); + VectorCopy( vecCenter, pPatch->origin ); + VectorCopy( vecNormal, pPatch->normal ); + + // Create the plane. + pPatch->plane = new dplane_t; + if ( !pPatch->plane ) + return false; + + VectorCopy( vecNormal, pPatch->plane->normal ); + pPatch->plane->dist = vecNormal.Dot( pPoints[0] ); + pPatch->plane->type = PlaneTypeForNormal( pPatch->plane->normal ); + pPatch->planeDist = pPatch->plane->dist; + + // Set the area. + pPatch->area = flArea; + + // Calculate the mins/maxs. + Vector vecMin( FLT_MAX, FLT_MAX, FLT_MAX ); + Vector vecMax( FLT_MIN, FLT_MIN, FLT_MIN ); + for ( int iPoint = 0; iPoint < 4; ++iPoint ) + { + for ( int iAxis = 0; iAxis < 3; ++iAxis ) + { + vecMin[iAxis] = min( vecMin[iAxis], pPoints[iPoint][iAxis] ); + vecMax[iAxis] = max( vecMax[iAxis], pPoints[iPoint][iAxis] ); + } + } + + VectorCopy( vecMin, pPatch->mins ); + VectorCopy( vecMax, pPatch->maxs ); + VectorCopy( vecMin, pPatch->face_mins ); + VectorCopy( vecMax, pPatch->face_maxs ); + + // Check for bumpmap. + dface_t *pFace = dfaces + pPatch->faceNumber; + texinfo_t *pTexInfo = &texinfo[pFace->texinfo]; + pPatch->needsBumpmap = pTexInfo->flags & SURF_BUMPLIGHT ? true : false; + + // Misc... + pPatch->m_IterationKey = 0; + + // Calculate the base light, area, and reflectivity. + BaseLightForFace( &g_pFaces[pPatch->faceNumber], pPatch->baselight, &pPatch->basearea, pPatch->reflectivity ); + + return true; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pPatch - +// *pPoints - +// &vecNormal - +// flArea - +//----------------------------------------------------------------------------- +bool CVRADDispColl::InitPatch( int iPatch, int iParentPatch, int iChild, Vector *pPoints, int *pIndices, float &flArea ) +{ + // Get the current patch. + CPatch *pPatch = &g_Patches[iPatch]; + if ( !pPatch ) + return false; + + // Clear the patch data. + memset( pPatch, 0, sizeof( CPatch ) ); + + // Setup the parent if we are not the parent. + CPatch *pParentPatch = NULL; + if ( iParentPatch != g_Patches.InvalidIndex() ) + { + // Get the parent patch. + pParentPatch = &g_Patches[iParentPatch]; + if ( !pParentPatch ) + return false; + } + + // Attach the face to the correct lists. + if ( !pParentPatch ) + { + // This is a parent. + pPatch->ndxNext = g_FacePatches.Element( GetParentIndex() ); + g_FacePatches[GetParentIndex()] = iPatch; + pPatch->faceNumber = GetParentIndex(); + } + else + { + pPatch->ndxNext = g_Patches.InvalidIndex(); + pPatch->faceNumber = pParentPatch->faceNumber; + + // Attach to the parent patch. + if ( iChild == 0 ) + { + pParentPatch->child1 = iPatch; + } + else + { + pParentPatch->child2 = iPatch; + } + } + + // Initialize parent and children indices. + pPatch->child1 = g_Patches.InvalidIndex(); + pPatch->child2 = g_Patches.InvalidIndex(); + pPatch->ndxNextClusterChild = g_Patches.InvalidIndex(); + pPatch->ndxNextParent = g_Patches.InvalidIndex(); + pPatch->parent = iParentPatch; + + // Get triangle edges. + Vector vecEdges[3]; + vecEdges[0] = pPoints[1] - pPoints[0]; + vecEdges[1] = pPoints[2] - pPoints[0]; + vecEdges[2] = pPoints[2] - pPoints[1]; + + // Find the longest edge. +// float flEdgeLength = 0.0f; +// for ( int iEdge = 0; iEdge < 3; ++iEdge ) +// { +// if ( flEdgeLength < vecEdges[iEdge].Length() ) +// { +// flEdgeLength = vecEdges[iEdge].Length(); +// } +// } + + // Calculate the triangle normal and area. + Vector vecNormal = vecEdges[1].Cross( vecEdges[0] ); + flArea = VectorNormalize( vecNormal ); + flArea *= 0.5f; + + // Initialize the patch scale. + pPatch->scale[0] = pPatch->scale[1] = 1.0f; + + // Set the patch chop - minchop (that is what the minimum area is based on). + pPatch->chop = dispchop; + + // Displacements are not sky! + pPatch->sky = false; + + // Copy the winding. + Vector vecCenter( 0.0f, 0.0f, 0.0f ); + pPatch->winding = AllocWinding( 3 ); + pPatch->winding->numpoints = 3; + for ( int iPoint = 0; iPoint < 3; ++iPoint ) + { + VectorCopy( pPoints[iPoint], pPatch->winding->p[iPoint] ); + VectorAdd( pPoints[iPoint], vecCenter, vecCenter ); + + pPatch->indices[iPoint] = static_cast<short>( pIndices[iPoint] ); + } + + // Set the origin and normal. + VectorScale( vecCenter, ( 1.0f / 3.0f ), vecCenter ); + VectorCopy( vecCenter, pPatch->origin ); + VectorCopy( vecNormal, pPatch->normal ); + + // Create the plane. + pPatch->plane = new dplane_t; + if ( !pPatch->plane ) + return false; + + VectorCopy( vecNormal, pPatch->plane->normal ); + pPatch->plane->dist = vecNormal.Dot( pPoints[0] ); + pPatch->plane->type = PlaneTypeForNormal( pPatch->plane->normal ); + pPatch->planeDist = pPatch->plane->dist; + + // Set the area. + pPatch->area = flArea; + + // Calculate the mins/maxs. + Vector vecMin( FLT_MAX, FLT_MAX, FLT_MAX ); + Vector vecMax( FLT_MIN, FLT_MIN, FLT_MIN ); + for ( int iPoint = 0; iPoint < 3; ++iPoint ) + { + for ( int iAxis = 0; iAxis < 3; ++iAxis ) + { + vecMin[iAxis] = min( vecMin[iAxis], pPoints[iPoint][iAxis] ); + vecMax[iAxis] = max( vecMax[iAxis], pPoints[iPoint][iAxis] ); + } + } + + VectorCopy( vecMin, pPatch->mins ); + VectorCopy( vecMax, pPatch->maxs ); + + if ( !pParentPatch ) + { + VectorCopy( vecMin, pPatch->face_mins ); + VectorCopy( vecMax, pPatch->face_maxs ); + } + else + { + VectorCopy( pParentPatch->face_mins, pPatch->face_mins ); + VectorCopy( pParentPatch->face_maxs, pPatch->face_maxs ); + } + + // Check for bumpmap. + dface_t *pFace = dfaces + pPatch->faceNumber; + texinfo_t *pTexInfo = &texinfo[pFace->texinfo]; + pPatch->needsBumpmap = pTexInfo->flags & SURF_BUMPLIGHT ? true : false; + + // Misc... + pPatch->m_IterationKey = 0; + + // Get the base light for the face. + if ( !pParentPatch ) + { + BaseLightForFace( &g_pFaces[pPatch->faceNumber], pPatch->baselight, &pPatch->basearea, pPatch->reflectivity ); + } + else + { + VectorCopy( pParentPatch->baselight, pPatch->baselight ); + pPatch->basearea = pParentPatch->basearea; + pPatch->reflectivity = pParentPatch->reflectivity; + } + + return true; +} + +void CVRADDispColl::AddPolysForRayTrace( void ) +{ + if ( !( m_nContents & MASK_OPAQUE ) ) + return; + + for ( int ndxTri = 0; ndxTri < m_aTris.Size(); ndxTri++ ) + { + CDispCollTri *tri = m_aTris.Base() + ndxTri; + int v[3]; + for ( int ndxv = 0; ndxv < 3; ndxv++ ) + v[ndxv] = tri->GetVert(ndxv); + + Vector fullCoverage; + fullCoverage.x = 1.0f; + g_RtEnv.AddTriangle( TRACE_ID_OPAQUE, m_aVerts[v[0]], m_aVerts[v[1]], m_aVerts[v[2]], fullCoverage ); + } +}
\ No newline at end of file diff --git a/utils/vrad/vrad_dispcoll.h b/utils/vrad/vrad_dispcoll.h new file mode 100644 index 0000000..787c313 --- /dev/null +++ b/utils/vrad/vrad_dispcoll.h @@ -0,0 +1,80 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef VRAD_DISPCOLL_H +#define VRAD_DISPCOLL_H +#pragma once + +#include <assert.h> +#include "DispColl_Common.h" + +//============================================================================= +// +// VRAD specific collision +// +#define VRAD_QUAD_SIZE 4 + +struct CPatch; + +class CVRADDispColl : public CDispCollTree +{ +public: + + // Creation/Destruction Functions + CVRADDispColl(); + ~CVRADDispColl(); + bool Create( CCoreDispInfo *pDisp ); + + // Patches. + bool InitPatch( int iPatch, int iParentPatch, int iChild, Vector *pPoints, int *pIndices, float &flArea ); + bool InitParentPatch( int iPatch, Vector *pPoints, float &flArea ); + float CreateParentPatches( void ); + void CreateChildPatches( int iParentPatch, int nLevel ); + void CreateChildPatchesFromRoot( int iParentPatch, int *pChildPatch ); + void CreateChildPatchesSub( int iParentPatch ); + + // Operations Functions + void BaseFacePlaneToDispUV( Vector const &vecPlanePt, Vector2D &dispUV ); + void DispUVToSurfPoint( Vector2D const &dispUV, Vector &vecPoint, float flPushEps ); + void DispUVToSurfNormal( Vector2D const &dispUV, Vector &vecNormal ); + + // Data. + inline float GetSampleRadius2( void ) { return m_flSampleRadius2; } + inline float GetPatchSampleRadius2( void ) { return m_flPatchSampleRadius2; } + + inline int GetParentIndex( void ) { return m_iParent; } + inline void GetParentFaceNormal( Vector &vecNormal ) { vecNormal = m_vecStabDir; } + + inline void GetVert( int iVert, Vector &vecVert ) { Assert( ( iVert >= 0 ) && ( iVert < GetSize() ) ); vecVert = m_aVerts[iVert]; } + inline void GetVertNormal( int iVert, Vector &vecNormal ) { Assert( ( iVert >= 0 ) && ( iVert < GetSize() ) ); vecNormal = m_aVertNormals[iVert]; } + inline Vector2D const& GetLuxelCoord( int iLuxel ) { Assert( ( iLuxel >= 0 ) && ( iLuxel < GetSize() ) ); return m_aLuxelCoords[iLuxel]; } + + // Raytracing + void AddPolysForRayTrace( void ); + +protected: + + void CalcSampleRadius2AndBox( dface_t *pFace ); + + // Utility. + void DispUVToSurf_TriTLToBR( Vector &vecPoint, float flPushEps, float flU, float flV, int nSnapU, int nSnapV, int nWidth, int nHeight ); + void DispUVToSurf_TriBLToTR( Vector &vecPoint, float flPushEps, float flU, float flV, int nSnapU, int nSnapV, int nWidth, int nHeight ); + void GetSurfaceMinMax( Vector &boxMin, Vector &boxMax ); + void GetMinorAxes( Vector const &vecNormal, int &nAxis0, int &nAxis1 ); + +protected: + + int m_iParent; // Parent index + float m_flSampleRadius2; // Sampling radius + float m_flPatchSampleRadius2; // Patch sampling radius (max bound) + float m_flSampleWidth; + float m_flSampleHeight; + CUtlVector<Vector2D> m_aLuxelCoords; // Lightmap coordinates. + CUtlVector<Vector> m_aVertNormals; // Displacement vertex normals +}; + +#endif // VRAD_DISPCOLL_H
\ No newline at end of file diff --git a/utils/vrad/vrad_dll.vpc b/utils/vrad/vrad_dll.vpc new file mode 100644 index 0000000..7ebef01 --- /dev/null +++ b/utils/vrad/vrad_dll.vpc @@ -0,0 +1,226 @@ +//----------------------------------------------------------------------------- +// VRAD_DLL.VPC +// +// Project Script +//----------------------------------------------------------------------------- + +$Macro SRCDIR "..\.." +$Macro OUTBINDIR "$SRCDIR\..\game\bin" + +$Include "$SRCDIR\vpc_scripts\source_dll_base.vpc" + +$Configuration +{ + $Compiler + { + $AdditionalIncludeDirectories "$BASE,..\common,..\vmpi,..\vmpi\mysql\mysqlpp\include,..\vmpi\mysql\include" + $PreprocessorDefinitions "$BASE;MPI;PROTECTED_THINGS_DISABLE;VRAD" + } + + $Linker + { + $AdditionalDependencies "$BASE ws2_32.lib" + } +} + +$Project "Vrad_dll" +{ + $Folder "Source Files" + { + $File "$SRCDIR\public\BSPTreeData.cpp" + $File "$SRCDIR\public\disp_common.cpp" + $File "$SRCDIR\public\disp_powerinfo.cpp" + $File "disp_vrad.cpp" + $File "imagepacker.cpp" + $File "incremental.cpp" + $File "leaf_ambient_lighting.cpp" + $File "lightmap.cpp" + $File "$SRCDIR\public\loadcmdline.cpp" + $File "$SRCDIR\public\lumpfiles.cpp" + $File "macro_texture.cpp" + $File "..\common\mpi_stats.cpp" + $File "mpivrad.cpp" + $File "..\common\MySqlDatabase.cpp" + $File "..\common\pacifier.cpp" + $File "..\common\physdll.cpp" + $File "radial.cpp" + $File "SampleHash.cpp" + $File "trace.cpp" + $File "..\common\utilmatlib.cpp" + $File "vismat.cpp" + $File "..\common\vmpi_tools_shared.cpp" + $File "..\common\vmpi_tools_shared.h" + $File "vrad.cpp" + $File "VRAD_DispColl.cpp" + $File "VradDetailProps.cpp" + $File "VRadDisps.cpp" + $File "vraddll.cpp" + $File "VRadStaticProps.cpp" + $File "$SRCDIR\public\zip_utils.cpp" + + $Folder "Common Files" + { + $File "..\common\bsplib.cpp" + $File "$SRCDIR\public\builddisp.cpp" + $File "$SRCDIR\public\ChunkFile.cpp" + $File "..\common\cmdlib.cpp" + $File "$SRCDIR\public\DispColl_Common.cpp" + $File "..\common\map_shared.cpp" + $File "..\common\polylib.cpp" + $File "..\common\scriplib.cpp" + $File "..\common\threads.cpp" + $File "..\common\tools_minidump.cpp" + $File "..\common\tools_minidump.h" + } + + $Folder "Public Files" + { + $File "$SRCDIR\public\CollisionUtils.cpp" + $File "$SRCDIR\public\filesystem_helpers.cpp" + $File "$SRCDIR\public\ScratchPad3D.cpp" + $File "$SRCDIR\public\ScratchPadUtils.cpp" + } + } + + $Folder "Header Files" + { + $File "disp_vrad.h" + $File "iincremental.h" + $File "imagepacker.h" + $File "incremental.h" + $File "leaf_ambient_lighting.h" + $File "lightmap.h" + $File "macro_texture.h" + $File "$SRCDIR\public\map_utils.h" + $File "mpivrad.h" + $File "radial.h" + $File "$SRCDIR\public\bitmap\tgawriter.h" + $File "vismat.h" + $File "vrad.h" + $File "VRAD_DispColl.h" + $File "vraddetailprops.h" + $File "vraddll.h" + + $Folder "Common Header Files" + { + $File "..\common\bsplib.h" + $File "..\common\cmdlib.h" + $File "..\common\consolewnd.h" + $File "..\vmpi\ichannel.h" + $File "..\vmpi\imysqlwrapper.h" + $File "..\vmpi\iphelpers.h" + $File "..\common\ISQLDBReplyTarget.h" + $File "..\common\map_shared.h" + $File "..\vmpi\messbuf.h" + $File "..\common\mpi_stats.h" + $File "..\common\MySqlDatabase.h" + $File "..\common\pacifier.h" + $File "..\common\polylib.h" + $File "..\common\scriplib.h" + $File "..\vmpi\threadhelpers.h" + $File "..\common\threads.h" + $File "..\common\utilmatlib.h" + $File "..\vmpi\vmpi_defs.h" + $File "..\vmpi\vmpi_dispatch.h" + $File "..\vmpi\vmpi_distribute_work.h" + $File "..\vmpi\vmpi_filesystem.h" + } + + $Folder "Public Header Files" + { + $File "$SRCDIR\public\mathlib\amd3dx.h" + $File "$SRCDIR\public\mathlib\ANORMS.H" + $File "$SRCDIR\public\basehandle.h" + $File "$SRCDIR\public\tier0\basetypes.h" + $File "$SRCDIR\public\tier1\bitbuf.h" + $File "$SRCDIR\public\bitvec.h" + $File "$SRCDIR\public\BSPFILE.H" + $File "$SRCDIR\public\bspflags.h" + $File "$SRCDIR\public\BSPTreeData.h" + $File "$SRCDIR\public\builddisp.h" + $File "$SRCDIR\public\mathlib\bumpvects.h" + $File "$SRCDIR\public\tier1\byteswap.h" + $File "$SRCDIR\public\tier1\characterset.h" + $File "$SRCDIR\public\tier1\checksum_crc.h" + $File "$SRCDIR\public\tier1\checksum_md5.h" + $File "$SRCDIR\public\ChunkFile.h" + $File "$SRCDIR\public\cmodel.h" + $File "$SRCDIR\public\CollisionUtils.h" + $File "$SRCDIR\public\tier0\commonmacros.h" + $File "$SRCDIR\public\mathlib\compressed_vector.h" + $File "$SRCDIR\public\const.h" + $File "$SRCDIR\public\coordsize.h" + $File "$SRCDIR\public\tier0\dbg.h" + $File "$SRCDIR\public\disp_common.h" + $File "$SRCDIR\public\disp_powerinfo.h" + $File "$SRCDIR\public\disp_vertindex.h" + $File "$SRCDIR\public\DispColl_Common.h" + $File "$SRCDIR\public\tier0\fasttimer.h" + $File "$SRCDIR\public\filesystem.h" + $File "$SRCDIR\public\filesystem_helpers.h" + $File "$SRCDIR\public\GameBSPFile.h" + $File "$SRCDIR\public\gametrace.h" + $File "$SRCDIR\public\mathlib\halton.h" + $File "$SRCDIR\public\materialsystem\hardwareverts.h" + $File "$SRCDIR\public\appframework\IAppSystem.h" + $File "$SRCDIR\public\tier0\icommandline.h" + $File "$SRCDIR\public\ihandleentity.h" + $File "$SRCDIR\public\materialsystem\imaterial.h" + $File "$SRCDIR\public\materialsystem\imaterialsystem.h" + $File "$SRCDIR\public\materialsystem\imaterialvar.h" + $File "$SRCDIR\public\tier1\interface.h" + $File "$SRCDIR\public\iscratchpad3d.h" + $File "$SRCDIR\public\ivraddll.h" + $File "$SRCDIR\public\materialsystem\materialsystem_config.h" + $File "$SRCDIR\public\mathlib\mathlib.h" + $File "$SRCDIR\public\tier0\memdbgon.h" + $File "$SRCDIR\public\optimize.h" + $File "$SRCDIR\public\phyfile.h" + $File "..\common\physdll.h" + $File "$SRCDIR\public\tier0\platform.h" + $File "$SRCDIR\public\tier0\protected_things.h" + $File "$SRCDIR\public\vstdlib\random.h" + $File "$SRCDIR\public\ScratchPad3D.h" + $File "$SRCDIR\public\ScratchPadUtils.h" + $File "$SRCDIR\public\string_t.h" + $File "$SRCDIR\public\tier1\strtools.h" + $File "$SRCDIR\public\studio.h" + $File "$SRCDIR\public\tier1\tokenreader.h" + $File "$SRCDIR\public\trace.h" + $File "$SRCDIR\public\tier1\utlbuffer.h" + $File "$SRCDIR\public\tier1\utldict.h" + $File "$SRCDIR\public\tier1\utlhash.h" + $File "$SRCDIR\public\tier1\utllinkedlist.h" + $File "$SRCDIR\public\tier1\utlmemory.h" + $File "$SRCDIR\public\tier1\utlrbtree.h" + $File "$SRCDIR\public\tier1\utlsymbol.h" + $File "$SRCDIR\public\tier1\utlvector.h" + $File "$SRCDIR\public\vcollide.h" + $File "$SRCDIR\public\mathlib\vector.h" + $File "$SRCDIR\public\mathlib\vector2d.h" + $File "$SRCDIR\public\mathlib\vector4d.h" + $File "$SRCDIR\public\mathlib\vmatrix.h" + $File "..\vmpi\vmpi.h" + $File "$SRCDIR\public\vphysics_interface.h" + $File "$SRCDIR\public\mathlib\vplane.h" + $File "$SRCDIR\public\tier0\vprof.h" + $File "$SRCDIR\public\vstdlib\vstdlib.h" + $File "$SRCDIR\public\vtf\vtf.h" + $File "$SRCDIR\public\wadtypes.h" + $File "$SRCDIR\public\worldsize.h" + } + } + + $Folder "Link Libraries" + { + $Lib bitmap + $Lib mathlib + $Lib raytrace + $Lib tier2 + $Lib vmpi + $Lib vtf + $Lib "$LIBCOMMON/lzma" + } + + $File "notes.txt" +} diff --git a/utils/vrad/vraddetailprops.cpp b/utils/vrad/vraddetailprops.cpp new file mode 100644 index 0000000..87d0e05 --- /dev/null +++ b/utils/vrad/vraddetailprops.cpp @@ -0,0 +1,1036 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Revision: $ +// $NoKeywords: $ +// +// This file contains code to allow us to associate client data with bsp leaves. +// +//=============================================================================// + +#include "vrad.h" +#include "Bsplib.h" +#include "GameBSPFile.h" +#include "UtlBuffer.h" +#include "utlvector.h" +#include "CModel.h" +#include "studio.h" +#include "pacifier.h" +#include "vraddetailprops.h" +#include "mathlib/halton.h" +#include "messbuf.h" +#include "byteswap.h" + +bool LoadStudioModel( char const* pModelName, CUtlBuffer& buf ); + + +//----------------------------------------------------------------------------- +// Purpose: Writes a glview text file containing the collision surface in question +// Input : *pCollide - +// *pFilename - +//----------------------------------------------------------------------------- +void DumpRayToGlView( Ray_t const& ray, float dist, Vector* pColor, const char *pFilename ) +{ + Vector dir = ray.m_Delta; + float len = VectorNormalize(dir); + if (len < 1e-3) + return; + + Vector up( 0, 0, 1 ); + Vector crossDir; + if (fabs(DotProduct(up, dir)) - 1.0f < -1e-3 ) + { + CrossProduct( dir, up, crossDir ); + VectorNormalize(crossDir); + } + else + { + up.Init( 0, 1, 0 ); + CrossProduct( dir, up, crossDir ); + VectorNormalize(crossDir); + } + + Vector end; + Vector start1, start2; + VectorMA( ray.m_Start, dist, ray.m_Delta, end ); + VectorMA( ray.m_Start, -2, crossDir, start1 ); + VectorMA( ray.m_Start, 2, crossDir, start2 ); + + FileHandle_t fp = g_pFileSystem->Open( pFilename, "a" ); + int vert = 0; + CmdLib_FPrintf( fp, "3\n" ); + CmdLib_FPrintf( fp, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", start1.x, start1.y, start1.z, + pColor->x, pColor->y, pColor->z ); + vert++; + CmdLib_FPrintf( fp, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", start2.x, start2.y, start2.z, + pColor->x, pColor->y, pColor->z ); + vert++; + CmdLib_FPrintf( fp, "%6.3f %6.3f %6.3f %.2f %.2f %.2f\n", end.x, end.y, end.z, + pColor->x, pColor->y, pColor->z ); + vert++; + g_pFileSystem->Close( fp ); +} + + +//----------------------------------------------------------------------------- +// This puppy is used to construct the game lumps +//----------------------------------------------------------------------------- +static CUtlVector<DetailPropLightstylesLump_t> s_DetailPropLightStyleLumpLDR; +static CUtlVector<DetailPropLightstylesLump_t> s_DetailPropLightStyleLumpHDR; +static CUtlVector<DetailPropLightstylesLump_t> *s_pDetailPropLightStyleLump = &s_DetailPropLightStyleLumpLDR; + +//----------------------------------------------------------------------------- +// An amount to add to each model to get to the model center +//----------------------------------------------------------------------------- +CUtlVector<Vector> g_ModelCenterOffset; +CUtlVector<Vector> g_SpriteCenterOffset; + +void VRadDetailProps_SetHDRMode( bool bHDR ) +{ + if( bHDR ) + { + s_pDetailPropLightStyleLump = &s_DetailPropLightStyleLumpHDR; + } + else + { + s_pDetailPropLightStyleLump = &s_DetailPropLightStyleLumpLDR; + } +} + +//----------------------------------------------------------------------------- +// Finds ambient sky lights +//----------------------------------------------------------------------------- +static directlight_t* FindAmbientSkyLight() +{ + static directlight_t *s_pCachedSkylight = NULL; + + // Don't keep searching for the same light. + if ( !s_pCachedSkylight ) + { + // find any ambient lights + directlight_t* dl; + for (dl = activelights; dl != 0; dl = dl->next) + { + if (dl->light.type == emit_skyambient) + { + s_pCachedSkylight = dl; + break; + } + } + } + + return s_pCachedSkylight; +} + + +//----------------------------------------------------------------------------- +// Compute world center of a prop +//----------------------------------------------------------------------------- +static void ComputeWorldCenter( DetailObjectLump_t& prop, Vector& center, Vector& normal ) +{ + // Transform the offset into world space + Vector forward, right; + AngleVectors( prop.m_Angles, &forward, &right, &normal ); + VectorCopy( prop.m_Origin, center ); + + // FIXME: Take orientation into account? + switch (prop.m_Type ) + { + case DETAIL_PROP_TYPE_MODEL: + VectorMA( center, g_ModelCenterOffset[prop.m_DetailModel].x, forward, center ); + VectorMA( center, -g_ModelCenterOffset[prop.m_DetailModel].y, right, center ); + VectorMA( center, g_ModelCenterOffset[prop.m_DetailModel].z, normal, center ); + break; + + case DETAIL_PROP_TYPE_SPRITE: + Vector vecOffset; + VectorMultiply( g_SpriteCenterOffset[prop.m_DetailModel], prop.m_flScale, vecOffset ); + VectorMA( center, vecOffset.x, forward, center ); + VectorMA( center, -vecOffset.y, right, center ); + VectorMA( center, vecOffset.z, normal, center ); + break; + } +} + + +//----------------------------------------------------------------------------- +// Computes max direct lighting for a single detal prop +//----------------------------------------------------------------------------- +static void ComputeMaxDirectLighting( DetailObjectLump_t& prop, Vector* maxcolor, int iThread ) +{ + // The max direct lighting must be along the direction to one + // of the static lights.... + + Vector origin, normal; + ComputeWorldCenter( prop, origin, normal ); + + if ( !origin.IsValid() || !normal.IsValid() ) + { + static bool s_Warned = false; + if ( !s_Warned ) + { + Warning("WARNING: Bogus detail props encountered!\n" ); + s_Warned = true; + } + + // fill with debug color + for ( int i = 0; i < MAX_LIGHTSTYLES; ++i) + { + maxcolor[i].Init(1,0,0); + } + return; + } + + int cluster = ClusterFromPoint(origin); + + Vector delta; + CUtlVector< directlight_t* > lights; + CUtlVector< Vector > directions; + + directlight_t* dl; + for (dl = activelights; dl != 0; dl = dl->next) + { + // skyambient doesn't affect dlights.. + if (dl->light.type == emit_skyambient) + continue; + + // is this lights cluster visible? + if ( PVSCheck( dl->pvs, cluster ) ) + { + lights.AddToTail(dl); + VectorSubtract( dl->light.origin, origin, delta ); + VectorNormalize( delta ); + directions.AddToTail( delta ); + } + } + + // Find the max illumination + int i; + for ( i = 0; i < MAX_LIGHTSTYLES; ++i) + { + maxcolor[i].Init(0,0,0); + } + + // NOTE: See version 10 for a method where we choose a normal based on whichever + // one produces the maximum possible illumination. This appeared to work better on + // e3_town, so I'm trying it now; hopefully it'll be good for all cases. + int j; + for ( j = 0; j < lights.Count(); ++j) + { + dl = lights[j]; + + SSE_sampleLightOutput_t out; + FourVectors origin4; + FourVectors normal4; + origin4.DuplicateVector( origin ); + normal4.DuplicateVector( normal ); + + GatherSampleLightSSE ( out, dl, -1, origin4, &normal4, 1, iThread ); + VectorMA( maxcolor[dl->light.style], out.m_flFalloff.m128_f32[0] * out.m_flDot[0].m128_f32[0], dl->light.intensity, maxcolor[dl->light.style] ); + } +} + + +//----------------------------------------------------------------------------- +// Computes the ambient term from a particular surface +//----------------------------------------------------------------------------- + +static void ComputeAmbientFromSurface( dface_t* pFace, directlight_t* pSkylight, + Vector& radcolor ) +{ + texinfo_t* pTex = &texinfo[pFace->texinfo]; + if (pTex) + { + // If we hit the sky, use the sky ambient + if (pTex->flags & SURF_SKY) + { + if (pSkylight) + { + // add in sky ambient + VectorDivide( pSkylight->light.intensity, 255.0f, radcolor ); + } + } + else + { + VectorMultiply( radcolor, dtexdata[pTex->texdata].reflectivity, radcolor ); + } + } +} + + +//----------------------------------------------------------------------------- +// Computes the lightmap color at a particular point +//----------------------------------------------------------------------------- + +static void ComputeLightmapColorFromAverage( dface_t* pFace, directlight_t* pSkylight, float scale, Vector pColor[MAX_LIGHTSTYLES] ) +{ + texinfo_t* pTex = &texinfo[pFace->texinfo]; + if (pTex->flags & SURF_SKY) + { + if (pSkylight) + { + // add in sky ambient + Vector amb = pSkylight->light.intensity / 255.0f; + pColor[0] += amb * scale; + } + return; + } + + for (int maps = 0 ; maps < MAXLIGHTMAPS && pFace->styles[maps] != 255 ; ++maps) + { + ColorRGBExp32* pAvgColor = dface_AvgLightColor( pFace, maps ); + + // this code expects values from [0..1] not [0..255] + Vector color; + color[0] = TexLightToLinear( pAvgColor->r, pAvgColor->exponent ); + color[1] = TexLightToLinear( pAvgColor->g, pAvgColor->exponent ); + color[2] = TexLightToLinear( pAvgColor->b, pAvgColor->exponent ); + + ComputeAmbientFromSurface( pFace, pSkylight, color ); + + int style = pFace->styles[maps]; + pColor[style] += color * scale; + } +} + + +//----------------------------------------------------------------------------- +// Returns true if the surface has bumped lightmaps +//----------------------------------------------------------------------------- + +static bool SurfHasBumpedLightmaps( dface_t *pSurf ) +{ + bool hasBumpmap = false; + if( ( texinfo[pSurf->texinfo].flags & SURF_BUMPLIGHT ) && + ( !( texinfo[pSurf->texinfo].flags & SURF_NOLIGHT ) ) ) + { + hasBumpmap = true; + } + return hasBumpmap; +} + +//----------------------------------------------------------------------------- +// Computes the lightmap color at a particular point +//----------------------------------------------------------------------------- + +static void ComputeLightmapColorPointSample( dface_t* pFace, directlight_t* pSkylight, Vector2D const& luv, float scale, Vector pColor[MAX_LIGHTSTYLES] ) +{ + // face unaffected by light + if (pFace->lightofs == -1 ) + return; + + int smax = ( pFace->m_LightmapTextureSizeInLuxels[0] ) + 1; + int tmax = ( pFace->m_LightmapTextureSizeInLuxels[1] ) + 1; + + // luv is in the space of the accumulated lightmap page; we need to convert + // it to be in the space of the surface + int ds = clamp( (int)luv.x, 0, smax-1 ); + int dt = clamp( (int)luv.y, 0, tmax-1 ); + + int offset = smax * tmax; + if ( SurfHasBumpedLightmaps( pFace ) ) + offset *= ( NUM_BUMP_VECTS + 1 ); + + ColorRGBExp32* pLightmap = (ColorRGBExp32*)&pdlightdata->Base()[pFace->lightofs]; + pLightmap += dt * smax + ds; + for (int maps = 0 ; maps < MAXLIGHTMAPS && pFace->styles[maps] != 255 ; ++maps) + { + int style = pFace->styles[maps]; + + Vector color; + color[0] = TexLightToLinear( pLightmap->r, pLightmap->exponent ); + color[1] = TexLightToLinear( pLightmap->g, pLightmap->exponent ); + color[2] = TexLightToLinear( pLightmap->b, pLightmap->exponent ); + + ComputeAmbientFromSurface( pFace, pSkylight, color ); + pColor[style] += color * scale; + + pLightmap += offset; + } +} + + +//----------------------------------------------------------------------------- +// Tests a particular node +//----------------------------------------------------------------------------- + +class CLightSurface : public IBSPNodeEnumerator +{ +public: + CLightSurface(int iThread) : m_pSurface(0), m_HitFrac(1.0f), m_bHasLuxel(false), m_iThread(iThread) {} + + // call back with a node and a context + bool EnumerateNode( int node, Ray_t const& ray, float f, int context ) + { + dface_t* pSkySurface = 0; + + // Compute the actual point + Vector pt; + VectorMA( ray.m_Start, f, ray.m_Delta, pt ); + + dnode_t* pNode = &dnodes[node]; + dface_t* pFace = &g_pFaces[pNode->firstface]; + for (int i=0 ; i < pNode->numfaces ; ++i, ++pFace) + { + // Don't take into account faces that are int a leaf + if ( !pFace->onNode ) + continue; + + // Don't test displacement faces + if ( pFace->dispinfo != -1 ) + continue; + + texinfo_t* pTex = &texinfo[pFace->texinfo]; + + // Don't immediately return when we hit sky; + // we may actually hit another surface + if (pTex->flags & SURF_SKY) + { + if (TestPointAgainstSkySurface( pt, pFace )) + { + pSkySurface = pFace; + } + + continue; + } + + if (TestPointAgainstSurface( pt, pFace, pTex )) + { + m_HitFrac = f; + m_pSurface = pFace; + m_bHasLuxel = true; + return false; + } + } + + // if we hit a sky surface, return it + m_pSurface = pSkySurface; + return (m_pSurface == 0); + } + + // call back with a leaf and a context + virtual bool EnumerateLeaf( int leaf, Ray_t const& ray, float start, float end, int context ) + { + bool hit = false; + dleaf_t* pLeaf = &dleafs[leaf]; + for (int i=0 ; i < pLeaf->numleaffaces ; ++i) + { + Assert( pLeaf->firstleafface + i < numleaffaces ); + Assert( dleaffaces[pLeaf->firstleafface + i] < numfaces ); + dface_t* pFace = &g_pFaces[dleaffaces[pLeaf->firstleafface + i]]; + + // Don't test displacement faces; we need to check another list + if ( pFace->dispinfo != -1 ) + continue; + + // Don't take into account faces that are on a node + if ( pFace->onNode ) + continue; + + // Find intersection point against detail brushes + texinfo_t* pTex = &texinfo[pFace->texinfo]; + + dplane_t* pPlane = &dplanes[pFace->planenum]; + + // Backface cull... + if (DotProduct( pPlane->normal, ray.m_Delta ) > 0) + continue; + + float startDotN = DotProduct( ray.m_Start, pPlane->normal ); + float deltaDotN = DotProduct( ray.m_Delta, pPlane->normal ); + + float front = startDotN + start * deltaDotN - pPlane->dist; + float back = startDotN + end * deltaDotN - pPlane->dist; + + int side = front < 0; + + // Blow it off if it doesn't split the plane... + if ( (back < 0) == side ) + continue; + + // Don't test a surface that is farther away from the closest found intersection + float f = front / (front-back); + float mid = start * (1.0f - f) + end * f; + if (mid >= m_HitFrac) + continue; + + Vector pt; + VectorMA( ray.m_Start, mid, ray.m_Delta, pt ); + + if (TestPointAgainstSurface( pt, pFace, pTex )) + { + m_HitFrac = mid; + m_pSurface = pFace; + hit = true; + m_bHasLuxel = true; + } + } + + // Now try to clip against all displacements in the leaf + float dist; + Vector2D luxelCoord; + dface_t *pDispFace; + StaticDispMgr()->ClipRayToDispInLeaf( s_DispTested[m_iThread], ray, leaf, dist, pDispFace, luxelCoord ); + if (dist < m_HitFrac) + { + m_HitFrac = dist; + m_pSurface = pDispFace; + Vector2DCopy( luxelCoord, m_LuxelCoord ); + hit = true; + m_bHasLuxel = true; + } + return !hit; + } + + bool FindIntersection( Ray_t const& ray ) + { + StaticDispMgr()->StartRayTest( s_DispTested[m_iThread] ); + return !EnumerateNodesAlongRay( ray, this, 0 ); + } + +private: + bool TestPointAgainstSurface( Vector const& pt, dface_t* pFace, texinfo_t* pTex ) + { + // no lightmaps on this surface? punt... + // FIXME: should be water surface? + if (pTex->flags & SURF_NOLIGHT) + return false; + + // See where in lightmap space our intersection point is + float s, t; + s = DotProduct (pt.Base(), pTex->lightmapVecsLuxelsPerWorldUnits[0]) + + pTex->lightmapVecsLuxelsPerWorldUnits[0][3]; + t = DotProduct (pt.Base(), pTex->lightmapVecsLuxelsPerWorldUnits[1]) + + pTex->lightmapVecsLuxelsPerWorldUnits[1][3]; + + // Not in the bounds of our lightmap? punt... + if( s < pFace->m_LightmapTextureMinsInLuxels[0] || t < pFace->m_LightmapTextureMinsInLuxels[1] ) + return false; + + // assuming a square lightmap (FIXME: which ain't always the case), + // lets see if it lies in that rectangle. If not, punt... + float ds = s - pFace->m_LightmapTextureMinsInLuxels[0]; + float dt = t - pFace->m_LightmapTextureMinsInLuxels[1]; + if( ds > pFace->m_LightmapTextureSizeInLuxels[0] || dt > pFace->m_LightmapTextureSizeInLuxels[1] ) + return false; + + m_LuxelCoord.x = ds; + m_LuxelCoord.y = dt; + + return true; + } + + bool TestPointAgainstSkySurface( Vector const &pt, dface_t *pFace ) + { + // Create sky face winding. + winding_t *pWinding = WindingFromFace( pFace, Vector( 0.0f, 0.0f, 0.0f ) ); + + // Test point in winding. (Since it is at the node, it is in the plane.) + bool bRet = PointInWinding( pt, pWinding ); + + FreeWinding( pWinding ); + + return bRet; + } + + +public: + int m_iThread; + dface_t* m_pSurface; + float m_HitFrac; + Vector2D m_LuxelCoord; + bool m_bHasLuxel; +}; + +bool CastRayInLeaf( int iThread, const Vector &start, const Vector &end, int leafIndex, float *pFraction, Vector *pNormal ) +{ + pFraction[0] = 1.0f; + + Ray_t ray; + ray.Init( start, end, vec3_origin, vec3_origin ); + CBaseTrace trace; + if ( TraceLeafBrushes( leafIndex, start, end, trace ) != 1.0f ) + { + pFraction[0] = trace.fraction; + *pNormal = trace.plane.normal; + } + else + { + Assert(!trace.startsolid && !trace.allsolid); + } + StaticDispMgr()->StartRayTest( s_DispTested[iThread] ); + // Now try to clip against all displacements in the leaf + float dist; + Vector normal; + StaticDispMgr()->ClipRayToDispInLeaf( s_DispTested[iThread], ray, leafIndex, dist, &normal ); + if ( dist < pFraction[0] ) + { + pFraction[0] = dist; + *pNormal = normal; + } + return pFraction[0] != 1.0f ? true : false; +} + +//----------------------------------------------------------------------------- +// Computes ambient lighting along a specified ray. +// Ray represents a cone, tanTheta is the tan of the inner cone angle +//----------------------------------------------------------------------------- +void CalcRayAmbientLighting( int iThread, const Vector &vStart, const Vector &vEnd, float tanTheta, Vector color[MAX_LIGHTSTYLES] ) +{ + Ray_t ray; + ray.Init( vStart, vEnd, vec3_origin, vec3_origin ); + + directlight_t *pSkyLight = FindAmbientSkyLight(); + + CLightSurface surfEnum(iThread); + if (!surfEnum.FindIntersection( ray )) + return; + + // compute the approximate radius of a circle centered around the intersection point + float dist = ray.m_Delta.Length() * tanTheta * surfEnum.m_HitFrac; + + // until 20" we use the point sample, then blend in the average until we're covering 40" + // This is attempting to model the ray as a cone - in the ideal case we'd simply sample all + // luxels in the intersection of the cone with the surface. Since we don't have surface + // neighbor information computed we'll just approximate that sampling with a blend between + // a point sample and the face average. + // This yields results that are similar in that aliasing is reduced at distance while + // point samples provide accuracy for intersections with near geometry + float scaleAvg = RemapValClamped( dist, 20, 40, 0.0f, 1.0f ); + + if ( !surfEnum.m_bHasLuxel ) + { + // don't have luxel UV, so just use average sample + scaleAvg = 1.0; + } + float scaleSample = 1.0f - scaleAvg; + + if (scaleAvg != 0) + { + ComputeLightmapColorFromAverage( surfEnum.m_pSurface, pSkyLight, scaleAvg, color ); + } + if (scaleSample != 0) + { + ComputeLightmapColorPointSample( surfEnum.m_pSurface, pSkyLight, surfEnum.m_LuxelCoord, scaleSample, color ); + } +} + +//----------------------------------------------------------------------------- +// Compute ambient lighting component at specified position. +//----------------------------------------------------------------------------- +static void ComputeAmbientLightingAtPoint( int iThread, const Vector &origin, Vector radcolor[NUMVERTEXNORMALS], Vector color[MAX_LIGHTSTYLES] ) +{ + // NOTE: I'm not dealing with shadow-casting static props here + // This is for speed, although we can add it if it turns out to + // be important + + // sample world by casting N rays distributed across a sphere + Vector upend; + + int j; + for ( j = 0; j < MAX_LIGHTSTYLES; ++j) + { + color[j].Init( 0,0,0 ); + } + + float tanTheta = tan(VERTEXNORMAL_CONE_INNER_ANGLE); + for (int i = 0; i < NUMVERTEXNORMALS; i++) + { + VectorMA( origin, COORD_EXTENT * 1.74, g_anorms[i], upend ); + + // Now that we've got a ray, see what surface we've hit + CalcRayAmbientLighting( iThread, origin, upend, tanTheta, color ); + +// DumpRayToGlView( ray, surfEnum.m_HitFrac, &color[0], "test.out" ); + } + + for ( j = 0; j < MAX_LIGHTSTYLES; ++j) + { + VectorMultiply( color[j], 255.0f / (float)NUMVERTEXNORMALS, color[j] ); + } +} + +//----------------------------------------------------------------------------- +// Trace hemispherical rays from a vertex, accumulating indirect +// sources at each ray termination. +//----------------------------------------------------------------------------- +void ComputeIndirectLightingAtPoint( Vector &position, Vector &normal, Vector &outColor, + int iThread, bool force_fast, bool bIgnoreNormals ) +{ + Ray_t ray; + CLightSurface surfEnum(iThread); + + outColor.Init(); + + + int nSamples = NUMVERTEXNORMALS; + if ( do_fast || force_fast ) + nSamples /= 4; + else + nSamples *= g_flSkySampleScale; + + float totalDot = 0; + DirectionalSampler_t sampler; + for (int j = 0; j < nSamples; j++) + { + Vector samplingNormal = sampler.NextValue(); + float dot; + + if ( bIgnoreNormals ) + dot = (0.7071/2); + else + dot = DotProduct( normal, samplingNormal ); + + if ( dot <= EQUAL_EPSILON ) + { + // reject angles behind our plane + continue; + } + + totalDot += dot; + + // trace to determine surface + Vector vEnd; + VectorScale( samplingNormal, MAX_TRACE_LENGTH, vEnd ); + VectorAdd( position, vEnd, vEnd ); + + ray.Init( position, vEnd, vec3_origin, vec3_origin ); + if ( !surfEnum.FindIntersection( ray ) ) + continue; + + // get color from surface lightmap + texinfo_t* pTex = &texinfo[surfEnum.m_pSurface->texinfo]; + if ( !pTex || pTex->flags & SURF_SKY ) + { + // ignore contribution from sky + // sky ambient already accounted for during direct pass + continue; + } + + if ( surfEnum.m_pSurface->styles[0] == 255 || surfEnum.m_pSurface->lightofs < 0 ) + { + // no light affects this face + continue; + } + + + Vector lightmapColor; + if ( !surfEnum.m_bHasLuxel ) + { + ColorRGBExp32* pAvgLightmapColor = dface_AvgLightColor( surfEnum.m_pSurface, 0 ); + ColorRGBExp32ToVector( *pAvgLightmapColor, lightmapColor ); + } + else + { + // get color from displacement + int smax = ( surfEnum.m_pSurface->m_LightmapTextureSizeInLuxels[0] ) + 1; + int tmax = ( surfEnum.m_pSurface->m_LightmapTextureSizeInLuxels[1] ) + 1; + + // luxelcoord is in the space of the accumulated lightmap page; we need to convert + // it to be in the space of the surface + int ds = clamp( (int)surfEnum.m_LuxelCoord.x, 0, smax-1 ); + int dt = clamp( (int)surfEnum.m_LuxelCoord.y, 0, tmax-1 ); + + ColorRGBExp32* pLightmap = (ColorRGBExp32*)&(*pdlightdata)[surfEnum.m_pSurface->lightofs]; + pLightmap += dt * smax + ds; + ColorRGBExp32ToVector( *pLightmap, lightmapColor ); + } + + float invLengthSqr = 1.0f / (1.0f + ((vEnd - position) * surfEnum.m_HitFrac / 128.0).LengthSqr()); + // Include falloff using invsqrlaw. + VectorMultiply( lightmapColor, invLengthSqr * dtexdata[pTex->texdata].reflectivity, lightmapColor ); + VectorAdd( outColor, lightmapColor, outColor ); + } + + if ( totalDot ) + { + VectorScale( outColor, 1.0f/totalDot, outColor ); + } +} + +static void ComputeAmbientLighting( int iThread, DetailObjectLump_t& prop, Vector color[MAX_LIGHTSTYLES] ) +{ + Vector origin, normal; + ComputeWorldCenter( prop, origin, normal ); + + if ( !origin.IsValid() || !normal.IsValid() ) + { + static bool s_Warned = false; + if ( !s_Warned ) + { + Warning("WARNING: Bogus detail props encountered!\n" ); + s_Warned = true; + } + + // fill with debug color + for ( int i = 0; i < MAX_LIGHTSTYLES; ++i) + { + color[i].Init(1,0,0); + } + return; + } + + Vector radcolor[NUMVERTEXNORMALS]; + ComputeAmbientLightingAtPoint( iThread, origin, radcolor, color ); +} + + +//----------------------------------------------------------------------------- +// Computes lighting for a single detal prop +//----------------------------------------------------------------------------- + +static void ComputeLighting( DetailObjectLump_t& prop, int iThread ) +{ + // We're going to take the maximum of the ambient lighting and + // the strongest directional light. This works because we're assuming + // the props will have built-in faked lighting. + + Vector directColor[MAX_LIGHTSTYLES]; + Vector ambColor[MAX_LIGHTSTYLES]; + + // Get the max influence of all direct lights + ComputeMaxDirectLighting( prop, directColor, iThread ); + + // Get the ambient lighting + lightstyles + ComputeAmbientLighting( iThread, prop, ambColor ); + + // Base lighting + Vector totalColor; + VectorAdd( directColor[0], ambColor[0], totalColor ); + VectorToColorRGBExp32( totalColor, prop.m_Lighting ); + + bool hasLightstyles = false; + prop.m_LightStyleCount = 0; + + // lightstyles + for (int i = 1; i < MAX_LIGHTSTYLES; ++i ) + { + VectorAdd( directColor[i], ambColor[i], totalColor ); + totalColor *= 0.5f; + + if ((totalColor[0] != 0.0f) || (totalColor[1] != 0.0f) || + (totalColor[2] != 0.0f) ) + { + if (!hasLightstyles) + { + prop.m_LightStyles = s_pDetailPropLightStyleLump->Size(); + hasLightstyles = true; + } + + int j = s_pDetailPropLightStyleLump->AddToTail(); + VectorToColorRGBExp32( totalColor, (*s_pDetailPropLightStyleLump)[j].m_Lighting ); + (*s_pDetailPropLightStyleLump)[j].m_Style = i; + ++prop.m_LightStyleCount; + } + } +} + + +//----------------------------------------------------------------------------- +// Unserialization +//----------------------------------------------------------------------------- +static void UnserializeModelDict( CUtlBuffer& buf ) +{ + // Get origin offset for each model... + int count = buf.GetInt(); + while ( --count >= 0 ) + { + DetailObjectDictLump_t lump; + buf.Get( &lump, sizeof(DetailObjectDictLump_t) ); + + int i = g_ModelCenterOffset.AddToTail(); + + CUtlBuffer mdlbuf; + if (LoadStudioModel( lump.m_Name, mdlbuf )) + { + studiohdr_t* pHdr = (studiohdr_t*)mdlbuf.Base(); + VectorAdd( pHdr->hull_min, pHdr->hull_max, g_ModelCenterOffset[i] ); + g_ModelCenterOffset[i] *= 0.5f; + } + else + { + g_ModelCenterOffset[i].Init(0,0,0); + } + } +} + +static void UnserializeSpriteDict( CUtlBuffer& buf ) +{ + // Get origin offset for each model... + int count = buf.GetInt(); + while ( --count >= 0 ) + { + DetailSpriteDictLump_t lump; + buf.Get( &lump, sizeof(DetailSpriteDictLump_t) ); + + // For these sprites, x goes out the front, y right, z up + int i = g_SpriteCenterOffset.AddToTail(); + g_SpriteCenterOffset[i].x = 0.0f; + g_SpriteCenterOffset[i].y = lump.m_LR.x + lump.m_UL.x; + g_SpriteCenterOffset[i].z = lump.m_LR.y + lump.m_UL.y; + g_SpriteCenterOffset[i] *= 0.5f; + } +} + + +//----------------------------------------------------------------------------- +// Unserializes the detail props +//----------------------------------------------------------------------------- +static int UnserializeDetailProps( DetailObjectLump_t*& pProps ) +{ + GameLumpHandle_t handle = g_GameLumps.GetGameLumpHandle( GAMELUMP_DETAIL_PROPS ); + + if (g_GameLumps.GetGameLumpVersion(handle) != GAMELUMP_DETAIL_PROPS_VERSION) + return 0; + + // Unserialize + CUtlBuffer buf( g_GameLumps.GetGameLump(handle), g_GameLumps.GameLumpSize( handle ), CUtlBuffer::READ_ONLY ); + + UnserializeModelDict( buf ); + UnserializeSpriteDict( buf ); + + // Now we're pointing to the detail prop data + // This actually works because the scope of the game lump data + // is global and the buf was just pointing to it. + int count = buf.GetInt(); + if (count) + { + pProps = (DetailObjectLump_t*)buf.PeekGet(); + } + else + { + pProps = 0; + } + return count; +} + + +//----------------------------------------------------------------------------- +// Writes the detail lighting lump +//----------------------------------------------------------------------------- +static void WriteDetailLightingLump( int lumpID, int lumpVersion, CUtlVector<DetailPropLightstylesLump_t> &lumpData ) +{ + GameLumpHandle_t handle = g_GameLumps.GetGameLumpHandle(lumpID); + if (handle != g_GameLumps.InvalidGameLump()) + g_GameLumps.DestroyGameLump(handle); + int lightsize = lumpData.Size() * sizeof(DetailPropLightstylesLump_t); + int lumpsize = lightsize + sizeof(int); + + handle = g_GameLumps.CreateGameLump( lumpID, lumpsize, 0, lumpVersion ); + + // Serialize the data + CUtlBuffer buf( g_GameLumps.GetGameLump(handle), lumpsize ); + buf.PutInt( lumpData.Size() ); + if (lightsize) + buf.Put( lumpData.Base(), lightsize ); +} + +static void WriteDetailLightingLumps( void ) +{ + WriteDetailLightingLump( GAMELUMP_DETAIL_PROP_LIGHTING, GAMELUMP_DETAIL_PROP_LIGHTING_VERSION, s_DetailPropLightStyleLumpLDR ); + WriteDetailLightingLump( GAMELUMP_DETAIL_PROP_LIGHTING_HDR, GAMELUMP_DETAIL_PROP_LIGHTING_HDR_VERSION, s_DetailPropLightStyleLumpHDR ); +} + +// need to do this so that if we are building HDR data, the LDR data is intact, and vice versa.s +void UnserializeDetailPropLighting( int lumpID, int lumpVersion, CUtlVector<DetailPropLightstylesLump_t> &lumpData ) +{ + GameLumpHandle_t handle = g_GameLumps.GetGameLumpHandle( lumpID ); + + if( handle == g_GameLumps.InvalidGameLump() ) + { + return; + } + + if (g_GameLumps.GetGameLumpVersion(handle) != lumpVersion) + return; + + // Unserialize + CUtlBuffer buf( g_GameLumps.GetGameLump(handle), g_GameLumps.GameLumpSize( handle ), CUtlBuffer::READ_ONLY ); + + int count = buf.GetInt(); + if( !count ) + { + return; + } + lumpData.SetCount( count ); + int lightsize = lumpData.Size() * sizeof(DetailPropLightstylesLump_t); + buf.Get( lumpData.Base(), lightsize ); +} + +DetailObjectLump_t *g_pMPIDetailProps = NULL; + +void VMPI_ProcessDetailPropWU( int iThread, int iWorkUnit, MessageBuffer *pBuf ) +{ + CUtlVector<DetailPropLightstylesLump_t> *pDetailPropLump = s_pDetailPropLightStyleLump; + + DetailObjectLump_t& prop = g_pMPIDetailProps[iWorkUnit]; + ComputeLighting( prop, iThread ); + + // Send the results back... + pBuf->write( &prop.m_Lighting, sizeof( prop.m_Lighting ) ); + pBuf->write( &prop.m_LightStyleCount, sizeof( prop.m_LightStyleCount ) ); + pBuf->write( &prop.m_LightStyles, sizeof( prop.m_LightStyles ) ); + + for ( int i=0; i < prop.m_LightStyleCount; i++ ) + { + DetailPropLightstylesLump_t *l = &pDetailPropLump->Element( i + prop.m_LightStyles ); + pBuf->write( &l->m_Lighting, sizeof( l->m_Lighting ) ); + pBuf->write( &l->m_Style, sizeof( l->m_Style ) ); + } +} + + +void VMPI_ReceiveDetailPropWU( int iWorkUnit, MessageBuffer *pBuf, int iWorker ) +{ + CUtlVector<DetailPropLightstylesLump_t> *pDetailPropLump = s_pDetailPropLightStyleLump; + + DetailObjectLump_t& prop = g_pMPIDetailProps[iWorkUnit]; + + pBuf->read( &prop.m_Lighting, sizeof( prop.m_Lighting ) ); + pBuf->read( &prop.m_LightStyleCount, sizeof( prop.m_LightStyleCount ) ); + pBuf->read( &prop.m_LightStyles, sizeof( prop.m_LightStyles ) ); + + pDetailPropLump->EnsureCount( prop.m_LightStyles + prop.m_LightStyleCount ); + + for ( int i=0; i < prop.m_LightStyleCount; i++ ) + { + DetailPropLightstylesLump_t *l = &pDetailPropLump->Element( i + prop.m_LightStyles ); + pBuf->read( &l->m_Lighting, sizeof( l->m_Lighting ) ); + pBuf->read( &l->m_Style, sizeof( l->m_Style ) ); + } +} + +//----------------------------------------------------------------------------- +// Computes lighting for the detail props +//----------------------------------------------------------------------------- +void ComputeDetailPropLighting( int iThread ) +{ + // illuminate them all + DetailObjectLump_t* pProps; + int count = UnserializeDetailProps( pProps ); + if (!count) + return; + + // unserialize the lump that we aren't computing. + if( g_bHDR ) + { + UnserializeDetailPropLighting( GAMELUMP_DETAIL_PROP_LIGHTING, GAMELUMP_DETAIL_PROP_LIGHTING_VERSION, s_DetailPropLightStyleLumpLDR ); + } + else + { + UnserializeDetailPropLighting( GAMELUMP_DETAIL_PROP_LIGHTING_HDR, GAMELUMP_DETAIL_PROP_LIGHTING_HDR_VERSION, s_DetailPropLightStyleLumpHDR ); + } + + StartPacifier("Computing detail prop lighting : "); + + for (int i = 0; i < count; ++i) + { + UpdatePacifier( (float)i / (float)count ); + ComputeLighting( pProps[i], iThread ); + } + + // Write detail prop lightstyle lump... + WriteDetailLightingLumps(); + EndPacifier( true ); +} diff --git a/utils/vrad/vraddetailprops.h b/utils/vrad/vraddetailprops.h new file mode 100644 index 0000000..0345d96 --- /dev/null +++ b/utils/vrad/vraddetailprops.h @@ -0,0 +1,34 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//=============================================================================// + +#ifndef VRADDETAILPROPS_H +#define VRADDETAILPROPS_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "bspfile.h" +#include "mathlib/anorms.h" + + +// Calculate the lighting at whatever surface the ray hits. +// Note: this ADDS to the values already in color. So if you want absolute +// values in there, then clear the values in color[] first. +void CalcRayAmbientLighting( + int iThread, + const Vector &vStart, + const Vector &vEnd, + float tanTheta, // tangent of the inner angle of the cone + Vector color[MAX_LIGHTSTYLES] // The color contribution from each lightstyle. + ); + +bool CastRayInLeaf( int iThread, const Vector &start, const Vector &end, int leafIndex, float *pFraction, Vector *pNormal ); + +void ComputeDetailPropLighting( int iThread ); + + +#endif // VRADDETAILPROPS_H diff --git a/utils/vrad/vraddisps.cpp b/utils/vrad/vraddisps.cpp new file mode 100644 index 0000000..d6bc6f7 --- /dev/null +++ b/utils/vrad/vraddisps.cpp @@ -0,0 +1,1759 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#include "vrad.h" +#include "utlvector.h" +#include "cmodel.h" +#include "BSPTreeData.h" +#include "VRAD_DispColl.h" +#include "CollisionUtils.h" +#include "lightmap.h" +#include "Radial.h" +#include "CollisionUtils.h" +#include "mathlib/bumpvects.h" +#include "utlrbtree.h" +#include "tier0/fasttimer.h" +#include "disp_vrad.h" + +class CBSPDispRayDistanceEnumerator; + +//============================================================================= +// +// Displacement/Face List +// +class CBSPDispFaceListEnumerator : public ISpatialLeafEnumerator, public IBSPTreeDataEnumerator +{ +public: + + //========================================================================= + // + // Construction/Deconstruction + // + CBSPDispFaceListEnumerator() {}; + virtual ~CBSPDispFaceListEnumerator() + { + m_DispList.Purge(); + m_FaceList.Purge(); + } + + // ISpatialLeafEnumerator + bool EnumerateLeaf( int ndxLeaf, int context ); + + // IBSPTreeDataEnumerator + bool FASTCALL EnumerateElement( int userId, int context ); + +public: + + CUtlVector<CVRADDispColl*> m_DispList; + CUtlVector<int> m_FaceList; +}; + + +//============================================================================= +// +// RayEnumerator +// +class CBSPDispRayEnumerator : public ISpatialLeafEnumerator, public IBSPTreeDataEnumerator +{ +public: + // ISpatialLeafEnumerator + bool EnumerateLeaf( int ndxLeaf, int context ); + + // IBSPTreeDataEnumerator + bool FASTCALL EnumerateElement( int userId, int context ); +}; + +//============================================================================= +// +// VRad Displacement Manager +// +class CVRadDispMgr : public IVRadDispMgr +{ +public: + + //========================================================================= + // + // Construction/Deconstruction + // + CVRadDispMgr(); + virtual ~CVRadDispMgr(); + + // creation/destruction + void Init( void ); + void Shutdown( void ); + + // "CalcPoints" + bool BuildDispSamples( lightinfo_t *pLightInfo, facelight_t *pFaceLight, int ndxFace ); + bool BuildDispLuxels( lightinfo_t *pLightInfo, facelight_t *pFaceLight, int ndxFace ); + bool BuildDispSamplesAndLuxels_DoFast( lightinfo_t *pLightInfo, facelight_t *pFaceLight, int ndxFace ); + + // patching functions + void MakePatches( void ); + void SubdividePatch( int iPatch ); + + // pre "FinalLightFace" + void InsertSamplesDataIntoHashTable( void ); + void InsertPatchSampleDataIntoHashTable( void ); + + // "FinalLightFace" + radial_t *BuildLuxelRadial( int ndxFace, int ndxStyle, bool bBump ); + bool SampleRadial( int ndxFace, radial_t *pRadial, Vector const &vPos, int ndxLxl, LightingValue_t *pLightSample, int sampleCount, bool bPatch ); + radial_t *BuildPatchRadial( int ndxFace, bool bBump ); + + // utility + void GetDispSurfNormal( int ndxFace, Vector &pt, Vector &ptNormal, bool bInside ); + void GetDispSurf( int ndxFace, CVRADDispColl **ppDispTree ); + + // bsp tree functions + bool ClipRayToDisp( DispTested_t &dispTested, Ray_t const &ray ); + bool ClipRayToDispInLeaf( DispTested_t &dispTested, Ray_t const &ray, int ndxLeaf ); + void ClipRayToDispInLeaf( DispTested_t &dispTested, Ray_t const &ray, int ndxLeaf, + float& dist, dface_t*& pFace, Vector2D& luxelCoord ); + void ClipRayToDispInLeaf( DispTested_t &dispTested, Ray_t const &ray, + int ndxLeaf, float& dist, Vector *pNormal ); + + void StartRayTest( DispTested_t &dispTested ); + void AddPolysForRayTrace( void ); + + // general timing -- should be moved!! + void StartTimer( const char *name ); + void EndTimer( void ); + + //========================================================================= + // + // Enumeration Methods + // + bool DispRay_EnumerateLeaf( int ndxLeaf, int context ); + bool DispRay_EnumerateElement( int userId, int context ); + bool DispRayDistance_EnumerateElement( int userId, CBSPDispRayDistanceEnumerator* pEnum ); + + bool DispFaceList_EnumerateLeaf( int ndxLeaf, int context ); + bool DispFaceList_EnumerateElement( int userId, int context ); + +private: + + //========================================================================= + // + // BSP Tree Helpers + // + void InsertDispIntoTree( int ndxDisp ); + void RemoveDispFromTree( int ndxDisp ); + + //========================================================================= + // + // Displacement Data Loader (from .bsp) + // + void UnserializeDisps( void ); + void DispBuilderInit( CCoreDispInfo *pBuilderDisp, dface_t *pFace, int ndxFace ); + + //========================================================================= + // + // Sampling Helpers + // + void RadialLuxelBuild( CVRADDispColl *pDispTree, radial_t *pRadial, int ndxStyle, bool bBump ); + void RadialLuxelAddSamples( int ndxFace, Vector const &luxelPt, Vector const &luxelNormal, float radius, + radial_t *pRadial, int ndxRadial, bool bBump, int lightStyle ); + + void RadialPatchBuild( CVRADDispColl *pDispTree, radial_t *pRadial, bool bBump ); + void RadialLuxelAddPatch( int ndxFace, Vector const &luxelPt, + Vector const &luxelNormal, float radius, + radial_t *pRadial, int ndxRadial, bool bBump, + CUtlVector<CPatch*> &interestingPatches ); + + bool IsNeighbor( int iDispFace, int iNeighborFace ); + + void GetInterestingPatchesForLuxels( + int ndxFace, + CUtlVector<CPatch*> &interestingPatches, + float patchSampleRadius ); + +private: + + struct DispCollTree_t + { + CVRADDispColl *m_pDispTree; + BSPTreeDataHandle_t m_Handle; + }; + + struct EnumContext_t + { + DispTested_t *m_pDispTested; + Ray_t const *m_pRay; + }; + + CUtlVector<DispCollTree_t> m_DispTrees; + + IBSPTreeData *m_pBSPTreeData; + + CBSPDispRayEnumerator m_EnumDispRay; + CBSPDispFaceListEnumerator m_EnumDispFaceList; + + int sampleCount; + Vector *m_pSamplePos; + + CFastTimer m_Timer; +}; + +//----------------------------------------------------------------------------- +// Purpose: expose IVRadDispMgr to vrad +//----------------------------------------------------------------------------- + +static CVRadDispMgr s_DispMgr; + +IVRadDispMgr *StaticDispMgr( void ) +{ + return &s_DispMgr; +} + + +//============================================================================= +// +// Displacement/Face List +// +// ISpatialLeafEnumerator +bool CBSPDispFaceListEnumerator::EnumerateLeaf( int ndxLeaf, int context ) +{ + return s_DispMgr.DispFaceList_EnumerateLeaf( ndxLeaf, context ); +} + +// IBSPTreeDataEnumerator +bool FASTCALL CBSPDispFaceListEnumerator::EnumerateElement( int userId, int context ) +{ + return s_DispMgr.DispFaceList_EnumerateElement( userId, context ); +} + + +//============================================================================= +// +// RayEnumerator +// +bool CBSPDispRayEnumerator::EnumerateLeaf( int ndxLeaf, int context ) +{ + return s_DispMgr.DispRay_EnumerateLeaf( ndxLeaf, context ); +} + +bool FASTCALL CBSPDispRayEnumerator::EnumerateElement( int userId, int context ) +{ + return s_DispMgr.DispRay_EnumerateElement( userId, context ); +} + + +//----------------------------------------------------------------------------- +// Here's an enumerator that we use for testing against disps in a leaf... +//----------------------------------------------------------------------------- + +class CBSPDispRayDistanceEnumerator : public IBSPTreeDataEnumerator +{ +public: + CBSPDispRayDistanceEnumerator() : m_Distance(1.0f), m_pSurface(0) {} + + // IBSPTreeDataEnumerator + bool FASTCALL EnumerateElement( int userId, int context ) + { + return s_DispMgr.DispRayDistance_EnumerateElement( userId, this ); + } + + float m_Distance; + dface_t* m_pSurface; + DispTested_t *m_pDispTested; + Ray_t const *m_pRay; + Vector2D m_LuxelCoord; + Vector m_Normal; +}; + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +CVRadDispMgr::CVRadDispMgr() +{ + m_pBSPTreeData = CreateBSPTreeData(); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +CVRadDispMgr::~CVRadDispMgr() +{ + DestroyBSPTreeData( m_pBSPTreeData ); +} + + +//----------------------------------------------------------------------------- +// Insert a displacement into the tree for collision +//----------------------------------------------------------------------------- +void CVRadDispMgr::InsertDispIntoTree( int ndxDisp ) +{ + DispCollTree_t &dispTree = m_DispTrees[ndxDisp]; + CDispCollTree *pDispTree = dispTree.m_pDispTree; + + // get the bounding box of the tree + Vector boxMin, boxMax; + pDispTree->GetBounds( boxMin, boxMax ); + + // add the displacement to the tree so we will collide against it + dispTree.m_Handle = m_pBSPTreeData->Insert( ndxDisp, boxMin, boxMax ); +} + + +//----------------------------------------------------------------------------- +// Remove a displacement from the tree for collision +//----------------------------------------------------------------------------- +void CVRadDispMgr::RemoveDispFromTree( int ndxDisp ) +{ + // release the tree handle + if( m_DispTrees[ndxDisp].m_Handle != TREEDATA_INVALID_HANDLE ) + { + m_pBSPTreeData->Remove( m_DispTrees[ndxDisp].m_Handle ); + m_DispTrees[ndxDisp].m_Handle = TREEDATA_INVALID_HANDLE; + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVRadDispMgr::Init( void ) +{ + // initialize the bsp tree + m_pBSPTreeData->Init( ToolBSPTree() ); + + // read in displacements that have been compiled into the bsp file + UnserializeDisps(); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVRadDispMgr::Shutdown( void ) +{ + // remove all displacements from the tree + for( int ndxDisp = m_DispTrees.Size(); ndxDisp >= 0; ndxDisp-- ) + { + RemoveDispFromTree( ndxDisp ); + } + + // shutdown the bsp tree + m_pBSPTreeData->Shutdown(); + + // purge the displacement collision tree list + m_DispTrees.Purge(); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVRadDispMgr::DispBuilderInit( CCoreDispInfo *pBuilderDisp, dface_t *pFace, int ndxFace ) +{ + // get the .bsp displacement + ddispinfo_t *pDisp = &g_dispinfo[pFace->dispinfo]; + if( !pDisp ) + return; + + // + // initlialize the displacement base surface + // + CCoreDispSurface *pSurf = pBuilderDisp->GetSurface(); + pSurf->SetPointCount( 4 ); + pSurf->SetHandle( ndxFace ); + pSurf->SetContents( pDisp->contents ); + + Vector pt[4]; + int ndxPt; + for( ndxPt = 0; ndxPt < 4; ndxPt++ ) + { + int eIndex = dsurfedges[pFace->firstedge+ndxPt]; + if( eIndex < 0 ) + { + pSurf->SetPoint( ndxPt, dvertexes[dedges[-eIndex].v[1]].point ); + } + else + { + pSurf->SetPoint( ndxPt, dvertexes[dedges[eIndex].v[0]].point ); + } + + VectorCopy( pSurf->GetPoint(ndxPt), pt[ndxPt] ); + } + + // + // calculate the displacement surface normal + // + Vector vFaceNormal; + pSurf->GetNormal( vFaceNormal ); + for( ndxPt = 0; ndxPt < 4; ndxPt++ ) + { + pSurf->SetPointNormal( ndxPt, vFaceNormal ); + } + + // set the surface initial point info + pSurf->SetPointStart( pDisp->startPosition ); + pSurf->FindSurfPointStartIndex(); + pSurf->AdjustSurfPointData(); + + Vector vecTmp( texinfo[pFace->texinfo].lightmapVecsLuxelsPerWorldUnits[0][0], + texinfo[pFace->texinfo].lightmapVecsLuxelsPerWorldUnits[0][1], + texinfo[pFace->texinfo].lightmapVecsLuxelsPerWorldUnits[0][2] ); + int nLuxelsPerWorldUnit = static_cast<int>( 1.0f / VectorLength( vecTmp ) ); + Vector vecU( texinfo[pFace->texinfo].lightmapVecsLuxelsPerWorldUnits[0][0], + texinfo[pFace->texinfo].lightmapVecsLuxelsPerWorldUnits[0][1], + texinfo[pFace->texinfo].lightmapVecsLuxelsPerWorldUnits[0][2] ); + Vector vecV( texinfo[pFace->texinfo].lightmapVecsLuxelsPerWorldUnits[1][0], + texinfo[pFace->texinfo].lightmapVecsLuxelsPerWorldUnits[1][1], + texinfo[pFace->texinfo].lightmapVecsLuxelsPerWorldUnits[1][2] ); + pSurf->CalcLuxelCoords( nLuxelsPerWorldUnit, false, vecU, vecV ); + + pBuilderDisp->SetNeighborData( pDisp->m_EdgeNeighbors, pDisp->m_CornerNeighbors ); + + CDispVert *pVerts = &g_DispVerts[ pDisp->m_iDispVertStart ]; + CDispTri *pTris = &g_DispTris[pDisp->m_iDispTriStart]; + + // + // initialize the displacement data + // + pBuilderDisp->InitDispInfo( + pDisp->power, + pDisp->minTess, + pDisp->smoothingAngle, + pVerts, + pTris ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVRadDispMgr::UnserializeDisps( void ) +{ + // temporarily create the "builder" displacements + CUtlVector<CCoreDispInfo*> builderDisps; + for ( int iDisp = 0; iDisp < g_dispinfo.Count(); ++iDisp ) + { + CCoreDispInfo *pDisp = new CCoreDispInfo; + if ( !pDisp ) + { + builderDisps.Purge(); + return; + } + + int nIndex = builderDisps.AddToTail(); + pDisp->SetListIndex( nIndex ); + builderDisps[nIndex] = pDisp; + } + + // Set them up as CDispUtilsHelpers. + for ( int iDisp = 0; iDisp < g_dispinfo.Count(); ++iDisp ) + { + builderDisps[iDisp]->SetDispUtilsHelperInfo( builderDisps.Base(), g_dispinfo.Count() ); + } + + // + // find all faces with displacement data and initialize + // + for( int ndxFace = 0; ndxFace < numfaces; ndxFace++ ) + { + dface_t *pFace = &g_pFaces[ndxFace]; + if( ValidDispFace( pFace ) ) + { + DispBuilderInit( builderDisps[pFace->dispinfo], pFace, ndxFace ); + } + } + + // generate the displacement surfaces + for( int iDisp = 0; iDisp < g_dispinfo.Count(); ++iDisp ) + { + builderDisps[iDisp]->Create(); + } + + // smooth edge normals + SmoothNeighboringDispSurfNormals( builderDisps.Base(), g_dispinfo.Count() ); + + // + // create the displacement collision tree and add it to the bsp tree + // + CVRADDispColl *pDispTrees = new CVRADDispColl[g_dispinfo.Count()]; + if( !pDispTrees ) + return; + + m_DispTrees.AddMultipleToTail( g_dispinfo.Count() ); + + for( int iDisp = 0; iDisp < g_dispinfo.Count(); iDisp++ ) + { + pDispTrees[iDisp].Create( builderDisps[iDisp] ); + + m_DispTrees[iDisp].m_pDispTree = &pDispTrees[iDisp]; + m_DispTrees[iDisp].m_Handle = TREEDATA_INVALID_HANDLE; + + InsertDispIntoTree( iDisp ); + } + + // free "builder" disps + builderDisps.Purge(); +} + +//----------------------------------------------------------------------------- +// Purpose: create a set of patches for each displacement surface to transfer +// bounced light around with +//----------------------------------------------------------------------------- +void CVRadDispMgr::MakePatches( void ) +{ + // Collect stats - keep track of the total displacement surface area. + float flTotalArea = 0.0f; + + // Create patches for all of the displacements. + int nTreeCount = m_DispTrees.Size(); + for( int iTree = 0; iTree < nTreeCount; ++iTree ) + { + // Get the current displacement collision tree. + CVRADDispColl *pDispTree = m_DispTrees[iTree].m_pDispTree; + if( !pDispTree ) + continue; + + flTotalArea += pDispTree->CreateParentPatches(); + } + + // Print stats. + qprintf( "%i Displacements\n", nTreeCount ); + qprintf( "%i Square Feet [%.2f Square Inches]\n", ( int )( flTotalArea / 144.0f ), flTotalArea ); +} +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVRadDispMgr::SubdividePatch( int iPatch ) +{ + // Get the current patch to subdivide. + CPatch *pPatch = &g_Patches[iPatch]; + if ( !pPatch ) + return; + + // Create children patches. + DispCollTree_t &dispTree = m_DispTrees[g_pFaces[pPatch->faceNumber].dispinfo]; + CVRADDispColl *pTree = dispTree.m_pDispTree; + if( pTree ) + { + pTree->CreateChildPatches( iPatch, 0 ); + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVRadDispMgr::StartRayTest( DispTested_t &dispTested ) +{ + if( m_DispTrees.Size() > 0 ) + { + if( dispTested.m_pTested == 0 ) + { + dispTested.m_pTested = new int[m_DispTrees.Size()]; + memset( dispTested.m_pTested, 0, m_DispTrees.Size() * sizeof( int ) ); + dispTested.m_Enum = 0; + } + ++dispTested.m_Enum; + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CVRadDispMgr::ClipRayToDisp( DispTested_t &dispTested, Ray_t const &ray ) +{ + StartRayTest( dispTested ); + + EnumContext_t ctx; + ctx.m_pRay = &ray; + ctx.m_pDispTested = &dispTested; + + // If it got through without a hit, it returns true + return !m_pBSPTreeData->EnumerateLeavesAlongRay( ray, &m_EnumDispRay, ( int )&ctx ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CVRadDispMgr::ClipRayToDispInLeaf( DispTested_t &dispTested, Ray_t const &ray, + int ndxLeaf ) +{ + EnumContext_t ctx; + ctx.m_pRay = &ray; + ctx.m_pDispTested = &dispTested; + + return !m_pBSPTreeData->EnumerateElementsInLeaf( ndxLeaf, &m_EnumDispRay, ( int )&ctx ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVRadDispMgr::ClipRayToDispInLeaf( DispTested_t &dispTested, Ray_t const &ray, + int ndxLeaf, float& dist, dface_t*& pFace, Vector2D& luxelCoord ) +{ + CBSPDispRayDistanceEnumerator rayTestEnum; + rayTestEnum.m_pRay = &ray; + rayTestEnum.m_pDispTested = &dispTested; + + m_pBSPTreeData->EnumerateElementsInLeaf( ndxLeaf, &rayTestEnum, 0 ); + + dist = rayTestEnum.m_Distance; + pFace = rayTestEnum.m_pSurface; + if (pFace) + { + Vector2DCopy( rayTestEnum.m_LuxelCoord, luxelCoord ); + } +} + +void CVRadDispMgr::ClipRayToDispInLeaf( DispTested_t &dispTested, Ray_t const &ray, + int ndxLeaf, float& dist, Vector *pNormal ) +{ + CBSPDispRayDistanceEnumerator rayTestEnum; + rayTestEnum.m_pRay = &ray; + rayTestEnum.m_pDispTested = &dispTested; + + m_pBSPTreeData->EnumerateElementsInLeaf( ndxLeaf, &rayTestEnum, 0 ); + dist = rayTestEnum.m_Distance; + if ( rayTestEnum.m_pSurface ) + { + *pNormal = rayTestEnum.m_Normal; + } +} + +void CVRadDispMgr::AddPolysForRayTrace( void ) +{ + int nTreeCount = m_DispTrees.Size(); + for( int iTree = 0; iTree < nTreeCount; ++iTree ) + { + // Get the current displacement collision tree. + CVRADDispColl *pDispTree = m_DispTrees[iTree].m_pDispTree; + + // Add the triangles of the tree to the RT environment + pDispTree->AddPolysForRayTrace(); + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVRadDispMgr::GetDispSurfNormal( int ndxFace, Vector &pt, Vector &ptNormal, + bool bInside ) +{ + // get the displacement surface data + DispCollTree_t &dispTree = m_DispTrees[g_pFaces[ndxFace].dispinfo]; + CVRADDispColl *pDispTree = dispTree.m_pDispTree; + + // find the parameterized displacement indices + Vector2D uv; + pDispTree->BaseFacePlaneToDispUV( pt, uv ); + + if( bInside ) + { + if( uv[0] < 0.0f || uv[0] > 1.0f ) { Msg( "Disp UV (%f) outside bounds!\n", uv[0] ); } + if( uv[1] < 0.0f || uv[1] > 1.0f ) { Msg( "Disp UV (%f) outside bounds!\n", uv[1] ); } + } + + if( uv[0] < 0.0f ) { uv[0] = 0.0f; } + if( uv[0] > 1.0f ) { uv[0] = 1.0f; } + if( uv[1] < 0.0f ) { uv[1] = 0.0f; } + if( uv[1] > 1.0f ) { uv[1] = 1.0f; } + + // get the normal at "pt" + pDispTree->DispUVToSurfNormal( uv, ptNormal ); + + // get the new "pt" + pDispTree->DispUVToSurfPoint( uv, pt, 1.0f ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVRadDispMgr::GetDispSurf( int ndxFace, CVRADDispColl **ppDispTree ) +{ + DispCollTree_t &dispTree = m_DispTrees[g_pFaces[ndxFace].dispinfo]; + *ppDispTree = dispTree.m_pDispTree; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CVRadDispMgr::DispRay_EnumerateLeaf( int ndxLeaf, int context ) +{ + return m_pBSPTreeData->EnumerateElementsInLeaf( ndxLeaf, &m_EnumDispRay, context ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CVRadDispMgr::DispRay_EnumerateElement( int userId, int context ) +{ + DispCollTree_t &dispTree = m_DispTrees[userId]; + EnumContext_t *pCtx = ( EnumContext_t* )context; + + // don't test twice (check tested value) + if( pCtx->m_pDispTested->m_pTested[userId] == pCtx->m_pDispTested->m_Enum ) + return true; + + // set the tested value + pCtx->m_pDispTested->m_pTested[userId] = pCtx->m_pDispTested->m_Enum; + + // false mean stop iterating -- return false if we hit! (NOTE: opposite return + // result of the collision tree's ray test, thus the !) + CBaseTrace trace; + trace.fraction = 1.0f; + return ( !dispTree.m_pDispTree->AABBTree_Ray( *pCtx->m_pRay, pCtx->m_pRay->InvDelta(), &trace, true ) ); +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- + +bool CVRadDispMgr::DispRayDistance_EnumerateElement( int userId, CBSPDispRayDistanceEnumerator* pCtx ) +{ + DispCollTree_t &dispTree = m_DispTrees[userId]; + + // don't test twice (check tested value) + if( pCtx->m_pDispTested->m_pTested[userId] == pCtx->m_pDispTested->m_Enum ) + return true; + + // set the tested value + pCtx->m_pDispTested->m_pTested[userId] = pCtx->m_pDispTested->m_Enum; + + // Test the ray, if it's closer than previous tests, use it! + RayDispOutput_t output; + output.ndxVerts[0] = -1; + output.ndxVerts[1] = -1; + output.ndxVerts[2] = -1; + output.ndxVerts[3] = -1; + output.u = -1.0f; + output.v = -1.0f; + output.dist = FLT_MAX; + + if (dispTree.m_pDispTree->AABBTree_Ray( *pCtx->m_pRay, output )) + { + if (output.dist < pCtx->m_Distance) + { + pCtx->m_Distance = output.dist; + pCtx->m_pSurface = &g_pFaces[dispTree.m_pDispTree->GetParentIndex()]; + + // Get the luxel coordinate + ComputePointFromBarycentric( + dispTree.m_pDispTree->GetLuxelCoord(output.ndxVerts[0]), + dispTree.m_pDispTree->GetLuxelCoord(output.ndxVerts[1]), + dispTree.m_pDispTree->GetLuxelCoord(output.ndxVerts[2]), + output.u, output.v, pCtx->m_LuxelCoord ); + + Vector v0,v1,v2; + dispTree.m_pDispTree->GetVert( output.ndxVerts[0], v0 ); + dispTree.m_pDispTree->GetVert( output.ndxVerts[1], v1 ); + dispTree.m_pDispTree->GetVert( output.ndxVerts[2], v2 ); + Vector e0 = v1-v0; + Vector e1 = v2-v0; + pCtx->m_Normal = CrossProduct( e0, e1 ); + VectorNormalize(pCtx->m_Normal); + } + } + + return true; +} + +//----------------------------------------------------------------------------- +// Test a ray against a particular dispinfo +//----------------------------------------------------------------------------- + +/* +float CVRadDispMgr::ClipRayToDisp( Ray_t const &ray, int dispinfo ) +{ + assert( m_DispTrees.IsValidIndex(dispinfo) ); + + RayDispOutput_t output; + if (!m_DispTrees[dispinfo].m_pDispTree->AABBTree_Ray( ray, output )) + return 1.0f; + return output.dist; +} +*/ + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CVRadDispMgr::DispFaceList_EnumerateLeaf( int ndxLeaf, int context ) +{ + // + // add the faces found in this leaf to the face list + // + dleaf_t *pLeaf = &dleafs[ndxLeaf]; + for( int ndxFace = 0; ndxFace < pLeaf->numleaffaces; ndxFace++ ) + { + // get the current face index + int ndxLeafFace = pLeaf->firstleafface + ndxFace; + + // check to see if the face already lives in the list + int ndx; + int size = m_EnumDispFaceList.m_FaceList.Size(); + for( ndx = 0; ndx < size; ndx++ ) + { + if( m_EnumDispFaceList.m_FaceList[ndx] == ndxLeafFace ) + break; + } + + if( ndx == size ) + { + int ndxList = m_EnumDispFaceList.m_FaceList.AddToTail(); + m_EnumDispFaceList.m_FaceList[ndxList] = ndxLeafFace; + } + } + + return m_pBSPTreeData->EnumerateElementsInLeaf( ndxLeaf, &m_EnumDispFaceList, context ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CVRadDispMgr::DispFaceList_EnumerateElement( int userId, int context ) +{ + DispCollTree_t &dispTree = m_DispTrees[userId]; + CVRADDispColl *pDispTree = dispTree.m_pDispTree; + if( !pDispTree ) + return false; + + // check to see if the displacement already lives in the list + int ndx; + int size = m_EnumDispFaceList.m_DispList.Size(); + for( ndx = 0; ndx < size; ndx++ ) + { + if( m_EnumDispFaceList.m_DispList[ndx] == pDispTree ) + break; + } + + if( ndx == size ) + { + int ndxList = m_EnumDispFaceList.m_DispList.AddToTail(); + m_EnumDispFaceList.m_DispList[ndxList] = pDispTree; + } + + return true; +} + + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +inline void GetSampleLight( facelight_t *pFaceLight, int ndxStyle, bool bBumped, + int ndxSample, LightingValue_t *pSampleLight ) +{ +// SampleLight[0].Init( 20.0f, 10.0f, 10.0f ); +// return; + + // get sample from bumped lighting data + if( bBumped ) + { + for( int ndxBump = 0; ndxBump < ( NUM_BUMP_VECTS+1 ); ndxBump++ ) + { + pSampleLight[ndxBump] = pFaceLight->light[ndxStyle][ndxBump][ndxSample]; + } + } + // just a generally lit surface + else + { + pSampleLight[0] = pFaceLight->light[ndxStyle][0][ndxSample]; + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void AddSampleLightToRadial( Vector const &samplePos, Vector const &sampleNormal, + LightingValue_t *pSampleLight, float sampleRadius2, + Vector const &luxelPos, Vector const &luxelNormal, + radial_t *pRadial, int ndxRadial, bool bBumped, + bool bNeighborBumped ) +{ + // check normals to see if sample contributes any light at all + float angle = sampleNormal.Dot( luxelNormal ); + if ( angle < 0.15f ) + return; + + // calculate the light vector + Vector vSegment = samplePos - luxelPos; + + // get the distance to the light + float dist = vSegment.Length(); + float dist2 = dist * dist; + + // Check to see if the light is within the influence. + float influence = 1.0f - ( dist2 / ( sampleRadius2 ) ); + if( influence <= 0.0f ) + return; + + influence *= angle; + + if( bBumped ) + { + if( bNeighborBumped ) + { + for( int ndxBump = 0; ndxBump < ( NUM_BUMP_VECTS+1 ); ndxBump++ ) + { + pRadial->light[ndxBump][ndxRadial].AddWeighted( pSampleLight[ndxBump], influence ); + } + pRadial->weight[ndxRadial] += influence; + } + else + { + influence *= 0.05f; + for( int ndxBump = 0; ndxBump < ( NUM_BUMP_VECTS+1 ); ndxBump++ ) + { + pRadial->light[ndxBump][ndxRadial].AddWeighted( pSampleLight[0], influence ); + } + pRadial->weight[ndxRadial] += influence; + } + } + else + { + pRadial->light[0][ndxRadial].AddWeighted( pSampleLight[0], influence ); + pRadial->weight[ndxRadial] += influence; + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CVRadDispMgr::IsNeighbor( int iFace, int iNeighborFace ) +{ + if ( iFace == iNeighborFace ) + return true; + + faceneighbor_t *pFaceNeighbor = &faceneighbor[iFace]; + for ( int iNeighbor = 0; iNeighbor < pFaceNeighbor->numneighbors; iNeighbor++ ) + { + if ( pFaceNeighbor->neighbor[iNeighbor] == iNeighborFace ) + return true; + } + + return false; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVRadDispMgr::RadialLuxelAddSamples( int ndxFace, Vector const &luxelPt, Vector const &luxelNormal, float radius, + radial_t *pRadial, int ndxRadial, bool bBump, int lightStyle ) +{ + // calculate one over the voxel size + float ooVoxelSize = 1.0f / SAMPLEHASH_VOXEL_SIZE; + + // + // find voxel info + // + int voxelMin[3], voxelMax[3]; + for( int axis = 0; axis < 3; axis++ ) + { + voxelMin[axis] = ( int )( ( luxelPt[axis] - radius ) * ooVoxelSize ); + voxelMax[axis] = ( int )( ( luxelPt[axis] + radius ) * ooVoxelSize ) + 1; + } + + SampleData_t sampleData; + for( int ndxZ = voxelMin[2]; ndxZ < voxelMax[2] + 1; ndxZ++ ) + { + for( int ndxY = voxelMin[1]; ndxY < voxelMax[1] + 1; ndxY++ ) + { + for( int ndxX = voxelMin[0]; ndxX < voxelMax[0] + 1; ndxX++ ) + { + sampleData.x = ndxX * 100; + sampleData.y = ndxY * 10; + sampleData.z = ndxZ; + + UtlHashHandle_t handle = g_SampleHashTable.Find( sampleData ); + if( handle != g_SampleHashTable.InvalidHandle() ) + { + SampleData_t *pSampleData = &g_SampleHashTable.Element( handle ); + int count = pSampleData->m_Samples.Count(); + for( int ndx = 0; ndx < count; ndx++ ) + { + SampleHandle_t sampleHandle = pSampleData->m_Samples.Element( ndx ); + int ndxSample = ( sampleHandle & 0x0000ffff ); + int ndxFaceLight = ( ( sampleHandle >> 16 ) & 0x0000ffff ); + + facelight_t *pFaceLight = &facelight[ndxFaceLight]; + if( pFaceLight && IsNeighbor( ndxFace, ndxFaceLight ) ) + { + // + // check for similar lightstyles + // + dface_t *pFace = &g_pFaces[ndxFaceLight]; + if( pFace ) + { + int ndxNeighborStyle = -1; + for( int ndxLightStyle = 0; ndxLightStyle < MAXLIGHTMAPS; ndxLightStyle++ ) + { + if( pFace->styles[ndxLightStyle] == lightStyle ) + { + ndxNeighborStyle = ndxLightStyle; + break; + } + } + if( ndxNeighborStyle == -1 ) + continue; + + // is this surface bumped??? + bool bNeighborBump = texinfo[pFace->texinfo].flags & SURF_BUMPLIGHT ? true : false; + + LightingValue_t sampleLight[NUM_BUMP_VECTS+1]; + GetSampleLight( pFaceLight, ndxNeighborStyle, bNeighborBump, ndxSample, sampleLight ); + AddSampleLightToRadial( pFaceLight->sample[ndxSample].pos, pFaceLight->sample[ndxSample].normal, + sampleLight, radius*radius, luxelPt, luxelNormal, pRadial, ndxRadial, + bBump, bNeighborBump ); + } + } + } + } + } + } + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVRadDispMgr::RadialLuxelBuild( CVRADDispColl *pDispTree, radial_t *pRadial, + int ndxStyle, bool bBump ) +{ + // + // get data lighting data + // + int ndxFace = pDispTree->GetParentIndex(); + + dface_t *pFace = &g_pFaces[ndxFace]; + facelight_t *pFaceLight = &facelight[ndxFace]; + + // get the influence radius + float radius2 = pDispTree->GetSampleRadius2(); + float radius = ( float )sqrt( radius2 ); + + int radialSize = pRadial->w * pRadial->h; + for( int ndxRadial = 0; ndxRadial < radialSize; ndxRadial++ ) + { + RadialLuxelAddSamples( ndxFace, pFaceLight->luxel[ndxRadial], pFaceLight->luxelNormals[ndxRadial], + radius, pRadial, ndxRadial, bBump, pFace->styles[ndxStyle] ); + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +radial_t *CVRadDispMgr::BuildLuxelRadial( int ndxFace, int ndxStyle, bool bBump ) +{ + // allocate the radial + radial_t *pRadial = AllocateRadial( ndxFace ); + if( !pRadial ) + return NULL; + + // + // step 1: get the displacement surface to be lit + // + DispCollTree_t &dispTree = m_DispTrees[g_pFaces[ndxFace].dispinfo]; + CVRADDispColl *pDispTree = dispTree.m_pDispTree; + if( !pDispTree ) + return NULL; + + // step 2: build radial luxels + RadialLuxelBuild( pDispTree, pRadial, ndxStyle, bBump ); + + // step 3: return the built radial + return pRadial; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CVRadDispMgr::SampleRadial( int ndxFace, radial_t *pRadial, Vector const &vPos, int ndxLxl, + LightingValue_t *pLightSample, int sampleCount, bool bPatch ) +{ + bool bGoodSample = true; + for ( int count = 0; count < sampleCount; count++ ) + { + pLightSample[count].Zero(); + + if ( pRadial->weight[ndxLxl] > 0.0f ) + { + pLightSample[count].AddWeighted( pRadial->light[count][ndxLxl], ( 1.0f / pRadial->weight[ndxLxl] ) ); + } + else + { + // error, luxel has no samples (not for patches) + if ( !bPatch ) + { + // Yes, 2550 is correct! + // pLightSample[count].Init( 2550.0f, 0.0f, 2550.0f ); + if( count == 0 ) + bGoodSample = false; + } + } + } + + return bGoodSample; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void GetPatchLight( CPatch *pPatch, bool bBump, Vector *pPatchLight ) +{ + VectorCopy( pPatch->totallight.light[0], pPatchLight[0] ); + + if( bBump ) + { + for( int ndxBump = 1; ndxBump < ( NUM_BUMP_VECTS + 1 ); ndxBump++ ) + { + VectorCopy( pPatch->totallight.light[ndxBump], pPatchLight[ndxBump] ); + } + } +} + +extern void GetBumpNormals( const float* sVect, const float* tVect, const Vector& flatNormal, + const Vector& phongNormal, Vector bumpNormals[NUM_BUMP_VECTS] ); +extern void PreGetBumpNormalsForDisp( texinfo_t *pTexinfo, Vector &vecU, Vector &vecV, Vector &vecNormal ); + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void AddPatchLightToRadial( Vector const &patchOrigin, Vector const &patchNormal, + Vector *pPatchLight, float patchRadius2, + Vector const &luxelPos, Vector const &luxelNormal, + radial_t *pRadial, int ndxRadial, bool bBump, + bool bNeighborBump ) +{ + // calculate the light vector + Vector vSegment = patchOrigin - luxelPos; + + // get the distance to the light + float dist = vSegment.Length(); + float dist2 = dist * dist; + + // Check to see if the light is within the sample influence. + float influence = 1.0f - ( dist2 / ( patchRadius2 ) ); + if ( influence <= 0.0f ) + return; + + if( bBump ) + { + Vector normals[NUM_BUMP_VECTS+1]; + normals[0] = luxelNormal; + texinfo_t *pTexinfo = &texinfo[g_pFaces[pRadial->facenum].texinfo]; + Vector vecTexU, vecTexV; + PreGetBumpNormalsForDisp( pTexinfo, vecTexU, vecTexV, normals[0] ); + GetBumpNormals( vecTexU, vecTexV, normals[0], normals[0], &normals[1] ); + + if( bNeighborBump ) + { + float flScale = patchNormal.Dot( normals[0] ); + flScale = max( 0.0f, flScale ); + float flBumpInfluence = influence * flScale; + + for( int ndxBump = 0; ndxBump < ( NUM_BUMP_VECTS+1 ); ndxBump++ ) + { + pRadial->light[ndxBump][ndxRadial].AddWeighted( pPatchLight[ndxBump], flBumpInfluence ); + } + + pRadial->weight[ndxRadial] += flBumpInfluence; + } + else + { + float flScale = patchNormal.Dot( normals[0] ); + flScale = max( 0.0f, flScale ); + float flBumpInfluence = influence * flScale * 0.05f; + + for( int ndxBump = 0; ndxBump < ( NUM_BUMP_VECTS+1 ); ndxBump++ ) + { + pRadial->light[ndxBump][ndxRadial].AddWeighted( pPatchLight[0], flBumpInfluence ); + } + + pRadial->weight[ndxRadial] += flBumpInfluence; + } + } + else + { + float flScale = patchNormal.Dot( luxelNormal ); + flScale = max( 0.0f, flScale ); + influence *= flScale; + pRadial->light[0][ndxRadial].AddWeighted( pPatchLight[0], influence ); + + // add the weight value + pRadial->weight[ndxRadial] += influence; + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVRadDispMgr::RadialLuxelAddPatch( int ndxFace, Vector const &luxelPt, + Vector const &luxelNormal, float radius, + radial_t *pRadial, int ndxRadial, bool bBump, + CUtlVector<CPatch*> &interestingPatches ) +{ +#ifdef SAMPLEHASH_QUERY_ONCE + for ( int i=0; i < interestingPatches.Count(); i++ ) + { + CPatch *pPatch = interestingPatches[i]; + bool bNeighborBump = texinfo[g_pFaces[pPatch->faceNumber].texinfo].flags & SURF_BUMPLIGHT ? true : false; + + Vector patchLight[NUM_BUMP_VECTS+1]; + GetPatchLight( pPatch, bBump, patchLight ); + AddPatchLightToRadial( pPatch->origin, pPatch->normal, patchLight, radius*radius, + luxelPt, luxelNormal, pRadial, ndxRadial, bBump, bNeighborBump ); + } +#else + // calculate one over the voxel size + float ooVoxelSize = 1.0f / SAMPLEHASH_VOXEL_SIZE; + + // + // find voxel info + // + int voxelMin[3], voxelMax[3]; + for ( int axis = 0; axis < 3; axis++ ) + { + voxelMin[axis] = ( int )( ( luxelPt[axis] - radius ) * ooVoxelSize ); + voxelMax[axis] = ( int )( ( luxelPt[axis] + radius ) * ooVoxelSize ) + 1; + } + + unsigned short curIterationKey = IncrementPatchIterationKey(); + PatchSampleData_t patchData; + for ( int ndxZ = voxelMin[2]; ndxZ < voxelMax[2] + 1; ndxZ++ ) + { + for ( int ndxY = voxelMin[1]; ndxY < voxelMax[1] + 1; ndxY++ ) + { + for ( int ndxX = voxelMin[0]; ndxX < voxelMax[0] + 1; ndxX++ ) + { + patchData.x = ndxX * 100; + patchData.y = ndxY * 10; + patchData.z = ndxZ; + + UtlHashHandle_t handle = g_PatchSampleHashTable.Find( patchData ); + if ( handle != g_PatchSampleHashTable.InvalidHandle() ) + { + PatchSampleData_t *pPatchData = &g_PatchSampleHashTable.Element( handle ); + int count = pPatchData->m_ndxPatches.Count(); + for ( int ndx = 0; ndx < count; ndx++ ) + { + int ndxPatch = pPatchData->m_ndxPatches.Element( ndx ); + CPatch *pPatch = &g_Patches.Element( ndxPatch ); + if ( pPatch && pPatch->m_IterationKey != curIterationKey ) + { + pPatch->m_IterationKey = curIterationKey; + + if ( IsNeighbor( ndxFace, pPatch->faceNumber ) ) + { + bool bNeighborBump = texinfo[g_pFaces[pPatch->faceNumber].texinfo].flags & SURF_BUMPLIGHT ? true : false; + + Vector patchLight[NUM_BUMP_VECTS+1]; + GetPatchLight( pPatch, bBump, patchLight ); + AddPatchLightToRadial( pPatch->origin, pPatch->normal, patchLight, radius*radius, + luxelPt, luxelNormal, pRadial, ndxRadial, bBump, bNeighborBump ); + } + } + } + } + } + } + } +#endif +} + + +void CVRadDispMgr::GetInterestingPatchesForLuxels( + int ndxFace, + CUtlVector<CPatch*> &interestingPatches, + float patchSampleRadius ) +{ + facelight_t *pFaceLight = &facelight[ndxFace]; + + // Get the max bounds of all voxels that these luxels touch. + Vector vLuxelMin( FLT_MAX, FLT_MAX, FLT_MAX ); + Vector vLuxelMax( -FLT_MAX, -FLT_MAX, -FLT_MAX ); + for ( int i=0; i < pFaceLight->numluxels; i++ ) + { + VectorMin( pFaceLight->luxel[i], vLuxelMin, vLuxelMin ); + VectorMax( pFaceLight->luxel[i], vLuxelMax, vLuxelMax ); + } + + int allVoxelMin[3], allVoxelMax[3]; + for ( int axis = 0; axis < 3; axis++ ) + { + allVoxelMin[axis] = ( int )( ( vLuxelMin[axis] - patchSampleRadius ) / SAMPLEHASH_VOXEL_SIZE ); + allVoxelMax[axis] = ( int )( ( vLuxelMax[axis] + patchSampleRadius ) / SAMPLEHASH_VOXEL_SIZE ) + 1; + } + int allVoxelSize[3] = { allVoxelMax[0] - allVoxelMin[0], allVoxelMax[1] - allVoxelMin[1], allVoxelMax[2] - allVoxelMin[2] }; + + + // Now figure out exactly which voxels these luxels touch. + CUtlVector<unsigned char> voxelBits; + voxelBits.SetSize( ((allVoxelSize[0] * allVoxelSize[1] * allVoxelSize[2]) + 7) / 8 ); + memset( voxelBits.Base(), 0, voxelBits.Count() ); + + for ( int i=0; i < pFaceLight->numluxels; i++ ) + { + int voxelMin[3], voxelMax[3]; + for ( int axis=0; axis < 3; axis++ ) + { + voxelMin[axis] = ( int )( ( pFaceLight->luxel[i][axis] - patchSampleRadius ) / SAMPLEHASH_VOXEL_SIZE ); + voxelMax[axis] = ( int )( ( pFaceLight->luxel[i][axis] + patchSampleRadius ) / SAMPLEHASH_VOXEL_SIZE ) + 1; + } + + for ( int x=voxelMin[0]; x < voxelMax[0]; x++ ) + { + for ( int y=voxelMin[1]; y < voxelMax[1]; y++ ) + { + for ( int z=voxelMin[2]; z < voxelMax[2]; z++ ) + { + int iBit = (z - allVoxelMin[2])*(allVoxelSize[0]*allVoxelSize[1]) + + (y-allVoxelMin[1])*allVoxelSize[0] + + (x-allVoxelMin[0]); + voxelBits[iBit>>3] |= (1 << (iBit & 7)); + } + } + } + } + + + // Now get the list of patches that touch those voxels. + unsigned short curIterationKey = IncrementPatchIterationKey(); + + for ( int x=0; x < allVoxelSize[0]; x++ ) + { + for ( int y=0; y < allVoxelSize[1]; y++ ) + { + for ( int z=0; z < allVoxelSize[2]; z++ ) + { + // Make sure this voxel has any luxels that care about it. + int iBit = z*(allVoxelSize[0]*allVoxelSize[1]) + y*allVoxelSize[0] + x; + unsigned char val = voxelBits[iBit>>3] & (1 << (iBit & 7)); + if ( !val ) + continue; + + PatchSampleData_t patchData; + patchData.x = (x + allVoxelMin[0]) * 100; + patchData.y = (y + allVoxelMin[1]) * 10; + patchData.z = (z + allVoxelMin[2]); + + UtlHashHandle_t handle = g_PatchSampleHashTable.Find( patchData ); + if ( handle != g_PatchSampleHashTable.InvalidHandle() ) + { + PatchSampleData_t *pPatchData = &g_PatchSampleHashTable.Element( handle ); + + // For all patches that touch this hash table element.. + for ( int ndx = 0; ndx < pPatchData->m_ndxPatches.Count(); ndx++ ) + { + int ndxPatch = pPatchData->m_ndxPatches.Element( ndx ); + CPatch *pPatch = &g_Patches.Element( ndxPatch ); + + // If we haven't touched the patch already and it's a valid neighbor, then we want to use it. + if ( pPatch && pPatch->m_IterationKey != curIterationKey ) + { + pPatch->m_IterationKey = curIterationKey; + + if ( IsNeighbor( ndxFace, pPatch->faceNumber ) ) + { + interestingPatches.AddToTail( pPatch ); + } + } + } + } + } + } + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVRadDispMgr::RadialPatchBuild( CVRADDispColl *pDispTree, radial_t *pRadial, + bool bBump ) +{ + // + // get data lighting data + // + int ndxFace = pDispTree->GetParentIndex(); + facelight_t *pFaceLight = &facelight[ndxFace]; + + // get the influence radius + float radius2 = pDispTree->GetPatchSampleRadius2(); + float radius = ( float )sqrt( radius2 ); + + CUtlVector<CPatch*> interestingPatches; +#ifdef SAMPLEHASH_QUERY_ONCE + GetInterestingPatchesForLuxels( ndxFace, interestingPatches, radius ); +#endif + + int radialSize = pRadial->w * pRadial->h; + for( int ndxRadial = 0; ndxRadial < radialSize; ndxRadial++ ) + { + RadialLuxelAddPatch( + ndxFace, + pFaceLight->luxel[ndxRadial], + pFaceLight->luxelNormals[ndxRadial], + radius, + pRadial, + ndxRadial, + bBump, + interestingPatches ); + } +} + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +radial_t *CVRadDispMgr::BuildPatchRadial( int ndxFace, bool bBump ) +{ + // allocate the radial + radial_t *pRadial = AllocateRadial( ndxFace ); + if( !pRadial ) + return NULL; + + // + // step 1: get the displacement surface to be lit + // + DispCollTree_t &dispTree = m_DispTrees[g_pFaces[ndxFace].dispinfo]; + CVRADDispColl *pDispTree = dispTree.m_pDispTree; + if( !pDispTree ) + return NULL; + + // step 2: build radial of patch light + RadialPatchBuild( pDispTree, pRadial, bBump ); + + // step 3: return the built radial + return pRadial; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool SampleInSolid( sample_t *pSample ) +{ + int ndxLeaf = PointLeafnum( pSample->pos ); + return ( dleafs[ndxLeaf].contents == CONTENTS_SOLID ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVRadDispMgr::InsertSamplesDataIntoHashTable( void ) +{ + int totalSamples = 0; +#if 0 + int totalSamplesInSolid = 0; +#endif + + for( int ndxFace = 0; ndxFace < numfaces; ndxFace++ ) + { + dface_t *pFace = &g_pFaces[ndxFace]; + facelight_t *pFaceLight = &facelight[ndxFace]; + if( !pFace || !pFaceLight ) + continue; + + if( texinfo[pFace->texinfo].flags & TEX_SPECIAL ) + continue; + +#if 0 + bool bDisp = ( pFace->dispinfo != -1 ); +#endif + // + // for each sample + // + for( int ndxSample = 0; ndxSample < pFaceLight->numsamples; ndxSample++ ) + { + sample_t *pSample = &pFaceLight->sample[ndxSample]; + if( pSample ) + { +#if 0 + if( bDisp ) + { + // test sample to see if the displacement samples resides in solid + if( SampleInSolid( pSample ) ) + { + totalSamplesInSolid++; + continue; + } + } +#endif + + // create the sample handle + SampleHandle_t sampleHandle = ndxSample; + sampleHandle |= ( ndxFace << 16 ); + + SampleData_AddSample( pSample, sampleHandle ); + } + + } + + totalSamples += pFaceLight->numsamples; + } + +#if 0 + // not implemented yet!!! + Msg( "%d samples in solid\n", totalSamplesInSolid ); +#endif + + // log the distribution + SampleData_Log(); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVRadDispMgr::InsertPatchSampleDataIntoHashTable( void ) +{ + // don't insert patch samples if we are not bouncing light + if( numbounce <= 0 ) + return; + + int totalPatchSamples = 0; + + for( int ndxFace = 0; ndxFace < numfaces; ndxFace++ ) + { + dface_t *pFace = &g_pFaces[ndxFace]; + facelight_t *pFaceLight = &facelight[ndxFace]; + if( !pFace || !pFaceLight ) + continue; + + if( texinfo[pFace->texinfo].flags & TEX_SPECIAL ) + continue; + + // + // for each patch + // + CPatch *pNextPatch = NULL; + if( g_FacePatches.Element( ndxFace ) != g_FacePatches.InvalidIndex() ) + { + for( CPatch *pPatch = &g_Patches.Element( g_FacePatches.Element( ndxFace ) ); pPatch; pPatch = pNextPatch ) + { + // next patch + pNextPatch = NULL; + if( pPatch->ndxNext != g_Patches.InvalidIndex() ) + { + pNextPatch = &g_Patches.Element( pPatch->ndxNext ); + } + + // skip patches with children + if( pPatch->child1 != g_Patches.InvalidIndex() ) + continue; + + int ndxPatch = pPatch - g_Patches.Base(); + PatchSampleData_AddSample( pPatch, ndxPatch ); + + totalPatchSamples++; + } + } + } +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVRadDispMgr::StartTimer( const char *name ) +{ + Msg( name ); + m_Timer.Start(); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +void CVRadDispMgr::EndTimer( void ) +{ + m_Timer.End(); + CCycleCount duration = m_Timer.GetDuration(); + double seconds = duration.GetSeconds(); + + Msg( "Done<%1.4lf sec>\n", seconds ); +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CVRadDispMgr::BuildDispSamples( lightinfo_t *pLightInfo, facelight_t *pFaceLight, int ndxFace ) +{ + // get the tree assosciated with the face + DispCollTree_t &dispTree = m_DispTrees[g_pFaces[ndxFace].dispinfo]; + CVRADDispColl *pDispTree = dispTree.m_pDispTree; + if( !pDispTree ) + return false; + + // lightmap size + int width = pLightInfo->face->m_LightmapTextureSizeInLuxels[0]+1; + int height = pLightInfo->face->m_LightmapTextureSizeInLuxels[1]+1; + + // calculate the steps in uv space + float stepU = 1.0f / ( float )width; + float stepV = 1.0f / ( float )height; + float halfStepU = stepU * 0.5f; + float halfStepV = stepV * 0.5f; + + // + // build the winding points (used to generate world space winding and + // calculate the area of the "sample") + // + int ndxU, ndxV; + + CUtlVector<sample_t> samples; + samples.SetCount( SINGLEMAP ); + sample_t *pSamples = samples.Base(); + + CUtlVector<Vector> worldPoints; + worldPoints.SetCount( SINGLEMAP ); + Vector *pWorldPoints = worldPoints.Base(); + + for( ndxV = 0; ndxV < ( height + 1 ); ndxV++ ) + { + for( ndxU = 0; ndxU < ( width + 1 ); ndxU++ ) + { + int ndx = ( ndxV * ( width + 1 ) ) + ndxU; + + Vector2D uv( ndxU * stepU, ndxV * stepV ); + pDispTree->DispUVToSurfPoint( uv, pWorldPoints[ndx], 0.0f ); + } + } + + for( ndxV = 0; ndxV < height; ndxV++ ) + { + for( ndxU = 0; ndxU < width; ndxU++ ) + { + // build the winding + winding_t *pWinding = AllocWinding( 4 ); + if( pWinding ) + { + pWinding->numpoints = 4; + pWinding->p[0] = pWorldPoints[(ndxV*(width+1))+ndxU]; + pWinding->p[1] = pWorldPoints[((ndxV+1)*(width+1))+ndxU]; + pWinding->p[2] = pWorldPoints[((ndxV+1)*(width+1))+(ndxU+1)]; + pWinding->p[3] = pWorldPoints[(ndxV*(width+1))+(ndxU+1)]; + + // calculate the area + float area = WindingArea( pWinding ); + + int ndxSample = ( ndxV * width ) + ndxU; + pSamples[ndxSample].w = pWinding; + pSamples[ndxSample].area = area; + } + else + { + Msg( "BuildDispSamples: WARNING - failed winding allocation\n" ); + } + } + } + + // + // build the samples points (based on s, t and sampleoffset (center of samples); + // generates world space position and normal) + // + for( ndxV = 0; ndxV < height; ndxV++ ) + { + for( ndxU = 0; ndxU < width; ndxU++ ) + { + int ndxSample = ( ndxV * width ) + ndxU; + pSamples[ndxSample].s = ndxU; + pSamples[ndxSample].t = ndxV; + pSamples[ndxSample].coord[0] = ( ndxU * stepU ) + halfStepU; + pSamples[ndxSample].coord[1] = ( ndxV * stepV ) + halfStepV; + pDispTree->DispUVToSurfPoint( pSamples[ndxSample].coord, pSamples[ndxSample].pos, 1.0f ); + pDispTree->DispUVToSurfNormal( pSamples[ndxSample].coord, pSamples[ndxSample].normal ); + } + } + + // + // copy over samples + // + pFaceLight->numsamples = width * height; + pFaceLight->sample = ( sample_t* )calloc( pFaceLight->numsamples, sizeof( *pFaceLight->sample ) ); + if( !pFaceLight->sample ) + return false; + + memcpy( pFaceLight->sample, pSamples, pFaceLight->numsamples * sizeof( *pFaceLight->sample ) ); + + // statistics - warning?! + if( pFaceLight->numsamples == 0 ) + { + Msg( "BuildDispSamples: WARNING - no samples %d\n", pLightInfo->face - g_pFaces ); + } + + return true; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CVRadDispMgr::BuildDispLuxels( lightinfo_t *pLightInfo, facelight_t *pFaceLight, int ndxFace ) +{ + // get the tree assosciated with the face + DispCollTree_t &dispTree = m_DispTrees[g_pFaces[ndxFace].dispinfo]; + CVRADDispColl *pDispTree = dispTree.m_pDispTree; + if( !pDispTree ) + return false; + + // lightmap size + int width = pLightInfo->face->m_LightmapTextureSizeInLuxels[0]+1; + int height = pLightInfo->face->m_LightmapTextureSizeInLuxels[1]+1; + + // calcuate actual luxel points + pFaceLight->numluxels = width * height; + pFaceLight->luxel = ( Vector* )calloc( pFaceLight->numluxels, sizeof( *pFaceLight->luxel ) ); + pFaceLight->luxelNormals = ( Vector* )calloc( pFaceLight->numluxels, sizeof( Vector ) ); + if( !pFaceLight->luxel || !pFaceLight->luxelNormals ) + return false; + + float stepU = 1.0f / ( float )( width - 1 ); + float stepV = 1.0f / ( float )( height - 1 ); + + for( int ndxV = 0; ndxV < height; ndxV++ ) + { + for( int ndxU = 0; ndxU < width; ndxU++ ) + { + int ndxLuxel = ( ndxV * width ) + ndxU; + + Vector2D uv( ndxU * stepU, ndxV * stepV ); + pDispTree->DispUVToSurfPoint( uv, pFaceLight->luxel[ndxLuxel], 1.0f ); + pDispTree->DispUVToSurfNormal( uv, pFaceLight->luxelNormals[ndxLuxel] ); + } + } + + return true; +} + + +//----------------------------------------------------------------------------- +//----------------------------------------------------------------------------- +bool CVRadDispMgr::BuildDispSamplesAndLuxels_DoFast( lightinfo_t *pLightInfo, facelight_t *pFaceLight, int ndxFace ) +{ + // get the tree assosciated with the face + DispCollTree_t &dispTree = m_DispTrees[g_pFaces[ndxFace].dispinfo]; + CVRADDispColl *pDispTree = dispTree.m_pDispTree; + if( !pDispTree ) + return false; + + // lightmap size + int width = pLightInfo->face->m_LightmapTextureSizeInLuxels[0]+1; + int height = pLightInfo->face->m_LightmapTextureSizeInLuxels[1]+1; + + // calcuate actual luxel points + pFaceLight->numsamples = width * height; + pFaceLight->sample = ( sample_t* )calloc( pFaceLight->numsamples, sizeof( *pFaceLight->sample ) ); + if( !pFaceLight->sample ) + return false; + + pFaceLight->numluxels = width * height; + pFaceLight->luxel = ( Vector* )calloc( pFaceLight->numluxels, sizeof( *pFaceLight->luxel ) ); + pFaceLight->luxelNormals = ( Vector* )calloc( pFaceLight->numluxels, sizeof( Vector ) ); + if( !pFaceLight->luxel || !pFaceLight->luxelNormals ) + return false; + + float stepU = 1.0f / ( float )( width - 1 ); + float stepV = 1.0f / ( float )( height - 1 ); + float halfStepU = stepU * 0.5f; + float halfStepV = stepV * 0.5f; + + for( int ndxV = 0; ndxV < height; ndxV++ ) + { + for( int ndxU = 0; ndxU < width; ndxU++ ) + { + int ndx = ( ndxV * width ) + ndxU; + + pFaceLight->sample[ndx].s = ndxU; + pFaceLight->sample[ndx].t = ndxV; + pFaceLight->sample[ndx].coord[0] = ( ndxU * stepU ) + halfStepU; + pFaceLight->sample[ndx].coord[1] = ( ndxV * stepV ) + halfStepV; + + pDispTree->DispUVToSurfPoint( pFaceLight->sample[ndx].coord, pFaceLight->sample[ndx].pos, 1.0f ); + pDispTree->DispUVToSurfNormal( pFaceLight->sample[ndx].coord, pFaceLight->sample[ndx].normal ); + + pFaceLight->luxel[ndx] = pFaceLight->sample[ndx].pos; + pFaceLight->luxelNormals[ndx] = pFaceLight->sample[ndx].normal; + } + } + + return true; +} diff --git a/utils/vrad/vraddll.cpp b/utils/vrad/vraddll.cpp new file mode 100644 index 0000000..bc9f2f4 --- /dev/null +++ b/utils/vrad/vraddll.cpp @@ -0,0 +1,243 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +//#include <strstrea.h> +#include "vraddll.h" +#include "bsplib.h" +#include "vrad.h" +#include "map_shared.h" +#include "lightmap.h" +#include "threads.h" + + +static CUtlVector<unsigned char> g_LastGoodLightData; +static CUtlVector<unsigned char> g_FacesTouched; + + +static CVRadDLL g_VRadDLL; +EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CVRadDLL, IVRadDLL, VRAD_INTERFACE_VERSION, g_VRadDLL ); +EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CVRadDLL, ILaunchableDLL, LAUNCHABLE_DLL_INTERFACE_VERSION, g_VRadDLL ); + + +// ---------------------------------------------------------------------------- // +// temporary static array data size tracking +// original data size = 143 megs +// - converting ddispindices, ddispverts, g_dispinfo, and dlightdata to CUtlVector +// - 51 megs +// ---------------------------------------------------------------------------- // + +class dat +{ +public: + char *name; + int size; +}; +#define DATENTRY(name) {#name, sizeof(name)} + +dat g_Dats[] = +{ + DATENTRY(dmodels), + DATENTRY(dvisdata), + DATENTRY(dlightdataLDR), + DATENTRY(dlightdataHDR), + DATENTRY(dentdata), + DATENTRY(dleafs), + DATENTRY(dplanes), + DATENTRY(dvertexes), + DATENTRY(g_vertnormalindices), + DATENTRY(g_vertnormals), + DATENTRY(texinfo), + DATENTRY(dtexdata), + DATENTRY(g_dispinfo), + DATENTRY(dorigfaces), + DATENTRY(g_primitives), + DATENTRY(g_primverts), + DATENTRY(g_primindices), + DATENTRY(dfaces), + DATENTRY(dedges), + DATENTRY(dleaffaces), + DATENTRY(dleafbrushes), + DATENTRY(dsurfedges), + DATENTRY(dbrushes), + DATENTRY(dbrushsides), + DATENTRY(dareas), + DATENTRY(dareaportals), + DATENTRY(dworldlights), + DATENTRY(dleafwaterdata), + DATENTRY(g_ClipPortalVerts), + DATENTRY(g_CubemapSamples), + DATENTRY(g_TexDataStringData), + DATENTRY(g_TexDataStringTable), + DATENTRY(g_Overlays) +}; + +int CalcDatSize() +{ + int ret = 0; + int count = sizeof( g_Dats ) / sizeof( g_Dats[0] ); + + int i; + for( i=1; i < count; i++ ) + { + if( g_Dats[i-1].size > g_Dats[i].size ) + { + dat temp = g_Dats[i-1]; + g_Dats[i-1] = g_Dats[i]; + g_Dats[i] = temp; + + if( i > 1 ) + i -= 2; + else + i -= 1; + } + } + + for( i=0; i < count; i++ ) + ret += g_Dats[i].size; + + return ret; +} + +int g_TotalDatSize = CalcDatSize(); + + + + +int CVRadDLL::main( int argc, char **argv ) +{ + return VRAD_Main( argc, argv ); +} + + +bool CVRadDLL::Init( char const *pFilename ) +{ + VRAD_Init(); + + // Set options and run vrad startup code. + do_fast = true; + g_bLowPriorityThreads = true; + g_pIncremental = GetIncremental(); + + VRAD_LoadBSP( pFilename ); + return true; +} + + +void CVRadDLL::Release() +{ +} + + +void CVRadDLL::GetBSPInfo( CBSPInfo *pInfo ) +{ + pInfo->dlightdata = pdlightdata->Base(); + pInfo->lightdatasize = pdlightdata->Count(); + + pInfo->dfaces = dfaces; + pInfo->m_pFacesTouched = g_FacesTouched.Base(); + pInfo->numfaces = numfaces; + + pInfo->dvertexes = dvertexes; + pInfo->numvertexes = numvertexes; + + pInfo->dedges = dedges; + pInfo->numedges = numedges; + + pInfo->dsurfedges = dsurfedges; + pInfo->numsurfedges = numsurfedges; + + pInfo->texinfo = texinfo.Base(); + pInfo->numtexinfo = texinfo.Count(); + + pInfo->g_dispinfo = g_dispinfo.Base(); + pInfo->g_numdispinfo = g_dispinfo.Count(); + + pInfo->dtexdata = dtexdata; + pInfo->numtexdata = numtexdata; + + pInfo->texDataStringData = g_TexDataStringData.Base(); + pInfo->nTexDataStringData = g_TexDataStringData.Count(); + + pInfo->texDataStringTable = g_TexDataStringTable.Base(); + pInfo->nTexDataStringTable = g_TexDataStringTable.Count(); +} + + +bool CVRadDLL::DoIncrementalLight( char const *pVMFFile ) +{ + char tempPath[MAX_PATH], tempFilename[MAX_PATH]; + GetTempPath( sizeof( tempPath ), tempPath ); + GetTempFileName( tempPath, "vmf_entities_", 0, tempFilename ); + + FileHandle_t fp = g_pFileSystem->Open( tempFilename, "wb" ); + if( !fp ) + return false; + + g_pFileSystem->Write( pVMFFile, strlen(pVMFFile)+1, fp ); + g_pFileSystem->Close( fp ); + + // Parse the new entities. + if( !LoadEntsFromMapFile( tempFilename ) ) + return false; + + // Create lights. + CreateDirectLights(); + + // set up sky cameras + ProcessSkyCameras(); + + g_bInterrupt = false; + if( RadWorld_Go() ) + { + // Save off the last finished lighting results for the BSP. + g_LastGoodLightData.CopyArray( pdlightdata->Base(), pdlightdata->Count() ); + if( g_pIncremental ) + g_pIncremental->GetFacesTouched( g_FacesTouched ); + + return true; + } + else + { + g_iCurFace = 0; + return false; + } +} + + +bool CVRadDLL::Serialize() +{ + if( !g_pIncremental ) + return false; + + if( g_LastGoodLightData.Count() > 0 ) + { + pdlightdata->CopyArray( g_LastGoodLightData.Base(), g_LastGoodLightData.Count() ); + + if( g_pIncremental->Serialize() ) + { + // Delete this so it doesn't keep re-saving it. + g_LastGoodLightData.Purge(); + return true; + } + } + + return false; +} + + +float CVRadDLL::GetPercentComplete() +{ + return (float)g_iCurFace / numfaces; +} + + +void CVRadDLL::Interrupt() +{ + g_bInterrupt = true; +} + + diff --git a/utils/vrad/vraddll.h b/utils/vrad/vraddll.h new file mode 100644 index 0000000..988daca --- /dev/null +++ b/utils/vrad/vraddll.h @@ -0,0 +1,34 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $NoKeywords: $ +//=============================================================================// + +#ifndef VRADDLL_H +#define VRADDLL_H +#ifdef _WIN32 +#pragma once +#endif + + +#include "ivraddll.h" +#include "ilaunchabledll.h" + + +class CVRadDLL : public IVRadDLL, public ILaunchableDLL +{ +// IVRadDLL overrides. +public: + virtual int main( int argc, char **argv ); + virtual bool Init( char const *pFilename ); + virtual void Release(); + virtual void GetBSPInfo( CBSPInfo *pInfo ); + virtual bool DoIncrementalLight( char const *pVMFFile ); + virtual bool Serialize(); + virtual float GetPercentComplete(); + virtual void Interrupt(); +}; + + +#endif // VRADDLL_H diff --git a/utils/vrad/vradstaticprops.cpp b/utils/vrad/vradstaticprops.cpp new file mode 100644 index 0000000..e4d92ed --- /dev/null +++ b/utils/vrad/vradstaticprops.cpp @@ -0,0 +1,2694 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +// $Revision: $ +// $NoKeywords: $ +// +// This file contains code to allow us to associate client data with bsp leaves. +// +//=============================================================================// + +#include "vrad.h" +#include "mathlib/vector.h" +#include "UtlBuffer.h" +#include "utlvector.h" +#include "GameBSPFile.h" +#include "BSPTreeData.h" +#include "VPhysics_Interface.h" +#include "Studio.h" +#include "Optimize.h" +#include "Bsplib.h" +#include "CModel.h" +#include "PhysDll.h" +#include "phyfile.h" +#include "collisionutils.h" +#include "tier1/KeyValues.h" +#include "pacifier.h" +#include "materialsystem/imaterial.h" +#include "materialsystem/hardwareverts.h" +#include "materialsystem/hardwaretexels.h" +#include "byteswap.h" +#include "mpivrad.h" +#include "vtf/vtf.h" +#include "tier1/utldict.h" +#include "tier1/utlsymbol.h" +#include "bitmap/tgawriter.h" + +#include "messbuf.h" +#include "vmpi.h" +#include "vmpi_distribute_work.h" + + +#define ALIGN_TO_POW2(x,y) (((x)+(y-1))&~(y-1)) + +// identifies a vertex embedded in solid +// lighting will be copied from nearest valid neighbor +struct badVertex_t +{ + int m_ColorVertex; + Vector m_Position; + Vector m_Normal; +}; + +// a final colored vertex +struct colorVertex_t +{ + Vector m_Color; + Vector m_Position; + bool m_bValid; +}; + +// a texel suitable for a model +struct colorTexel_t +{ + Vector m_Color; + Vector m_WorldPosition; + Vector m_WorldNormal; + float m_fDistanceToTri; // If we are outside of the triangle, how far away is it? + bool m_bValid; + bool m_bPossiblyInteresting; + +}; + +class CComputeStaticPropLightingResults +{ +public: + ~CComputeStaticPropLightingResults() + { + m_ColorVertsArrays.PurgeAndDeleteElements(); + m_ColorTexelsArrays.PurgeAndDeleteElements(); + } + + CUtlVector< CUtlVector<colorVertex_t>* > m_ColorVertsArrays; + CUtlVector< CUtlVector<colorTexel_t>* > m_ColorTexelsArrays; +}; + +//----------------------------------------------------------------------------- +struct Rasterizer +{ + struct Location + { + Vector barycentric; + Vector2D uv; + bool insideTriangle; + }; + + Rasterizer(Vector2D t0, Vector2D t1, Vector2D t2, size_t resX, size_t resY) + : mT0(t0) + , mT1(t1) + , mT2(t2) + , mResX(resX) + , mResY(resY) + , mUvStepX(1.0f / resX) + , mUvStepY(1.0f / resY) + { + Build(); + } + + CUtlVector< Location >::iterator begin() { return mRasterizedLocations.begin(); } + CUtlVector< Location >::iterator end() { return mRasterizedLocations.end(); } + + void Build(); + + inline size_t GetRow(float y) const { return size_t(y * mResY); } + inline size_t GetCol(float x) const { return size_t(x * mResX); } + + inline size_t GetLinearPos( const CUtlVector< Location >::iterator& it ) const + { + // Given an iterator, return what the linear position in the buffer would be for the data. + return (size_t)(GetRow(it->uv.y) * mResX) + + (size_t)(GetCol(it->uv.x)); + } + +private: + const Vector2D mT0, mT1, mT2; + const size_t mResX, mResY; + const float mUvStepX, mUvStepY; + + // Right now, we just fill this out and directly iterate over it. + // It could be large. This is a memory/speed tradeoff. We could instead generate them + // on demand. + CUtlVector< Location > mRasterizedLocations; +}; + +//----------------------------------------------------------------------------- +inline Vector ComputeBarycentric( Vector2D _edgeC, Vector2D _edgeA, Vector2D _edgeB, float _dAA, float _dAB, float _dBB, float _invDenom ) +{ + float dCA = _edgeC.Dot(_edgeA); + float dCB = _edgeC.Dot(_edgeB); + + Vector retVal; + retVal.y = (_dBB * dCA - _dAB * dCB) * _invDenom; + retVal.z = (_dAA * dCB - _dAB * dCA) * _invDenom; + retVal.x = 1.0f - retVal.y - retVal.z; + + return retVal; +} + +//----------------------------------------------------------------------------- +void Rasterizer::Build() +{ + // For now, use the barycentric method. It's easy, I'm lazy. + // We can optimize later if it's a performance issue. + const float baseX = mUvStepX / 2.0f; + const float baseY = mUvStepY / 2.0f; + + + float fMinX = min(min(mT0.x, mT1.x), mT2.x); + float fMinY = min(min(mT0.y, mT1.y), mT2.y); + float fMaxX = max(max(mT0.x, mT1.x), mT2.x); + float fMaxY = max(max(mT0.y, mT1.y), mT2.y); + + // Degenerate. Consider warning about these, but otherwise no problem. + if (fMinX == fMaxX || fMinY == fMaxY) + return; + + // Clamp to 0..1 + fMinX = max(0, fMinX); + fMinY = max(0, fMinY); + fMaxX = min(1.0f, fMaxX); + fMaxY = min(1.0f, fMaxY); + + // We puff the interesting area up by 1 so we can hit an inflated region for the necessary bilerp data. + // If we wanted to support better texturing (almost definitely unnecessary), we'd change this to a larger size. + const int kFilterSampleRadius = 1; + + int iMinX = GetCol(fMinX) - kFilterSampleRadius; + int iMinY = GetRow(fMinY) - kFilterSampleRadius; + int iMaxX = GetCol(fMaxX) + 1 + kFilterSampleRadius; + int iMaxY = GetRow(fMaxY) + 1 + kFilterSampleRadius; + + // Clamp to valid texture (integer) locations + iMinX = max(0, iMinX); + iMinY = max(0, iMinY); + iMaxX = min(iMaxX, mResX - 1); + iMaxY = min(iMaxY, mResY - 1); + + // Set the size to be as expected. + // TODO: Pass this in from outside to minimize allocations + int count = (iMaxY - iMinY + 1) + * (iMaxX - iMinX + 1); + mRasterizedLocations.EnsureCount(count); + memset( mRasterizedLocations.Base(), 0, mRasterizedLocations.Count() * sizeof( Location ) ); + + // Computing Barycentrics adapted from here http://gamedev.stackexchange.com/questions/23743/whats-the-most-efficient-way-to-find-barycentric-coordinates + Vector2D edgeA = mT1 - mT0; + Vector2D edgeB = mT2 - mT0; + + float dAA = edgeA.Dot(edgeA); + float dAB = edgeA.Dot(edgeB); + float dBB = edgeB.Dot(edgeB); + float invDenom = 1.0f / (dAA * dBB - dAB * dAB); + + int linearPos = 0; + for (int j = iMinY; j <= iMaxY; ++j) { + for (int i = iMinX; i <= iMaxX; ++i) { + Vector2D testPt( i * mUvStepX + baseX, j * mUvStepY + baseY ); + Vector barycentric = ComputeBarycentric( testPt - mT0, edgeA, edgeB, dAA, dAB, dBB, invDenom ); + + // Test whether the point is inside the triangle. + // MCJOHNTODO: Edge rules and whatnot--right now we re-rasterize points on the edge. + Location& newLoc = mRasterizedLocations[linearPos++]; + newLoc.barycentric = barycentric; + newLoc.uv = testPt; + + newLoc.insideTriangle = (barycentric.x >= 0.0f && barycentric.x <= 1.0f && barycentric.y >= 0.0f && barycentric.y <= 1.0f && barycentric.z >= 0.0f && barycentric.z <= 1.0f); + } + } +} + + +//----------------------------------------------------------------------------- +// Globals +//----------------------------------------------------------------------------- +CUtlSymbolTable g_ForcedTextureShadowsModels; + +// DON'T USE THIS FROM WITHIN A THREAD. THERE IS A THREAD CONTEXT CREATED +// INSIDE PropTested_t. USE THAT INSTEAD. +IPhysicsCollision *s_pPhysCollision = NULL; + +static void ConvertTexelDataToTexture(unsigned int _resX, unsigned int _resY, ImageFormat _destFmt, const CUtlVector<colorTexel_t>& _srcTexels, CUtlMemory<byte>* _outTexture); + +// Such a monstrosity. :( +static void GenerateLightmapSamplesForMesh( const matrix3x4_t& _matPos, const matrix3x4_t& _matNormal, int _iThread, int _skipProp, int _nFlags, int _lightmapResX, int _lightmapResY, + studiohdr_t* _pStudioHdr, mstudiomodel_t* _pStudioModel, OptimizedModel::ModelHeader_t* _pVtxModel, int _meshID, + CComputeStaticPropLightingResults *_pResults ); + +// Debug function, converts lightmaps to linear space then dumps them out. +// TODO: Write out the file in a .dds instead of a .tga, in whatever format we're supposed to use. +static void DumpLightmapLinear( const char* _dstFilename, const CUtlVector<colorTexel_t>& _srcTexels, int _width, int _height ); + + +//----------------------------------------------------------------------------- +// Vrad's static prop manager +//----------------------------------------------------------------------------- + +class CVradStaticPropMgr : public IVradStaticPropMgr +{ +public: + // constructor, destructor + CVradStaticPropMgr(); + virtual ~CVradStaticPropMgr(); + + // methods of IStaticPropMgr + void Init(); + void Shutdown(); + + // iterate all the instanced static props and compute their vertex lighting + void ComputeLighting( int iThread ); + +private: + // VMPI stuff. + static void VMPI_ProcessStaticProp_Static( int iThread, uint64 iStaticProp, MessageBuffer *pBuf ); + static void VMPI_ReceiveStaticPropResults_Static( uint64 iStaticProp, MessageBuffer *pBuf, int iWorker ); + void VMPI_ProcessStaticProp( int iThread, int iStaticProp, MessageBuffer *pBuf ); + void VMPI_ReceiveStaticPropResults( int iStaticProp, MessageBuffer *pBuf, int iWorker ); + + // local thread version + static void ThreadComputeStaticPropLighting( int iThread, void *pUserData ); + void ComputeLightingForProp( int iThread, int iStaticProp ); + + // Methods associated with unserializing static props + void UnserializeModelDict( CUtlBuffer& buf ); + void UnserializeModels( CUtlBuffer& buf ); + void UnserializeStaticProps(); + + // Creates a collision model + void CreateCollisionModel( char const* pModelName ); + +private: + // Unique static prop models + struct StaticPropDict_t + { + vcollide_t m_loadedModel; + CPhysCollide* m_pModel; + Vector m_Mins; // Bounding box is in local coordinates + Vector m_Maxs; + studiohdr_t* m_pStudioHdr; + CUtlBuffer m_VtxBuf; + CUtlVector<int> m_textureShadowIndex; // each texture has an index if this model casts texture shadows + CUtlVector<int> m_triangleMaterialIndex;// each triangle has an index if this model casts texture shadows + }; + + struct MeshData_t + { + CUtlVector<Vector> m_VertexColors; + CUtlMemory<byte> m_TexelsEncoded; + int m_nLod; + }; + + // A static prop instance + struct CStaticProp + { + Vector m_Origin; + QAngle m_Angles; + Vector m_mins; + Vector m_maxs; + Vector m_LightingOrigin; + int m_ModelIdx; + BSPTreeDataHandle_t m_Handle; + CUtlVector<MeshData_t> m_MeshData; + int m_Flags; + bool m_bLightingOriginValid; + + // Note that all lightmaps for a given prop share the same resolution (and format)--and there can be multiple lightmaps + // per prop (if there are multiple pieces--the watercooler is an example). + // This is effectively because there's not a good way in hammer for a prop to say "this should be the resolution + // of each of my sub-pieces." + ImageFormat m_LightmapImageFormat; + unsigned int m_LightmapImageWidth; + unsigned int m_LightmapImageHeight; + + }; + + // Enumeration context + struct EnumContext_t + { + PropTested_t* m_pPropTested; + Ray_t const* m_pRay; + }; + + // The list of all static props + CUtlVector <StaticPropDict_t> m_StaticPropDict; + CUtlVector <CStaticProp> m_StaticProps; + + bool m_bIgnoreStaticPropTrace; + + void ComputeLighting( CStaticProp &prop, int iThread, int prop_index, CComputeStaticPropLightingResults *pResults ); + void ApplyLightingToStaticProp( int iStaticProp, CStaticProp &prop, const CComputeStaticPropLightingResults *pResults ); + + void SerializeLighting(); + void AddPolysForRayTrace(); + void BuildTriList( CStaticProp &prop ); +}; + + +//----------------------------------------------------------------------------- +// Expose IVradStaticPropMgr to vrad +//----------------------------------------------------------------------------- + +static CVradStaticPropMgr g_StaticPropMgr; +IVradStaticPropMgr* StaticPropMgr() +{ + return &g_StaticPropMgr; +} + + +//----------------------------------------------------------------------------- +// constructor, destructor +//----------------------------------------------------------------------------- + +CVradStaticPropMgr::CVradStaticPropMgr() +{ + // set to ignore static prop traces + m_bIgnoreStaticPropTrace = false; +} + +CVradStaticPropMgr::~CVradStaticPropMgr() +{ +} + +//----------------------------------------------------------------------------- +// Makes sure the studio model is a static prop +//----------------------------------------------------------------------------- + +bool IsStaticProp( studiohdr_t* pHdr ) +{ + if (!(pHdr->flags & STUDIOHDR_FLAGS_STATIC_PROP)) + return false; + + return true; +} + + +//----------------------------------------------------------------------------- +// Load a file into a Utlbuf +//----------------------------------------------------------------------------- +static bool LoadFile( char const* pFileName, CUtlBuffer& buf ) +{ + if ( !g_pFullFileSystem ) + return false; + + return g_pFullFileSystem->ReadFile( pFileName, NULL, buf ); +} + + +//----------------------------------------------------------------------------- +// Constructs the file name from the model name +//----------------------------------------------------------------------------- +static char const* ConstructFileName( char const* pModelName ) +{ + static char buf[1024]; + sprintf( buf, "%s%s", gamedir, pModelName ); + return buf; +} + + +//----------------------------------------------------------------------------- +// Computes a convex hull from a studio mesh +//----------------------------------------------------------------------------- +static CPhysConvex* ComputeConvexHull( mstudiomesh_t* pMesh, studiohdr_t *pStudioHdr ) +{ + const mstudio_meshvertexdata_t *vertData = pMesh->GetVertexData( (void *)pStudioHdr ); + Assert( vertData ); // This can only return NULL on X360 for now + + // Generate a list of all verts in the mesh + Vector** ppVerts = (Vector**)_alloca(pMesh->numvertices * sizeof(Vector*) ); + for (int i = 0; i < pMesh->numvertices; ++i) + { + ppVerts[i] = vertData->Position(i); + } + + // Generate a convex hull from the verts + return s_pPhysCollision->ConvexFromVerts( ppVerts, pMesh->numvertices ); +} + + +//----------------------------------------------------------------------------- +// Computes a convex hull from the studio model +//----------------------------------------------------------------------------- +CPhysCollide* ComputeConvexHull( studiohdr_t* pStudioHdr ) +{ + CUtlVector<CPhysConvex*> convexHulls; + + for (int body = 0; body < pStudioHdr->numbodyparts; ++body ) + { + mstudiobodyparts_t *pBodyPart = pStudioHdr->pBodypart( body ); + for( int model = 0; model < pBodyPart->nummodels; ++model ) + { + mstudiomodel_t *pStudioModel = pBodyPart->pModel( model ); + for( int mesh = 0; mesh < pStudioModel->nummeshes; ++mesh ) + { + // Make a convex hull for each mesh + // NOTE: This won't work unless the model has been compiled + // with $staticprop + mstudiomesh_t *pStudioMesh = pStudioModel->pMesh( mesh ); + convexHulls.AddToTail( ComputeConvexHull( pStudioMesh, pStudioHdr ) ); + } + } + } + + // Convert an array of convex elements to a compiled collision model + // (this deletes the convex elements) + return s_pPhysCollision->ConvertConvexToCollide( convexHulls.Base(), convexHulls.Size() ); +} + + +//----------------------------------------------------------------------------- +// Load studio model vertex data from a file... +//----------------------------------------------------------------------------- + +bool LoadStudioModel( char const* pModelName, CUtlBuffer& buf ) +{ + // No luck, gotta build it + // Construct the file name... + if (!LoadFile( pModelName, buf )) + { + Warning("Error! Unable to load model \"%s\"\n", pModelName ); + return false; + } + + // Check that it's valid + if (strncmp ((const char *) buf.PeekGet(), "IDST", 4) && + strncmp ((const char *) buf.PeekGet(), "IDAG", 4)) + { + Warning("Error! Invalid model file \"%s\"\n", pModelName ); + return false; + } + + studiohdr_t* pHdr = (studiohdr_t*)buf.PeekGet(); + + Studio_ConvertStudioHdrToNewVersion( pHdr ); + + if (pHdr->version != STUDIO_VERSION) + { + Warning("Error! Invalid model version \"%s\"\n", pModelName ); + return false; + } + + if (!IsStaticProp(pHdr)) + { + Warning("Error! To use model \"%s\"\n" + " as a static prop, it must be compiled with $staticprop!\n", pModelName ); + return false; + } + + // ensure reset + pHdr->pVertexBase = NULL; + pHdr->pIndexBase = NULL; + + return true; +} + +bool LoadStudioCollisionModel( char const* pModelName, CUtlBuffer& buf ) +{ + char tmp[1024]; + Q_strncpy( tmp, pModelName, sizeof( tmp ) ); + Q_SetExtension( tmp, ".phy", sizeof( tmp ) ); + // No luck, gotta build it + if (!LoadFile( tmp, buf )) + { + // this is not an error, the model simply has no PHY file + return false; + } + + phyheader_t *header = (phyheader_t *)buf.PeekGet(); + + if ( header->size != sizeof(*header) || header->solidCount <= 0 ) + return false; + + return true; +} + +bool LoadVTXFile( char const* pModelName, const studiohdr_t *pStudioHdr, CUtlBuffer& buf ) +{ + char filename[MAX_PATH]; + + // construct filename + Q_StripExtension( pModelName, filename, sizeof( filename ) ); + strcat( filename, ".dx80.vtx" ); + + if ( !LoadFile( filename, buf ) ) + { + Warning( "Error! Unable to load file \"%s\"\n", filename ); + return false; + } + + OptimizedModel::FileHeader_t* pVtxHdr = (OptimizedModel::FileHeader_t *)buf.Base(); + + // Check that it's valid + if ( pVtxHdr->version != OPTIMIZED_MODEL_FILE_VERSION ) + { + Warning( "Error! Invalid VTX file version: %d, expected %d \"%s\"\n", pVtxHdr->version, OPTIMIZED_MODEL_FILE_VERSION, filename ); + return false; + } + if ( pVtxHdr->checkSum != pStudioHdr->checksum ) + { + Warning( "Error! Invalid VTX file checksum: %d, expected %d \"%s\"\n", pVtxHdr->checkSum, pStudioHdr->checksum, filename ); + return false; + } + + return true; +} + +//----------------------------------------------------------------------------- +// Gets a vertex position from a strip index +//----------------------------------------------------------------------------- +inline static Vector* PositionFromIndex( const mstudio_meshvertexdata_t *vertData, mstudiomesh_t* pMesh, OptimizedModel::StripGroupHeader_t* pStripGroup, int i ) +{ + OptimizedModel::Vertex_t* pVert = pStripGroup->pVertex( i ); + return vertData->Position( pVert->origMeshVertID ); +} + + +//----------------------------------------------------------------------------- +// Purpose: Writes a glview text file containing the collision surface in question +// Input : *pCollide - +// *pFilename - +//----------------------------------------------------------------------------- +void DumpCollideToGlView( vcollide_t *pCollide, const char *pFilename ) +{ + if ( !pCollide ) + return; + + Msg("Writing %s...\n", pFilename ); + + FILE *fp = fopen( pFilename, "w" ); + for (int i = 0; i < pCollide->solidCount; ++i) + { + Vector *outVerts; + int vertCount = s_pPhysCollision->CreateDebugMesh( pCollide->solids[i], &outVerts ); + int triCount = vertCount / 3; + int vert = 0; + + unsigned char r = (i & 1) * 64 + 64; + unsigned char g = (i & 2) * 64 + 64; + unsigned char b = (i & 4) * 64 + 64; + + float fr = r / 255.0f; + float fg = g / 255.0f; + float fb = b / 255.0f; + + for ( int i = 0; i < triCount; i++ ) + { + fprintf( fp, "3\n" ); + fprintf( fp, "%6.3f %6.3f %6.3f %.2f %.3f %.3f\n", + outVerts[vert].x, outVerts[vert].y, outVerts[vert].z, fr, fg, fb ); + vert++; + fprintf( fp, "%6.3f %6.3f %6.3f %.2f %.3f %.3f\n", + outVerts[vert].x, outVerts[vert].y, outVerts[vert].z, fr, fg, fb ); + vert++; + fprintf( fp, "%6.3f %6.3f %6.3f %.2f %.3f %.3f\n", + outVerts[vert].x, outVerts[vert].y, outVerts[vert].z, fr, fg, fb ); + vert++; + } + s_pPhysCollision->DestroyDebugMesh( vertCount, outVerts ); + } + fclose( fp ); +} + + +static bool PointInTriangle( const Vector2D &p, const Vector2D &v0, const Vector2D &v1, const Vector2D &v2 ) +{ + float coords[3]; + GetBarycentricCoords2D( v0, v1, v2, p, coords ); + for ( int i = 0; i < 3; i++ ) + { + if ( coords[i] < 0.0f || coords[i] > 1.0f ) + return false; + } + float sum = coords[0] + coords[1] + coords[2]; + if ( sum > 1.0f ) + return false; + return true; +} + +bool LoadFileIntoBuffer( CUtlBuffer &buf, const char *pFilename ) +{ + FileHandle_t fileHandle = g_pFileSystem->Open( pFilename, "rb" ); + if ( !fileHandle ) + return false; + + // Get the file size + int texSize = g_pFileSystem->Size( fileHandle ); + buf.EnsureCapacity( texSize ); + int nBytesRead = g_pFileSystem->Read( buf.Base(), texSize, fileHandle ); + g_pFileSystem->Close( fileHandle ); + buf.SeekPut( CUtlBuffer::SEEK_HEAD, nBytesRead ); + buf.SeekGet( CUtlBuffer::SEEK_HEAD, 0 ); + return true; +} + +// keeps a list of all textures that cast shadows via alpha channel +class CShadowTextureList +{ +public: + // This loads a vtf and converts it to RGB8888 format + unsigned char *LoadVTFRGB8888( const char *pName, int *pWidth, int *pHeight, bool *pClampU, bool *pClampV ) + { + char szPath[MAX_PATH]; + Q_strncpy( szPath, "materials/", sizeof( szPath ) ); + Q_strncat( szPath, pName, sizeof( szPath ), COPY_ALL_CHARACTERS ); + Q_strncat( szPath, ".vtf", sizeof( szPath ), COPY_ALL_CHARACTERS ); + Q_FixSlashes( szPath, CORRECT_PATH_SEPARATOR ); + + CUtlBuffer buf; + if ( !LoadFileIntoBuffer( buf, szPath ) ) + return NULL; + IVTFTexture *pTex = CreateVTFTexture(); + if (!pTex->Unserialize( buf )) + return NULL; + Msg("Loaded alpha texture %s\n", szPath ); + unsigned char *pSrcImage = pTex->ImageData( 0, 0, 0, 0, 0, 0 ); + int iWidth = pTex->Width(); + int iHeight = pTex->Height(); + ImageFormat dstFormat = IMAGE_FORMAT_RGBA8888; + ImageFormat srcFormat = pTex->Format(); + *pClampU = (pTex->Flags() & TEXTUREFLAGS_CLAMPS) ? true : false; + *pClampV = (pTex->Flags() & TEXTUREFLAGS_CLAMPT) ? true : false; + unsigned char *pDstImage = new unsigned char[ImageLoader::GetMemRequired( iWidth, iHeight, 1, dstFormat, false )]; + + if( !ImageLoader::ConvertImageFormat( pSrcImage, srcFormat, + pDstImage, dstFormat, iWidth, iHeight, 0, 0 ) ) + { + delete[] pDstImage; + return NULL; + } + + *pWidth = iWidth; + *pHeight = iHeight; + return pDstImage; + } + + // Checks the database for the material and loads if necessary + // returns true if found and pIndex will be the index, -1 if no alpha shadows + bool FindOrLoadIfValid( const char *pMaterialName, int *pIndex ) + { + *pIndex = -1; + int index = m_Textures.Find(pMaterialName); + bool bFound = false; + if ( index != m_Textures.InvalidIndex() ) + { + bFound = true; + *pIndex = index; + } + else + { + KeyValues *pVMT = new KeyValues("vmt"); + CUtlBuffer buf(0,0,CUtlBuffer::TEXT_BUFFER); + LoadFileIntoBuffer( buf, pMaterialName ); + if ( pVMT->LoadFromBuffer( pMaterialName, buf ) ) + { + bFound = true; + if ( pVMT->FindKey("$translucent") || pVMT->FindKey("$alphatest") ) + { + KeyValues *pBaseTexture = pVMT->FindKey("$basetexture"); + if ( pBaseTexture ) + { + const char *pBaseTextureName = pBaseTexture->GetString(); + if ( pBaseTextureName ) + { + int w, h; + bool bClampU = false; + bool bClampV = false; + unsigned char *pImageBits = LoadVTFRGB8888( pBaseTextureName, &w, &h, &bClampU, &bClampV ); + if ( pImageBits ) + { + int index = m_Textures.Insert( pMaterialName ); + m_Textures[index].InitFromRGB8888( w, h, pImageBits ); + *pIndex = index; + if ( pVMT->FindKey("$nocull") ) + { + // UNDONE: Support this? Do we need to emit two triangles? + m_Textures[index].allowBackface = true; + } + m_Textures[index].clampU = bClampU; + m_Textures[index].clampV = bClampV; + delete[] pImageBits; + } + } + } + } + + } + pVMT->deleteThis(); + } + + return bFound; + } + + + // iterate the textures for the model and load each one into the database + // this is used on models marked to cast texture shadows + void LoadAllTexturesForModel( studiohdr_t *pHdr, int *pTextureList ) + { + for ( int i = 0; i < pHdr->numtextures; i++ ) + { + int textureIndex = -1; + // try to add each texture to the transparent shadow manager + char szPath[MAX_PATH]; + + // iterate quietly through all specified directories until a valid material is found + for ( int j = 0; j < pHdr->numcdtextures; j++ ) + { + Q_strncpy( szPath, "materials/", sizeof( szPath ) ); + Q_strncat( szPath, pHdr->pCdtexture( j ), sizeof( szPath ) ); + const char *textureName = pHdr->pTexture( i )->pszName(); + Q_strncat( szPath, textureName, sizeof( szPath ), COPY_ALL_CHARACTERS ); + Q_strncat( szPath, ".vmt", sizeof( szPath ), COPY_ALL_CHARACTERS ); + Q_FixSlashes( szPath, CORRECT_PATH_SEPARATOR ); + if ( FindOrLoadIfValid( szPath, &textureIndex ) ) + break; + } + + pTextureList[i] = textureIndex; + } + } + + int AddMaterialEntry( int shadowTextureIndex, const Vector2D &t0, const Vector2D &t1, const Vector2D &t2 ) + { + int index = m_MaterialEntries.AddToTail(); + m_MaterialEntries[index].textureIndex = shadowTextureIndex; + m_MaterialEntries[index].uv[0] = t0; + m_MaterialEntries[index].uv[1] = t1; + m_MaterialEntries[index].uv[2] = t2; + return index; + } + + // HACKHACK: Compute the average coverage for this triangle by sampling the AABB of its texture space + float ComputeCoverageForTriangle( int shadowTextureIndex, const Vector2D &t0, const Vector2D &t1, const Vector2D &t2 ) + { + float umin = min(t0.x, t1.x); + umin = min(umin, t2.x); + float umax = max(t0.x, t1.x); + umax = max(umax, t2.x); + + float vmin = min(t0.y, t1.y); + vmin = min(vmin, t2.y); + float vmax = max(t0.y, t1.y); + vmax = max(vmax, t2.y); + + // UNDONE: Do something about tiling + umin = clamp(umin, 0, 1); + umax = clamp(umax, 0, 1); + vmin = clamp(vmin, 0, 1); + vmax = clamp(vmax, 0, 1); + Assert(umin>=0.0f && umax <= 1.0f); + Assert(vmin>=0.0f && vmax <= 1.0f); + const alphatexture_t &tex = m_Textures.Element(shadowTextureIndex); + int u0 = umin * (tex.width-1); + int u1 = umax * (tex.width-1); + int v0 = vmin * (tex.height-1); + int v1 = vmax * (tex.height-1); + + int total = 0; + int count = 0; + for ( int v = v0; v <= v1; v++ ) + { + int row = (v * tex.width); + for ( int u = u0; u <= u1; u++ ) + { + total += tex.pAlphaTexels[row + u]; + count++; + } + } + if ( count ) + { + float coverage = float(total) / (count * 255.0f); + return coverage; + } + return 1.0f; + } + + int SampleMaterial( int materialIndex, const Vector &coords, bool bBackface ) + { + const materialentry_t &mat = m_MaterialEntries[materialIndex]; + const alphatexture_t &tex = m_Textures.Element(m_MaterialEntries[materialIndex].textureIndex); + if ( bBackface && !tex.allowBackface ) + return 0; + Vector2D uv = coords.x * mat.uv[0] + coords.y * mat.uv[1] + coords.z * mat.uv[2]; + int u = RoundFloatToInt( uv[0] * tex.width ); + int v = RoundFloatToInt( uv[1] * tex.height ); + + // asume power of 2, clamp or wrap + // UNDONE: Support clamp? This code should work +#if 0 + u = tex.clampU ? clamp(u,0,(tex.width-1)) : (u & (tex.width-1)); + v = tex.clampV ? clamp(v,0,(tex.height-1)) : (v & (tex.height-1)); +#else + // for now always wrap + u &= (tex.width-1); + v &= (tex.height-1); +#endif + + return tex.pAlphaTexels[v * tex.width + u]; + } + + struct alphatexture_t + { + short width; + short height; + bool allowBackface; + bool clampU; + bool clampV; + unsigned char *pAlphaTexels; + + void InitFromRGB8888( int w, int h, unsigned char *pTexels ) + { + width = w; + height = h; + pAlphaTexels = new unsigned char[w*h]; + for ( int i = 0; i < h; i++ ) + { + for ( int j = 0; j < w; j++ ) + { + int index = (i*w) + j; + pAlphaTexels[index] = pTexels[index*4 + 3]; + } + } + } + }; + struct materialentry_t + { + int textureIndex; + Vector2D uv[3]; + }; + // this is the list of textures we've loaded + // only load each one once + CUtlDict< alphatexture_t, unsigned short > m_Textures; + CUtlVector<materialentry_t> m_MaterialEntries; +}; + +// global to keep the shadow-casting texture list and their alpha bits +CShadowTextureList g_ShadowTextureList; + +float ComputeCoverageFromTexture( float b0, float b1, float b2, int32 hitID ) +{ + const float alphaScale = 1.0f / 255.0f; + // UNDONE: Pass ray down to determine backfacing? + //Vector normal( tri.m_flNx, tri.m_flNy, tri.m_flNz ); + //bool bBackface = DotProduct(delta, tri.N) > 0 ? true : false; + Vector coords(b0,b1,b2); + return alphaScale * g_ShadowTextureList.SampleMaterial( g_RtEnv.GetTriangleMaterial(hitID), coords, false ); +} + +// this is here to strip models/ or .mdl or whatnot +void CleanModelName( const char *pModelName, char *pOutput, int outLen ) +{ + // strip off leading models/ if it exists + const char *pModelDir = "models/"; + int modelLen = Q_strlen(pModelDir); + + if ( !Q_strnicmp(pModelName, pModelDir, modelLen ) ) + { + pModelName += modelLen; + } + Q_strncpy( pOutput, pModelName, outLen ); + + // truncate any .mdl extension + char *dot = strchr(pOutput,'.'); + if ( dot ) + { + *dot = 0; + } + +} + + +void ForceTextureShadowsOnModel( const char *pModelName ) +{ + char buf[1024]; + CleanModelName( pModelName, buf, sizeof(buf) ); + if ( !g_ForcedTextureShadowsModels.Find(buf).IsValid()) + { + g_ForcedTextureShadowsModels.AddString(buf); + } +} + +bool IsModelTextureShadowsForced( const char *pModelName ) +{ + char buf[1024]; + CleanModelName( pModelName, buf, sizeof(buf) ); + return g_ForcedTextureShadowsModels.Find(buf).IsValid(); +} + + +//----------------------------------------------------------------------------- +// Creates a collision model (based on the render geometry!) +//----------------------------------------------------------------------------- +void CVradStaticPropMgr::CreateCollisionModel( char const* pModelName ) +{ + CUtlBuffer buf; + CUtlBuffer bufvtx; + CUtlBuffer bufphy; + + int i = m_StaticPropDict.AddToTail(); + m_StaticPropDict[i].m_pModel = NULL; + m_StaticPropDict[i].m_pStudioHdr = NULL; + + if ( !LoadStudioModel( pModelName, buf ) ) + { + VectorCopy( vec3_origin, m_StaticPropDict[i].m_Mins ); + VectorCopy( vec3_origin, m_StaticPropDict[i].m_Maxs ); + return; + } + + studiohdr_t* pHdr = (studiohdr_t*)buf.Base(); + + VectorCopy( pHdr->hull_min, m_StaticPropDict[i].m_Mins ); + VectorCopy( pHdr->hull_max, m_StaticPropDict[i].m_Maxs ); + + if ( LoadStudioCollisionModel( pModelName, bufphy ) ) + { + phyheader_t header; + bufphy.Get( &header, sizeof(header) ); + + vcollide_t *pCollide = &m_StaticPropDict[i].m_loadedModel; + s_pPhysCollision->VCollideLoad( pCollide, header.solidCount, (const char *)bufphy.PeekGet(), bufphy.TellPut() - bufphy.TellGet() ); + m_StaticPropDict[i].m_pModel = m_StaticPropDict[i].m_loadedModel.solids[0]; + + /* + static int propNum = 0; + char tmp[128]; + sprintf( tmp, "staticprop%03d.txt", propNum ); + DumpCollideToGlView( pCollide, tmp ); + ++propNum; + */ + } + else + { + // mark this as unused + m_StaticPropDict[i].m_loadedModel.solidCount = 0; + + // CPhysCollide* pPhys = CreatePhysCollide( pHdr, pVtxHdr ); + m_StaticPropDict[i].m_pModel = ComputeConvexHull( pHdr ); + } + + // clone it + m_StaticPropDict[i].m_pStudioHdr = (studiohdr_t *)malloc( buf.Size() ); + memcpy( m_StaticPropDict[i].m_pStudioHdr, (studiohdr_t*)buf.Base(), buf.Size() ); + + if ( !LoadVTXFile( pModelName, m_StaticPropDict[i].m_pStudioHdr, m_StaticPropDict[i].m_VtxBuf ) ) + { + // failed, leave state identified as disabled + m_StaticPropDict[i].m_VtxBuf.Purge(); + } + + if ( g_bTextureShadows ) + { + if ( (pHdr->flags & STUDIOHDR_FLAGS_CAST_TEXTURE_SHADOWS) || IsModelTextureShadowsForced(pModelName) ) + { + m_StaticPropDict[i].m_textureShadowIndex.RemoveAll(); + m_StaticPropDict[i].m_triangleMaterialIndex.RemoveAll(); + m_StaticPropDict[i].m_textureShadowIndex.AddMultipleToTail( pHdr->numtextures ); + g_ShadowTextureList.LoadAllTexturesForModel( pHdr, m_StaticPropDict[i].m_textureShadowIndex.Base() ); + } + } +} + + +//----------------------------------------------------------------------------- +// Unserialize static prop model dictionary +//----------------------------------------------------------------------------- +void CVradStaticPropMgr::UnserializeModelDict( CUtlBuffer& buf ) +{ + int count = buf.GetInt(); + while ( --count >= 0 ) + { + StaticPropDictLump_t lump; + buf.Get( &lump, sizeof(StaticPropDictLump_t) ); + + CreateCollisionModel( lump.m_Name ); + } +} + +void CVradStaticPropMgr::UnserializeModels( CUtlBuffer& buf ) +{ + int count = buf.GetInt(); + + + m_StaticProps.AddMultipleToTail(count); + for ( int i = 0; i < count; ++i ) + { + StaticPropLump_t lump; + buf.Get( &lump, sizeof(StaticPropLump_t) ); + + VectorCopy( lump.m_Origin, m_StaticProps[i].m_Origin ); + VectorCopy( lump.m_Angles, m_StaticProps[i].m_Angles ); + VectorCopy( lump.m_LightingOrigin, m_StaticProps[i].m_LightingOrigin ); + m_StaticProps[i].m_bLightingOriginValid = ( lump.m_Flags & STATIC_PROP_USE_LIGHTING_ORIGIN ) > 0; + m_StaticProps[i].m_ModelIdx = lump.m_PropType; + m_StaticProps[i].m_Handle = TREEDATA_INVALID_HANDLE; + m_StaticProps[i].m_Flags = lump.m_Flags; + + // Changed this from using DXT1 to RGB888 because the compression artifacts were pretty nasty. + // TODO: Consider changing back or basing this on user selection in hammer. + m_StaticProps[i].m_LightmapImageFormat = IMAGE_FORMAT_RGB888; + m_StaticProps[i].m_LightmapImageWidth = lump.m_nLightmapResolutionX; + m_StaticProps[i].m_LightmapImageHeight = lump.m_nLightmapResolutionY; + } +} + +//----------------------------------------------------------------------------- +// Unserialize static props +//----------------------------------------------------------------------------- + +void CVradStaticPropMgr::UnserializeStaticProps() +{ + // Unserialize static props, insert them into the appropriate leaves + GameLumpHandle_t handle = g_GameLumps.GetGameLumpHandle( GAMELUMP_STATIC_PROPS ); + int size = g_GameLumps.GameLumpSize( handle ); + if (!size) + return; + + if ( g_GameLumps.GetGameLumpVersion( handle ) != GAMELUMP_STATIC_PROPS_VERSION ) + { + Error( "Cannot load the static props... encountered a stale map version. Re-vbsp the map." ); + } + + if ( g_GameLumps.GetGameLump( handle ) ) + { + CUtlBuffer buf( g_GameLumps.GetGameLump(handle), size, CUtlBuffer::READ_ONLY ); + UnserializeModelDict( buf ); + + // Skip the leaf list data + int count = buf.GetInt(); + buf.SeekGet( CUtlBuffer::SEEK_CURRENT, count * sizeof(StaticPropLeafLump_t) ); + + UnserializeModels( buf ); + } +} + +//----------------------------------------------------------------------------- +// Level init, shutdown +//----------------------------------------------------------------------------- + +void CVradStaticPropMgr::Init() +{ + CreateInterfaceFn physicsFactory = GetPhysicsFactory(); + if ( !physicsFactory ) + Error( "Unable to load vphysics DLL." ); + + s_pPhysCollision = (IPhysicsCollision *)physicsFactory( VPHYSICS_COLLISION_INTERFACE_VERSION, NULL ); + if( !s_pPhysCollision ) + { + Error( "Unable to get '%s' for physics interface.", VPHYSICS_COLLISION_INTERFACE_VERSION ); + return; + } + + // Read in static props that have been compiled into the bsp file + UnserializeStaticProps(); +} + +void CVradStaticPropMgr::Shutdown() +{ + + // Remove all static prop model data + for (int i = m_StaticPropDict.Size(); --i >= 0; ) + { + studiohdr_t *pStudioHdr = m_StaticPropDict[i].m_pStudioHdr; + if ( pStudioHdr ) + { + if ( pStudioHdr->pVertexBase ) + { + free( pStudioHdr->pVertexBase ); + } + free( pStudioHdr ); + } + } + + m_StaticProps.Purge(); + m_StaticPropDict.Purge(); +} + +void ComputeLightmapColor( dface_t* pFace, Vector &color ) +{ + texinfo_t* pTex = &texinfo[pFace->texinfo]; + if ( pTex->flags & SURF_SKY ) + { + // sky ambient already accounted for in direct component + return; + } +} + +bool PositionInSolid( Vector &position ) +{ + int ndxLeaf = PointLeafnum( position ); + if ( dleafs[ndxLeaf].contents & CONTENTS_SOLID ) + { + // position embedded in solid + return true; + } + + return false; +} + +//----------------------------------------------------------------------------- +// Trace from a vertex to each direct light source, accumulating its contribution. +//----------------------------------------------------------------------------- +void ComputeDirectLightingAtPoint( Vector &position, Vector &normal, Vector &outColor, int iThread, + int static_prop_id_to_skip=-1, int nLFlags = 0) +{ + SSE_sampleLightOutput_t sampleOutput; + + outColor.Init(); + + // Iterate over all direct lights and accumulate their contribution + int cluster = ClusterFromPoint( position ); + for ( directlight_t *dl = activelights; dl != NULL; dl = dl->next ) + { + if ( dl->light.style ) + { + // skip lights with style + continue; + } + + // is this lights cluster visible? + if ( !PVSCheck( dl->pvs, cluster ) ) + continue; + + // push the vertex towards the light to avoid surface acne + Vector adjusted_pos = position; + float flEpsilon = 0.0; + + if (dl->light.type != emit_skyambient) + { + // push towards the light + Vector fudge; + if ( dl->light.type == emit_skylight ) + fudge = -( dl->light.normal); + else + { + fudge = dl->light.origin-position; + VectorNormalize( fudge ); + } + fudge *= 4.0; + adjusted_pos += fudge; + } + else + { + // push out along normal + adjusted_pos += 4.0 * normal; +// flEpsilon = 1.0; + } + + FourVectors adjusted_pos4; + FourVectors normal4; + adjusted_pos4.DuplicateVector( adjusted_pos ); + normal4.DuplicateVector( normal ); + + GatherSampleLightSSE( sampleOutput, dl, -1, adjusted_pos4, &normal4, 1, iThread, nLFlags | GATHERLFLAGS_FORCE_FAST, + static_prop_id_to_skip, flEpsilon ); + + VectorMA( outColor, sampleOutput.m_flFalloff.m128_f32[0] * sampleOutput.m_flDot[0].m128_f32[0], dl->light.intensity, outColor ); + } +} + +//----------------------------------------------------------------------------- +// Takes the results from a ComputeLighting call and applies it to the static prop in question. +//----------------------------------------------------------------------------- +void CVradStaticPropMgr::ApplyLightingToStaticProp( int iStaticProp, CStaticProp &prop, const CComputeStaticPropLightingResults *pResults ) +{ + if ( pResults->m_ColorVertsArrays.Count() == 0 && pResults->m_ColorTexelsArrays.Count() == 0 ) + return; + + StaticPropDict_t &dict = m_StaticPropDict[prop.m_ModelIdx]; + studiohdr_t *pStudioHdr = dict.m_pStudioHdr; + OptimizedModel::FileHeader_t *pVtxHdr = (OptimizedModel::FileHeader_t *)dict.m_VtxBuf.Base(); + Assert( pStudioHdr && pVtxHdr ); + + int iCurColorVertsArray = 0; + int iCurColorTexelsArray = 0; + + for ( int bodyID = 0; bodyID < pStudioHdr->numbodyparts; ++bodyID ) + { + OptimizedModel::BodyPartHeader_t* pVtxBodyPart = pVtxHdr->pBodyPart( bodyID ); + mstudiobodyparts_t *pBodyPart = pStudioHdr->pBodypart( bodyID ); + + for ( int modelID = 0; modelID < pBodyPart->nummodels; ++modelID ) + { + OptimizedModel::ModelHeader_t* pVtxModel = pVtxBodyPart->pModel( modelID ); + mstudiomodel_t *pStudioModel = pBodyPart->pModel( modelID ); + + const CUtlVector<colorVertex_t> *colorVerts = pResults->m_ColorVertsArrays.Count() ? pResults->m_ColorVertsArrays[iCurColorVertsArray++] : nullptr; + const CUtlVector<colorTexel_t> *colorTexels = pResults->m_ColorTexelsArrays.Count() ? pResults->m_ColorTexelsArrays[iCurColorTexelsArray++] : nullptr; + + for ( int nLod = 0; nLod < pVtxHdr->numLODs; nLod++ ) + { + OptimizedModel::ModelLODHeader_t *pVtxLOD = pVtxModel->pLOD( nLod ); + + for ( int nMesh = 0; nMesh < pStudioModel->nummeshes; ++nMesh ) + { + mstudiomesh_t* pMesh = pStudioModel->pMesh( nMesh ); + OptimizedModel::MeshHeader_t* pVtxMesh = pVtxLOD->pMesh( nMesh ); + + for ( int nGroup = 0; nGroup < pVtxMesh->numStripGroups; ++nGroup ) + { + OptimizedModel::StripGroupHeader_t* pStripGroup = pVtxMesh->pStripGroup( nGroup ); + int nMeshIdx = prop.m_MeshData.AddToTail(); + + if (colorVerts) + { + prop.m_MeshData[nMeshIdx].m_VertexColors.AddMultipleToTail( pStripGroup->numVerts ); + prop.m_MeshData[nMeshIdx].m_nLod = nLod; + + for ( int nVertex = 0; nVertex < pStripGroup->numVerts; ++nVertex ) + { + int nIndex = pMesh->vertexoffset + pStripGroup->pVertex( nVertex )->origMeshVertID; + + Assert( nIndex < pStudioModel->numvertices ); + prop.m_MeshData[nMeshIdx].m_VertexColors[nVertex] = (*colorVerts)[nIndex].m_Color; + } + } + + if (colorTexels) + { + // TODO: Consider doing this work in the worker threads, because then we distribute it. + ConvertTexelDataToTexture(prop.m_LightmapImageWidth, prop.m_LightmapImageHeight, prop.m_LightmapImageFormat, (*colorTexels), &prop.m_MeshData[nMeshIdx].m_TexelsEncoded); + + if (g_bDumpPropLightmaps) + { + char buffer[_MAX_PATH]; + V_snprintf( + buffer, + _MAX_PATH - 1, + "staticprop_lightmap_%d_%.0f_%.0f_%.0f_%s_%d_%d_%d_%d_%d.tga", + iStaticProp, + prop.m_Origin.x, + prop.m_Origin.y, + prop.m_Origin.z, + dict.m_pStudioHdr->pszName(), + bodyID, + modelID, + nLod, + nMesh, + nGroup + ); + + for ( int i = 0; buffer[i]; ++i ) + { + if (buffer[i] == '/' || buffer[i] == '\\') + buffer[i] = '-'; + } + DumpLightmapLinear( buffer, (*colorTexels), prop.m_LightmapImageWidth, prop.m_LightmapImageHeight ); + } + } + } + } + } + } + } +} + +//----------------------------------------------------------------------------- +// Trace rays from each unique vertex, accumulating direct and indirect +// sources at each ray termination. Use the winding data to distribute the unique vertexes +// into the rendering layout. +//----------------------------------------------------------------------------- +void CVradStaticPropMgr::ComputeLighting( CStaticProp &prop, int iThread, int prop_index, CComputeStaticPropLightingResults *pResults ) +{ + CUtlVector<badVertex_t> badVerts; + + StaticPropDict_t &dict = m_StaticPropDict[prop.m_ModelIdx]; + studiohdr_t *pStudioHdr = dict.m_pStudioHdr; + OptimizedModel::FileHeader_t *pVtxHdr = (OptimizedModel::FileHeader_t *)dict.m_VtxBuf.Base(); + if ( !pStudioHdr || !pVtxHdr ) + { + // must have model and its verts for lighting computation + // game will fallback to fullbright + return; + } + + const bool withVertexLighting = (prop.m_Flags & STATIC_PROP_NO_PER_VERTEX_LIGHTING) == 0; + const bool withTexelLighting = (prop.m_Flags & STATIC_PROP_NO_PER_TEXEL_LIGHTING) == 0; + + if (!withVertexLighting && !withTexelLighting) + return; + + const int skip_prop = (g_bDisablePropSelfShadowing || (prop.m_Flags & STATIC_PROP_NO_SELF_SHADOWING)) ? prop_index : -1; + const int nFlags = ( prop.m_Flags & STATIC_PROP_IGNORE_NORMALS ) ? GATHERLFLAGS_IGNORE_NORMALS : 0; + + VMPI_SetCurrentStage( "ComputeLighting" ); + + matrix3x4_t matPos, matNormal; + AngleMatrix(prop.m_Angles, prop.m_Origin, matPos); + AngleMatrix(prop.m_Angles, matNormal); + + for ( int bodyID = 0; bodyID < pStudioHdr->numbodyparts; ++bodyID ) + { + OptimizedModel::BodyPartHeader_t* pVtxBodyPart = pVtxHdr->pBodyPart( bodyID ); + mstudiobodyparts_t *pBodyPart = pStudioHdr->pBodypart( bodyID ); + + for ( int modelID = 0; modelID < pBodyPart->nummodels; ++modelID ) + { + OptimizedModel::ModelHeader_t* pVtxModel = pVtxBodyPart->pModel(modelID); + mstudiomodel_t *pStudioModel = pBodyPart->pModel( modelID ); + + if (withTexelLighting) + { + CUtlVector<colorTexel_t> *pColorTexelArray = new CUtlVector<colorTexel_t>; + pResults->m_ColorTexelsArrays.AddToTail(pColorTexelArray); + } + + // light all unique vertexes + CUtlVector<colorVertex_t> *pColorVertsArray = new CUtlVector<colorVertex_t>; + pResults->m_ColorVertsArrays.AddToTail( pColorVertsArray ); + + CUtlVector<colorVertex_t> &colorVerts = *pColorVertsArray; + colorVerts.EnsureCount( pStudioModel->numvertices ); + memset( colorVerts.Base(), 0, colorVerts.Count() * sizeof(colorVertex_t) ); + + int numVertexes = 0; + for ( int meshID = 0; meshID < pStudioModel->nummeshes; ++meshID ) + { + mstudiomesh_t *pStudioMesh = pStudioModel->pMesh( meshID ); + const mstudio_meshvertexdata_t *vertData = pStudioMesh->GetVertexData((void *)pStudioHdr); + + Assert(vertData); // This can only return NULL on X360 for now + + // TODO: Move this into its own function. In fact, refactor this whole function. + if (withTexelLighting) + { + GenerateLightmapSamplesForMesh( matPos, matNormal, iThread, skip_prop, nFlags, prop.m_LightmapImageWidth, prop.m_LightmapImageHeight, pStudioHdr, pStudioModel, pVtxModel, meshID, pResults ); + } + + // If we do lightmapping, we also do vertex lighting as a potential fallback. This may change. + for ( int vertexID = 0; vertexID < pStudioMesh->numvertices; ++vertexID ) + { + Vector sampleNormal; + Vector samplePosition; + // transform position and normal into world coordinate system + VectorTransform(*vertData->Position(vertexID), matPos, samplePosition); + VectorTransform(*vertData->Normal(vertexID), matNormal, sampleNormal); + + if ( PositionInSolid( samplePosition ) ) + { + // vertex is in solid, add to the bad list, and recover later + badVertex_t badVertex; + badVertex.m_ColorVertex = numVertexes; + badVertex.m_Position = samplePosition; + badVertex.m_Normal = sampleNormal; + badVerts.AddToTail( badVertex ); + } + else + { + Vector direct_pos=samplePosition; + + + + Vector directColor(0,0,0); + ComputeDirectLightingAtPoint( direct_pos, + sampleNormal, directColor, iThread, + skip_prop, nFlags ); + Vector indirectColor(0,0,0); + + if (g_bShowStaticPropNormals) + { + directColor= sampleNormal; + directColor += Vector(1.0,1.0,1.0); + directColor *= 50.0; + } + else + { + if (numbounce >= 1) + ComputeIndirectLightingAtPoint( + samplePosition, sampleNormal, + indirectColor, iThread, true, + ( prop.m_Flags & STATIC_PROP_IGNORE_NORMALS) != 0 ); + } + + colorVerts[numVertexes].m_bValid = true; + colorVerts[numVertexes].m_Position = samplePosition; + VectorAdd( directColor, indirectColor, colorVerts[numVertexes].m_Color ); + } + + numVertexes++; + } + } + + // color in the bad vertexes + // when entire model has no lighting origin and no valid neighbors + // must punt, leave black coloring + if ( badVerts.Count() && ( prop.m_bLightingOriginValid || badVerts.Count() != numVertexes ) ) + { + for ( int nBadVertex = 0; nBadVertex < badVerts.Count(); nBadVertex++ ) + { + Vector bestPosition; + if ( prop.m_bLightingOriginValid ) + { + // use the specified lighting origin + VectorCopy( prop.m_LightingOrigin, bestPosition ); + } + else + { + // find the closest valid neighbor + int best = 0; + float closest = FLT_MAX; + for ( int nColorVertex = 0; nColorVertex < numVertexes; nColorVertex++ ) + { + if ( !colorVerts[nColorVertex].m_bValid ) + { + // skip invalid neighbors + continue; + } + Vector delta; + VectorSubtract( colorVerts[nColorVertex].m_Position, badVerts[nBadVertex].m_Position, delta ); + float distance = VectorLength( delta ); + if ( distance < closest ) + { + closest = distance; + best = nColorVertex; + } + } + + // use the best neighbor as the direction to crawl + VectorCopy( colorVerts[best].m_Position, bestPosition ); + } + + // crawl toward best position + // sudivide to determine a closer valid point to the bad vertex, and re-light + Vector midPosition; + int numIterations = 20; + while ( --numIterations > 0 ) + { + VectorAdd( bestPosition, badVerts[nBadVertex].m_Position, midPosition ); + VectorScale( midPosition, 0.5f, midPosition ); + if ( PositionInSolid( midPosition ) ) + break; + bestPosition = midPosition; + } + + // re-light from better position + Vector directColor; + ComputeDirectLightingAtPoint( bestPosition, badVerts[nBadVertex].m_Normal, directColor, iThread ); + + Vector indirectColor; + ComputeIndirectLightingAtPoint( bestPosition, badVerts[nBadVertex].m_Normal, + indirectColor, iThread, true ); + + // save results, not changing valid status + // to ensure this offset position is not considered as a viable candidate + colorVerts[badVerts[nBadVertex].m_ColorVertex].m_Position = bestPosition; + VectorAdd( directColor, indirectColor, colorVerts[badVerts[nBadVertex].m_ColorVertex].m_Color ); + } + } + + // discard bad verts + badVerts.Purge(); + } + } +} + +//----------------------------------------------------------------------------- +// Write the lighitng to bsp pak lump +//----------------------------------------------------------------------------- +void CVradStaticPropMgr::SerializeLighting() +{ + char filename[MAX_PATH]; + CUtlBuffer utlBuf; + + // illuminate them all + int count = m_StaticProps.Count(); + if ( !count ) + { + // nothing to do + return; + } + + char mapName[MAX_PATH]; + Q_FileBase( source, mapName, sizeof( mapName ) ); + + int size; + for (int i = 0; i < count; ++i) + { + // no need to write this file if we didn't compute the data + // props marked this way will not load the info anyway + if ( m_StaticProps[i].m_Flags & STATIC_PROP_NO_PER_VERTEX_LIGHTING ) + continue; + + if (g_bHDR) + { + sprintf( filename, "sp_hdr_%d.vhv", i ); + } + else + { + sprintf( filename, "sp_%d.vhv", i ); + } + + int totalVertexes = 0; + for ( int j=0; j<m_StaticProps[i].m_MeshData.Count(); j++ ) + { + totalVertexes += m_StaticProps[i].m_MeshData[j].m_VertexColors.Count(); + } + + // allocate a buffer with enough padding for alignment + size = sizeof( HardwareVerts::FileHeader_t ) + + m_StaticProps[i].m_MeshData.Count()*sizeof(HardwareVerts::MeshHeader_t) + + totalVertexes*4 + 2*512; + utlBuf.EnsureCapacity( size ); + Q_memset( utlBuf.Base(), 0, size ); + + HardwareVerts::FileHeader_t *pVhvHdr = (HardwareVerts::FileHeader_t *)utlBuf.Base(); + + // align to start of vertex data + unsigned char *pVertexData = (unsigned char *)(sizeof( HardwareVerts::FileHeader_t ) + m_StaticProps[i].m_MeshData.Count()*sizeof(HardwareVerts::MeshHeader_t)); + pVertexData = (unsigned char*)pVhvHdr + ALIGN_TO_POW2( (unsigned int)pVertexData, 512 ); + + // construct header + pVhvHdr->m_nVersion = VHV_VERSION; + pVhvHdr->m_nChecksum = m_StaticPropDict[m_StaticProps[i].m_ModelIdx].m_pStudioHdr->checksum; + pVhvHdr->m_nVertexFlags = VERTEX_COLOR; + pVhvHdr->m_nVertexSize = 4; + pVhvHdr->m_nVertexes = totalVertexes; + pVhvHdr->m_nMeshes = m_StaticProps[i].m_MeshData.Count(); + + for (int n=0; n<pVhvHdr->m_nMeshes; n++) + { + // construct mesh dictionary + HardwareVerts::MeshHeader_t *pMesh = pVhvHdr->pMesh( n ); + pMesh->m_nLod = m_StaticProps[i].m_MeshData[n].m_nLod; + pMesh->m_nVertexes = m_StaticProps[i].m_MeshData[n].m_VertexColors.Count(); + pMesh->m_nOffset = (unsigned int)pVertexData - (unsigned int)pVhvHdr; + + // construct vertexes + for (int k=0; k<pMesh->m_nVertexes; k++) + { + Vector &vertexColor = m_StaticProps[i].m_MeshData[n].m_VertexColors[k]; + + ColorRGBExp32 rgbColor; + VectorToColorRGBExp32( vertexColor, rgbColor ); + unsigned char dstColor[4]; + ConvertRGBExp32ToRGBA8888( &rgbColor, dstColor ); + + // b,g,r,a order + pVertexData[0] = dstColor[2]; + pVertexData[1] = dstColor[1]; + pVertexData[2] = dstColor[0]; + pVertexData[3] = dstColor[3]; + pVertexData += 4; + } + } + + // align to end of file + pVertexData = (unsigned char *)((unsigned int)pVertexData - (unsigned int)pVhvHdr); + pVertexData = (unsigned char*)pVhvHdr + ALIGN_TO_POW2( (unsigned int)pVertexData, 512 ); + + AddBufferToPak( GetPakFile(), filename, (void*)pVhvHdr, pVertexData - (unsigned char*)pVhvHdr, false ); + } + + for (int i = 0; i < count; ++i) + { + const int kAlignment = 512; + // no need to write this file if we didn't compute the data + // props marked this way will not load the info anyway + if (m_StaticProps[i].m_Flags & STATIC_PROP_NO_PER_TEXEL_LIGHTING) + continue; + + sprintf(filename, "texelslighting_%d.ppl", i); + + ImageFormat fmt = m_StaticProps[i].m_LightmapImageFormat; + + unsigned int totalTexelSizeBytes = 0; + for (int j = 0; j < m_StaticProps[i].m_MeshData.Count(); j++) + { + totalTexelSizeBytes += m_StaticProps[i].m_MeshData[j].m_TexelsEncoded.Count(); + } + + // allocate a buffer with enough padding for alignment + size = sizeof(HardwareTexels::FileHeader_t) + + m_StaticProps[i].m_MeshData.Count() * sizeof(HardwareTexels::MeshHeader_t) + + totalTexelSizeBytes + + 2 * kAlignment; + + utlBuf.EnsureCapacity(size); + Q_memset(utlBuf.Base(), 0, size); + + HardwareTexels::FileHeader_t *pVhtHdr = (HardwareTexels::FileHeader_t *)utlBuf.Base(); + + // align start of texel data + unsigned char *pTexelData = (unsigned char *)(sizeof(HardwareTexels::FileHeader_t) + m_StaticProps[i].m_MeshData.Count() * sizeof(HardwareTexels::MeshHeader_t)); + pTexelData = (unsigned char*)pVhtHdr + ALIGN_TO_POW2((unsigned int)pTexelData, kAlignment); + + pVhtHdr->m_nVersion = VHT_VERSION; + pVhtHdr->m_nChecksum = m_StaticPropDict[m_StaticProps[i].m_ModelIdx].m_pStudioHdr->checksum; + pVhtHdr->m_nTexelFormat = fmt; + pVhtHdr->m_nMeshes = m_StaticProps[i].m_MeshData.Count(); + + for (int n = 0; n < pVhtHdr->m_nMeshes; n++) + { + HardwareTexels::MeshHeader_t *pMesh = pVhtHdr->pMesh(n); + pMesh->m_nLod = m_StaticProps[i].m_MeshData[n].m_nLod; + pMesh->m_nOffset = (unsigned int)pTexelData - (unsigned int)pVhtHdr; + pMesh->m_nBytes = m_StaticProps[i].m_MeshData[n].m_TexelsEncoded.Count(); + pMesh->m_nWidth = m_StaticProps[i].m_LightmapImageWidth; + pMesh->m_nHeight = m_StaticProps[i].m_LightmapImageHeight; + + Q_memcpy(pTexelData, m_StaticProps[i].m_MeshData[n].m_TexelsEncoded.Base(), m_StaticProps[i].m_MeshData[n].m_TexelsEncoded.Count()); + pTexelData += m_StaticProps[i].m_MeshData[n].m_TexelsEncoded.Count(); + } + + pTexelData = (unsigned char *)((unsigned int)pTexelData - (unsigned int)pVhtHdr); + pTexelData = (unsigned char*)pVhtHdr + ALIGN_TO_POW2((unsigned int)pTexelData, kAlignment); + + AddBufferToPak(GetPakFile(), filename, (void*)pVhtHdr, pTexelData - (unsigned char*)pVhtHdr, false); + } +} + +void CVradStaticPropMgr::VMPI_ProcessStaticProp_Static( int iThread, uint64 iStaticProp, MessageBuffer *pBuf ) +{ + g_StaticPropMgr.VMPI_ProcessStaticProp( iThread, iStaticProp, pBuf ); +} + +void CVradStaticPropMgr::VMPI_ReceiveStaticPropResults_Static( uint64 iStaticProp, MessageBuffer *pBuf, int iWorker ) +{ + g_StaticPropMgr.VMPI_ReceiveStaticPropResults( iStaticProp, pBuf, iWorker ); +} + +//----------------------------------------------------------------------------- +// Called on workers to do the computation for a static prop and send +// it to the master. +//----------------------------------------------------------------------------- +void CVradStaticPropMgr::VMPI_ProcessStaticProp( int iThread, int iStaticProp, MessageBuffer *pBuf ) +{ + // Compute the lighting. + CComputeStaticPropLightingResults results; + ComputeLighting( m_StaticProps[iStaticProp], iThread, iStaticProp, &results ); + + VMPI_SetCurrentStage( "EncodeLightingResults" ); + + // Encode the results. + int nLists = results.m_ColorVertsArrays.Count(); + pBuf->write( &nLists, sizeof( nLists ) ); + + for ( int i=0; i < nLists; i++ ) + { + CUtlVector<colorVertex_t> &curList = *results.m_ColorVertsArrays[i]; + int count = curList.Count(); + pBuf->write( &count, sizeof( count ) ); + pBuf->write( curList.Base(), curList.Count() * sizeof( colorVertex_t ) ); + } + + nLists = results.m_ColorTexelsArrays.Count(); + pBuf->write(&nLists, sizeof(nLists)); + + for (int i = 0; i < nLists; i++) + { + CUtlVector<colorTexel_t> &curList = *results.m_ColorTexelsArrays[i]; + int count = curList.Count(); + pBuf->write(&count, sizeof(count)); + pBuf->write(curList.Base(), curList.Count() * sizeof(colorTexel_t)); + } +} + +//----------------------------------------------------------------------------- +// Called on the master when a worker finishes processing a static prop. +//----------------------------------------------------------------------------- +void CVradStaticPropMgr::VMPI_ReceiveStaticPropResults( int iStaticProp, MessageBuffer *pBuf, int iWorker ) +{ + // Read in the results. + CComputeStaticPropLightingResults results; + + int nLists; + pBuf->read( &nLists, sizeof( nLists ) ); + + for ( int i=0; i < nLists; i++ ) + { + CUtlVector<colorVertex_t> *pList = new CUtlVector<colorVertex_t>; + results.m_ColorVertsArrays.AddToTail( pList ); + + int count; + pBuf->read( &count, sizeof( count ) ); + pList->SetSize( count ); + pBuf->read( pList->Base(), count * sizeof( colorVertex_t ) ); + } + + pBuf->read(&nLists, sizeof(nLists)); + + for (int i = 0; i < nLists; i++) + { + CUtlVector<colorTexel_t> *pList = new CUtlVector<colorTexel_t>; + results.m_ColorTexelsArrays.AddToTail(pList); + + int count; + pBuf->read(&count, sizeof(count)); + pList->SetSize(count); + pBuf->read(pList->Base(), count * sizeof(colorTexel_t)); + } + + // Apply the results. + ApplyLightingToStaticProp( iStaticProp, m_StaticProps[iStaticProp], &results ); +} + + +void CVradStaticPropMgr::ComputeLightingForProp( int iThread, int iStaticProp ) +{ + // Compute the lighting. + CComputeStaticPropLightingResults results; + ComputeLighting( m_StaticProps[iStaticProp], iThread, iStaticProp, &results ); + ApplyLightingToStaticProp( iStaticProp, m_StaticProps[iStaticProp], &results ); +} + +void CVradStaticPropMgr::ThreadComputeStaticPropLighting( int iThread, void *pUserData ) +{ + while (1) + { + int j = GetThreadWork (); + if (j == -1) + break; + CComputeStaticPropLightingResults results; + g_StaticPropMgr.ComputeLightingForProp( iThread, j ); + } +} + +//----------------------------------------------------------------------------- +// Computes lighting for the static props. +// Must be after all other surface lighting has been computed for the indirect sampling. +//----------------------------------------------------------------------------- +void CVradStaticPropMgr::ComputeLighting( int iThread ) +{ + // illuminate them all + int count = m_StaticProps.Count(); + if ( !count ) + { + // nothing to do + return; + } + + StartPacifier( "Computing static prop lighting : " ); + + // ensure any traces against us are ignored because we have no inherit lighting contribution + m_bIgnoreStaticPropTrace = true; + + if ( g_bUseMPI ) + { + // Distribute the work among the workers. + VMPI_SetCurrentStage( "CVradStaticPropMgr::ComputeLighting" ); + + DistributeWork( + count, + VMPI_DISTRIBUTEWORK_PACKETID, + &CVradStaticPropMgr::VMPI_ProcessStaticProp_Static, + &CVradStaticPropMgr::VMPI_ReceiveStaticPropResults_Static ); + } + else + { + RunThreadsOn(count, true, ThreadComputeStaticPropLighting); + } + + // restore default + m_bIgnoreStaticPropTrace = false; + + // save data to bsp + SerializeLighting(); + + EndPacifier( true ); +} + +//----------------------------------------------------------------------------- +// Adds all static prop polys to the ray trace store. +//----------------------------------------------------------------------------- +void CVradStaticPropMgr::AddPolysForRayTrace( void ) +{ + int count = m_StaticProps.Count(); + if ( !count ) + { + // nothing to do + return; + } + + // Triangle coverage of 1 (full coverage) + Vector fullCoverage; + fullCoverage.x = 1.0f; + + for ( int nProp = 0; nProp < count; ++nProp ) + { + CStaticProp &prop = m_StaticProps[nProp]; + StaticPropDict_t &dict = m_StaticPropDict[prop.m_ModelIdx]; + + if ( prop.m_Flags & STATIC_PROP_NO_SHADOW ) + continue; + + // If not using static prop polys, use AABB + if ( !g_bStaticPropPolys ) + { + if ( dict.m_pModel ) + { + VMatrix xform; + xform.SetupMatrixOrgAngles ( prop.m_Origin, prop.m_Angles ); + ICollisionQuery *queryModel = s_pPhysCollision->CreateQueryModel( dict.m_pModel ); + for ( int nConvex = 0; nConvex < queryModel->ConvexCount(); ++nConvex ) + { + for ( int nTri = 0; nTri < queryModel->TriangleCount( nConvex ); ++nTri ) + { + Vector verts[3]; + queryModel->GetTriangleVerts( nConvex, nTri, verts ); + for ( int nVert = 0; nVert < 3; ++nVert ) + verts[nVert] = xform.VMul4x3(verts[nVert]); + g_RtEnv.AddTriangle ( TRACE_ID_STATICPROP | nProp, verts[0], verts[1], verts[2], fullCoverage ); + } + } + s_pPhysCollision->DestroyQueryModel( queryModel ); + } + else + { + VectorAdd ( dict.m_Mins, prop.m_Origin, prop.m_mins ); + VectorAdd ( dict.m_Maxs, prop.m_Origin, prop.m_maxs ); + g_RtEnv.AddAxisAlignedRectangularSolid ( TRACE_ID_STATICPROP | nProp, prop.m_mins, prop.m_maxs, fullCoverage ); + } + + continue; + } + + studiohdr_t *pStudioHdr = dict.m_pStudioHdr; + OptimizedModel::FileHeader_t *pVtxHdr = (OptimizedModel::FileHeader_t *)dict.m_VtxBuf.Base(); + if ( !pStudioHdr || !pVtxHdr ) + { + // must have model and its verts for decoding triangles + return; + } + // only init the triangle table the first time + bool bInitTriangles = dict.m_triangleMaterialIndex.Count() ? false : true; + int triangleIndex = 0; + + // meshes are deeply hierarchial, divided between three stores, follow the white rabbit + // body parts -> models -> lod meshes -> strip groups -> strips + // the vertices and indices are pooled, the trick is knowing the offset to determine your indexed base + for ( int bodyID = 0; bodyID < pStudioHdr->numbodyparts; ++bodyID ) + { + OptimizedModel::BodyPartHeader_t* pVtxBodyPart = pVtxHdr->pBodyPart( bodyID ); + mstudiobodyparts_t *pBodyPart = pStudioHdr->pBodypart( bodyID ); + + for ( int modelID = 0; modelID < pBodyPart->nummodels; ++modelID ) + { + OptimizedModel::ModelHeader_t* pVtxModel = pVtxBodyPart->pModel( modelID ); + mstudiomodel_t *pStudioModel = pBodyPart->pModel( modelID ); + + // assuming lod 0, could iterate if required + int nLod = 0; + OptimizedModel::ModelLODHeader_t *pVtxLOD = pVtxModel->pLOD( nLod ); + + for ( int nMesh = 0; nMesh < pStudioModel->nummeshes; ++nMesh ) + { + // check if this mesh's material is in the no shadow material name list + mstudiomesh_t* pMesh = pStudioModel->pMesh( nMesh ); + mstudiotexture_t *pTxtr=pStudioHdr->pTexture(pMesh->material); + //printf("mat idx=%d mat name=%s\n",pMesh->material,pTxtr->pszName()); + bool bSkipThisMesh = false; + for(int check=0; check<g_NonShadowCastingMaterialStrings.Count(); check++) + { + if ( Q_stristr( pTxtr->pszName(), + g_NonShadowCastingMaterialStrings[check] ) ) + { + //printf("skip mat name=%s\n",pTxtr->pszName()); + bSkipThisMesh = true; + break; + } + } + if ( bSkipThisMesh) + continue; + + int shadowTextureIndex = -1; + if ( dict.m_textureShadowIndex.Count() ) + { + shadowTextureIndex = dict.m_textureShadowIndex[pMesh->material]; + } + + + OptimizedModel::MeshHeader_t* pVtxMesh = pVtxLOD->pMesh( nMesh ); + const mstudio_meshvertexdata_t *vertData = pMesh->GetVertexData( (void *)pStudioHdr ); + Assert( vertData ); // This can only return NULL on X360 for now + + for ( int nGroup = 0; nGroup < pVtxMesh->numStripGroups; ++nGroup ) + { + OptimizedModel::StripGroupHeader_t* pStripGroup = pVtxMesh->pStripGroup( nGroup ); + + int nStrip; + for ( nStrip = 0; nStrip < pStripGroup->numStrips; nStrip++ ) + { + OptimizedModel::StripHeader_t *pStrip = pStripGroup->pStrip( nStrip ); + + if ( pStrip->flags & OptimizedModel::STRIP_IS_TRILIST ) + { + for ( int i = 0; i < pStrip->numIndices; i += 3 ) + { + int idx = pStrip->indexOffset + i; + + unsigned short i1 = *pStripGroup->pIndex( idx ); + unsigned short i2 = *pStripGroup->pIndex( idx + 1 ); + unsigned short i3 = *pStripGroup->pIndex( idx + 2 ); + + int vertex1 = pStripGroup->pVertex( i1 )->origMeshVertID; + int vertex2 = pStripGroup->pVertex( i2 )->origMeshVertID; + int vertex3 = pStripGroup->pVertex( i3 )->origMeshVertID; + + // transform position into world coordinate system + matrix3x4_t matrix; + AngleMatrix( prop.m_Angles, prop.m_Origin, matrix ); + + Vector position1; + Vector position2; + Vector position3; + VectorTransform( *vertData->Position( vertex1 ), matrix, position1 ); + VectorTransform( *vertData->Position( vertex2 ), matrix, position2 ); + VectorTransform( *vertData->Position( vertex3 ), matrix, position3 ); + unsigned short flags = 0; + int materialIndex = -1; + Vector color = vec3_origin; + if ( shadowTextureIndex >= 0 ) + { + if ( bInitTriangles ) + { + // add texture space and texture index to material database + // now + float coverage = g_ShadowTextureList.ComputeCoverageForTriangle(shadowTextureIndex, *vertData->Texcoord(vertex1), *vertData->Texcoord(vertex2), *vertData->Texcoord(vertex3) ); + if ( coverage < 1.0f ) + { + materialIndex = g_ShadowTextureList.AddMaterialEntry( shadowTextureIndex, *vertData->Texcoord(vertex1), *vertData->Texcoord(vertex2), *vertData->Texcoord(vertex3) ); + color.x = coverage; + } + else + { + materialIndex = -1; + } + dict.m_triangleMaterialIndex.AddToTail(materialIndex); + } + else + { + materialIndex = dict.m_triangleMaterialIndex[triangleIndex]; + triangleIndex++; + } + if ( materialIndex >= 0 ) + { + flags = FCACHETRI_TRANSPARENT; + } + } +// printf( "\ngl 3\n" ); +// printf( "gl %6.3f %6.3f %6.3f 1 0 0\n", XYZ(position1)); +// printf( "gl %6.3f %6.3f %6.3f 0 1 0\n", XYZ(position2)); +// printf( "gl %6.3f %6.3f %6.3f 0 0 1\n", XYZ(position3)); + g_RtEnv.AddTriangle( TRACE_ID_STATICPROP | nProp, + position1, position2, position3, + color, flags, materialIndex); + } + } + else + { + // all tris expected to be discrete tri lists + // must fixme if stripping ever occurs + printf( "unexpected strips found\n" ); + Assert( 0 ); + return; + } + } + } + } + } + } + } +} + +struct tl_tri_t +{ + Vector p0; + Vector p1; + Vector p2; + Vector n0; + Vector n1; + Vector n2; + + bool operator == (const tl_tri_t &t) const + { + return ( p0 == t.p0 && + p1 == t.p1 && + p2 == t.p2 && + n0 == t.n0 && + n1 == t.n1 && + n2 == t.n2 ); + } +}; + +struct tl_vert_t +{ + Vector m_position; + CUtlLinkedList< tl_tri_t, int > m_triList; +}; + +void AddTriVertsToList( CUtlVector< tl_vert_t > &triListVerts, int vertIndex, Vector vertPosition, Vector p0, Vector p1, Vector p2, Vector n0, Vector n1, Vector n2 ) +{ + tl_tri_t tlTri; + + tlTri.p0 = p0; + tlTri.p1 = p1; + tlTri.p2 = p2; + tlTri.n0 = n0; + tlTri.n1 = n1; + tlTri.n2 = n2; + + triListVerts.EnsureCapacity( vertIndex+1 ); + + triListVerts[vertIndex].m_position = vertPosition; + + int index = triListVerts[vertIndex].m_triList.Find( tlTri ); + if ( !triListVerts[vertIndex].m_triList.IsValidIndex( index ) ) + { + // not in list, add to list of triangles + triListVerts[vertIndex].m_triList.AddToTail( tlTri ); + } +} + +//----------------------------------------------------------------------------- +// Builds a list of tris for every vertex +//----------------------------------------------------------------------------- +void CVradStaticPropMgr::BuildTriList( CStaticProp &prop ) +{ + // the generated list will consist of a list of verts + // each vert will have a linked list of triangles that it belongs to + CUtlVector< tl_vert_t > triListVerts; + + StaticPropDict_t &dict = m_StaticPropDict[prop.m_ModelIdx]; + studiohdr_t *pStudioHdr = dict.m_pStudioHdr; + OptimizedModel::FileHeader_t *pVtxHdr = (OptimizedModel::FileHeader_t *)dict.m_VtxBuf.Base(); + if ( !pStudioHdr || !pVtxHdr ) + { + // must have model and its verts for decoding triangles + return; + } + + // meshes are deeply hierarchial, divided between three stores, follow the white rabbit + // body parts -> models -> lod meshes -> strip groups -> strips + // the vertices and indices are pooled, the trick is knowing the offset to determine your indexed base + for ( int bodyID = 0; bodyID < pStudioHdr->numbodyparts; ++bodyID ) + { + OptimizedModel::BodyPartHeader_t* pVtxBodyPart = pVtxHdr->pBodyPart( bodyID ); + mstudiobodyparts_t *pBodyPart = pStudioHdr->pBodypart( bodyID ); + + for ( int modelID = 0; modelID < pBodyPart->nummodels; ++modelID ) + { + OptimizedModel::ModelHeader_t* pVtxModel = pVtxBodyPart->pModel( modelID ); + mstudiomodel_t *pStudioModel = pBodyPart->pModel( modelID ); + + // get the specified lod, assuming lod 0 + int nLod = 0; + OptimizedModel::ModelLODHeader_t *pVtxLOD = pVtxModel->pLOD( nLod ); + + // must reset because each model has their own vertexes [0..n] + // in order for this to be monolithic for the entire prop the list must be segmented + triListVerts.Purge(); + + for ( int nMesh = 0; nMesh < pStudioModel->nummeshes; ++nMesh ) + { + mstudiomesh_t* pMesh = pStudioModel->pMesh( nMesh ); + OptimizedModel::MeshHeader_t* pVtxMesh = pVtxLOD->pMesh( nMesh ); + const mstudio_meshvertexdata_t *vertData = pMesh->GetVertexData( (void *)pStudioHdr ); + Assert( vertData ); // This can only return NULL on X360 for now + + for ( int nGroup = 0; nGroup < pVtxMesh->numStripGroups; ++nGroup ) + { + OptimizedModel::StripGroupHeader_t* pStripGroup = pVtxMesh->pStripGroup( nGroup ); + + int nStrip; + for ( nStrip = 0; nStrip < pStripGroup->numStrips; nStrip++ ) + { + OptimizedModel::StripHeader_t *pStrip = pStripGroup->pStrip( nStrip ); + + if ( pStrip->flags & OptimizedModel::STRIP_IS_TRILIST ) + { + for ( int i = 0; i < pStrip->numIndices; i += 3 ) + { + int idx = pStrip->indexOffset + i; + + unsigned short i1 = *pStripGroup->pIndex( idx ); + unsigned short i2 = *pStripGroup->pIndex( idx + 1 ); + unsigned short i3 = *pStripGroup->pIndex( idx + 2 ); + + int vertex1 = pStripGroup->pVertex( i1 )->origMeshVertID; + int vertex2 = pStripGroup->pVertex( i2 )->origMeshVertID; + int vertex3 = pStripGroup->pVertex( i3 )->origMeshVertID; + + // transform position into world coordinate system + matrix3x4_t matrix; + AngleMatrix( prop.m_Angles, prop.m_Origin, matrix ); + + Vector position1; + Vector position2; + Vector position3; + VectorTransform( *vertData->Position( vertex1 ), matrix, position1 ); + VectorTransform( *vertData->Position( vertex2 ), matrix, position2 ); + VectorTransform( *vertData->Position( vertex3 ), matrix, position3 ); + + Vector normal1; + Vector normal2; + Vector normal3; + VectorTransform( *vertData->Normal( vertex1 ), matrix, normal1 ); + VectorTransform( *vertData->Normal( vertex2 ), matrix, normal2 ); + VectorTransform( *vertData->Normal( vertex3 ), matrix, normal3 ); + + AddTriVertsToList( triListVerts, pMesh->vertexoffset + vertex1, position1, position1, position2, position3, normal1, normal2, normal3 ); + AddTriVertsToList( triListVerts, pMesh->vertexoffset + vertex2, position2, position1, position2, position3, normal1, normal2, normal3 ); + AddTriVertsToList( triListVerts, pMesh->vertexoffset + vertex3, position3, position1, position2, position3, normal1, normal2, normal3 ); + } + } + else + { + // all tris expected to be discrete tri lists + // must fixme if stripping ever occurs + printf( "unexpected strips found\n" ); + Assert( 0 ); + return; + } + } + } + } + } + } +} + +const vertexFileHeader_t * mstudiomodel_t::CacheVertexData( void *pModelData ) +{ + studiohdr_t *pActiveStudioHdr = static_cast<studiohdr_t *>(pModelData); + Assert( pActiveStudioHdr ); + + if ( pActiveStudioHdr->pVertexBase ) + { + return (vertexFileHeader_t *)pActiveStudioHdr->pVertexBase; + } + + // mandatory callback to make requested data resident + // load and persist the vertex file + char fileName[MAX_PATH]; + strcpy( fileName, "models/" ); + strcat( fileName, pActiveStudioHdr->pszName() ); + Q_StripExtension( fileName, fileName, sizeof( fileName ) ); + strcat( fileName, ".vvd" ); + + // load the model + FileHandle_t fileHandle = g_pFileSystem->Open( fileName, "rb" ); + if ( !fileHandle ) + { + Error( "Unable to load vertex data \"%s\"\n", fileName ); + } + + // Get the file size + int vvdSize = g_pFileSystem->Size( fileHandle ); + if ( vvdSize == 0 ) + { + g_pFileSystem->Close( fileHandle ); + Error( "Bad size for vertex data \"%s\"\n", fileName ); + } + + vertexFileHeader_t *pVvdHdr = (vertexFileHeader_t *)malloc( vvdSize ); + g_pFileSystem->Read( pVvdHdr, vvdSize, fileHandle ); + g_pFileSystem->Close( fileHandle ); + + // check header + if ( pVvdHdr->id != MODEL_VERTEX_FILE_ID ) + { + Error("Error Vertex File %s id %d should be %d\n", fileName, pVvdHdr->id, MODEL_VERTEX_FILE_ID); + } + if ( pVvdHdr->version != MODEL_VERTEX_FILE_VERSION ) + { + Error("Error Vertex File %s version %d should be %d\n", fileName, pVvdHdr->version, MODEL_VERTEX_FILE_VERSION); + } + if ( pVvdHdr->checksum != pActiveStudioHdr->checksum ) + { + Error("Error Vertex File %s checksum %d should be %d\n", fileName, pVvdHdr->checksum, pActiveStudioHdr->checksum); + } + + // need to perform mesh relocation fixups + // allocate a new copy + vertexFileHeader_t *pNewVvdHdr = (vertexFileHeader_t *)malloc( vvdSize ); + if ( !pNewVvdHdr ) + { + Error( "Error allocating %d bytes for Vertex File '%s'\n", vvdSize, fileName ); + } + + // load vertexes and run fixups + Studio_LoadVertexes( pVvdHdr, pNewVvdHdr, 0, true ); + + // discard original + free( pVvdHdr ); + pVvdHdr = pNewVvdHdr; + + pActiveStudioHdr->pVertexBase = (void*)pVvdHdr; + return pVvdHdr; +} + +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +// ------------------------------------------------------------------------------------------------ +struct ColorTexelValue +{ + Vector mLinearColor; // Linear color value for this texel + bool mValidData; // Whether there is valid data in this texel. + size_t mTriangleIndex; // Which triangle we used to generate the texel. +}; + +// ------------------------------------------------------------------------------------------------ +inline int ComputeLinearPos( int _x, int _y, int _resX, int _resY ) +{ + return Min( Max( 0, _y ), _resY - 1 ) * _resX + + Min( Max( 0, _x ), _resX - 1 ); +} + +// ------------------------------------------------------------------------------------------------ +inline float ComputeBarycentricDistanceToTri( Vector _barycentricCoord, Vector2D _v[3] ) +{ + Vector2D realPos = _barycentricCoord.x * _v[0] + + _barycentricCoord.y * _v[1] + + _barycentricCoord.z * _v[2]; + + int minIndex = 0; + float minVal = _barycentricCoord[0]; + for (int i = 1; i < 3; ++i) { + if (_barycentricCoord[i] < minVal) { + minVal = _barycentricCoord[i]; + minIndex = i; + } + } + + Vector2D& first = _v[ (minIndex + 1) % 3]; + Vector2D& second = _v[ (minIndex + 2) % 3]; + + return CalcDistanceToLineSegment2D( realPos, first, second ); +} + +// ------------------------------------------------------------------------------------------------ +static void GenerateLightmapSamplesForMesh( const matrix3x4_t& _matPos, const matrix3x4_t& _matNormal, int _iThread, int _skipProp, int _flags, int _lightmapResX, int _lightmapResY, studiohdr_t* _pStudioHdr, mstudiomodel_t* _pStudioModel, OptimizedModel::ModelHeader_t* _pVtxModel, int _meshID, CComputeStaticPropLightingResults *_outResults ) +{ + // Could iterate and gen this if needed. + int nLod = 0; + + OptimizedModel::ModelLODHeader_t *pVtxLOD = _pVtxModel->pLOD(nLod); + + CUtlVector<colorTexel_t> &colorTexels = (*_outResults->m_ColorTexelsArrays.Tail()); + const int cTotalPixelCount = _lightmapResX * _lightmapResY; + colorTexels.EnsureCount(cTotalPixelCount); + memset(colorTexels.Base(), 0, colorTexels.Count() * sizeof(colorTexel_t)); + + for (int i = 0; i < colorTexels.Count(); ++i) { + colorTexels[i].m_fDistanceToTri = FLT_MAX; + } + + mstudiomesh_t* pMesh = _pStudioModel->pMesh(_meshID); + OptimizedModel::MeshHeader_t* pVtxMesh = pVtxLOD->pMesh(_meshID); + const mstudio_meshvertexdata_t *vertData = pMesh->GetVertexData((void *)_pStudioHdr); + Assert(vertData); // This can only return NULL on X360 for now + + for (int nGroup = 0; nGroup < pVtxMesh->numStripGroups; ++nGroup) + { + OptimizedModel::StripGroupHeader_t* pStripGroup = pVtxMesh->pStripGroup(nGroup); + + int nStrip; + for (nStrip = 0; nStrip < pStripGroup->numStrips; nStrip++) + { + OptimizedModel::StripHeader_t *pStrip = pStripGroup->pStrip(nStrip); + + // If this hits, re-factor the code to iterate over triangles, and build the triangles + // from the underlying structures. + Assert((pStrip->flags & OptimizedModel::STRIP_IS_TRISTRIP) == 0); + + if (pStrip->flags & OptimizedModel::STRIP_IS_TRILIST) + { + for (int i = 0; i < pStrip->numIndices; i += 3) + { + int idx = pStrip->indexOffset + i; + + unsigned short i1 = *pStripGroup->pIndex(idx); + unsigned short i2 = *pStripGroup->pIndex(idx + 1); + unsigned short i3 = *pStripGroup->pIndex(idx + 2); + + int vertex1 = pStripGroup->pVertex(i1)->origMeshVertID; + int vertex2 = pStripGroup->pVertex(i2)->origMeshVertID; + int vertex3 = pStripGroup->pVertex(i3)->origMeshVertID; + + Vector modelPos[3] = { + *vertData->Position(vertex1), + *vertData->Position(vertex2), + *vertData->Position(vertex3) + }; + + Vector modelNormal[3] = { + *vertData->Normal(vertex1), + *vertData->Normal(vertex2), + *vertData->Normal(vertex3) + }; + + Vector worldPos[3]; + Vector worldNormal[3]; + + VectorTransform(modelPos[0], _matPos, worldPos[0]); + VectorTransform(modelPos[1], _matPos, worldPos[1]); + VectorTransform(modelPos[2], _matPos, worldPos[2]); + + VectorTransform(modelNormal[0], _matNormal, worldNormal[0]); + VectorTransform(modelNormal[1], _matNormal, worldNormal[1]); + VectorTransform(modelNormal[2], _matNormal, worldNormal[2]); + + Vector2D texcoord[3] = { + *vertData->Texcoord(vertex1), + *vertData->Texcoord(vertex2), + *vertData->Texcoord(vertex3) + }; + + Rasterizer rasterizer(texcoord[0], texcoord[1], texcoord[2], + _lightmapResX, _lightmapResY); + + for (auto it = rasterizer.begin(); it != rasterizer.end(); ++it) + { + size_t linearPos = rasterizer.GetLinearPos(it); + Assert(linearPos < cTotalPixelCount); + + if ( colorTexels[linearPos].m_bValid ) + { + continue; + } + + float ourDistancetoTri = ComputeBarycentricDistanceToTri( it->barycentric, texcoord ); + + bool doWrite = it->insideTriangle + || !colorTexels[linearPos].m_bPossiblyInteresting + || colorTexels[linearPos].m_fDistanceToTri > ourDistancetoTri; + + if (doWrite) + { + Vector itWorldPos = worldPos[0] * it->barycentric.x + + worldPos[1] * it->barycentric.y + + worldPos[2] * it->barycentric.z; + + Vector itWorldNormal = worldNormal[0] * it->barycentric.x + + worldNormal[1] * it->barycentric.y + + worldNormal[2] * it->barycentric.z; + itWorldNormal.NormalizeInPlace(); + + colorTexels[linearPos].m_WorldPosition = itWorldPos; + colorTexels[linearPos].m_WorldNormal = itWorldNormal; + colorTexels[linearPos].m_bValid = it->insideTriangle; + colorTexels[linearPos].m_bPossiblyInteresting = true; + colorTexels[linearPos].m_fDistanceToTri = ourDistancetoTri; + } + } + } + } + } + } + + // Process neighbors to the valid region. Walk through the existing array, look for samples that + // are not valid but are adjacent to valid samples. Works if we are only bilinearly sampling + // on the other side. + // First attempt: Just pretend the triangle was larger and cast a ray from this new world pos + // as above. + int linearPos = 0; + for ( int j = 0; j < _lightmapResY; ++j ) + { + for (int i = 0; i < _lightmapResX; ++i ) + { + bool shouldProcess = colorTexels[linearPos].m_bValid; + // Are any of the eight neighbors valid?? + if ( colorTexels[linearPos].m_bPossiblyInteresting ) + { + // Look at our neighborhood (3x3 centerd on us). + shouldProcess = shouldProcess + || colorTexels[ComputeLinearPos( i - 1, j - 1, _lightmapResX, _lightmapResY )].m_bValid // TL + || colorTexels[ComputeLinearPos( i , j - 1, _lightmapResX, _lightmapResY )].m_bValid // T + || colorTexels[ComputeLinearPos( i + 1, j - 1, _lightmapResX, _lightmapResY )].m_bValid // TR + + || colorTexels[ComputeLinearPos( i - 1, j , _lightmapResX, _lightmapResY )].m_bValid // L + || colorTexels[ComputeLinearPos( i + 1, j , _lightmapResX, _lightmapResY )].m_bValid // R + + || colorTexels[ComputeLinearPos( i - 1, j + 1, _lightmapResX, _lightmapResY )].m_bValid // BL + || colorTexels[ComputeLinearPos( i , j + 1, _lightmapResX, _lightmapResY )].m_bValid // B + || colorTexels[ComputeLinearPos( i + 1, j + 1, _lightmapResX, _lightmapResY )].m_bValid; // BR + } + + if (shouldProcess) + { + Vector directColor(0, 0, 0), + indirectColor(0, 0, 0); + + + ComputeDirectLightingAtPoint( colorTexels[linearPos].m_WorldPosition, colorTexels[linearPos].m_WorldNormal, directColor, _iThread, _skipProp, _flags); + + if (numbounce >= 1) { + ComputeIndirectLightingAtPoint( colorTexels[linearPos].m_WorldPosition, colorTexels[linearPos].m_WorldNormal, indirectColor, _iThread, true, (_flags & GATHERLFLAGS_IGNORE_NORMALS) != 0 ); + } + + VectorAdd(directColor, indirectColor, colorTexels[linearPos].m_Color); + } + + ++linearPos; + } + } +} + +// ------------------------------------------------------------------------------------------------ +static int GetTexelCount(unsigned int _resX, unsigned int _resY, bool _mipmaps) +{ + // Because they are unsigned, this is a != check--but if we were to change to ints, this would be + // the right assert (and it's no worse than != now). + Assert(_resX > 0 && _resY > 0); + + if (_mipmaps == false) + return _resX * _resY; + + int retVal = 0; + while (_resX > 1 || _resY > 1) + { + retVal += _resX * _resY; + _resX = max(1, _resX >> 1); + _resY = max(1, _resY >> 1); + } + + // Add in the 1x1 mipmap level, which wasn't hit above. This could be done in the initializer of + // retVal, but it's more obvious here. + retVal += 1; + + return retVal; +} + +// ------------------------------------------------------------------------------------------------ +static void FilterFineMipmap(unsigned int _resX, unsigned int _resY, const CUtlVector<colorTexel_t>& _srcTexels, CUtlVector<Vector>* _outLinear) +{ + Assert(_outLinear); + // We can't filter in place, so go ahead and create a linear buffer here. + CUtlVector<Vector> filterSrc; + filterSrc.EnsureCount(_srcTexels.Count()); + + for (int i = 0; i < _srcTexels.Count(); ++i) + { + ColorRGBExp32 rgbColor; + VectorToColorRGBExp32(_srcTexels[i].m_Color, rgbColor); + ConvertRGBExp32ToLinear( &rgbColor, &(filterSrc[i]) ); + } + + const int cRadius = 1; + const float cOneOverDiameter = 1.0f / pow(2.0f * cRadius + 1.0f, 2.0f) ; + // Filter here. + for (int j = 0; j < _resY; ++j) + { + for (int i = 0; i < _resX; ++i) + { + Vector value(0, 0, 0); + int thisIndex = ComputeLinearPos(i, j, _resX, _resY); + + if (!_srcTexels[thisIndex].m_bValid) + { + (*_outLinear)[thisIndex] = filterSrc[thisIndex]; + continue; + } + + // TODO: Check ASM for this, unroll by hand if needed. + for ( int offsetJ = -cRadius; offsetJ <= cRadius; ++offsetJ ) + { + for ( int offsetI = -cRadius; offsetI <= cRadius; ++offsetI ) + { + int finalIndex = ComputeLinearPos( i + offsetI, j + offsetJ, _resX, _resY ); + if ( !_srcTexels[finalIndex].m_bValid ) + { + finalIndex = thisIndex; + } + + value += filterSrc[finalIndex]; + } + } + + (*_outLinear)[thisIndex] = value * cOneOverDiameter; + } + } +} + +// ------------------------------------------------------------------------------------------------ +static void BuildFineMipmap(unsigned int _resX, unsigned int _resY, bool _applyFilter, const CUtlVector<colorTexel_t>& _srcTexels, CUtlVector<RGB888_t>* _outTexelsRGB888, CUtlVector<Vector>* _outLinear) +{ + // At least one of these needs to be non-null, otherwise what are we doing here? + Assert(_outTexelsRGB888 || _outLinear); + Assert(!_applyFilter || _outLinear); + Assert(_srcTexels.Count() == GetTexelCount(_resX, _resY, false)); + + int texelCount = GetTexelCount(_resX, _resY, true); + + if (_outTexelsRGB888) + (*_outTexelsRGB888).EnsureCount(texelCount); + + if (_outLinear) + (*_outLinear).EnsureCount(GetTexelCount(_resX, _resY, false)); + + // This code can take awhile, so minimize the branchiness of the inner-loop. + if (_applyFilter) + { + + FilterFineMipmap(_resX, _resY, _srcTexels, _outLinear); + + if ( _outTexelsRGB888 ) + { + for (int i = 0; i < _srcTexels.Count(); ++i) + { + RGBA8888_t encodedColor; + + Vector linearColor = (*_outLinear)[i]; + + ConvertLinearToRGBA8888( &linearColor, (unsigned char*)&encodedColor ); + (*_outTexelsRGB888)[i].r = encodedColor.r; + (*_outTexelsRGB888)[i].g = encodedColor.g; + (*_outTexelsRGB888)[i].b = encodedColor.b; + } + } + } + else + { + for (int i = 0; i < _srcTexels.Count(); ++i) + { + ColorRGBExp32 rgbColor; + RGBA8888_t encodedColor; + VectorToColorRGBExp32(_srcTexels[i].m_Color, rgbColor); + ConvertRGBExp32ToRGBA8888(&rgbColor, (unsigned char*)&encodedColor, (_outLinear ? (&(*_outLinear)[i]) : NULL) ); + // We drop alpha on the floor here, if this were to fire we'd need to consider using a different compressed format. + Assert(encodedColor.a == 0xFF); + + if (_outTexelsRGB888) + { + (*_outTexelsRGB888)[i].r = encodedColor.r; + (*_outTexelsRGB888)[i].g = encodedColor.g; + (*_outTexelsRGB888)[i].b = encodedColor.b; + } + } + } +} + +// ------------------------------------------------------------------------------------------------ +static void FilterCoarserMipmaps(unsigned int _resX, unsigned int _resY, CUtlVector<Vector>* _scratchLinear, CUtlVector<RGB888_t> *_outTexelsRGB888) +{ + Assert(_outTexelsRGB888); + + int srcResX = _resX; + int srcResY = _resY; + int dstResX = max(1, (srcResX >> 1)); + int dstResY = max(1, (srcResY >> 1)); + int dstOffset = GetTexelCount(srcResX, srcResY, false); + + // Build mipmaps here, after being converted to linear space. + // TODO: Should do better filtering for downsampling. But this will work for now. + while (srcResX > 1 || srcResY > 1) + { + for (int j = 0; j < srcResY; j += 2) { + for (int i = 0; i < srcResX; i += 2) { + int srcCol0 = i; + int srcCol1 = i + 1 > srcResX - 1 ? srcResX - 1 : i + 1; + int srcRow0 = j; + int srcRow1 = j + 1 > srcResY - 1 ? srcResY - 1 : j + 1;; + + int dstCol = i >> 1; + int dstRow = j >> 1; + + + const Vector& tl = (*_scratchLinear)[srcCol0 + (srcRow0 * srcResX)]; + const Vector& tr = (*_scratchLinear)[srcCol1 + (srcRow0 * srcResX)]; + const Vector& bl = (*_scratchLinear)[srcCol0 + (srcRow1 * srcResX)]; + const Vector& br = (*_scratchLinear)[srcCol1 + (srcRow1 * srcResX)]; + + Vector sample = (tl + tr + bl + br) / 4.0f; + + ConvertLinearToRGBA8888(&sample, (unsigned char*)&(*_outTexelsRGB888)[dstOffset + dstCol + dstRow * dstResX]); + + // Also overwrite the srcBuffer to filter the next loop. This is safe because we won't be reading this source value + // again during this mipmap level. + (*_scratchLinear)[dstCol + dstRow * dstResX] = sample; + } + } + + srcResX = dstResX; + srcResY = dstResY; + dstResX = max(1, (srcResX >> 1)); + dstResY = max(1, (srcResY >> 1)); + dstOffset += GetTexelCount(srcResX, srcResY, false); + } +} + +// ------------------------------------------------------------------------------------------------ +static void ConvertToDestinationFormat(unsigned int _resX, unsigned int _resY, ImageFormat _destFmt, const CUtlVector<RGB888_t>& _scratchRBG888, CUtlMemory<byte>* _outTexture) +{ + const ImageFormat cSrcImageFormat = IMAGE_FORMAT_RGB888; + + // Converts from the scratch RGB888 buffer, which should be fully filled out to the output texture. + int destMemoryUsage = ImageLoader::GetMemRequired(_resX, _resY, 1, _destFmt, true); + (*_outTexture).EnsureCapacity(destMemoryUsage); + + int srcResX = _resX; + int srcResY = _resY; + int srcOffset = 0; + int dstOffset = 0; + + // The usual case--that they'll be different. + if (cSrcImageFormat != _destFmt) + { + while (srcResX > 1 || srcResY > 1) + { + // Convert this mipmap level. + ImageLoader::ConvertImageFormat((unsigned char*)(&_scratchRBG888[srcOffset]), cSrcImageFormat, (*_outTexture).Base() + dstOffset, _destFmt, srcResX, srcResY); + + // Then update offsets for the next mipmap level. + srcOffset += GetTexelCount(srcResX, srcResY, false); + dstOffset += ImageLoader::GetMemRequired(srcResX, srcResY, 1, _destFmt, false); + + srcResX = max(1, (srcResX >> 1)); + srcResY = max(1, (srcResY >> 1)); + } + + // Do the 1x1 level also. + ImageLoader::ConvertImageFormat((unsigned char*)_scratchRBG888.Base() + srcOffset, cSrcImageFormat, (*_outTexture).Base() + dstOffset, _destFmt, srcResX, srcResY); + } else { + // But sometimes (particularly for debugging) they will be the same. + Q_memcpy( (*_outTexture).Base(), _scratchRBG888.Base(), destMemoryUsage ); + } +} + +// ------------------------------------------------------------------------------------------------ +static void ConvertTexelDataToTexture(unsigned int _resX, unsigned int _resY, ImageFormat _destFmt, const CUtlVector<colorTexel_t>& _srcTexels, CUtlMemory<byte>* _outTexture) +{ + Assert(_outTexture); + Assert(_srcTexels.Count() == _resX * _resY); + + CUtlVector<RGB888_t> scratchRGB888; + CUtlVector<Vector> scratchLinear; + + BuildFineMipmap(_resX, _resY, true, _srcTexels, &scratchRGB888, &scratchLinear); + FilterCoarserMipmaps(_resX, _resY, &scratchLinear, &scratchRGB888 ); + ConvertToDestinationFormat(_resX, _resY, _destFmt, scratchRGB888, _outTexture); +} + +// ------------------------------------------------------------------------------------------------ +static void DumpLightmapLinear( const char* _dstFilename, const CUtlVector<colorTexel_t>& _srcTexels, int _width, int _height ) +{ + CUtlVector< Vector > linearFloats; + CUtlVector< BGR888_t > linearBuffer; + BuildFineMipmap( _width, _height, true, _srcTexels, NULL, &linearFloats ); + linearBuffer.SetCount( linearFloats.Count() ); + + for ( int i = 0; i < linearFloats.Count(); ++i ) { + linearBuffer[i].b = RoundFloatToByte(linearFloats[i].z * 255.0f); + linearBuffer[i].g = RoundFloatToByte(linearFloats[i].y * 255.0f); + linearBuffer[i].r = RoundFloatToByte(linearFloats[i].x * 255.0f); + } + + TGAWriter::WriteTGAFile( _dstFilename, _width, _height, IMAGE_FORMAT_BGR888, (uint8*)(linearBuffer.Base()), _width * ImageLoader::SizeInBytes(IMAGE_FORMAT_BGR888) ); +} |