Counter Strike : Global Offensive Source Code
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1203 lines
36 KiB

//===== Copyright 1996-2005, Valve Corporation, All rights reserved. ======//
//
// Purpose:
//
// $Workfile: $
// $Date: $
// $NoKeywords: $
//===========================================================================//
#include "render_pch.h"
#include "gl_cvars.h"
#include "gl_model_private.h"
#include "gl_lightmap.h"
#include "disp.h"
#include "mathlib/mathlib.h"
#include "gl_rsurf.h"
#include "gl_matsysiface.h"
#include "zone.h"
#include "materialsystem/imesh.h"
#include "iscratchpad3d.h"
#include "decal_private.h"
#include "con_nprint.h"
#include "dispcoll_common.h"
#include "cmodel_private.h"
#include "collisionutils.h"
#include "tier0/dbg.h"
#include "gl_rmain.h"
#include "lightcache.h"
#include "disp_tesselate.h"
#include "shadowmgr.h"
#include "debugoverlay.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
//-----------------------------------------------------------------------------
// Globals.
//-----------------------------------------------------------------------------
Vector modelorg;
//-----------------------------------------------------------------------------
// CEngineTesselateHelper implements the abstract parts of the tesselation code.
// We're only interested in the final triangles anyway, right??
//-----------------------------------------------------------------------------
class CEngineTesselateHelper : public CBaseTesselateHelper
{
public:
void EndTriangle()
{
// Put all triangles in here.
int iVertOffset = m_pDisp->m_iVertOffset;
// Add this tri to our mesh.
m_IndexMesh.Index( m_TempIndices[0] + iVertOffset );
m_IndexMesh.AdvanceIndex();
m_IndexMesh.Index( m_TempIndices[1] + iVertOffset );
m_IndexMesh.AdvanceIndex();
m_IndexMesh.Index( m_TempIndices[2] + iVertOffset );
m_IndexMesh.AdvanceIndex();
// Store off the indices...
m_pDisp->m_Indices[m_nIndices] = m_TempIndices[0] + iVertOffset;
m_pDisp->m_Indices[m_nIndices+1] = m_TempIndices[1] + iVertOffset;
m_pDisp->m_Indices[m_nIndices+2] = m_TempIndices[2] + iVertOffset;
m_nIndices += 3;
}
DispNodeInfo_t& GetNodeInfo( int iNodeBit )
{
return m_pDisp->m_pNodeInfo[iNodeBit];
}
public:
// The mesh that we specify indices into while tesselating.
CMeshBuilder m_IndexMesh;
CDispInfo *m_pDisp;
};
//-----------------------------------------------------------------------------
// CDispInfo implementation.
//-----------------------------------------------------------------------------
inline CVertIndex CDispInfo::IndexToVert( int index ) const
{
if( index == -1 )
return CVertIndex( -1, -1 );
else
return CVertIndex( index % GetSideLength(), index / GetSideLength() );
}
void CDispInfo::UpdateBoundingBox()
{
m_BBoxMin.Init( 1e24, 1e24, 1e24 );
m_BBoxMax.Init( -1e24, -1e24, -1e24 );
for( int i=0; i < NumVerts(); i++ )
{
const Vector &pos = m_MeshReader.Position( i );
VectorMin( pos, m_BBoxMin, m_BBoxMin );
VectorMax( pos, m_BBoxMax, m_BBoxMax );
}
UpdateNodeBoundingBoxes();
}
void CDispInfo::UpdateNodeBoundingBoxes()
{
// init all nodes to an invalid AABB
for ( int i = 0; i < m_pPowerInfo->m_NodeCount; i++ )
{
m_pNodeInfo[i].m_mins.Init( FLT_MAX, FLT_MAX, FLT_MAX );
m_pNodeInfo[i].m_maxs.Init( -FLT_MAX, -FLT_MAX, -FLT_MAX );
}
UpdateNodeBoundingBoxes_R( m_pPowerInfo->m_RootNode, 0, 0 );
}
void CDispInfo::UpdateNodeBoundingBoxes_R( CVertIndex const &nodeIndex, int iNodeBitIndex, int iLevel )
{
int iNodeIndex = VertIndex( nodeIndex );
DispNodeInfo_t& nodeInfo = m_pNodeInfo[iNodeBitIndex];
nodeInfo.m_maxs.Init( -FLT_MAX, -FLT_MAX, -FLT_MAX );
nodeInfo.m_mins.Init( FLT_MAX, FLT_MAX, FLT_MAX );
if ( ( iLevel+1 < m_Power ) && ( nodeInfo.m_Flags & DispNodeInfo_t::CHILDREN_HAVE_TRIANGLES ) )
{
// Recurse into child nodes.
int iChildNodeBit = iNodeBitIndex + 1;
for( int iChild=0; iChild < 4; iChild++ )
{
CVertIndex const &childNode = m_pPowerInfo->m_pChildVerts[iNodeIndex].m_Verts[iChild];
UpdateNodeBoundingBoxes_R( childNode, iChildNodeBit, iLevel + 1 );
// expand box by child bounds
DispNodeInfo_t& childNodeInfo = m_pNodeInfo[iChildNodeBit];
if ( childNodeInfo.m_mins.x != FLT_MAX )
{
VectorMax( childNodeInfo.m_maxs, nodeInfo.m_maxs, nodeInfo.m_maxs );
VectorMin( childNodeInfo.m_mins, nodeInfo.m_mins, nodeInfo.m_mins );
}
iChildNodeBit += m_pPowerInfo->m_NodeIndexIncrements[iLevel];
}
}
// BBox around triangles in this node
for ( int i = 0; i < nodeInfo.m_Count; i += 3 )
{
int iIndexStart = nodeInfo.m_FirstTesselationIndex + i;
unsigned short tempIndices[3] =
{
m_MeshReader.Index( iIndexStart+0 ) - m_iVertOffset,
m_MeshReader.Index( iIndexStart+1 ) - m_iVertOffset,
m_MeshReader.Index( iIndexStart+2 ) - m_iVertOffset
};
for ( int j = 0; j < 3; j++ )
{
const Vector &v0 = m_MeshReader.Position( tempIndices[j] );
VectorMax( v0, nodeInfo.m_maxs, nodeInfo.m_maxs );
VectorMin( v0, nodeInfo.m_mins, nodeInfo.m_mins );
}
}
}
inline void CDispInfo::DecalProjectVert( Vector const &vPos, CDispDecalBase *pDecalBase, ShadowInfo_t const* pInfo, Vector &out )
{
if (!pInfo)
{
CDispDecal* pDispDecal = static_cast<CDispDecal*>(pDecalBase);
out.x = vPos.Dot( pDispDecal->m_TextureSpaceBasis[0] ) - pDispDecal->m_pDecal->dx + .5f;
out.y = vPos.Dot( pDispDecal->m_TextureSpaceBasis[1] ) - pDispDecal->m_pDecal->dy + .5f;
out.z = 0;
}
else
{
Vector3DMultiplyPosition( pInfo->m_WorldToShadow, vPos, out );
}
}
// ----------------------------------------------------------------------------- //
// This version works for normal decals
// ----------------------------------------------------------------------------- //
void CDispInfo::TestAddDecalTri( int iIndexStart, unsigned short decalHandle, CDispDecal *pDispDecal )
{
decal_t *pDecal = pDispDecal->m_pDecal;
// If the decal is too far away from the plane of this triangle, reject it.
unsigned short tempIndices[3] =
{
m_MeshReader.Index( iIndexStart+0 ) - m_iVertOffset,
m_MeshReader.Index( iIndexStart+1 ) - m_iVertOffset,
m_MeshReader.Index( iIndexStart+2 ) - m_iVertOffset
};
const Vector &v0 = m_MeshReader.Position( tempIndices[0] );
const Vector &v1 = m_MeshReader.Position( tempIndices[1] );
const Vector &v2 = m_MeshReader.Position( tempIndices[2] );
Vector vNormal = (v2 - v0).Cross( v1 - v0 );
VectorNormalize( vNormal );
if ( vNormal.Dot( pDecal->position - v0 ) >= pDispDecal->m_flSize )
return;
// Setup verts.
CDecalVert verts[3];
int iVert;
for( iVert=0; iVert < 3; iVert++ )
{
CDecalVert *pOutVert = &verts[iVert];
pOutVert->m_vPos = m_MeshReader.Position( tempIndices[iVert] );
{
float x = pOutVert->m_cLMCoords.x;
float y = pOutVert->m_cLMCoords.y;
m_MeshReader.TexCoord2f( tempIndices[iVert], 1, x, y );
pOutVert->m_cLMCoords.x = x;
pOutVert->m_cLMCoords.y = y;
}
// garymcthack - what about m_ParentTexCoords?
Vector tmp;
DecalProjectVert( pOutVert->m_vPos, pDispDecal, 0, tmp );
pOutVert->m_ctCoords.x = tmp.x;
pOutVert->m_ctCoords.y = tmp.y;
}
// Clip them.
CDecalVert *pClipped;
CDecalVert *pOutVerts = NULL;
pClipped = R_DoDecalSHClip( &verts[0], pOutVerts, pDecal, 3, vec3_origin );
int outCount = pDecal->clippedVertCount;
if ( outCount > 2 )
{
outCount = MIN( outCount, CDispDecalFragment::MAX_VERTS );
// Allocate a new fragment...
CDispDecalFragment* pFragment = AllocateDispDecalFragment( decalHandle, outCount );
// Alrighty, store the triangles!
for( iVert=0; iVert < outCount; iVert++ )
{
pFragment->m_pVerts[iVert].m_vPos = pClipped[iVert].m_vPos;
// garymcthack - need to make this work for displacements
// pFragment->m_tCoords[iVert] = pClipped[iVert].m_tCoords;
// garymcthack - need to change m_TCoords to m_ParentTexCoords
pFragment->m_pVerts[iVert].m_ctCoords = pClipped[iVert].m_ctCoords;
pFragment->m_pVerts[iVert].m_cLMCoords = pClipped[iVert].m_cLMCoords;
}
/*
static int three = 0;
static int total = 0;
total++;
if( outCount == 3 )
{
three++;
}
//if( )
{
char buffer[256];
sprintf(buffer, "Verts: 3:%i 4+:%i (%i)\n",three, total, sizeof(CDecalVert));
Msg(buffer);
}
*/
pFragment->m_pDecal = pDecal;
pFragment->m_nVerts = outCount;
pDispDecal->m_nVerts += pFragment->m_nVerts;
pDispDecal->m_nTris += pFragment->m_nVerts - 2;
}
}
// ----------------------------------------------------------------------------- //
// This version works for shadow decals
// ----------------------------------------------------------------------------- //
void CDispInfo::TestAddDecalTri( int iIndexStart, unsigned short decalHandle, CDispShadowDecal *pDecal )
{
unsigned short tempIndices[3] =
{
m_MeshReader.Index( iIndexStart+0 ) - m_iVertOffset,
m_MeshReader.Index( iIndexStart+1 ) - m_iVertOffset,
m_MeshReader.Index( iIndexStart+2 ) - m_iVertOffset
};
#ifndef DEDICATED
// Setup verts.
Vector vPositions[3] ={
GetOverlayPos( &m_MeshReader, tempIndices[0] ),
GetOverlayPos( &m_MeshReader, tempIndices[1] ),
GetOverlayPos( &m_MeshReader, tempIndices[2] )
};
Vector* ppPosition[3] = { &vPositions[0], &vPositions[1], &vPositions[2] };
ShadowVertex_t** ppClipVertex;
ShadowClipState_t clip;
int count = g_pShadowMgr->ProjectAndClipVerticesEx( pDecal->m_Shadow, 3, ppPosition, &ppClipVertex, clip ); // using the thread-safe version
if (count < 3)
return;
// Ok, clipping happened; lets create a decal fragment.
Assert( count <= CDispShadowFragment::MAX_VERTS );
// Allocate a new fragment...
CDispShadowFragment* pFragment = AllocateShadowDecalFragment( decalHandle, count );
// Copy the fragment data in place
pFragment->m_nVerts = count;
for (int i = 0; i < count; ++i )
{
VectorCopy( ppClipVertex[i]->m_Position, pFragment->m_ShadowVerts[i].m_Position );
VectorCopy( ppClipVertex[i]->m_ShadowSpaceTexCoord, pFragment->m_ShadowVerts[i].m_ShadowSpaceTexCoord );
// Make sure it's been clipped
Assert( pFragment->m_ShadowVerts[i].m_ShadowSpaceTexCoord[0] >= -1e-3f );
Assert( pFragment->m_ShadowVerts[i].m_ShadowSpaceTexCoord[0] - 1.0f <= 1e-3f );
Assert( pFragment->m_ShadowVerts[i].m_ShadowSpaceTexCoord[1] >= -1e-3f );
Assert( pFragment->m_ShadowVerts[i].m_ShadowSpaceTexCoord[1] - 1.0f <= 1e-3f );
}
// Update the number of triangles in the decal
pDecal->m_nVerts += pFragment->m_nVerts;
pDecal->m_nTris += pFragment->m_nVerts - 2;
Assert( pDecal->m_nTris != 0 );
#endif
}
void CDispInfo::CullDecals(
int iNodeBit,
CDispDecal **decals,
int nDecals,
CDispDecal **childDecals,
int &nChildDecals )
{
// Only let the decals through that can affect this node or its children.
nChildDecals = 0;
for( int iDecal=0; iDecal < nDecals; iDecal++ )
{
if( decals[iDecal]->m_NodeIntersect.Get( iNodeBit ) )
{
childDecals[nChildDecals] = decals[iDecal];
++nChildDecals;
}
}
}
//-----------------------------------------------------------------------------
// Retesselates a displacement
//-----------------------------------------------------------------------------
void CDispInfo::TesselateDisplacement()
{
// Clear decals. They get regenerated in TesselateDisplacement_R.
ClearAllDecalFragments();
// Blow away cached shadow decals
ClearAllShadowDecalFragments();
int nMaxIndices = Square( GetSideLength() - 1 ) * 6;
CEngineTesselateHelper helper;
helper.m_pDisp = this;
helper.m_IndexMesh.BeginModify( m_pMesh->m_pMesh, 0, 0, m_iIndexOffset, nMaxIndices );
helper.m_pActiveVerts = m_ActiveVerts.Base();
helper.m_pPowerInfo = GetPowerInfo();
// Generate the indices.
::TesselateDisplacement<CEngineTesselateHelper>( &helper ); // (implemented in disp_tesselate.h)
helper.m_IndexMesh.EndModify();
m_nIndices = helper.m_nIndices;
}
void CDispInfo::SpecifyDynamicMesh()
{
CMatRenderContextPtr pRenderContext( materials );
// Specify the vertices and indices.
IMesh *pMesh = pRenderContext->GetDynamicMesh( true );
CMeshBuilder builder;
builder.Begin( pMesh, MATERIAL_TRIANGLES, NumVerts(), m_nIndices );
// This should mirror how FillStaticBuffer works.
int nVerts = NumVerts();
for( int iVert=0; iVert < nVerts; iVert++ )
{
CDispRenderVert *pVert = &m_Verts[iVert];
builder.Position3fv( pVert->m_vPos.Base() );
builder.TexCoord2fv( 0, pVert->m_vTexCoord.Base() );
builder.TexCoord2fv( 1, pVert->m_LMCoords.Base() );
builder.TexCoord2f( 2, m_BumpSTexCoordOffset, 0 );
builder.Normal3fv( pVert->m_vNormal.Base() );
builder.TangentS3fv( pVert->m_vSVector.Base() );
builder.TangentT3fv( pVert->m_vTVector.Base() );
builder.AdvanceVertex();
}
for( int iIndex=0; iIndex < m_nIndices; iIndex++ )
{
builder.Index( m_Indices[iIndex] - m_iVertOffset );
builder.AdvanceIndex();
}
builder.End( false, true );
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CDispInfo::SpecifyWalkableDynamicMesh( void )
{
// Specify the vertices and indices.
CMatRenderContextPtr pRenderContext( materials );
#ifdef DEDICATED
IMesh *pMesh = pRenderContext->GetDynamicMesh( false, NULL, NULL, NULL );
#else
IMesh *pMesh = pRenderContext->GetDynamicMesh( false, NULL, NULL, g_materialTranslucentSingleColor );
g_materialTranslucentSingleColor->ColorModulate( 1.0f, 1.0f, 0.0f );
g_materialTranslucentSingleColor->AlphaModulate( 0.33f );
#endif
CMeshBuilder builder;
builder.Begin( pMesh, MATERIAL_TRIANGLES, NumVerts(), m_nWalkIndexCount );
int nVerts = NumVerts();
for( int iVert=0; iVert < nVerts; iVert++ )
{
builder.Position3fv( m_Verts[iVert].m_vPos.Base() );
builder.AdvanceVertex();
}
for( int iIndex=0; iIndex < m_nWalkIndexCount; iIndex++ )
{
builder.Index( m_pWalkIndices[iIndex] );
builder.AdvanceIndex();
}
builder.End( false, true );
}
//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
void CDispInfo::SpecifyBuildableDynamicMesh( void )
{
// Specify the vertices and indices.
CMatRenderContextPtr pRenderContext( materials );
#ifdef DEDICATED
IMesh *pMesh = pRenderContext->GetDynamicMesh( false, NULL, NULL, NULL );
#else
g_materialTranslucentSingleColor->ColorModulate( 0.0f, 1.0f, 1.0f );
g_materialTranslucentSingleColor->AlphaModulate( 0.33f );
IMesh *pMesh = pRenderContext->GetDynamicMesh( false, NULL, NULL, g_materialTranslucentSingleColor );
#endif
CMeshBuilder builder;
builder.Begin( pMesh, MATERIAL_TRIANGLES, NumVerts(), m_nBuildIndexCount );
int nVerts = NumVerts();
for( int iVert=0; iVert < nVerts; iVert++ )
{
builder.Position3fv( m_Verts[iVert].m_vPos.Base() );
builder.AdvanceVertex();
}
for( int iIndex=0; iIndex < m_nBuildIndexCount; iIndex++ )
{
builder.Index( m_pBuildIndices[iIndex] );
builder.AdvanceIndex();
}
builder.End( false, true );
}
void CDispInfo::InitializeActiveVerts()
{
// Mark the corners vertices and root node by default..
m_ActiveVerts.ClearAll();
m_ActiveVerts.Set( VertIndex( 0, 0 ) );
m_ActiveVerts.Set( VertIndex( GetSideLength()-1, 0 ) );
m_ActiveVerts.Set( VertIndex( GetSideLength()-1, GetSideLength()-1 ) );
m_ActiveVerts.Set( VertIndex( 0, GetSideLength()-1 ) );
m_ActiveVerts.Set( VertIndex( m_pPowerInfo->m_RootNode ) );
// Force the midpoint active on any edges where there are sub displacements.
for( int iSide=0; iSide < 4; iSide++ )
{
CDispNeighbor *pSide = &m_EdgeNeighbors[iSide];
if( (pSide->m_SubNeighbors[0].IsValid() && pSide->m_SubNeighbors[0].m_Span != CORNER_TO_CORNER) ||
(pSide->m_SubNeighbors[1].IsValid() && pSide->m_SubNeighbors[1].m_Span != CORNER_TO_CORNER) )
{
int iEdgeDim = g_EdgeDims[iSide];
CVertIndex nodeIndex;
nodeIndex[iEdgeDim] = g_EdgeSideLenMul[iSide] * m_pPowerInfo->m_SideLengthM1;
nodeIndex[!iEdgeDim] = m_pPowerInfo->m_MidPoint;
m_ActiveVerts.Set( VertIndex( nodeIndex ) );
}
}
}
void CDispInfo::ClearLOD()
{
// First, everything as inactive.
m_ActiveVerts.ClearAll();
}
extern ConVar mat_surfaceid;
extern ConVar mat_surfacemat;
bool DispInfoRenderDebugModes()
{
if( ShouldDrawInWireFrameMode() || mat_luxels.GetInt() || r_DispWalkable.GetInt() ||
r_DispBuildable.GetInt()
#if !defined( DEDICATED )
|| mat_surfaceid.GetInt() || mat_surfacemat.GetInt()
#endif // DEDICATED
)
return true;
return false;
}
bool CDispInfo::Render( CGroupMesh *pGroup, bool bAllowDebugModes )
{
#ifndef DEDICATED
if( !m_pMesh )
{
Assert( !"CDispInfo::Render: m_pMesh == NULL" );
return false;
}
if ( bAllowDebugModes )
{
CMatRenderContextPtr pRenderContext( materials );
// Wireframe?
if( ShouldDrawInWireFrameMode() )
{
// BUGBUG: The draw order for this is wrong - we need to write Z here or this won't show up
// BUGBUG: Move this wireframe draw to the end of the scene instead of during normal disp draw (which is first)
int nWireFrameMode = WireFrameMode();
if ( nWireFrameMode == 2 )
{
pRenderContext->Bind( g_materialWorldWireframeZBuffer );
}
else
{
pRenderContext->Bind( g_materialWorldWireframeGreen );
}
SpecifyDynamicMesh();
}
if( mat_luxels.GetInt() )
{
pRenderContext->Bind( MSurf_TexInfo( m_ParentSurfID )->material );
//SpecifyDynamicMesh();
pGroup->m_pMesh->Draw( m_iIndexOffset, m_nIndices );
pRenderContext->Bind( g_materialDebugLuxels );
SpecifyDynamicMesh();
}
if ( r_DispWalkable.GetInt() || r_DispBuildable.GetInt() )
{
pRenderContext->Bind( MSurf_TexInfo( m_ParentSurfID )->material );
pGroup->m_pMesh->Draw( m_iIndexOffset, m_nIndices );
if ( r_DispWalkable.GetInt() )
SpecifyWalkableDynamicMesh();
if ( r_DispBuildable.GetInt() )
SpecifyBuildableDynamicMesh();
}
#if !defined( DEDICATED )
if ( mat_surfaceid.GetInt() )
{
Vector bbMin, bbMax, vecCenter;
GetBoundingBox( bbMin, bbMax );
VectorAdd( bbMin, bbMax, vecCenter );
vecCenter *= 0.5f;
int nInt = ( mat_surfaceid.GetInt() != 2 ) ? size_cast< int >( (intp)m_ParentSurfID ) : (msurface2_t*)m_ParentSurfID - host_state.worldbrush->surfaces2;
char buf[32];
Q_snprintf( buf, sizeof( buf ), "%d", nInt );
CDebugOverlay::AddTextOverlay( vecCenter, 0, buf );
}
if ( mat_surfacemat.GetInt() )
{
Vector bbMin, bbMax, vecCenter;
GetBoundingBox( bbMin, bbMax );
VectorAdd( bbMin, bbMax, vecCenter );
vecCenter *= 0.5f;
mtexinfo_t * pTexInfo = MSurf_TexInfo(m_ParentSurfID);
const char *pFullMaterialName = pTexInfo->material ? pTexInfo->material->GetName() : "no material";
const char *pSlash = strrchr( pFullMaterialName, '/' );
const char *pMaterialName = strrchr( pFullMaterialName, '\\' );
if (pSlash > pMaterialName)
pMaterialName = pSlash;
if (pMaterialName)
++pMaterialName;
else
pMaterialName = pFullMaterialName;
CDebugOverlay::AddTextOverlay( vecCenter, 0, pMaterialName );
}
#endif // DEDICATED
}
else
{
if( pGroup->m_nVisible < pGroup->m_Visible.Count() )
{
// Don't bother if all faces are backfacing, or somesuch...
if (m_nIndices)
{
pGroup->m_Visible[pGroup->m_nVisible].m_FirstIndex = m_iIndexOffset;
pGroup->m_Visible[pGroup->m_nVisible].m_NumIndices = m_nIndices;
pGroup->m_VisibleDisps[pGroup->m_nVisible] = this;
pGroup->m_nVisible++;
pGroup->m_pGroup->m_nVisible++;
}
}
else
{
Assert( !"Overflowed visible mesh list" );
}
}
#endif
return true;
}
struct ProcessLightmapSampleData_t;
typedef void ProcessLightmapSampleFunc_t( const ProcessLightmapSampleData_t &data, const Vector &vPos, const Vector &vNormal, const Vector &vTangentS, const Vector &vTangentT, int t, int s, int tmax, int smax );
struct ProcessLightmapSampleData_t
{
float m_ooQuadraticAttn;
float m_ooRadiusSq;
Vector m_Intensity;
float m_LightDistSqr;
Vector m_vLightOrigin;
ProcessLightmapSampleFunc_t *pProcessLightmapSampleDataFunc;
};
static void ProcessLightmapSample( const ProcessLightmapSampleData_t &data, const Vector &vPos, const Vector &vNormal, const Vector &vTangentS, const Vector &vTangentT, int t, int s, int tmax, int smax )
{
#if !defined( DEDICATED )
float distSqr = data.m_vLightOrigin.DistToSqr( vPos );
if( distSqr < data.m_LightDistSqr )
{
float scale = (distSqr != 0.0f) ? data.m_ooQuadraticAttn / distSqr : 1.0f;
// Apply a little extra attenuation
scale *= (1.0f - distSqr * data.m_ooRadiusSq);
if (scale > 2.0f)
scale = 2.0f;
int index = t*smax + s;
VectorMA( blocklights[0][index].AsVector3D(),
scale, data.m_Intensity,
blocklights[0][index].AsVector3D() );
}
#endif
}
static void ProcessLightmapSampleBumped( const ProcessLightmapSampleData_t &data, const Vector &vPos, const Vector &vNormal, const Vector &vTangentS, const Vector &vTangentT, int t, int s, int tmax, int smax )
{
#if !defined( DEDICATED )
float distSqr = data.m_vLightOrigin.DistToSqr( vPos );
if( distSqr < data.m_LightDistSqr )
{
float scale = (distSqr != 0.0f) ? data.m_ooQuadraticAttn / distSqr : 1.0f;
// Get the vector from the surface to the light in world space
Vector vLightVecWorld;
VectorSubtract( data.m_vLightOrigin, vPos, vLightVecWorld );
VectorNormalize( vLightVecWorld );
// Transform the vector from the surface to the light into tangent space
Vector vLightVecTangent;
vLightVecTangent.x = DotProduct( vTangentS, vLightVecWorld );
vLightVecTangent.y = DotProduct( vTangentT, vLightVecWorld );
vLightVecTangent.z = DotProduct( vNormal, vLightVecWorld );
// Apply a little extra attenuation
scale *= (1.0f - distSqr * data.m_ooRadiusSq);
if (scale > 2.0f)
scale = 2.0f;
int index = t*smax + s;
float directionalAtten;
directionalAtten = fpmax( 0.0f, vLightVecTangent.z );
VectorMA( blocklights[0][index].AsVector3D(), scale * directionalAtten,
data.m_Intensity,
blocklights[0][index].AsVector3D() );
directionalAtten = fpmax( 0.0f, DotProduct( vLightVecTangent, g_localBumpBasis[0] ) );
VectorMA( blocklights[1][index].AsVector3D(), scale * directionalAtten,
data.m_Intensity,
blocklights[1][index].AsVector3D() );
directionalAtten = fpmax( 0.0f, DotProduct( vLightVecTangent, g_localBumpBasis[1] ) );
VectorMA( blocklights[2][index].AsVector3D(), scale * directionalAtten,
data.m_Intensity,
blocklights[2][index].AsVector3D() );
directionalAtten = fpmax( 0.0f, DotProduct( vLightVecTangent, g_localBumpBasis[2] ) );
VectorMA( blocklights[3][index].AsVector3D(), scale * directionalAtten,
data.m_Intensity,
blocklights[3][index].AsVector3D() );
}
#endif
}
//-----------------------------------------------------------------------------
// Alpha channel modulation
//-----------------------------------------------------------------------------
static void ProcessLightmapSampleAlpha( const ProcessLightmapSampleData_t &data, const Vector &vPos, const Vector &vNormal, const Vector &vTangentS, const Vector &vTangentT, int t, int s, int tmax, int smax )
{
#if !defined( DEDICATED )
float distSqr = data.m_vLightOrigin.DistToSqr( vPos );
if( distSqr < data.m_LightDistSqr )
{
float scale = (distSqr != 0.0f) ? data.m_ooQuadraticAttn / distSqr : 1.0f;
// Apply a little extra attenuation
scale *= (1.0f - distSqr * data.m_ooRadiusSq);
if (scale > 1.0f)
scale = 1.0f;
int index = t*smax + s;
blocklights[0][index][3] += scale * data.m_Intensity[0];
}
#endif
}
// This iterates over all the lightmap samples and for each one, calls:
// T::ProcessLightmapSample( Vector const &vPos, int t, int s, int tmax, int smax );
void IterateLightmapSamples( CDispInfo *pDisp, const ProcessLightmapSampleData_t &data )
{
ASSERT_SURF_VALID( pDisp->m_ParentSurfID );
if ( !g_DispLightmapSamplePositions.Count() )
{
ExecuteNTimes( 20, Warning( "Cannot update displacement for dlight - set 'r_dlightsenable 1' and reload the map! (data may also have been culled by MakeGameData)\n" ) );
return;
}
int smax = MSurf_LightmapExtents( pDisp->m_ParentSurfID )[0] + 1;
int tmax = MSurf_LightmapExtents( pDisp->m_ParentSurfID )[1] + 1;
unsigned char *pCurSample = &g_DispLightmapSamplePositions[pDisp->m_iLightmapSamplePositionStart];
for( int t = 0 ; t<tmax ; t++ )
{
for( int s=0 ; s<smax ; s++ )
{
// Figure out what triangle this sample is on.
// NOTE: this usually stores 4 bytes per lightmap sample.
// It's a lot simpler and faster to just store the position but then it's
// 16 bytes instead of 4.
int iTri;
if( *pCurSample == 255 )
{
++pCurSample;
iTri = *pCurSample + 255;
}
else
{
iTri = *pCurSample;
}
++pCurSample;
float a = (float)*(pCurSample++) / 255.0f;
float b = (float)*(pCurSample++) / 255.0f;
float c = (float)*(pCurSample++) / 255.0f;
CTriInfo *pTri = &pDisp->m_pPowerInfo->m_pTriInfos[iTri];
Vector vPos =
pDisp->m_MeshReader.Position( pTri->m_Indices[0] ) * a +
pDisp->m_MeshReader.Position( pTri->m_Indices[1] ) * b +
pDisp->m_MeshReader.Position( pTri->m_Indices[2] ) * c;
Vector vNormal, vTangentS, vTangentT;
if( pDisp->NumLightMaps() > 1 )
{
vNormal =
pDisp->m_MeshReader.Normal( pTri->m_Indices[0] ) * a +
pDisp->m_MeshReader.Normal( pTri->m_Indices[1] ) * b +
pDisp->m_MeshReader.Normal( pTri->m_Indices[2] ) * c;
vTangentS =
pDisp->m_MeshReader.TangentS( pTri->m_Indices[0] ) * a +
pDisp->m_MeshReader.TangentS( pTri->m_Indices[1] ) * b +
pDisp->m_MeshReader.TangentS( pTri->m_Indices[2] ) * c;
vTangentT =
pDisp->m_MeshReader.TangentT( pTri->m_Indices[0] ) * a +
pDisp->m_MeshReader.TangentT( pTri->m_Indices[1] ) * b +
pDisp->m_MeshReader.TangentT( pTri->m_Indices[2] ) * c;
}
(*data.pProcessLightmapSampleDataFunc)( data, vPos, vNormal, vTangentS, vTangentT, t, s, tmax, smax );
}
}
}
void CDispInfo::AddSingleDynamicLight( dlight_t& dl )
{
#ifndef DEDICATED
ProcessLightmapSampleData_t data;
data.m_LightDistSqr = dl.GetRadiusSquared();
float lightStyleValue = LightStyleValue( dl.style );
data.m_Intensity[0] = TexLightToLinear( dl.color.r, dl.color.exponent ) * lightStyleValue;
data.m_Intensity[1] = TexLightToLinear( dl.color.g, dl.color.exponent ) * lightStyleValue;
data.m_Intensity[2] = TexLightToLinear( dl.color.b, dl.color.exponent ) * lightStyleValue;
float minlight = fpmax( g_flMinLightingValue, dl.minlight );
float ooQuadraticAttn = data.m_LightDistSqr * minlight; // / maxIntensity;
data.m_ooQuadraticAttn = ooQuadraticAttn;
data.m_vLightOrigin = dl.origin;
data.m_ooRadiusSq = 1.0f / dl.GetRadiusSquared();;
data.pProcessLightmapSampleDataFunc = &ProcessLightmapSample;
// Touch all the lightmap samples.
IterateLightmapSamples( this, data );
#endif
}
void CDispInfo::AddSingleDynamicLightBumped( dlight_t& dl )
{
#ifndef DEDICATED
ProcessLightmapSampleData_t data;
data.m_LightDistSqr = dl.GetRadiusSquared();
float lightStyleValue = LightStyleValue( dl.style );
data.m_Intensity[0] = TexLightToLinear( dl.color.r, dl.color.exponent ) * lightStyleValue;
data.m_Intensity[1] = TexLightToLinear( dl.color.g, dl.color.exponent ) * lightStyleValue;
data.m_Intensity[2] = TexLightToLinear( dl.color.b, dl.color.exponent ) * lightStyleValue;
float minlight = fpmax( g_flMinLightingValue, dl.minlight );
float ooQuadraticAttn = data.m_LightDistSqr * minlight; // / maxIntensity;
data.m_ooQuadraticAttn = ooQuadraticAttn;
data.m_vLightOrigin = dl.origin;
data.m_ooRadiusSq = 1.0f / dl.GetRadiusSquared();
data.pProcessLightmapSampleDataFunc = &ProcessLightmapSampleBumped;
// Touch all the lightmap samples.
IterateLightmapSamples( this, data );
#endif
}
void CDispInfo::AddSingleDynamicAlphaLight( dlight_t& dl )
{
#ifndef DEDICATED
ProcessLightmapSampleData_t data;
data.m_LightDistSqr = dl.GetRadiusSquared();
float lightStyleValue = LightStyleValue( dl.style );
data.m_Intensity[0] = TexLightToLinear( dl.color.r, dl.color.exponent ) * lightStyleValue;
if ( dl.flags & DLIGHT_SUBTRACT_DISPLACEMENT_ALPHA )
data.m_Intensity *= -1.0f;
float minlight = MAX( g_flMinLightingValue, dl.minlight );
float ooQuadraticAttn = data.m_LightDistSqr * minlight; // / maxIntensity;
data.m_ooQuadraticAttn = ooQuadraticAttn;
data.m_vLightOrigin = dl.origin;
data.m_ooRadiusSq = 1.0f / dl.GetRadiusSquared();
data.pProcessLightmapSampleDataFunc = &ProcessLightmapSampleAlpha;
// Touch all the lightmap samples.
IterateLightmapSamples( this, data );
#endif
}
//-----------------------------------------------------------------------------
// A little cache to help us not project vertices multiple times
//-----------------------------------------------------------------------------
class CDecalNodeSetupCache
{
public:
CDecalNodeSetupCache() : m_CurrentCacheIndex(0) {}
Vector m_ProjectedVert[MAX_DISPVERTS];
int m_CacheIndex[MAX_DISPVERTS];
bool IsCached( int v ) { return m_CacheIndex[v] == m_CurrentCacheIndex; }
void MarkCached( int v ) { m_CacheIndex[v] = m_CurrentCacheIndex; }
void ResetCache() { ++m_CurrentCacheIndex; }
private:
int m_CurrentCacheIndex;
};
//-----------------------------------------------------------------------------
// Check to see which nodes are hit by a decal
//-----------------------------------------------------------------------------
bool CDispInfo::SetupDecalNodeIntersect_R( CVertIndex const &nodeIndex,
int iNodeBitIndex, CDispDecalBase *pDispDecal, ShadowInfo_t const* pInfo,
int iLevel, CDecalNodeSetupCache* pCache )
{
int iNodeIndex = VertIndex( nodeIndex );
if( iLevel+1 < m_Power )
{
// Recurse into child nodes.
bool anyChildIntersected = false;
int iChildNodeBit = iNodeBitIndex + 1;
for( int iChild=0; iChild < 4; iChild++ )
{
CVertIndex const &childNode = m_pPowerInfo->m_pChildVerts[iNodeIndex].m_Verts[iChild];
// If any of our children intersect, then we do too...
if (SetupDecalNodeIntersect_R( childNode, iChildNodeBit, pDispDecal, pInfo, iLevel + 1, pCache ) )
anyChildIntersected = true;
iChildNodeBit += m_pPowerInfo->m_NodeIndexIncrements[iLevel];
}
if (anyChildIntersected)
{
pDispDecal->m_NodeIntersect.Set( iNodeBitIndex );
return true;
}
// None of our children intersect this decal, so neither does the node
return false;
}
// Expand our box by the node and by its side verts.
Vector vMin, vMax;
if (!pCache->IsCached(iNodeIndex))
{
DecalProjectVert( m_MeshReader.Position( iNodeIndex ), pDispDecal, pInfo, pCache->m_ProjectedVert[iNodeIndex] );
pCache->MarkCached(iNodeIndex);
}
vMin = pCache->m_ProjectedVert[iNodeIndex];
vMax = pCache->m_ProjectedVert[iNodeIndex];
// Now test each neighbor + child vert to see if it should exist.
for( int i=0; i < 4; i++ )
{
CVertIndex const &sideVert = m_pPowerInfo->m_pSideVerts[iNodeIndex].m_Verts[i];
CVertIndex const &cornerVert = m_pPowerInfo->m_pSideVertCorners[iNodeIndex].m_Verts[i];
int iSideIndex = VertIndex(sideVert);
if (!pCache->IsCached(iSideIndex))
{
DecalProjectVert( m_MeshReader.Position( iSideIndex ), pDispDecal, pInfo, pCache->m_ProjectedVert[iSideIndex] );
pCache->MarkCached(iSideIndex);
}
VectorMin( pCache->m_ProjectedVert[iSideIndex], vMin, vMin );
VectorMax( pCache->m_ProjectedVert[iSideIndex], vMax, vMax );
int iCornerIndex = VertIndex(cornerVert);
if (!pCache->IsCached(iCornerIndex))
{
DecalProjectVert( m_MeshReader.Position( iCornerIndex ), pDispDecal, pInfo, pCache->m_ProjectedVert[iCornerIndex] );
pCache->MarkCached(iCornerIndex);
}
VectorMin( pCache->m_ProjectedVert[iCornerIndex], vMin, vMin );
VectorMax( pCache->m_ProjectedVert[iCornerIndex], vMax, vMax );
}
// Now just see if our bbox intersects the [0,0] - [1,1] bbox, which is where this
// decal sits.
if( vMin.x <= 1 && vMax.x >= 0 && vMin.y <= 1 && vMax.y >= 0 )
{
// Z cull for shadows...
if( pInfo )
{
if ((vMax.z < 0) || (vMin.z > pInfo->m_MaxDist))
return false;
}
// Ok, this node is needed and its children may be needed as well.
pDispDecal->m_NodeIntersect.Set( iNodeBitIndex );
return true;
}
return false;
}
void CDispInfo::SetupDecalNodeIntersect( CVertIndex const &nodeIndex, int iNodeBitIndex,
CDispDecalBase *pDispDecal, ShadowInfo_t const* pInfo )
{
pDispDecal->m_NodeIntersect.ClearAll();
// Generate a vertex cache, so we're not continually reprojecting vertices...
static CDecalNodeSetupCache cache;
cache.ResetCache();
bool anyIntersection = SetupDecalNodeIntersect_R(
nodeIndex, iNodeBitIndex, pDispDecal, pInfo, 0, &cache );
pDispDecal->m_Flags |= CDispDecalBase::NODE_BITFIELD_COMPUTED;
if (anyIntersection)
pDispDecal->m_Flags &= ~CDispDecalBase::NO_INTERSECTION;
else
pDispDecal->m_Flags |= CDispDecalBase::NO_INTERSECTION;
}
Vector CDispInfo::GetFlatVert( int iVertex )
{
int sideLength = m_pPowerInfo->GetSideLength();
int x = iVertex % sideLength;
int y = iVertex / sideLength;
float ooInt = 1.0f / ( float )( sideLength - 1 );
// Lerp between the left and right edges to get a line along 'x'.
Vector endPts[2];
VectorLerp( m_BaseSurfacePositions[0], m_BaseSurfacePositions[1], y*ooInt, endPts[0] );
VectorLerp( m_BaseSurfacePositions[3], m_BaseSurfacePositions[2], y*ooInt, endPts[1] );
// Lerp along the X line.
Vector vOutputPos;
VectorLerp( endPts[0], endPts[1], x*ooInt, vOutputPos );
// This can be used to verify that the position generated here is correct.
// It should be the same as CCoreDispInfo::GetFlatVert.
// Assert( vOutputPos.DistTo( m_Verts[iVertex].m_vFlatPos ) < 0.1f );
// Voila!
return vOutputPos;
}
//-----------------------------------------------------------------------------
// Computes the texture + lightmap coordinate given a displacement uv
//-----------------------------------------------------------------------------
void CDispInfo::ComputeLightmapAndTextureCoordinate( RayDispOutput_t const& output,
Vector2D* luv, Vector2D* tuv )
{
#ifndef DEDICATED
// lightmap coordinate
if( luv )
{
ComputePointFromBarycentric(
m_MeshReader.TexCoordVector2D( output.ndxVerts[0], DISP_LMCOORDS_STAGE ),
m_MeshReader.TexCoordVector2D( output.ndxVerts[1], DISP_LMCOORDS_STAGE ),
m_MeshReader.TexCoordVector2D( output.ndxVerts[2], DISP_LMCOORDS_STAGE ),
output.u, output.v, *luv );
// luv is in the space of the accumulated lightmap page; we need to convert
// it to be in the space of the surface
int lightmapPageWidth, lightmapPageHeight;
materials->GetLightmapPageSize(
SortInfoToLightmapPage(MSurf_MaterialSortID( m_ParentSurfID ) ),
&lightmapPageWidth, &lightmapPageHeight );
luv->x *= lightmapPageWidth;
luv->y *= lightmapPageHeight;
luv->x -= 0.5f + MSurf_OffsetIntoLightmapPage( m_ParentSurfID )[0];
luv->y -= 0.5f + MSurf_OffsetIntoLightmapPage( m_ParentSurfID )[1];
}
// texture coordinate
if( tuv )
{
// Compute base face (u,v) at each of the three vertices
int size = (1 << m_Power) + 1;
Vector2D baseUV[3];
for (int i = 0; i < 3; ++i )
{
baseUV[i].y = (int)(output.ndxVerts[i] / size);
baseUV[i].x = output.ndxVerts[i] - size * baseUV[i].y;
baseUV[i] /= size - 1;
}
Vector2D basefaceUV;
ComputePointFromBarycentric( baseUV[0], baseUV[1], baseUV[2],
output.u, output.v, basefaceUV );
// Convert the base face uv to a texture uv based on the base face texture coords
TexCoordInQuadFromBarycentric( m_BaseSurfaceTexCoords[0],
m_BaseSurfaceTexCoords[3], m_BaseSurfaceTexCoords[2], m_BaseSurfaceTexCoords[1],
basefaceUV, *tuv );
}
#endif
}
//-----------------------------------------------------------------------------
// Cast a ray against this surface
//-----------------------------------------------------------------------------
bool CDispInfo::TestRay( Ray_t const& ray, float start, float end, float& dist,
Vector2D* luv, Vector2D* tuv )
{
// Get the index associated with this disp info....
int idx = DispInfo_ComputeIndex( host_state.worldbrush->hDispInfos, this );
CDispCollTree* pTree = CollisionBSPData_GetCollisionTree( idx );
if (!pTree)
return false;
CBaseTrace tr;
tr.fraction = 1.0f;
// Only test the portion of the ray between start and end
Vector startpt, endpt,endpt2;
VectorMA( ray.m_Start, start, ray.m_Delta, startpt );
VectorMA( ray.m_Start, end, ray.m_Delta, endpt );
Ray_t shortenedRay;
shortenedRay.Init( startpt, endpt );
RayDispOutput_t output;
output.dist = 1.0f;
if (pTree->AABBTree_Ray( shortenedRay, output ))
{
Assert( (output.u <= 1.0f) && (output.v <= 1.0f ));
Assert( (output.u >= 0.0f) && (output.v >= 0.0f ));
// Compute the actual distance along the ray
dist = start * (1.0f - output.dist) + end * output.dist;
// Compute lightmap + texture coordinates
ComputeLightmapAndTextureCoordinate( output, luv, tuv );
return true;
}
return false;
}
const CPowerInfo* CDispInfo::GetPowerInfo() const
{
return m_pPowerInfo;
}
CDispNeighbor* CDispInfo::GetEdgeNeighbor( int index )
{
Assert( index >= 0 && index < ARRAYSIZE( m_EdgeNeighbors ) );
return &m_EdgeNeighbors[index];
}
CDispCornerNeighbors* CDispInfo::GetCornerNeighbors( int index )
{
Assert( index >= 0 && index < ARRAYSIZE( m_CornerNeighbors ) );
return &m_CornerNeighbors[index];
}
CDispUtilsHelper* CDispInfo::GetDispUtilsByIndex( int index )
{
return GetDispByIndex( index );
}