2020-08-31 18:50:41 +02:00
|
|
|
/*
|
|
|
|
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 );
|
2020-08-31 00:15:53 +02:00
|
|
|
}
|