summaryrefslogtreecommitdiff
path: root/utils/vrad
diff options
context:
space:
mode:
Diffstat (limited to 'utils/vrad')
-rw-r--r--utils/vrad/disp_vrad.cpp332
-rw-r--r--utils/vrad/disp_vrad.h22
-rw-r--r--utils/vrad/iincremental.h71
-rw-r--r--utils/vrad/imagepacker.cpp141
-rw-r--r--utils/vrad/imagepacker.h51
-rw-r--r--utils/vrad/incremental.cpp766
-rw-r--r--utils/vrad/incremental.h175
-rw-r--r--utils/vrad/leaf_ambient_lighting.cpp708
-rw-r--r--utils/vrad/leaf_ambient_lighting.h17
-rw-r--r--utils/vrad/lightmap.cpp3598
-rw-r--r--utils/vrad/lightmap.h141
-rw-r--r--utils/vrad/macro_texture.cpp166
-rw-r--r--utils/vrad/macro_texture.h24
-rw-r--r--utils/vrad/mpivrad.cpp496
-rw-r--r--utils/vrad/mpivrad.h36
-rw-r--r--utils/vrad/notes.txt22
-rw-r--r--utils/vrad/origface.cpp51
-rw-r--r--utils/vrad/radial.cpp882
-rw-r--r--utils/vrad/radial.h76
-rw-r--r--utils/vrad/samplehash.cpp230
-rw-r--r--utils/vrad/trace.cpp653
-rw-r--r--utils/vrad/vismat.cpp483
-rw-r--r--utils/vrad/vismat.h34
-rw-r--r--utils/vrad/vrad.cpp2960
-rw-r--r--utils/vrad/vrad.h614
-rw-r--r--utils/vrad/vrad_dispcoll.cpp1080
-rw-r--r--utils/vrad/vrad_dispcoll.h80
-rw-r--r--utils/vrad/vrad_dll.vpc226
-rw-r--r--utils/vrad/vraddetailprops.cpp1036
-rw-r--r--utils/vrad/vraddetailprops.h34
-rw-r--r--utils/vrad/vraddisps.cpp1759
-rw-r--r--utils/vrad/vraddll.cpp243
-rw-r--r--utils/vrad/vraddll.h34
-rw-r--r--utils/vrad/vradstaticprops.cpp2694
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 &centroid = 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) );
+}