Paranoia2_original/cl_dll/render/gl_studiodecal_new.cpp

1781 lines
54 KiB
C++

/*
gl_studiodecal_new.cpp - project and rendering studio decals
Copyright (C) 2018 Uncle Mike
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
*/
#include "hud.h"
#include "cl_util.h"
#include "gl_local.h"
#include "com_model.h"
#include "r_studioint.h"
#include "pm_movevars.h"
#include "gl_studio.h"
#include "gl_sprite.h"
#include "event_api.h"
#include <mathlib.h>
#include "pm_defs.h"
#include "stringlib.h"
#include "triangleapi.h"
#include "entity_types.h"
#include "gl_shader.h"
#include "gl_world.h"
/*
=============================================================
COMMON ROUTINES
=============================================================
*/
/*
====================
PushEntityState
====================
*/
void CStudioModelRenderer :: PushEntityState( cl_entity_t *ent )
{
ASSERT( ent != NULL );
m_savestate = ent->curstate;
m_saveorigin = ent->origin;
m_saveangles = ent->angles;
}
/*
====================
PopEntityState
====================
*/
void CStudioModelRenderer :: PopEntityState( cl_entity_t *ent )
{
ent->curstate = m_savestate;
ent->origin = m_saveorigin;
ent->angles = m_saveangles;
}
/*
====================
EntityToModelState
create snapshot of current model state
====================
*/
void CStudioModelRenderer :: EntityToModelState( modelstate_t *state, const cl_entity_t *ent )
{
state->sequence = ent->curstate.sequence;
state->frame = (int)( ent->curstate.frame * 8.0f );
state->blending[0] = ent->curstate.blending[0];
state->blending[1] = ent->curstate.blending[1];
state->controller[0] = ent->curstate.controller[0];
state->controller[1] = ent->curstate.controller[1];
state->controller[2] = ent->curstate.controller[2];
state->controller[3] = ent->curstate.controller[3];
state->body = ent->curstate.body;
state->skin = ent->curstate.skin;
}
/*
====================
ModelStateToEntity
setup entity pose for decal shooting
====================
*/
void CStudioModelRenderer :: ModelStateToEntity( cl_entity_t *ent, const modelstate_t *state )
{
ent->curstate.sequence = state->sequence;
ent->curstate.frame = (float)state->frame * (1.0f / 8.0f);
ent->curstate.blending[0] = state->blending[0];
ent->curstate.blending[1] = state->blending[1];
ent->curstate.controller[0] = state->controller[0];
ent->curstate.controller[1] = state->controller[1];
ent->curstate.controller[2] = state->controller[2];
ent->curstate.controller[3] = state->controller[3];
ent->curstate.body = state->body;
ent->curstate.skin = state->skin;
}
/*
=============================================================
CLEANUP ROUTINES
=============================================================
*/
void CStudioModelRenderer :: PurgeDecals( ModelInstance_t *inst )
{
for( int i = 0; i < inst->m_DecalList.Count(); i++ )
{
studiodecal_t *pDecal = &inst->m_DecalList[i];
DeleteVBOMesh( &pDecal->mesh );
}
// release himself
inst->m_DecalList.Purge();
}
void CStudioModelRenderer :: PurgeDecals( cl_entity_t *e )
{
if( !e || e->modelhandle == INVALID_HANDLE )
return;
PurgeDecals( &m_ModelInstances[e->modelhandle] );
}
void CStudioModelRenderer :: StudioClearDecals( void )
{
if( !g_fRenderInitialized ) return;
for( int i = 0; i < m_ModelInstances.Count(); i++ )
PurgeDecals( &m_ModelInstances[i] );
// clear BSP-decals too
ClearDecals();
}
//-----------------------------------------------------------------------------
// Purpose: Removes all the decals on a model instance
//-----------------------------------------------------------------------------
void CStudioModelRenderer :: RemoveAllDecals( int entityIndex )
{
if( !g_fRenderInitialized ) return;
PurgeDecals( GET_ENTITY( entityIndex ));
}
/*
=============================================================
SERIALIZE
=============================================================
*/
int CStudioModelRenderer :: StudioDecalList( decallist_t *pBaseList, int count )
{
if( !g_fRenderInitialized )
return 0;
int maxStudioDecals = MAX_STUDIO_DECALS + (MAX_BMODEL_DECALS - count);
decallist_t *pList = pBaseList + count; // shift list to first free slot
cl_entity_t *pEntity = NULL;
int total = 0;
for( int i = 0; i < m_ModelInstances.Count(); i++ )
{
ModelInstance_t *inst = &m_ModelInstances[i];
if( !inst->m_DecalList.Count( ))
continue; // no decals?
// setup the decal entity
pEntity = inst->m_pEntity;
for( int i = 0; i < inst->m_DecalList.Count(); i++ )
{
studiodecal_t *pdecal = &inst->m_DecalList[i];
const DecalGroupEntry *tex = pdecal->texinfo;
if( !tex || !tex->group || FBitSet( pdecal->flags, FDECAL_DONTSAVE ))
continue; // ???
pList[total].depth = pdecal->depth; // FIXME: calc depth properly
pList[total].flags = pdecal->flags|FDECAL_STUDIO;
pList[total].entityIndex = pEntity->index;
pList[total].position = pdecal->position;
pList[total].impactPlaneNormal = pdecal->normal;
pList[total].studio_state = inst->pose_stamps[pdecal->modelpose];
Q_snprintf( pList[total].name, sizeof( pList[0].name ), "%s@%s", tex->group->GetName(), tex->m_DecalName );
pList[total].scale = 1.0f; // unused
total++;
// check for list overflow
if( total >= maxStudioDecals )
{
ALERT( at_error, "StudioDecalList: too many studio decals on save\restore\n" );
goto end_serialize;
}
}
}
end_serialize:
total += SaveDecalList( pBaseList, count + total );
return total;
}
/*
=============================================================
DECAL CLIPPING & PLACING
=============================================================
*/
/*
====================
ComputePoseToDecal
Create a transform that projects world coordinates
into a basis for the decal
====================
*/
bool CStudioModelRenderer :: ComputePoseToDecal( const Vector &vecNormal, const Vector &vecEnd )
{
Vector decalU, decalV, decalN, vecDelta;
Vector vecSrc = vecEnd + vecNormal.Normalize() * 5.0f; // magic Valve constant
matrix3x4 worldToDecal;
// Bloat a little bit so we get the intersection
vecDelta = (vecEnd - vecSrc) * 1.1f;
// Get the z axis
decalN = vecDelta * -1.0f;
if( decalN.NormalizeLength() == 0.0f )
return false;
// Deal with the u axis
decalU = CrossProduct( Vector( 0.0f, 0.0f, 1.0f ), decalN );
if( decalU.NormalizeLength() < 1e-3 )
{
// if up parallel or antiparallel to ray, deal...
Vector fixup( 0.0f, 1.0f, 0.0f );
decalU = CrossProduct( fixup, decalN );
if( decalU.NormalizeLength() < 1e-3 )
return false;
}
decalV = CrossProduct( decalN, decalU );
// Since I want world-to-decal, I gotta take the inverse of the decal
// to world. Assuming post-multiplying column vectors, the decal to world =
// [ Ux Vx Nx | vecEnd[0] ]
// [ Uy Vy Ny | vecEnd[1] ]
// [ Uz Vz Nz | vecEnd[2] ]
worldToDecal[0][0] = decalU.x;
worldToDecal[1][0] = decalU.y;
worldToDecal[2][0] = decalU.z;
worldToDecal[0][1] = decalV.x;
worldToDecal[1][1] = decalV.y;
worldToDecal[2][1] = decalV.z;
worldToDecal[0][2] = decalN.x;
worldToDecal[1][2] = decalN.y;
worldToDecal[2][2] = decalN.z;
// g-cont. just invert matrix here?
worldToDecal[3][0] = -DotProduct( vecEnd, decalU );
worldToDecal[3][1] = -DotProduct( vecEnd, decalV );
worldToDecal[3][2] = -DotProduct( vecEnd, decalN );
// Compute transforms from pose space to decal plane space
for( int i = 0; i < m_pStudioHeader->numbones; i++ )
{
m_pdecaltransform[i] = worldToDecal.ConcatTransforms( m_pworldtransform[i] );
}
return true;
}
/*
====================
IsFrontFacing
side checking
====================
*/
_forceinline bool CStudioModelRenderer :: IsFrontFacing( const svert_t *vert )
{
float z = 0.0f;
Vector decalN;
// NOTE: This only works to rotate normals if there's no scale in the
// pose to world transforms. If we ever add scale, we'll need to
// multiply by the inverse transpose of the pose to decal
if( vert->weight[0] == 255 )
{
decalN.x = m_pdecaltransform[vert->boneid[0]][0][2];
decalN.y = m_pdecaltransform[vert->boneid[0]][1][2];
decalN.z = m_pdecaltransform[vert->boneid[0]][2][2];
z = DotProduct( vert->normal, decalN );
}
else
{
for( int i = 0; i < MAXSTUDIOBONEWEIGHTS; i++ )
{
if( vert->boneid[i] == -1 )
break;
decalN.x = m_pdecaltransform[vert->boneid[i]][0][2];
decalN.y = m_pdecaltransform[vert->boneid[i]][1][2];
decalN.z = m_pdecaltransform[vert->boneid[i]][2][2];
z += DotProduct( vert->normal, decalN ) * (vert->weight[i] / 255.0f);
}
}
return ( z >= 0.1f );
}
/*
====================
GetDecalRotateTransform
====================
*/
_forceinline matrix3x3 CStudioModelRenderer :: GetDecalRotateTransform( byte vertexBone )
{
matrix3x3 transform;
transform[0][0] = m_pdecaltransform[vertexBone][0][0];
transform[0][1] = m_pdecaltransform[vertexBone][1][0];
transform[0][2] = m_pdecaltransform[vertexBone][2][0];
transform[1][0] = m_pdecaltransform[vertexBone][0][1];
transform[1][1] = m_pdecaltransform[vertexBone][1][1];
transform[1][2] = m_pdecaltransform[vertexBone][2][1];
transform[2][0] = m_pdecaltransform[vertexBone][0][2];
transform[2][1] = m_pdecaltransform[vertexBone][1][2];
transform[2][2] = m_pdecaltransform[vertexBone][2][2];
return transform;
}
/*
====================
TransformToDecalSpace
====================
*/
_forceinline bool CStudioModelRenderer :: TransformToDecalSpace( DecalBuildInfo_t& build, const svert_t *vert, Vector2D& uv )
{
cl_entity_t *e = RI->currententity;
// NOTE: This only works to rotate normals if there's no scale in the
// pose to world transforms. If we ever add scale, we'll need to
// multiply by the inverse transpose of the pose to world
float z;
if( vert->weight[0] == 255 )
{
matrix3x3 decalMat = GetDecalRotateTransform( vert->boneid[0] );
uv.x = (DotProduct( vert->vertex, decalMat[0] ) + m_pdecaltransform[vert->boneid[0]][3][0]);
uv.y = -(DotProduct( vert->vertex, decalMat[1] ) + m_pdecaltransform[vert->boneid[0]][3][1]);
z = DotProduct( vert->vertex, decalMat[2] ) + m_pdecaltransform[vert->boneid[0]][3][2];
}
else
{
uv.x = uv.y = z = 0.0f;
for( int i = 0; i < MAXSTUDIOBONEWEIGHTS; i++ )
{
if( vert->boneid[i] == -1 )
break;
matrix3x3 decalMat = GetDecalRotateTransform( vert->boneid[i] );
float weight = (vert->weight[i] / 255.0f);
uv.x += (DotProduct( vert->vertex, decalMat[0] ) + m_pdecaltransform[vert->boneid[i]][3][0]) * weight;
uv.y += -(DotProduct( vert->vertex, decalMat[1] ) + m_pdecaltransform[vert->boneid[i]][3][1]) * weight;
z += (DotProduct( vert->vertex, decalMat[2] ) + m_pdecaltransform[vert->boneid[i]][3][2]) * weight;
}
}
// poke thru
if( FBitSet( e->curstate.iuser1, CF_STATIC_ENTITY ) && !e->efrag )
return ( fabs( z ) < build.m_Radius );
return true;
}
/*
====================
ProjectDecalOntoMesh
====================
*/
void CStudioModelRenderer :: ProjectDecalOntoMesh( DecalBuildInfo_t& build )
{
float invRadius = (build.m_Radius != 0.0f) ? 1.0f / build.m_Radius : 1.0f;
// for this to work, the plane and intercept must have been transformed
// into pose space. Also, we'll not be bothering with flexes.
for( int j = 0; j < m_nNumArrayVerts; j++ )
{
// just walk by unique verts
svert_t *vert = &m_arrayxvert[j];
// No decal vertex yet...
build.m_pVertexInfo[j].m_VertexIndex = INVALID_HANDLE;
// We need to know if the normal is pointing in the negative direction
// if so, blow off all triangles connected to that vertex.
build.m_pVertexInfo[j].m_FrontFacing = IsFrontFacing( vert );
if( !build.m_pVertexInfo[j].m_FrontFacing )
continue;
build.m_pVertexInfo[j].m_InValidArea = TransformToDecalSpace( build, vert, build.m_pVertexInfo[j].m_UV );
build.m_pVertexInfo[j].m_UV *= build.vecDecalScale * 0.5f;
build.m_pVertexInfo[j].m_UV[0] += 0.5f;
build.m_pVertexInfo[j].m_UV[1] += 0.5f;
}
}
/*
====================
ComputeClipFlags
====================
*/
int CStudioModelRenderer :: ComputeClipFlags( const Vector2D &uv )
{
// Otherwise we gotta do the test
int flags = 0;
if( uv.x < 0.0f )
SetBits( flags, DECAL_CLIP_MINUSU );
else if( uv.x > 1.0f )
SetBits( flags, DECAL_CLIP_PLUSU );
if( uv.y < 0.0f )
SetBits( flags, DECAL_CLIP_MINUSV );
else if( uv.y > 1.0f )
SetBits( flags, DECAL_CLIP_PLUSV );
return flags;
}
/*
====================
ConvertMeshVertexToDecalVertex
Converts a mesh index to a svert_t
====================
*/
void CStudioModelRenderer :: ConvertMeshVertexToDecalVertex( DecalBuildInfo_t& build, int vertIndex, svert_t *out )
{
// copy over the data (we use through access for all submodel verts)
*out = m_arrayxvert[vertIndex];
// get the texture coords from the decal planar projection
out->stcoord[0] = build.m_pVertexInfo[vertIndex].m_UV.x;
out->stcoord[1] = build.m_pVertexInfo[vertIndex].m_UV.y;
// if meshIndex is valid this vertex was created from original
out->m_MeshVertexIndex = vertIndex;
}
/*
====================
AddVertexToDecal
Adds a vertex to the list of vertices
for this studiomesh
====================
*/
word CStudioModelRenderer :: AddVertexToDecal( DecalBuildInfo_t& build, int vertIndex )
{
DecalVertexInfo_t* pVertexInfo = build.m_pVertexInfo;
// If we've never seen this vertex before, we need to add a new decal vert
if( pVertexInfo[vertIndex].m_VertexIndex == INVALID_HANDLE )
{
int v = build.m_Vertices.AddToTail();
// Copy over the data;
ConvertMeshVertexToDecalVertex( build, vertIndex, &build.m_Vertices[v] );
#ifdef _DEBUG
// Make sure clipped vertices are in the right range...
if( build.m_UseClipVert )
{
ASSERT(( build.m_Vertices[v].stcoord[0] >= -1e-3 ) && ( build.m_Vertices[v].stcoord[0] - 1.0f < 1e-3 ));
ASSERT(( build.m_Vertices[v].stcoord[1] >= -1e-3 ) && ( build.m_Vertices[v].stcoord[1] - 1.0f < 1e-3 ));
}
#endif
// store off the index of this vertex so we can reference it again
pVertexInfo[vertIndex].m_VertexIndex = v;
}
return pVertexInfo[vertIndex].m_VertexIndex;
}
/*
====================
AddVertexToDecal
This creates a unique vertex
====================
*/
word CStudioModelRenderer :: AddVertexToDecal( DecalBuildInfo_t& build, svert_t *vert )
{
// Try to see if the clipped vertex already exists in our decal list...
// Only search for matches with verts appearing in the current decal
for( int i = 0; i < build.m_Vertices.Count(); i++ )
{
svert_t *test = &build.m_Vertices[i];
// Only bother to check against clipped vertices
if( test->m_MeshVertexIndex != INVALID_HANDLE )
continue;
if( !VectorCompareEpsilon( test->vertex, vert->vertex, 1e-3 ))
continue;
if( !VectorCompareEpsilon( test->normal, vert->normal, 1e-3 ))
continue;
return i;
}
// this path is the path taken by clipped vertices
ASSERT(( vert->stcoord[0] >= -1e-3 ) && ( vert->stcoord[0] - 1.0f < 1e-3 ));
ASSERT(( vert->stcoord[1] >= -1e-3 ) && ( vert->stcoord[1] - 1.0f < 1e-3 ));
// must create a new vertex...
return build.m_Vertices.AddToTail( *vert );
}
//-----------------------------------------------------------------------------
// Creates a new vertex where the edge intersects the plane
//-----------------------------------------------------------------------------
int CStudioModelRenderer :: IntersectPlane( DecalClipState_t& state, int start, int end, int normalInd, float val )
{
svert_t *startVert = &state.m_ClipVerts[start];
svert_t *endVert = &state.m_ClipVerts[end];
Vector2D dir = Vector2D ( endVert->stcoord[0] - startVert->stcoord[0], endVert->stcoord[1] - startVert->stcoord[1] );
ASSERT( dir[normalInd] != 0.0f );
float t = (val - startVert->stcoord[normalInd]) / dir[normalInd];
// Allocate a clipped vertex
svert_t *out = &state.m_ClipVerts[state.m_ClipVertCount];
int newVert = state.m_ClipVertCount++;
// the clipped vertex has no analogue in the original mesh
out->m_MeshVertexIndex = INVALID_HANDLE;
// just select bone by interpolation factor
if( t <= 0.5f )
{
memcpy( out->boneid, startVert->boneid, 4 );
memcpy( out->weight, startVert->weight, 4 );
}
else
{
memcpy( out->boneid, endVert->boneid, 4 );
memcpy( out->weight, endVert->weight, 4 );
}
// interpolate position
out->vertex = startVert->vertex * (1.0f - t) + endVert->vertex * t;
// interpolate tangent
out->tangent = startVert->tangent * (1.0f - t) + endVert->tangent * t;
out->tangent = out->tangent.Normalize();
// interpolate binormal
out->binormal = startVert->binormal * (1.0f - t) + endVert->binormal * t;
out->binormal = out->binormal.Normalize();
// interpolate normal
out->normal = startVert->normal * (1.0f - t) + endVert->normal * t;
out->normal = out->normal.Normalize();
// interpolate texture coord
out->stcoord[0] = startVert->stcoord[0] + (endVert->stcoord[0] - startVert->stcoord[0]) * t;
out->stcoord[1] = startVert->stcoord[1] + (endVert->stcoord[1] - startVert->stcoord[1]) * t;
out->stcoord[2] = startVert->stcoord[2] + (endVert->stcoord[2] - startVert->stcoord[2]) * t;
out->stcoord[3] = startVert->stcoord[3] + (endVert->stcoord[3] - startVert->stcoord[3]) * t;
ASSERT( !IS_NAN( out->stcoord[0] ) && !IS_NAN( out->stcoord[1] ));
// interpolate lighting (we need unpack, interpolate and pack again)
for( int map = 0; map < MAXLIGHTMAPS; map++ )
{
color24 startCol = UnpackColor( startVert->light[map] );
color24 startNrm = UnpackColor( startVert->deluxe[map] );
color24 endCol = UnpackColor( endVert->light[map] );
color24 endNrm = UnpackColor( endVert->deluxe[map] );
out->light[map] = PackColor( startCol * (1.0f - t) + endCol * t );
out->deluxe[map] = PackColor( startNrm * (1.0f - t) + endNrm * t );
}
// Compute the clip flags baby...
state.m_ClipFlags[newVert] = ComputeClipFlags( Vector2D( out->stcoord ));
return newVert;
}
/*
====================
ClipTriangleAgainstPlane
Clips a triangle against a plane,
use clip flags to speed it up
====================
*/
void CStudioModelRenderer :: ClipTriangleAgainstPlane( DecalClipState_t& state, int normalInd, int flag, float val )
{
// Ye Olde Sutherland-Hodgman clipping algorithm
int start = state.m_Indices[state.m_Pass][state.m_VertCount - 1];
bool startInside = FBitSet( state.m_ClipFlags[start], flag ) == 0;
int outVertCount = 0;
for( int i = 0; i < state.m_VertCount; i++ )
{
int end = state.m_Indices[state.m_Pass][i];
bool endInside = (state.m_ClipFlags[end] & flag) == 0;
if( endInside )
{
if( !startInside )
{
int clipVert = IntersectPlane( state, start, end, normalInd, val );
state.m_Indices[!state.m_Pass][outVertCount++] = clipVert;
}
state.m_Indices[!state.m_Pass][outVertCount++] = end;
}
else
{
if( startInside )
{
int clipVert = IntersectPlane( state, start, end, normalInd, val );
state.m_Indices[!state.m_Pass][outVertCount++] = clipVert;
}
}
start = end;
startInside = endInside;
}
state.m_Pass = !state.m_Pass;
state.m_VertCount = outVertCount;
}
/*
====================
AddClippedDecalToTriangle
Adds the clipped triangle to the decal
====================
*/
void CStudioModelRenderer :: AddClippedDecalToTriangle( DecalBuildInfo_t& build, DecalClipState_t& clipState )
{
word indices[7];
int i;
ASSERT( clipState.m_VertCount <= 7 );
// Yeah baby yeah!! Add this sucka
for( i = 0; i < clipState.m_VertCount; i++ )
{
// First add the vertices
int vertIdx = clipState.m_Indices[clipState.m_Pass][i];
if( vertIdx < 3 )
{
indices[i] = AddVertexToDecal( build, clipState.m_ClipVerts[vertIdx].m_MeshVertexIndex );
}
else
{
indices[i] = AddVertexToDecal( build, &clipState.m_ClipVerts[vertIdx] );
}
}
// Add a trifan worth of triangles
for( i = 1; i < clipState.m_VertCount - 1; i++ )
{
build.m_Indices.AddToTail( indices[0] );
build.m_Indices.AddToTail( indices[i] );
build.m_Indices.AddToTail( indices[i+1] );
}
}
/*
====================
ClipDecal
Clips the triangle to +/- radius
====================
*/
bool CStudioModelRenderer :: ClipDecal( DecalBuildInfo_t& build, int i1, int i2, int i3, int *pClipFlags )
{
DecalClipState_t clipState;
clipState.m_VertCount = 3;
ConvertMeshVertexToDecalVertex( build, i1, &clipState.m_ClipVerts[0] );
ConvertMeshVertexToDecalVertex( build, i2, &clipState.m_ClipVerts[1] );
ConvertMeshVertexToDecalVertex( build, i3, &clipState.m_ClipVerts[2] );
clipState.m_ClipVertCount = 3;
for( int i = 0; i < 3; i++ )
{
clipState.m_ClipFlags[i] = pClipFlags[i];
clipState.m_Indices[0][i] = i;
}
clipState.m_Pass = 0;
// Clip against each plane
ClipTriangleAgainstPlane( clipState, 0, DECAL_CLIP_MINUSU, 0.0f );
if( clipState.m_VertCount < 3 )
return false;
ClipTriangleAgainstPlane( clipState, 0, DECAL_CLIP_PLUSU, 1.0f );
if( clipState.m_VertCount < 3 )
return false;
ClipTriangleAgainstPlane( clipState, 1, DECAL_CLIP_MINUSV, 0.0f );
if( clipState.m_VertCount < 3 )
return false;
ClipTriangleAgainstPlane( clipState, 1, DECAL_CLIP_PLUSV, 1.0f );
if( clipState.m_VertCount < 3 )
return false;
// Only add the clipped decal to the triangle if it's one bone
// otherwise just return if it was clipped
if( build.m_UseClipVert )
{
AddClippedDecalToTriangle( build, clipState );
}
return true;
}
/*
====================
AddTriangleToDecal
Adds a decal to a triangle,
but only if it should
====================
*/
void CStudioModelRenderer :: AddTriangleToDecal( DecalBuildInfo_t& build, int i1, int i2, int i3 )
{
DecalVertexInfo_t* pVertexInfo = build.m_pVertexInfo;
// All must be front-facing for a decal to be added
if(( !pVertexInfo[i1].m_FrontFacing ) || ( !pVertexInfo[i2].m_FrontFacing ) || ( !pVertexInfo[i3].m_FrontFacing ))
return;
// This is used to prevent poke through; if the points are too far away
// from the contact point, then don't add the decal
if(( !pVertexInfo[i1].m_InValidArea ) && ( !pVertexInfo[i2].m_InValidArea ) && ( !pVertexInfo[i3].m_InValidArea ))
return;
// Clip to +/- radius
int clipFlags[3];
clipFlags[0] = ComputeClipFlags( pVertexInfo[i1].m_UV );
clipFlags[1] = ComputeClipFlags( pVertexInfo[i2].m_UV );
clipFlags[2] = ComputeClipFlags( pVertexInfo[i3].m_UV );
// Cull... The result is non-zero if they're all outside the same plane
if(( clipFlags[0] & ( clipFlags[1] & clipFlags[2] )) != 0 )
return;
bool doClip = true;
// Trivial accept for skinned polys... if even one vert is inside the draw region, accept
if(( !build.m_UseClipVert ) && ( !clipFlags[0] || !clipFlags[1] || !clipFlags[2] ))
doClip = false;
// Trivial accept... no clip flags set means all in
// Don't clip if we have more than one bone... we'll need to do skinning
// and we can't clip the bone indices
// We *do* want to clip in the one bone case though; useful for static props.
if( doClip && ( clipFlags[0] || clipFlags[1] || clipFlags[2] ))
{
bool validTri = ClipDecal( build, i1, i2, i3, clipFlags );
// Don't add the triangle if we culled the triangle or if
// we had one or less bones
if( build.m_UseClipVert || ( !validTri ))
return;
}
// Add the vertices to the decal since there was no clipping
build.m_Indices.AddToTail( AddVertexToDecal( build, i1 ));
build.m_Indices.AddToTail( AddVertexToDecal( build, i2 ));
build.m_Indices.AddToTail( AddVertexToDecal( build, i3 ));
}
word CStudioModelRenderer :: ShaderDecalForward( studiodecal_t *pDecal, bool vertex_lighting )
{
char glname[64];
char options[MAX_OPTIONS_LENGTH];
if( pDecal->forwardScene.IsValid( ))
return pDecal->forwardScene.GetHandle();
Q_strncpy( glname, "forward/decal_studio", sizeof( glname ));
memset( options, 0, sizeof( options ));
mstudiomaterial_t *mat = (mstudiomaterial_t *)RI->currentmodel->materials;
const DecalGroupEntry *texinfo = pDecal->texinfo;
vbomesh_t *pMesh = pDecal->modelmesh;
short *pskinref = (short *)((byte *)m_pStudioHeader + m_pStudioHeader->skinindex);
mat = &mat[pskinref[pMesh->skinref]];
if( tr.fogEnabled )
GL_AddShaderDirective( options, "APPLY_FOG_EXP" );
if( !texinfo->opaque )
{
// GL_DST_COLOR, GL_SRC_COLOR
GL_AddShaderDirective( options, "APPLY_COLORBLEND" );
}
else
{
if( texinfo->gl_heightmap_id != tr.whiteTexture && texinfo->matdesc->reliefScale > 0.0f )
{
if( cv_parallax->value == 1.0f )
GL_AddShaderDirective( options, "PARALLAX_SIMPLE" );
else if( cv_parallax->value >= 2.0f )
GL_AddShaderDirective( options, "PARALLAX_OCCLUSION" );
}
}
if( !texinfo->opaque || FBitSet( mat->flags, STUDIO_NF_FULLBRIGHT ) || R_FullBright( ))
{
GL_AddShaderDirective( options, "LIGHTING_FULLBRIGHT" );
}
else
{
if( CVAR_TO_BOOL( cv_brdf ))
GL_AddShaderDirective( options, "APPLY_PBS" );
if( vertex_lighting )
GL_AddShaderDirective( options, "VERTEX_LIGHTING" );
else if( FBitSet( mat->flags, STUDIO_NF_FLATSHADE ))
GL_AddShaderDirective( options, "LIGHTING_FLATSHADE" );
// debug visualization
if( r_lightmap->value > 0.0f && r_lightmap->value <= 2.0f )
{
if( r_lightmap->value == 1.0f && worldmodel->lightdata )
GL_AddShaderDirective( options, "LIGHTMAP_DEBUG" );
else if( r_lightmap->value == 2.0f && FBitSet( world->features, WORLD_HAS_DELUXEMAP ))
GL_AddShaderDirective( options, "LIGHTVEC_DEBUG" );
}
if( texinfo->gl_heightmap_id != tr.whiteTexture || texinfo->gl_normalmap_id != tr.normalmapTexture )
GL_AddShaderDirective( options, "COMPUTE_TBN" );
// deluxemap required
if( CVAR_TO_BOOL( cv_bump ) && FBitSet( world->features, WORLD_HAS_DELUXEMAP ) && texinfo->gl_normalmap_id != tr.normalmapTexture )
{
GL_AddShaderDirective( options, "HAS_NORMALMAP" );
GL_EncodeNormal( options, mat->gl_normalmap_id );
}
// deluxemap required
if( CVAR_TO_BOOL( cv_specular ) && FBitSet( world->features, WORLD_HAS_DELUXEMAP ) && texinfo->gl_specular_id != tr.blackTexture )
GL_AddShaderDirective( options, "HAS_GLOSSMAP" );
}
if( m_pStudioHeader->numbones == 1 )
GL_AddShaderDirective( options, "MAXSTUDIOBONES 1" );
if( FBitSet( m_pStudioHeader->flags, STUDIO_HAS_BONEWEIGHTS ))
GL_AddShaderDirective( options, "APPLY_BONE_WEIGHTING" );
if( FBitSet( mat->flags, STUDIO_NF_MASKED ))
GL_AddShaderDirective( options, "ALPHA_TEST" );
word shaderNum = GL_FindUberShader( glname, options );
if( !shaderNum ) return 0;
// done
pDecal->forwardScene.SetShader( shaderNum );
return shaderNum;
}
void CStudioModelRenderer :: ComputeDecalTBN( DecalBuildInfo_t& build )
{
if( build.m_pTexInfo->gl_normalmap_id == tr.normalmapTexture && build.m_pTexInfo->gl_heightmap_id == tr.whiteTexture )
return; // not needs
// build a map from vertex to a list of triangles that share the vert.
CUtlArray<CIntVector> vertToTriMap;
vertToTriMap.AddMultipleToTail( build.m_Vertices.Count() );
int triID;
for( triID = 0; triID < (build.m_Indices.Count() / 3); triID++ )
{
vertToTriMap[build.m_Indices[triID*3+0]].AddToTail( triID );
vertToTriMap[build.m_Indices[triID*3+1]].AddToTail( triID );
vertToTriMap[build.m_Indices[triID*3+2]].AddToTail( triID );
}
// calculate the tangent space for each triangle.
CUtlArray<Vector> triSVect, triTVect;
triSVect.AddMultipleToTail( (build.m_Indices.Count() / 3) );
triTVect.AddMultipleToTail( (build.m_Indices.Count() / 3) );
float *v[3], *tc[3];
for( triID = 0; triID < (build.m_Indices.Count() / 3); triID++ )
{
for( int i = 0; i < 3; i++ )
{
v[i] = (float *)&build.m_Vertices[build.m_Indices[triID*3+i]].vertex;
tc[i] = (float *)&build.m_Vertices[build.m_Indices[triID*3+i]].stcoord;
}
CalcTBN( v[0], v[1], v[2], tc[0], tc[1], tc[2], triSVect[triID], triTVect[triID] );
}
// calculate an average tangent space for each vertex.
for( int vertID = 0; vertID < build.m_Vertices.Count(); vertID++ )
{
svert_t *v = &build.m_Vertices[vertID];
const Vector &normal = v->normal;
Vector &finalSVect = v->tangent;
Vector &finalTVect = v->binormal;
Vector sVect, tVect;
sVect = tVect = g_vecZero;
for( triID = 0; triID < vertToTriMap[vertID].Size(); triID++ )
{
sVect += triSVect[vertToTriMap[vertID][triID]];
tVect += triTVect[vertToTriMap[vertID][triID]];
}
Vector tmpVect = CrossProduct( sVect, tVect );
bool leftHanded = DotProduct( tmpVect, normal ) < 0.0f;
if( !leftHanded )
{
tVect = CrossProduct( normal, sVect );
sVect = CrossProduct( tVect, normal );
}
else
{
tVect = CrossProduct( sVect, normal );
sVect = CrossProduct( normal, tVect );
}
finalSVect = sVect.Normalize();
finalTVect = tVect.Normalize();
}
}
void CStudioModelRenderer :: DecalCreateBuffer( DecalBuildInfo_t& build, studiodecal_t *pDecal )
{
vbomesh_t *pMesh = pDecal->modelmesh;
mstudiomaterial_t *pmaterial = (mstudiomaterial_t *)RI->currentmodel->materials;
short *pskinref = (short *)((byte *)m_pStudioHeader + m_pStudioHeader->skinindex);
pmaterial = &pmaterial[pskinref[pMesh->skinref]];
bool has_boneweights = ( RI->currentmodel->poseToBone != NULL ) ? true : false;
bool has_vertexlight = ( build.m_pVertexLight ) ? true : false;
bool has_lightmap = false;
bool has_bumpmap = false;
if( pDecal->texinfo->gl_normalmap_id != tr.normalmapTexture || pDecal->texinfo->gl_heightmap_id != tr.whiteTexture )
has_bumpmap = true;
ShaderDecalForward( pDecal, has_vertexlight );
vbomesh_t *pOut = &pDecal->mesh;
pOut->numVerts = build.m_Vertices.Count();
pOut->numElems = build.m_Indices.Count();
GL_CheckVertexArrayBinding();
// determine optimal mesh loader
uint attribs = ComputeAttribFlags( m_pStudioHeader->numbones, has_bumpmap, has_boneweights, has_vertexlight, has_lightmap );
uint type = SelectMeshLoader( m_pStudioHeader->numbones, has_bumpmap, has_boneweights, has_vertexlight, has_lightmap );
// move data to video memory
if( glConfig.version < ACTUAL_GL_VERSION )
m_pfnMeshLoaderGL21[type].CreateBuffer( pOut, build.m_Vertices.Base() );
else m_pfnMeshLoaderGL30[type].CreateBuffer( pOut, build.m_Vertices.Base() );
CreateIndexBuffer( pOut, build.m_Indices.Base() );
// link it with vertex array object
pglGenVertexArrays( 1, &pOut->vao );
pglBindVertexArray( pOut->vao );
if( glConfig.version < ACTUAL_GL_VERSION )
m_pfnMeshLoaderGL21[type].BindBuffer( pOut, attribs );
else m_pfnMeshLoaderGL30[type].BindBuffer( pOut, attribs );
BindIndexBuffer( pOut );
pglBindVertexArray( GL_FALSE );
// update stats
tr.total_vbo_memory += pOut->cacheSize;
}
void CStudioModelRenderer :: AllocDecalForMesh( DecalBuildInfo_t& build )
{
if( build.m_Vertices.Count() <= 0 || build.m_Indices.Count() <= 0 )
return; // something went wrong
int index = m_pModelInstance->m_DecalList.AddToTail();
studiodecal_t *pDecal = &m_pModelInstance->m_DecalList[index];
memset( pDecal, 0, sizeof( studiodecal_t ));
// copy settings
pDecal->normal = build.vecLocalNormal;
pDecal->position = build.vecLocalEnd;
pDecal->flags = build.decalFlags;
if( !build.current )
{
// move modelstate into static memory
build.poseState = m_pModelInstance->pose_stamps.AddToTail( *build.modelState );
build.current = pDecal; // first decal is saved, next ignored
}
else SetBits( pDecal->flags, FDECAL_DONTSAVE );
pDecal->modelmesh = build.m_pModelMesh;
pDecal->modelpose = build.poseState;
pDecal->texinfo = build.m_pTexInfo;
pDecal->depth = build.decalDepth;
// calc TBN if needs
ComputeDecalTBN( build );
// place vertices into VBO
DecalCreateBuffer( build, pDecal );
}
void CStudioModelRenderer :: AddDecalToMesh( DecalBuildInfo_t& build )
{
const vbomesh_t *mesh = build.m_pModelMesh;
Vector absmin, absmax;
// setup mesh bounds
TransformAABB( m_pModelInstance->m_pbones[mesh->parentbone], mesh->mins, mesh->maxs, absmin, absmax );
if( !BoundsAndSphereIntersect( absmin, absmax, build.vecLocalEnd, build.m_Radius ))
return; // no intersection
build.m_Vertices.Purge();
build.m_Indices.Purge();
for( int j = 0; j < build.m_pDecalMesh->numindices; j += 3 )
{
int starttri = build.m_pDecalMesh->firstindex + j;
AddTriangleToDecal( build, m_arrayelems[starttri+0], m_arrayelems[starttri+1], m_arrayelems[starttri+2] );
}
// allocate decal for mesh
AllocDecalForMesh( build );
}
void CStudioModelRenderer :: AddDecalToModel( DecalBuildInfo_t& buildInfo )
{
Vector *pstudioverts = (Vector *)((byte *)m_pStudioHeader + m_pSubModel->vertindex);
Vector *pstudionorms = (Vector *)((byte *)m_pStudioHeader + m_pSubModel->normindex);
byte *pvertbone = ((byte *)m_pStudioHeader + m_pSubModel->vertinfoindex);
short *pskinref;
int i, numVerts;
// if weights was missed their offsets just equal to 0
mstudioboneweight_t *pvertweight = (mstudioboneweight_t *)((byte *)m_pStudioHeader + m_pSubModel->blendvertinfoindex);
StudioMesh_t *pDecalMesh = (StudioMesh_t *)stackalloc( m_pSubModel->nummesh * sizeof( StudioMesh_t ));
pskinref = (short *)((byte *)m_pStudioHeader + m_pStudioHeader->skinindex);
if( !pDecalMesh ) return; // empty mesh?
buildInfo.m_pVertexLight = NULL;
if( buildInfo.modelLight != NULL )
{
int offset = (byte *)m_pSubModel - (byte *)m_pStudioHeader; // search for submodel offset
for( i = 0; i < MAXSTUDIOMODELS; i++ )
{
if( buildInfo.modelLight->submodels[i].submodel_offset == offset )
break;
}
// has vertexlighting for this submodel
if( i != MAXSTUDIOMODELS )
buildInfo.m_pVertexLight = &buildInfo.modelLight->verts[buildInfo.modelLight->submodels[i].vertex_offset];
}
m_nNumArrayVerts = m_nNumArrayElems = 0;
m_nNumLightVerts = 0;
// build all the data for current submodel
for( i = 0; i < m_pSubModel->nummesh; i++ )
{
mstudiomesh_t *pmesh = (mstudiomesh_t *)((byte *)m_pStudioHeader + m_pSubModel->meshindex) + i;
mstudiomaterial_t *pmaterial = &RI->currentmodel->materials[pskinref[pmesh->skinref]];
short *ptricmds = (short *)((byte *)m_pStudioHeader + pmesh->triindex);
StudioMesh_t *pCurMesh = pDecalMesh + i;
pCurMesh->firstvertex = m_nNumArrayVerts;
pCurMesh->firstindex = m_nNumArrayElems;
pCurMesh->numvertices = 0;
pCurMesh->numindices = 0;
mstudiotexture_t *ptexture = pmaterial->pSource;
float s = 1.0f / (float)ptexture->width;
float t = 1.0f / (float)ptexture->height;
while( numVerts = *( ptricmds++ ))
{
bool strip = ( numVerts < 0 ) ? false : true;
int vertexState = 0;
numVerts = abs( numVerts );
for( ; numVerts > 0; numVerts--, ptricmds += 4 )
{
// build in indices
if( vertexState++ < 3 )
{
m_arrayelems[m_nNumArrayElems++] = m_nNumArrayVerts;
}
else if( strip )
{
// flip triangles between clockwise and counter clockwise
if( vertexState & 1 )
{
// draw triangle [n-2 n-1 n]
m_arrayelems[m_nNumArrayElems++] = m_nNumArrayVerts - 2;
m_arrayelems[m_nNumArrayElems++] = m_nNumArrayVerts - 1;
m_arrayelems[m_nNumArrayElems++] = m_nNumArrayVerts;
}
else
{
// draw triangle [n-1 n-2 n]
m_arrayelems[m_nNumArrayElems++] = m_nNumArrayVerts - 1;
m_arrayelems[m_nNumArrayElems++] = m_nNumArrayVerts - 2;
m_arrayelems[m_nNumArrayElems++] = m_nNumArrayVerts;
}
}
else
{
// draw triangle fan [0 n-1 n]
m_arrayelems[m_nNumArrayElems++] = m_nNumArrayVerts - ( vertexState - 1 );
m_arrayelems[m_nNumArrayElems++] = m_nNumArrayVerts - 1;
m_arrayelems[m_nNumArrayElems++] = m_nNumArrayVerts;
}
// store mesh into uniform vertex for consistency
svert_t *out = &m_arrayxvert[m_nNumArrayVerts];
out->vertex = pstudioverts[ptricmds[0]];
out->normal = pstudionorms[ptricmds[1]].NormalizeFast();
out->binormal = g_vecZero;
out->tangent = g_vecZero;
out->stcoord[0] = 0.0f;
out->stcoord[1] = 0.0f;
if( RI->currentmodel->poseToBone != NULL )
{
mstudioboneweight_t *pCurWeight = &pvertweight[ptricmds[0]];
out->boneid[0] = pCurWeight->bone[0];
out->boneid[1] = pCurWeight->bone[1];
out->boneid[2] = pCurWeight->bone[2];
out->boneid[3] = pCurWeight->bone[3];
out->weight[0] = pCurWeight->weight[0];
out->weight[1] = pCurWeight->weight[1];
out->weight[2] = pCurWeight->weight[2];
out->weight[3] = pCurWeight->weight[3];
}
else
{
out->boneid[0] = pvertbone[ptricmds[0]];
out->boneid[1] = -1;
out->boneid[2] = -1;
out->boneid[3] = -1;
out->weight[0] = 255;
out->weight[1] = 0;
out->weight[2] = 0;
out->weight[3] = 0;
}
if( FBitSet( ptexture->flags, STUDIO_NF_CHROME ))
{
// probably always equal 64 (see studiomdl.c for details)
out->stcoord[2] = s;
out->stcoord[3] = t;
}
else if( FBitSet( ptexture->flags, STUDIO_NF_UV_COORDS ))
{
out->stcoord[2] = HalfToFloat( ptricmds[2] );
out->stcoord[3] = HalfToFloat( ptricmds[3] );
}
else
{
out->stcoord[2] = ptricmds[2] * s;
out->stcoord[3] = ptricmds[3] * t;
}
// accumulate vertex lighting too
if( buildInfo.m_pVertexLight != NULL )
{
dvertlight_t *vl = &buildInfo.m_pVertexLight[m_nNumLightVerts++];
// now setup light and deluxe vector
for( int map = 0; map < MAXLIGHTMAPS; map++ )
{
out->light[map] = PackColor( vl->light[map] );
out->deluxe[map] = PackColor( vl->deluxe[map] );
}
}
m_nNumArrayVerts++;
}
}
pCurMesh->numvertices = m_nNumArrayVerts - pCurMesh->firstvertex;
pCurMesh->numindices = m_nNumArrayElems - pCurMesh->firstindex;
}
// should keep all the verts of this submodel, because we use direct access by vertex number
buildInfo.m_pVertexInfo = (DecalVertexInfo_t *)stackalloc( m_nNumArrayVerts * sizeof( DecalVertexInfo_t ));
// project all vertices for this group into decal space
// Note we do this work at a mesh level instead of a model level
// because vertices are not shared across mesh boundaries
ProjectDecalOntoMesh( buildInfo );
// NOTE: we should add the individual decals for each mesh
// to effectively sorting while renderer translucent meshes
for( i = 0; i < m_pSubModel->nummesh; i++ )
{
// setup mesh pointers
buildInfo.m_pModelMesh = &m_pVboModel->meshes[i];
buildInfo.m_pDecalMesh = pDecalMesh + i;
AddDecalToMesh( buildInfo );
}
}
/*
====================
StudioDecalShoot
simplified version
====================
*/
void CStudioModelRenderer :: StudioDecalShoot( struct pmtrace_s *tr, const char *name, bool visent )
{
if( !g_fRenderInitialized )
return;
if( tr->allsolid || tr->fraction == 1.0 || tr->ent < 0 )
return;
physent_t *pe = NULL;
if( visent ) pe = gEngfuncs.pEventAPI->EV_GetVisent( tr->ent );
else pe = gEngfuncs.pEventAPI->EV_GetPhysent( tr->ent );
if( !pe ) return;
Vector scale = Vector( 1.0f, 1.0f, 1.0f );
int entityIndex = pe->info;
int flags = FDECAL_STUDIO;
modelstate_t state;
int modelIndex = 0;
// modelindex is needs for properly save\restore
cl_entity_t *ent = GET_ENTITY( entityIndex );
if( !ent )
{
// something very bad happens...
ALERT( at_error, "StudioDecal: ent == NULL\n" );
return;
}
modelIndex = ent->curstate.modelindex;
// restore model in case decalmessage was delivered early than delta-update
if( !ent->model && modelIndex != 0 )
ent->model = IEngineStudio.GetModelByIndex( modelIndex );
if( !ent->model || ent->model->type != mod_studio )
return;
Vector vecNormal = tr->plane.normal;
Vector vecEnd = tr->endpos;
PushEntityState( ent );
// create snapshot of current model state
EntityToModelState( &state, ent );
if( FBitSet( ent->curstate.iuser1, CF_STATIC_ENTITY ))
{
if( ent->curstate.startpos != g_vecZero )
scale = Vector( 1.0f / ent->curstate.startpos.x, 1.0f / ent->curstate.startpos.y, 1.0f / ent->curstate.startpos.z );
}
// studio decals is always converted into local space to avoid troubles with precision and modelscale
matrix4x4 mat = matrix4x4( ent->origin, ent->angles, scale );
vecNormal = mat.VectorIRotate( vecNormal );
vecEnd = mat.VectorITransform( vecEnd );
SetBits( flags, FDECAL_LOCAL_SPACE ); // now it's in local space
// simulate network degradation for match results
vecNormal.x = Q_rint( vecNormal.x * 1000.0f ) * 0.001f;
vecNormal.y = Q_rint( vecNormal.y * 1000.0f ) * 0.001f;
vecNormal.z = Q_rint( vecNormal.z * 1000.0f ) * 0.001f;
vecEnd.x = Q_rint( vecEnd.x );
vecEnd.y = Q_rint( vecEnd.y );
vecEnd.z = Q_rint( vecEnd.z );
StudioDecalShoot( vecNormal, vecEnd, name, ent, flags, &state );
PopEntityState( ent );
}
/*
====================
StudioDecalShoot
NOTE: name is decalgroup
====================
*/
void CStudioModelRenderer :: StudioDecalShoot( const Vector &vecNormal, const Vector &vecEnd, const char *name, cl_entity_t *ent, int flags, modelstate_t *state )
{
DecalBuildInfo_t buildInfo;
float flLocalScale = 1.0f;
if( !g_fRenderInitialized )
return;
// setup studio pointers
if( !StudioSetEntity( ent ))
return;
if( m_pStudioHeader->numbodyparts == 0 )
return; // null model?
// this sucker is state needed only when building decals
buildInfo.m_pTexInfo = DecalGroup::GetEntry( name, flags );
if( !buildInfo.m_pTexInfo ) return;
DecalGroupEntry *entry = (DecalGroupEntry *)buildInfo.m_pTexInfo;
// time to cache decal textures
entry->PreloadTextures();
if( entry->gl_diffuse_id == 0 )
{
ALERT( at_warning, "StudioDecal: decal texture '%s' was missed\n", entry->m_DecalName );
return; // decal texture was missed.
}
// make sure what time is actual
tr.time = GET_CLIENT_TIME();
tr.oldtime = GET_CLIENT_OLDTIME();
if( FBitSet( flags, FDECAL_LOCAL_SPACE ))
{
if( FBitSet( ent->curstate.iuser1, CF_STATIC_ENTITY ))
{
if( ent->curstate.startpos != g_vecZero )
{
flLocalScale = ent->curstate.startpos.Average();
flLocalScale = 1.0f / flLocalScale, 1.0f;
}
}
// invalidate bonecache
m_pModelInstance->bonecache.frame = -999.0f;
// make sure what model is in local space
ent->curstate.startpos = g_vecZero;
ent->origin = g_vecZero;
ent->angles = g_vecZero;
}
else
{
ALERT( at_warning, "StudioDecal: '%s' not in local space. Ignored!\n", entry->m_DecalName );
return;
}
// we are in decal-shooting mode
m_fShootDecal = true;
// setup bones
StudioSetUpTransform ( );
StudioSetupBones ( );
m_fShootDecal = false;
if( FBitSet( ent->curstate.iuser1, CF_STATIC_ENTITY ) && ( ent->curstate.colormap > 0 ) && world->vertex_lighting != NULL )
{
ModelInstance_t *inst = m_pModelInstance;
// only do it while restore mode and once only
if( !RENDER_GET_PARM( PARM_CLIENT_ACTIVE, 0 ) && !inst->m_VlCache && !FBitSet( inst->info_flags, MF_VL_BAD_CACHE ))
{
// we need to get right pointers from vertex lighted meshes
CacheVertexLight( RI->currententity );
}
}
// setup worldtransform array
if( RI->currentmodel->poseToBone != NULL )
{
for( int i = 0; i < m_pStudioHeader->numbones; i++ )
m_pworldtransform[i] = m_pModelInstance->m_pbones[i].ConcatTransforms( RI->currentmodel->poseToBone->posetobone[i] );
}
else
{
// no pose to bone just copy the bones
for( int i = 0; i < m_pStudioHeader->numbones; i++ )
m_pworldtransform[i] = m_pModelInstance->m_pbones[i];
}
if( !ComputePoseToDecal( vecNormal, vecEnd ))
return;
check_decals:
// too many decals on model, remove
while( m_pModelInstance->m_DecalList.Count() > r_studio_decals->value )
{
// decals with this depth level are fragments
// of single decal and should be removed together
int depth = m_pModelInstance->m_DecalList[0].depth;
for( int i = m_pModelInstance->m_DecalList.Count(); --i >= 0; )
{
studiodecal_t *pDecal = &m_pModelInstance->m_DecalList[i];
if( pDecal->depth == depth )
{
DeleteVBOMesh( &pDecal->mesh );
memset( pDecal, 0 , sizeof( *pDecal ));
m_pModelInstance->m_DecalList.Remove( i );
goto check_decals;
}
}
}
// local scale it's used to keep decal constant size while model is scaled
float xsize = buildInfo.m_pTexInfo->xsize * flLocalScale;
float ysize = buildInfo.m_pTexInfo->ysize * flLocalScale;
SetBits( flags, FDECAL_NORANDOM );
buildInfo.m_Radius = sqrt( xsize * xsize + ysize * ysize ) * 0.5f;
buildInfo.vecDecalScale = Vector2D( 1.0f / ysize, 1.0f / xsize );
buildInfo.m_UseClipVert = ( m_pStudioHeader->numbones <= 1 ); // produce clipped verts only for static props
buildInfo.decalDepth = m_pModelInstance->m_DecalCount++;
buildInfo.decalFlags = (byte)flags;
buildInfo.vecLocalNormal = vecNormal;
buildInfo.vecLocalEnd = vecEnd;
buildInfo.modelState = state;
buildInfo.modelLight = NULL;
buildInfo.current = NULL;
// special check for per-vertex lighting
if( FBitSet( ent->curstate.iuser1, CF_STATIC_ENTITY ) && ( ent->curstate.colormap > 0 ) && world->vertex_lighting != NULL )
{
int cacheID = RI->currententity->curstate.colormap - 1;
dvlightlump_t *dvl = world->vertex_lighting;
if( cacheID < dvl->nummodels && dvl->dataofs[cacheID] != -1 )
buildInfo.modelLight = (dmodelvertlight_t *)((byte *)world->vertex_lighting + dvl->dataofs[cacheID]);
}
// step over all body parts + add decals to them all!
for( int k = 0; k < m_pStudioHeader->numbodyparts; k++ )
{
// grab the model for this body part
StudioSetupModel( k, &m_pSubModel, &m_pVboModel );
AddDecalToModel( buildInfo );
}
}
/*
=============================================================
DECALS RENDERING
=============================================================
*/
void CStudioModelRenderer :: SetDecalUniforms( studiodecal_t *pDecal )
{
cl_entity_t *e = RI->currententity;
ModelInstance_t *inst = m_pModelInstance = &m_ModelInstances[e->modelhandle];
bool vertex_lighting = FBitSet( m_pModelInstance->info_flags, MF_VERTEX_LIGHTING ) ? true : false;
// rebuild shader if changed
ShaderDecalForward( pDecal, vertex_lighting );
GL_BindShader( pDecal->forwardScene.GetShader());
glsl_program_t *shader = RI->currentshader;
int num_bones = Q_min( m_pStudioHeader->numbones, glConfig.max_skinning_bones );
mstudiomaterial_t *pmaterial = (mstudiomaterial_t *)RI->currentmodel->materials;
vbomesh_t *pMesh = pDecal->modelmesh;
Vector4D lightstyles, lightdir;
int width, height;
int m_skinnum = bound( 0, e->curstate.skin, m_pStudioHeader->numskinfamilies - 1 );
short *pskinref = (short *)((byte *)m_pStudioHeader + m_pStudioHeader->skinindex);
if( m_skinnum != 0 && m_skinnum < m_pStudioHeader->numskinfamilies )
pskinref += (m_skinnum * m_pStudioHeader->numskinref);
pmaterial = &pmaterial[pskinref[pMesh->skinref]];
CDynLight *pl = RI->currentlight; // may be NULL
mstudiolight_t *light = &inst->light;
int map;
// setup specified uniforms (and texture bindings)
for( int i = 0; i < shader->numUniforms; i++ )
{
uniform_t *u = &shader->uniforms[i];
switch( u->type )
{
case UT_COLORMAP:
u->SetValue( pmaterial->gl_diffuse_id );
break;
case UT_DECALMAP:
u->SetValue( pDecal->texinfo->gl_diffuse_id );
break;
case UT_NORMALMAP:
u->SetValue( pDecal->texinfo->gl_normalmap_id );
break;
case UT_GLOSSMAP:
u->SetValue( pDecal->texinfo->gl_specular_id );
break;
case UT_HEIGHTMAP:
u->SetValue( pDecal->texinfo->gl_heightmap_id );
break;
case UT_PROJECTMAP:
if( pl && pl->type == LIGHT_SPOT )
u->SetValue( pl->spotlightTexture );
else u->SetValue( tr.whiteTexture );
break;
case UT_SHADOWMAP:
case UT_SHADOWMAP0:
if( pl ) u->SetValue( pl->shadowTexture[0] );
else u->SetValue( tr.depthTexture );
break;
case UT_SHADOWMAP1:
if( pl ) u->SetValue( pl->shadowTexture[1] );
else u->SetValue( tr.depthTexture );
break;
case UT_SHADOWMAP2:
if( pl ) u->SetValue( pl->shadowTexture[2] );
else u->SetValue( tr.depthTexture );
break;
case UT_SHADOWMAP3:
if( pl ) u->SetValue( pl->shadowTexture[3] );
else u->SetValue( tr.depthTexture );
break;
case UT_LIGHTMAP:
case UT_DELUXEMAP:
// unacceptable for studiomodels
u->SetValue( tr.whiteTexture );
break;
case UT_SCREENMAP:
u->SetValue( tr.screen_color );
break;
case UT_DEPTHMAP:
u->SetValue( tr.screen_depth );
break;
case UT_ENVMAP0:
case UT_ENVMAP:
if( inst->cubemap[0] != NULL )
u->SetValue( inst->cubemap[0]->texture );
else u->SetValue( tr.whiteCubeTexture );
break;
case UT_ENVMAP1:
if( inst->cubemap[1] != NULL )
u->SetValue( inst->cubemap[1]->texture );
else u->SetValue( tr.whiteCubeTexture );
break;
case UT_BSPPLANESMAP:
u->SetValue( tr.packed_planes_texture );
break;
case UT_BSPNODESMAP:
u->SetValue( tr.packed_nodes_texture );
break;
case UT_BSPLIGHTSMAP:
u->SetValue( tr.packed_lights_texture );
break;
case UT_RENDERALPHA:
if( e->curstate.rendermode == kRenderTransTexture || e->curstate.rendermode == kRenderTransAdd )
u->SetValue( e->curstate.renderamt / 255.0f );
else u->SetValue( 1.0f );
break;
case UT_FOGPARAMS:
if( e->curstate.renderfx == SKYBOX_ENTITY )
u->SetValue( tr.fogColor[0], tr.fogColor[1], tr.fogColor[2], tr.fogSkyDensity );
else u->SetValue( tr.fogColor[0], tr.fogColor[1], tr.fogColor[2], tr.fogDensity );
break;
case UT_BONESARRAY:
u->SetValue( &inst->m_glstudiobones[0][0], num_bones * 3 );
break;
case UT_BONEQUATERNION:
u->SetValue( &inst->m_studioquat[0][0], num_bones );
break;
case UT_BONEPOSITION:
u->SetValue( &inst->m_studiopos[0][0], num_bones );
break;
case UT_SCREENSIZEINV:
u->SetValue( 1.0f / (float)glState.width, 1.0f / (float)glState.height );
break;
case UT_ZFAR:
u->SetValue( -tr.farclip * 1.74f );
break;
case UT_LIGHTSTYLES:
for( map = 0; map < MAXLIGHTMAPS; map++ )
{
if( inst->styles[map] != 255 )
lightstyles[map] = tr.lightstyle[inst->styles[map]];
else lightstyles[map] = 0.0f;
}
u->SetValue( lightstyles.x, lightstyles.y, lightstyles.z, lightstyles.w );
break;
case UT_REALTIME:
u->SetValue( (float)tr.time );
break;
case UT_VIEWORIGIN:
u->SetValue( GetVieworg().x, GetVieworg().y, GetVieworg().z );
break;
case UT_GAMMATABLE:
u->SetValue( &tr.gamma_table[0][0], 64 );
break;
case UT_LIGHTDIR:
if( pl )
{
if( pl->type == LIGHT_DIRECTIONAL ) lightdir = -tr.sky_normal;
else lightdir = pl->frustum.GetPlane( FRUSTUM_FAR )->normal;
u->SetValue( lightdir.x, lightdir.y, lightdir.z, pl->fov );
}
else u->SetValue( light->normal.x, light->normal.y, light->normal.z );
break;
case UT_LIGHTDIFFUSE:
if( pl ) u->SetValue( pl->color.x, pl->color.y, pl->color.z );
else u->SetValue( light->diffuse.x, light->diffuse.y, light->diffuse.z );
break;
case UT_LIGHTSHADE:
u->SetValue( (float)light->ambientlight / 255.0f, (float)light->shadelight / 255.0f );
break;
case UT_LIGHTORIGIN:
if( pl ) u->SetValue( pl->origin.x, pl->origin.y, pl->origin.z, ( 1.0f / pl->radius ));
break;
case UT_LIGHTVIEWPROJMATRIX:
if( pl )
{
GLfloat gl_lightViewProjMatrix[16];
pl->lightviewProjMatrix.CopyToArray( gl_lightViewProjMatrix );
u->SetValue( &gl_lightViewProjMatrix[0] );
}
break;
case UT_DIFFUSEFACTOR:
u->SetValue( tr.diffuseFactor );
break;
case UT_AMBIENTFACTOR:
if( pl && pl->type == LIGHT_DIRECTIONAL )
u->SetValue( tr.sun_ambient );
else u->SetValue( tr.ambientFactor );
break;
case UT_SUNREFRACT:
u->SetValue( tr.sun_refract );
break;
case UT_AMBIENTCUBE:
u->SetValue( &light->ambient[0][0], 6 );
break;
case UT_LERPFACTOR:
u->SetValue( inst->lerpFactor );
break;
case UT_SMOOTHNESS:
u->SetValue( pDecal->texinfo->matdesc->smoothness );
break;
case UT_RELIEFPARAMS:
width = RENDER_GET_PARM( PARM_TEX_WIDTH, pDecal->texinfo->gl_heightmap_id );
height = RENDER_GET_PARM( PARM_TEX_HEIGHT, pDecal->texinfo->gl_heightmap_id );
u->SetValue( (float)width, (float)height, pDecal->texinfo->matdesc->reliefScale, cv_shadow_offset->value );
break;
default:
ALERT( at_error, "Unhandled uniform %s\n", u->name );
break;
}
}
}
//-----------------------------------------------------------------------------
// Draws all the decals on a particular model
//-----------------------------------------------------------------------------
void CStudioModelRenderer :: DrawDecal( CSolidEntry *entry, GLenum cull_mode )
{
if( !StudioSetEntity( entry ))
return;
if( m_pModelInstance->visframe != tr.realframecount )
return; // model is culled
if( !m_pModelInstance->m_DecalList.Count())
return; // no decals for this model
if( CVAR_TO_BOOL( r_polyoffset ))
{
pglEnable( GL_POLYGON_OFFSET_FILL );
pglPolygonOffset( -1.0f, -r_polyoffset->value );
}
GL_Blend( GL_TRUE );
GL_DepthMask( GL_FALSE );
GL_AlphaTest( GL_FALSE );
GL_Cull( cull_mode );
// Draw decals in history order or reverse order
if( cull_mode == GL_FRONT )
{
// direct order
for( int i = 0; i < m_pModelInstance->m_DecalList.Count(); i++ )
{
studiodecal_t *pDecal = &m_pModelInstance->m_DecalList[i];
if( entry->m_pMesh->uniqueID != pDecal->modelmesh->uniqueID )
continue;
SetDecalUniforms( pDecal );
if( pDecal->texinfo->opaque )
pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
else pglBlendFunc( GL_DST_COLOR, GL_SRC_COLOR );
DrawMeshFromBuffer( &pDecal->mesh );
}
}
else
{
// reverse order
for( int i = m_pModelInstance->m_DecalList.Count(); --i >= 0; )
{
studiodecal_t *pDecal = &m_pModelInstance->m_DecalList[i];
if( entry->m_pMesh->uniqueID != pDecal->modelmesh->uniqueID )
continue;
SetDecalUniforms( pDecal );
if( pDecal->texinfo->opaque )
pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
else pglBlendFunc( GL_DST_COLOR, GL_SRC_COLOR );
DrawMeshFromBuffer( &pDecal->mesh );
}
}
if( CVAR_TO_BOOL( r_polyoffset ))
pglDisable( GL_POLYGON_OFFSET_FILL );
GL_CleanupAllTextureUnits();
GL_DepthMask( GL_TRUE );
GL_Blend( GL_FALSE );
GL_Cull( GL_FRONT );
}