xash3d-fwgs/ref/vk/vk_studio.c
Ivan 'provod' Avdeev a2b0164e03 vk: put barney back together
Fixes computing total vertices/indices count. Were referencing the same `pmesh` for all meshes.

Floating and missing heads issue seems to be due to incorrect fixed animation frames. I.e. animation frames contain offsets to the correct positions. Should be fixed when animations are done.
2023-06-03 12:10:56 -07:00

3700 lines
100 KiB
C

#include "vk_studio.h"
#include "com_model.h"
#include "vk_common.h"
#include "vk_textures.h"
#include "vk_render.h"
#include "vk_geometry.h"
#include "vk_previous_frame.h"
#include "vk_renderstate.h"
#include "vk_math.h"
#include "vk_cvar.h"
#include "camera.h"
#include "r_speeds.h"
#include "xash3d_mathlib.h"
#include "const.h"
#include "r_studioint.h"
#include "triangleapi.h"
#include "studio.h"
#include "pm_local.h"
#include "pmtrace.h"
#include "protocol.h"
#include "enginefeatures.h"
#include "pm_movevars.h"
#include <memory.h>
#include <stdlib.h>
#define EVENT_CLIENT 5000 // less than this value it's a server-side studio events
#define MAX_LOCALLIGHTS 4
// TODO get rid of this
#define ENGINE_GET_PARM_ (*gEngine.EngineGetParm)
#define ENGINE_GET_PARM( parm ) ENGINE_GET_PARM_( ( parm ), 0 )
// FIXME VK should not be declared here
colorVec R_LightVec( const float *start, const float *end, float *lightspot, float *lightvec );
typedef struct
{
char name[MAX_OSPATH];
char modelname[MAX_OSPATH];
model_t *model;
} player_model_t;
cvar_t r_shadows = { (char*)"r_shadows", (char*)"0", 0 };
typedef struct sortedmesh_s
{
const mstudiomesh_t *mesh;
int flags; // face flags
} sortedmesh_t;
typedef struct
{
double time;
double frametime;
int framecount; // studio framecount
qboolean interpolate;
int rendermode, rendermode2;
float blend; // blend value
// bones
matrix3x4 rotationmatrix;
matrix3x4 bonestransform[MAXSTUDIOBONES];
matrix3x4 lighttransform[MAXSTUDIOBONES];
// boneweighting stuff
matrix3x4 worldtransform[MAXSTUDIOBONES];
// cached bones
matrix3x4 cached_bonestransform[MAXSTUDIOBONES];
matrix3x4 cached_lighttransform[MAXSTUDIOBONES];
char cached_bonenames[MAXSTUDIOBONES][32];
int cached_numbones; // number of bones in cache
sortedmesh_t meshes[MAXSTUDIOMESHES]; // sorted meshes
vec3_t verts[MAXSTUDIOVERTS];
vec3_t norms[MAXSTUDIOVERTS];
vec3_t tangents[MAXSTUDIOVERTS];
vec3_t prev_verts[MAXSTUDIOVERTS]; // last frame state for motion vectors
// lighting state
float ambientlight;
float shadelight;
vec3_t lightvec; // averaging light direction
vec3_t lightspot; // shadow spot
vec3_t lightcolor; // averaging lightcolor
vec3_t blightvec[MAXSTUDIOBONES]; // bone light vecs
vec3_t lightvalues[MAXSTUDIOVERTS]; // precomputed lightvalues per each shared vertex of submodel
// chrome stuff
vec3_t chrome_origin;
vec2_t chrome[MAXSTUDIOVERTS]; // texture coords for surface normals
vec3_t chromeright[MAXSTUDIOBONES]; // chrome vector "right" in bone reference frames
vec3_t chromeup[MAXSTUDIOBONES]; // chrome vector "up" in bone reference frames
int chromeage[MAXSTUDIOBONES]; // last time chrome vectors were updated
// glowshell stuff
int normaltable[MAXSTUDIOVERTS]; // glowshell uses this
// elights cache
int numlocallights;
int lightage[MAXSTUDIOBONES];
dlight_t *locallight[MAX_LOCALLIGHTS];
color24 locallightcolor[MAX_LOCALLIGHTS];
vec4_t lightpos[MAXSTUDIOVERTS][MAX_LOCALLIGHTS];
vec3_t lightbonepos[MAXSTUDIOBONES][MAX_LOCALLIGHTS];
float locallightR2[MAX_LOCALLIGHTS];
// playermodels
player_model_t player_models[MAX_CLIENTS];
// drawelements renderer
uint numverts;
uint numelems;
} studio_draw_state_t;
// studio-related cvars
cvar_t *cl_righthand = NULL;
static r_studio_interface_t *pStudioDraw;
static studio_draw_state_t g_studio; // global studio state
static struct {
int models_count;
} g_studio_stats;
// global variables
static qboolean m_fDoRemap;
mstudiomodel_t *m_pSubModel;
mstudiobodyparts_t *m_pBodyPart;
player_info_t *m_pPlayerInfo;
studiohdr_t *m_pStudioHeader;
float m_flGaitMovement;
int g_iBackFaceCull;
int g_nTopColor, g_nBottomColor; // remap colors
int g_nFaceFlags, g_nForceFaceFlags;
// FIXME VK this should be promoted to somewhere global-ish, and done properly
// For now it's just a hack to get studio models to compile basically
static struct {
qboolean drawWorld; // ignore world for drawing PlayerModel
cl_entity_t *currententity;
model_t *currentmodel;
} RI;
typedef struct {
const mstudiomodel_t *key_submodel;
vk_render_model_t render_model;
r_geometry_range_t geometry_range;
vk_render_geometry_t *geometries;
int geometries_count;
} r_studio_model_cache_entry_t;
static struct {
#define MAX_CACHED_STUDIO_MODELS 1024
// TODO proper map/hash table
r_studio_model_cache_entry_t entries[MAX_CACHED_STUDIO_MODELS];
int entries_count;
} g_studio_cache;
void R_StudioCacheClear( void ) {
for (int i = 0; i < g_studio_cache.entries_count; ++i) {
r_studio_model_cache_entry_t *const entry = g_studio_cache.entries + i;
ASSERT(entry->key_submodel);
VK_RenderModelDestroy(&entry->render_model);
R_GeometryRangeFree(&entry->geometry_range);
Mem_Free(entry->geometries);
entry->key_submodel = 0;
entry->geometries = NULL;
entry->geometries_count = 0;
}
g_studio_cache.entries_count = 0;
}
void R_StudioInit( void )
{
Matrix3x4_LoadIdentity( g_studio.rotationmatrix );
// g-cont. cvar disabled by Valve
// gEngine.Cvar_RegisterVariable( &r_shadows );
g_studio.interpolate = true;
g_studio.framecount = 0;
m_fDoRemap = false;
R_SpeedsRegisterMetric(&g_studio_stats.models_count, "models_studio", kSpeedsMetricCount);
}
/*
================
R_StudioSetupTimings
init current time for a given model
================
*/
static void R_StudioSetupTimings( void )
{
if( RI.drawWorld )
{
// synchronize with server time
g_studio.time = gpGlobals->time;
g_studio.frametime = gpGlobals->time - gpGlobals->oldtime;
}
else
{
// menu stuff
g_studio.time = gpGlobals->realtime;
g_studio.frametime = gpGlobals->frametime;
}
}
/*
================
R_AllowFlipViewModel
should a flip the viewmodel if cl_righthand is set to 1
================
*/
static qboolean R_AllowFlipViewModel( cl_entity_t *e )
{
if( cl_righthand && cl_righthand->value > 0 )
{
if( e == gEngine.GetViewModel() )
return true;
}
return false;
}
/*
================
R_StudioComputeBBox
Compute a full bounding box for current sequence
================
*/
static qboolean R_StudioComputeBBox( vec3_t bbox[8] )
{
vec3_t studio_mins, studio_maxs;
vec3_t mins, maxs, p1, p2;
cl_entity_t *e = RI.currententity;
mstudioseqdesc_t *pseqdesc;
int i;
if( !m_pStudioHeader )
return false;
// check if we have valid mins\maxs
if( !VectorCompare( vec3_origin, RI.currentmodel->mins ))
{
// clipping bounding box
VectorCopy( RI.currentmodel->mins, mins );
VectorCopy( RI.currentmodel->maxs, maxs );
}
else
{
ClearBounds( mins, maxs );
}
// check sequence range
if( e->curstate.sequence < 0 || e->curstate.sequence >= m_pStudioHeader->numseq )
e->curstate.sequence = 0;
pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + e->curstate.sequence;
// add sequence box to the model box
AddPointToBounds( pseqdesc->bbmin, mins, maxs );
AddPointToBounds( pseqdesc->bbmax, mins, maxs );
ClearBounds( studio_mins, studio_maxs );
// compute a full bounding box
for( i = 0; i < 8; i++ )
{
p1[0] = ( i & 1 ) ? mins[0] : maxs[0];
p1[1] = ( i & 2 ) ? mins[1] : maxs[1];
p1[2] = ( i & 4 ) ? mins[2] : maxs[2];
Matrix3x4_VectorTransform( g_studio.rotationmatrix, p1, p2 );
AddPointToBounds( p2, studio_mins, studio_maxs );
if( bbox ) VectorCopy( p2, bbox[i] );
}
/* VK FIXME NOT IMPLEMENTED */
/* if( !bbox && R_CullModel( e, studio_mins, studio_maxs )) */
/* return false; // model culled */
return true; // visible
}
void R_StudioComputeSkinMatrix( const mstudioboneweight_t *boneweights, matrix3x4 *worldtransform, matrix3x4 result )
{
float flWeight0, flWeight1, flWeight2, flWeight3;
int i, numbones = 0;
float flTotal;
for( i = 0; i < MAXSTUDIOBONEWEIGHTS; i++ )
{
if( boneweights->bone[i] != -1 )
numbones++;
}
if( numbones == 4 )
{
vec4_t *boneMat0 = (vec4_t *)worldtransform[boneweights->bone[0]];
vec4_t *boneMat1 = (vec4_t *)worldtransform[boneweights->bone[1]];
vec4_t *boneMat2 = (vec4_t *)worldtransform[boneweights->bone[2]];
vec4_t *boneMat3 = (vec4_t *)worldtransform[boneweights->bone[3]];
flWeight0 = boneweights->weight[0] / 255.0f;
flWeight1 = boneweights->weight[1] / 255.0f;
flWeight2 = boneweights->weight[2] / 255.0f;
flWeight3 = boneweights->weight[3] / 255.0f;
flTotal = flWeight0 + flWeight1 + flWeight2 + flWeight3;
if( flTotal < 1.0f ) flWeight0 += 1.0f - flTotal; // compensate rounding error
result[0][0] = boneMat0[0][0] * flWeight0 + boneMat1[0][0] * flWeight1 + boneMat2[0][0] * flWeight2 + boneMat3[0][0] * flWeight3;
result[0][1] = boneMat0[0][1] * flWeight0 + boneMat1[0][1] * flWeight1 + boneMat2[0][1] * flWeight2 + boneMat3[0][1] * flWeight3;
result[0][2] = boneMat0[0][2] * flWeight0 + boneMat1[0][2] * flWeight1 + boneMat2[0][2] * flWeight2 + boneMat3[0][2] * flWeight3;
result[0][3] = boneMat0[0][3] * flWeight0 + boneMat1[0][3] * flWeight1 + boneMat2[0][3] * flWeight2 + boneMat3[0][3] * flWeight3;
result[1][0] = boneMat0[1][0] * flWeight0 + boneMat1[1][0] * flWeight1 + boneMat2[1][0] * flWeight2 + boneMat3[1][0] * flWeight3;
result[1][1] = boneMat0[1][1] * flWeight0 + boneMat1[1][1] * flWeight1 + boneMat2[1][1] * flWeight2 + boneMat3[1][1] * flWeight3;
result[1][2] = boneMat0[1][2] * flWeight0 + boneMat1[1][2] * flWeight1 + boneMat2[1][2] * flWeight2 + boneMat3[1][2] * flWeight3;
result[1][3] = boneMat0[1][3] * flWeight0 + boneMat1[1][3] * flWeight1 + boneMat2[1][3] * flWeight2 + boneMat3[1][3] * flWeight3;
result[2][0] = boneMat0[2][0] * flWeight0 + boneMat1[2][0] * flWeight1 + boneMat2[2][0] * flWeight2 + boneMat3[2][0] * flWeight3;
result[2][1] = boneMat0[2][1] * flWeight0 + boneMat1[2][1] * flWeight1 + boneMat2[2][1] * flWeight2 + boneMat3[2][1] * flWeight3;
result[2][2] = boneMat0[2][2] * flWeight0 + boneMat1[2][2] * flWeight1 + boneMat2[2][2] * flWeight2 + boneMat3[2][2] * flWeight3;
result[2][3] = boneMat0[2][3] * flWeight0 + boneMat1[2][3] * flWeight1 + boneMat2[2][3] * flWeight2 + boneMat3[2][3] * flWeight3;
}
else if( numbones == 3 )
{
vec4_t *boneMat0 = (vec4_t *)worldtransform[boneweights->bone[0]];
vec4_t *boneMat1 = (vec4_t *)worldtransform[boneweights->bone[1]];
vec4_t *boneMat2 = (vec4_t *)worldtransform[boneweights->bone[2]];
flWeight0 = boneweights->weight[0] / 255.0f;
flWeight1 = boneweights->weight[1] / 255.0f;
flWeight2 = boneweights->weight[2] / 255.0f;
flTotal = flWeight0 + flWeight1 + flWeight2;
if( flTotal < 1.0f ) flWeight0 += 1.0f - flTotal; // compensate rounding error
result[0][0] = boneMat0[0][0] * flWeight0 + boneMat1[0][0] * flWeight1 + boneMat2[0][0] * flWeight2;
result[0][1] = boneMat0[0][1] * flWeight0 + boneMat1[0][1] * flWeight1 + boneMat2[0][1] * flWeight2;
result[0][2] = boneMat0[0][2] * flWeight0 + boneMat1[0][2] * flWeight1 + boneMat2[0][2] * flWeight2;
result[0][3] = boneMat0[0][3] * flWeight0 + boneMat1[0][3] * flWeight1 + boneMat2[0][3] * flWeight2;
result[1][0] = boneMat0[1][0] * flWeight0 + boneMat1[1][0] * flWeight1 + boneMat2[1][0] * flWeight2;
result[1][1] = boneMat0[1][1] * flWeight0 + boneMat1[1][1] * flWeight1 + boneMat2[1][1] * flWeight2;
result[1][2] = boneMat0[1][2] * flWeight0 + boneMat1[1][2] * flWeight1 + boneMat2[1][2] * flWeight2;
result[1][3] = boneMat0[1][3] * flWeight0 + boneMat1[1][3] * flWeight1 + boneMat2[1][3] * flWeight2;
result[2][0] = boneMat0[2][0] * flWeight0 + boneMat1[2][0] * flWeight1 + boneMat2[2][0] * flWeight2;
result[2][1] = boneMat0[2][1] * flWeight0 + boneMat1[2][1] * flWeight1 + boneMat2[2][1] * flWeight2;
result[2][2] = boneMat0[2][2] * flWeight0 + boneMat1[2][2] * flWeight1 + boneMat2[2][2] * flWeight2;
result[2][3] = boneMat0[2][3] * flWeight0 + boneMat1[2][3] * flWeight1 + boneMat2[2][3] * flWeight2;
}
else if( numbones == 2 )
{
vec4_t *boneMat0 = (vec4_t *)worldtransform[boneweights->bone[0]];
vec4_t *boneMat1 = (vec4_t *)worldtransform[boneweights->bone[1]];
flWeight0 = boneweights->weight[0] / 255.0f;
flWeight1 = boneweights->weight[1] / 255.0f;
flTotal = flWeight0 + flWeight1;
if( flTotal < 1.0f ) flWeight0 += 1.0f - flTotal; // compensate rounding error
result[0][0] = boneMat0[0][0] * flWeight0 + boneMat1[0][0] * flWeight1;
result[0][1] = boneMat0[0][1] * flWeight0 + boneMat1[0][1] * flWeight1;
result[0][2] = boneMat0[0][2] * flWeight0 + boneMat1[0][2] * flWeight1;
result[0][3] = boneMat0[0][3] * flWeight0 + boneMat1[0][3] * flWeight1;
result[1][0] = boneMat0[1][0] * flWeight0 + boneMat1[1][0] * flWeight1;
result[1][1] = boneMat0[1][1] * flWeight0 + boneMat1[1][1] * flWeight1;
result[1][2] = boneMat0[1][2] * flWeight0 + boneMat1[1][2] * flWeight1;
result[1][3] = boneMat0[1][3] * flWeight0 + boneMat1[1][3] * flWeight1;
result[2][0] = boneMat0[2][0] * flWeight0 + boneMat1[2][0] * flWeight1;
result[2][1] = boneMat0[2][1] * flWeight0 + boneMat1[2][1] * flWeight1;
result[2][2] = boneMat0[2][2] * flWeight0 + boneMat1[2][2] * flWeight1;
result[2][3] = boneMat0[2][3] * flWeight0 + boneMat1[2][3] * flWeight1;
}
else
{
Matrix3x4_Copy( result, worldtransform[boneweights->bone[0]] );
}
}
/*
===============
pfnGetCurrentEntity
===============
*/
static cl_entity_t *pfnGetCurrentEntity( void )
{
return RI.currententity;
}
/*
===============
pfnPlayerInfo
===============
*/
player_info_t *pfnPlayerInfo( int index )
{
if( !RI.drawWorld )
index = -1;
return gEngine.pfnPlayerInfo( index );
}
/*
===============
pfnMod_ForName
===============
*/
static model_t *pfnMod_ForName( const char *model, int crash )
{
return gEngine.Mod_ForName( model, crash, false );
}
/*
===============
pfnGetPlayerState
===============
*/
entity_state_t *R_StudioGetPlayerState( int index )
{
if( !RI.drawWorld )
return &RI.currententity->curstate;
return gEngine.pfnGetPlayerState( index );
}
/*
===============
pfnGetViewEntity
===============
*/
static cl_entity_t *pfnGetViewEntity( void )
{
return gEngine.GetViewModel();
}
static void pfnGetEngineTimes( int *framecount, double *current, double *old )
{
/* FIXME VK NOT IMPLEMENTED */
/* if( framecount ) *framecount = tr.realframecount; */
if( framecount ) *framecount = 0;
if( current ) *current = gpGlobals->time;
if( old ) *old = gpGlobals->oldtime;
}
static void pfnGetViewInfo( float *origin, float *upv, float *rightv, float *forwardv )
{
if( origin ) VectorCopy( g_camera.vieworg, origin );
if( forwardv ) VectorCopy( g_camera.vforward, forwardv );
if( rightv ) VectorCopy( g_camera.vright, rightv );
if( upv ) VectorCopy( g_camera.vup, upv );
}
static model_t *R_GetChromeSprite( void )
{
return gEngine.GetDefaultSprite( REF_CHROME_SPRITE );
}
static int fixme_studio_models_drawn;
static void pfnGetModelCounters( int **s, int **a )
{
*s = &g_studio.framecount;
/* FIXME VK NOT IMPLEMENTED */
/* *a = &r_stats.c_studio_models_drawn; */
*a = &fixme_studio_models_drawn;
}
static void pfnGetAliasScale( float *x, float *y )
{
if( x ) *x = 1.0f;
if( y ) *y = 1.0f;
}
static float ****pfnStudioGetBoneTransform( void )
{
return (float ****)g_studio.bonestransform;
}
/*
===============
pfnStudioGetLightTransform
===============
*/
static float ****pfnStudioGetLightTransform( void )
{
return (float ****)g_studio.lighttransform;
}
/*
===============
pfnStudioGetAliasTransform
===============
*/
static float ***pfnStudioGetAliasTransform( void )
{
return NULL;
}
/*
===============
pfnStudioGetRotationMatrix
===============
*/
static float ***pfnStudioGetRotationMatrix( void )
{
return (float ***)g_studio.rotationmatrix;
}
/*
====================
StudioPlayerBlend
====================
*/
void R_StudioPlayerBlend( mstudioseqdesc_t *pseqdesc, int *pBlend, float *pPitch )
{
// calc up/down pointing
*pBlend = (*pPitch * 3.0f);
if( *pBlend < pseqdesc->blendstart[0] )
{
*pPitch -= pseqdesc->blendstart[0] / 3.0f;
*pBlend = 0;
}
else if( *pBlend > pseqdesc->blendend[0] )
{
*pPitch -= pseqdesc->blendend[0] / 3.0f;
*pBlend = 255;
}
else
{
if( pseqdesc->blendend[0] - pseqdesc->blendstart[0] < 0.1f ) // catch qc error
*pBlend = 127;
else *pBlend = 255 * (*pBlend - pseqdesc->blendstart[0]) / (pseqdesc->blendend[0] - pseqdesc->blendstart[0]);
*pPitch = 0.0f;
}
}
/*
====================
R_StudioLerpMovement
====================
*/
void R_StudioLerpMovement( cl_entity_t *e, double time, vec3_t origin, vec3_t angles )
{
float f = 1.0f;
// don't do it if the goalstarttime hasn't updated in a while.
// NOTE: Because we need to interpolate multiplayer characters, the interpolation time limit
// was increased to 1.0 s., which is 2x the max lag we are accounting for.
if( g_studio.interpolate && ( time < e->curstate.animtime + 1.0f ) && ( e->curstate.animtime != e->latched.prevanimtime ))
f = ( time - e->curstate.animtime ) / ( e->curstate.animtime - e->latched.prevanimtime );
// Con_Printf( "%4.2f %.2f %.2f\n", f, e->curstate.animtime, g_studio.time );
VectorLerp( e->latched.prevorigin, f, e->curstate.origin, origin );
if( !VectorCompareEpsilon( e->curstate.angles, e->latched.prevangles, ON_EPSILON ))
{
vec4_t q, q1, q2;
AngleQuaternion( e->curstate.angles, q1, false );
AngleQuaternion( e->latched.prevangles, q2, false );
QuaternionSlerp( q2, q1, f, q );
QuaternionAngle( q, angles );
}
else VectorCopy( e->curstate.angles, angles );
}
/*
====================
StudioSetUpTransform
====================
*/
void R_StudioSetUpTransform( cl_entity_t *e )
{
vec3_t origin, angles;
VectorCopy( e->origin, origin );
VectorCopy( e->angles, angles );
// interpolate monsters position (moved into UpdateEntityFields by user request)
if( e->curstate.movetype == MOVETYPE_STEP && !FBitSet( gEngine.EngineGetParm( PARM_FEATURES, 0 ), ENGINE_COMPUTE_STUDIO_LERP ))
{
R_StudioLerpMovement( e, g_studio.time, origin, angles );
}
if( !FBitSet( gEngine.EngineGetParm( PARM_FEATURES, 0 ), ENGINE_COMPENSATE_QUAKE_BUG ))
angles[PITCH] = -angles[PITCH]; // stupid quake bug
// don't rotate clients, only aim
if( e->player ) angles[PITCH] = 0.0f;
Matrix3x4_CreateFromEntity( g_studio.rotationmatrix, angles, origin, 1.0f );
/* FIXME VK NOT IMPLEMENTED */
/* if( tr.fFlipViewModel ) */
/* { */
/* g_studio.rotationmatrix[0][1] = -g_studio.rotationmatrix[0][1]; */
/* g_studio.rotationmatrix[1][1] = -g_studio.rotationmatrix[1][1]; */
/* g_studio.rotationmatrix[2][1] = -g_studio.rotationmatrix[2][1]; */
/* } */
}
/*
====================
StudioEstimateFrame
====================
*/
float R_StudioEstimateFrame( cl_entity_t *e, mstudioseqdesc_t *pseqdesc, double time )
{
double dfdt, f;
if( g_studio.interpolate )
{
if( time < e->curstate.animtime ) dfdt = 0.0;
else dfdt = (time - e->curstate.animtime) * e->curstate.framerate * pseqdesc->fps;
}
else dfdt = 0;
if( pseqdesc->numframes <= 1 ) f = 0.0;
else f = (e->curstate.frame * (pseqdesc->numframes - 1)) / 256.0f;
f += dfdt;
if( pseqdesc->flags & STUDIO_LOOPING )
{
if( pseqdesc->numframes > 1 )
f -= (int)(f / (pseqdesc->numframes - 1)) * (pseqdesc->numframes - 1);
if( f < 0 ) f += (pseqdesc->numframes - 1);
}
else
{
if( f >= pseqdesc->numframes - 1.001 )
f = pseqdesc->numframes - 1.001;
if( f < 0.0 ) f = 0.0;
}
return f;
}
/*
====================
StudioEstimateInterpolant
====================
*/
float R_StudioEstimateInterpolant( cl_entity_t *e )
{
float dadt = 1.0f;
if( g_studio.interpolate && ( e->curstate.animtime >= e->latched.prevanimtime + 0.01f ))
{
dadt = ( g_studio.time - e->curstate.animtime ) / 0.1f;
if( dadt > 2.0f ) dadt = 2.0f;
}
return dadt;
}
/*
====================
CL_GetSequenceDuration
====================
*/
float CL_GetSequenceDuration( cl_entity_t *ent, int sequence )
{
studiohdr_t *pstudiohdr;
mstudioseqdesc_t *pseqdesc;
if( ent->model != NULL && ent->model->type == mod_studio )
{
pstudiohdr = (studiohdr_t *)gEngine.Mod_Extradata( mod_studio, ent->model );
if( pstudiohdr )
{
sequence = bound( 0, sequence, pstudiohdr->numseq - 1 );
pseqdesc = (mstudioseqdesc_t *)((byte *)pstudiohdr + pstudiohdr->seqindex) + sequence;
if( pseqdesc->numframes > 1 && pseqdesc->fps > 0 )
return (float)pseqdesc->numframes / (float)pseqdesc->fps;
}
}
return 0.1f;
}
/*
====================
StudioFxTransform
====================
*/
void R_StudioFxTransform( cl_entity_t *ent, matrix3x4 transform )
{
switch( ent->curstate.renderfx )
{
case kRenderFxDistort:
case kRenderFxHologram:
if( !gEngine.COM_RandomLong( 0, 49 ))
{
int axis = gEngine.COM_RandomLong( 0, 1 );
if( axis == 1 ) axis = 2; // choose between x & z
VectorScale( transform[axis], gEngine.COM_RandomFloat( 1.0f, 1.484f ), transform[axis] );
}
else if( !gEngine.COM_RandomLong( 0, 49 ))
{
float offset;
int axis = gEngine.COM_RandomLong( 0, 1 );
if( axis == 1 ) axis = 2; // choose between x & z
offset = gEngine.COM_RandomFloat( -10.0f, 10.0f );
transform[gEngine.COM_RandomLong( 0, 2 )][3] += offset;
}
break;
case kRenderFxExplode:
{
float scale;
scale = 1.0f + ( g_studio.time - ent->curstate.animtime ) * 10.0f;
if( scale > 2.0f ) scale = 2.0f; // don't blow up more than 200%
transform[0][1] *= scale;
transform[1][1] *= scale;
transform[2][1] *= scale;
}
break;
}
}
/*
====================
StudioCalcBoneAdj
====================
*/
void R_StudioCalcBoneAdj( float dadt, float *adj, const byte *pcontroller1, const byte *pcontroller2, byte mouthopen )
{
mstudiobonecontroller_t *pbonecontroller;
float value = 0.0f;
int i, j;
pbonecontroller = (mstudiobonecontroller_t *)((byte *)m_pStudioHeader + m_pStudioHeader->bonecontrollerindex);
for( j = 0; j < m_pStudioHeader->numbonecontrollers; j++ )
{
i = pbonecontroller[j].index;
if( i == STUDIO_MOUTH )
{
// mouth hardcoded at controller 4
value = (float)mouthopen / 64.0f;
value = bound( 0.0f, value, 1.0f );
value = (1.0f - value) * pbonecontroller[j].start + value * pbonecontroller[j].end;
}
else if( i < 4 )
{
// check for 360% wrapping
if( FBitSet( pbonecontroller[j].type, STUDIO_RLOOP ))
{
if( abs( pcontroller1[i] - pcontroller2[i] ) > 128 )
{
int a = (pcontroller1[i] + 128) % 256;
int b = (pcontroller2[i] + 128) % 256;
value = (( a * dadt ) + ( b * ( 1.0f - dadt )) - 128) * (360.0f / 256.0f) + pbonecontroller[j].start;
}
else
{
value = ((pcontroller1[i] * dadt + (pcontroller2[i]) * (1.0f - dadt))) * (360.0f / 256.0f) + pbonecontroller[j].start;
}
}
else
{
value = (pcontroller1[i] * dadt + pcontroller2[i] * (1.0f - dadt)) / 255.0f;
value = bound( 0.0f, value, 1.0f );
value = (1.0f - value) * pbonecontroller[j].start + value * pbonecontroller[j].end;
}
}
switch( pbonecontroller[j].type & STUDIO_TYPES )
{
case STUDIO_XR:
case STUDIO_YR:
case STUDIO_ZR:
adj[j] = DEG2RAD( value );
break;
case STUDIO_X:
case STUDIO_Y:
case STUDIO_Z:
adj[j] = value;
break;
}
}
}
/*
====================
StudioCalcRotations
====================
*/
void R_StudioCalcRotations( cl_entity_t *e, float pos[][3], vec4_t *q, mstudioseqdesc_t *pseqdesc, mstudioanim_t *panim, float f )
{
int i, frame;
float adj[MAXSTUDIOCONTROLLERS];
float s, dadt;
mstudiobone_t *pbone;
// bah, fix this bug with changing sequences too fast
if( f > pseqdesc->numframes - 1 )
{
f = 0.0f;
}
else if( f < -0.01f )
{
// BUG ( somewhere else ) but this code should validate this data.
// This could cause a crash if the frame # is negative, so we'll go ahead
// and clamp it here
f = -0.01f;
}
frame = (int)f;
dadt = R_StudioEstimateInterpolant( e );
s = (f - frame);
// add in programtic controllers
pbone = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex);
R_StudioCalcBoneAdj( dadt, adj, e->curstate.controller, e->latched.prevcontroller, e->mouth.mouthopen );
for( i = 0; i < m_pStudioHeader->numbones; i++, pbone++, panim++ )
{
R_StudioCalcBoneQuaternion( frame, s, pbone, panim, adj, q[i] );
R_StudioCalcBonePosition( frame, s, pbone, panim, adj, pos[i] );
}
if( pseqdesc->motiontype & STUDIO_X ) pos[pseqdesc->motionbone][0] = 0.0f;
if( pseqdesc->motiontype & STUDIO_Y ) pos[pseqdesc->motionbone][1] = 0.0f;
if( pseqdesc->motiontype & STUDIO_Z ) pos[pseqdesc->motionbone][2] = 0.0f;
}
/*
====================
StudioMergeBones
====================
*/
void R_StudioMergeBones( cl_entity_t *e, model_t *m_pSubModel )
{
int i, j;
mstudiobone_t *pbones;
mstudioseqdesc_t *pseqdesc;
mstudioanim_t *panim;
matrix3x4 bonematrix;
static vec4_t q[MAXSTUDIOBONES];
static float pos[MAXSTUDIOBONES][3];
float f;
if( e->curstate.sequence >= m_pStudioHeader->numseq )
e->curstate.sequence = 0;
pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + e->curstate.sequence;
f = R_StudioEstimateFrame( e, pseqdesc, g_studio.time );
panim = gEngine.R_StudioGetAnim( m_pStudioHeader, m_pSubModel, pseqdesc );
R_StudioCalcRotations( e, pos, q, pseqdesc, panim, f );
pbones = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex);
for( i = 0; i < m_pStudioHeader->numbones; i++ )
{
for( j = 0; j < g_studio.cached_numbones; j++ )
{
if( !Q_stricmp( pbones[i].name, g_studio.cached_bonenames[j] ))
{
Matrix3x4_Copy( g_studio.bonestransform[i], g_studio.cached_bonestransform[j] );
Matrix3x4_Copy( g_studio.lighttransform[i], g_studio.cached_lighttransform[j] );
break;
}
}
if( j >= g_studio.cached_numbones )
{
Matrix3x4_FromOriginQuat( bonematrix, q[i], pos[i] );
if( pbones[i].parent == -1 )
{
Matrix3x4_ConcatTransforms( g_studio.bonestransform[i], g_studio.rotationmatrix, bonematrix );
Matrix3x4_Copy( g_studio.lighttransform[i], g_studio.bonestransform[i] );
// apply client-side effects to the transformation matrix
R_StudioFxTransform( e, g_studio.bonestransform[i] );
}
else
{
Matrix3x4_ConcatTransforms( g_studio.bonestransform[i], g_studio.bonestransform[pbones[i].parent], bonematrix );
Matrix3x4_ConcatTransforms( g_studio.lighttransform[i], g_studio.lighttransform[pbones[i].parent], bonematrix );
}
}
}
}
/*
====================
StudioSetupBones
====================
*/
void R_StudioSetupBones( cl_entity_t *e )
{
float f;
mstudiobone_t *pbones;
mstudioseqdesc_t *pseqdesc;
mstudioanim_t *panim;
matrix3x4 bonematrix;
static vec3_t pos[MAXSTUDIOBONES];
static vec4_t q[MAXSTUDIOBONES];
static vec3_t pos2[MAXSTUDIOBONES];
static vec4_t q2[MAXSTUDIOBONES];
static vec3_t pos3[MAXSTUDIOBONES];
static vec4_t q3[MAXSTUDIOBONES];
static vec3_t pos4[MAXSTUDIOBONES];
static vec4_t q4[MAXSTUDIOBONES];
int i;
if( e->curstate.sequence >= m_pStudioHeader->numseq )
e->curstate.sequence = 0;
pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + e->curstate.sequence;
f = R_StudioEstimateFrame( e, pseqdesc, g_studio.time );
panim = gEngine.R_StudioGetAnim( m_pStudioHeader, RI.currentmodel, pseqdesc );
R_StudioCalcRotations( e, pos, q, pseqdesc, panim, f );
if( pseqdesc->numblends > 1 )
{
float s;
float dadt;
panim += m_pStudioHeader->numbones;
R_StudioCalcRotations( e, pos2, q2, pseqdesc, panim, f );
dadt = R_StudioEstimateInterpolant( e );
s = (e->curstate.blending[0] * dadt + e->latched.prevblending[0] * (1.0f - dadt)) / 255.0f;
R_StudioSlerpBones( m_pStudioHeader->numbones, q, pos, q2, pos2, s );
if( pseqdesc->numblends == 4 )
{
panim += m_pStudioHeader->numbones;
R_StudioCalcRotations( e, pos3, q3, pseqdesc, panim, f );
panim += m_pStudioHeader->numbones;
R_StudioCalcRotations( e, pos4, q4, pseqdesc, panim, f );
s = (e->curstate.blending[0] * dadt + e->latched.prevblending[0] * (1.0f - dadt)) / 255.0f;
R_StudioSlerpBones( m_pStudioHeader->numbones, q3, pos3, q4, pos4, s );
s = (e->curstate.blending[1] * dadt + e->latched.prevblending[1] * (1.0f - dadt)) / 255.0f;
R_StudioSlerpBones( m_pStudioHeader->numbones, q, pos, q3, pos3, s );
}
}
if( g_studio.interpolate && e->latched.sequencetime && ( e->latched.sequencetime + 0.2f > g_studio.time ) && ( e->latched.prevsequence < m_pStudioHeader->numseq ))
{
// blend from last sequence
static vec3_t pos1b[MAXSTUDIOBONES];
static vec4_t q1b[MAXSTUDIOBONES];
float s;
pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + e->latched.prevsequence;
panim = gEngine.R_StudioGetAnim( m_pStudioHeader, RI.currentmodel, pseqdesc );
// clip prevframe
R_StudioCalcRotations( e, pos1b, q1b, pseqdesc, panim, e->latched.prevframe );
if( pseqdesc->numblends > 1 )
{
panim += m_pStudioHeader->numbones;
R_StudioCalcRotations( e, pos2, q2, pseqdesc, panim, e->latched.prevframe );
s = (e->latched.prevseqblending[0]) / 255.0f;
R_StudioSlerpBones( m_pStudioHeader->numbones, q1b, pos1b, q2, pos2, s );
if( pseqdesc->numblends == 4 )
{
panim += m_pStudioHeader->numbones;
R_StudioCalcRotations( e, pos3, q3, pseqdesc, panim, e->latched.prevframe );
panim += m_pStudioHeader->numbones;
R_StudioCalcRotations( e, pos4, q4, pseqdesc, panim, e->latched.prevframe );
s = (e->latched.prevseqblending[0]) / 255.0f;
R_StudioSlerpBones( m_pStudioHeader->numbones, q3, pos3, q4, pos4, s );
s = (e->latched.prevseqblending[1]) / 255.0f;
R_StudioSlerpBones( m_pStudioHeader->numbones, q1b, pos1b, q3, pos3, s );
}
}
s = 1.0f - ( g_studio.time - e->latched.sequencetime ) / 0.2f;
R_StudioSlerpBones( m_pStudioHeader->numbones, q, pos, q1b, pos1b, s );
}
else
{
// store prevframe otherwise
e->latched.prevframe = f;
}
pbones = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex);
// calc gait animation
if( m_pPlayerInfo && m_pPlayerInfo->gaitsequence != 0 )
{
qboolean copy_bones = true;
if( m_pPlayerInfo->gaitsequence >= m_pStudioHeader->numseq )
m_pPlayerInfo->gaitsequence = 0;
pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + m_pPlayerInfo->gaitsequence;
panim = gEngine.R_StudioGetAnim( m_pStudioHeader, RI.currentmodel, pseqdesc );
R_StudioCalcRotations( e, pos2, q2, pseqdesc, panim, m_pPlayerInfo->gaitframe );
for( i = 0; i < m_pStudioHeader->numbones; i++ )
{
if( !Q_strcmp( pbones[i].name, "Bip01 Spine" ))
copy_bones = false;
else if( !Q_strcmp( pbones[pbones[i].parent].name, "Bip01 Pelvis" ))
copy_bones = true;
if( !copy_bones ) continue;
VectorCopy( pos2[i], pos[i] );
Vector4Copy( q2[i], q[i] );
}
}
for( i = 0; i < m_pStudioHeader->numbones; i++ )
{
Matrix3x4_FromOriginQuat( bonematrix, q[i], pos[i] );
if( pbones[i].parent == -1 )
{
Matrix3x4_ConcatTransforms( g_studio.bonestransform[i], g_studio.rotationmatrix, bonematrix );
Matrix3x4_Copy( g_studio.lighttransform[i], g_studio.bonestransform[i] );
// apply client-side effects to the transformation matrix
R_StudioFxTransform( e, g_studio.bonestransform[i] );
}
else
{
Matrix3x4_ConcatTransforms( g_studio.bonestransform[i], g_studio.bonestransform[pbones[i].parent], bonematrix );
Matrix3x4_ConcatTransforms( g_studio.lighttransform[i], g_studio.lighttransform[pbones[i].parent], bonematrix );
}
}
}
/*
====================
StudioSaveBones
====================
*/
static void R_StudioSaveBones( void )
{
mstudiobone_t *pbones;
int i;
pbones = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex);
g_studio.cached_numbones = m_pStudioHeader->numbones;
for( i = 0; i < m_pStudioHeader->numbones; i++ )
{
Matrix3x4_Copy( g_studio.cached_bonestransform[i], g_studio.bonestransform[i] );
Matrix3x4_Copy( g_studio.cached_lighttransform[i], g_studio.lighttransform[i] );
Q_strncpy( g_studio.cached_bonenames[i], pbones[i].name, 32 );
}
}
/*
====================
StudioBuildNormalTable
NOTE: m_pSubModel must be set
====================
*/
void R_StudioBuildNormalTable( void )
{
cl_entity_t *e = RI.currententity;
mstudiomesh_t *pmesh;
int i, j;
ASSERT( m_pSubModel != NULL );
// reset chrome cache
for( i = 0; i < m_pStudioHeader->numbones; i++ )
g_studio.chromeage[i] = 0;
for( i = 0; i < m_pSubModel->numverts; i++ )
g_studio.normaltable[i] = -1;
for( j = 0; j < m_pSubModel->nummesh; j++ )
{
short *ptricmds;
pmesh = (mstudiomesh_t *)((byte *)m_pStudioHeader + m_pSubModel->meshindex) + j;
ptricmds = (short *)((byte *)m_pStudioHeader + pmesh->triindex);
while(( i = *( ptricmds++ )))
{
if( i < 0 ) i = -i;
for( ; i > 0; i--, ptricmds += 4 )
{
if( g_studio.normaltable[ptricmds[0]] < 0 )
g_studio.normaltable[ptricmds[0]] = ptricmds[1];
}
}
}
g_studio.chrome_origin[0] = cos( r_glowshellfreq->value * g_studio.time ) * 4000.0f;
g_studio.chrome_origin[1] = sin( r_glowshellfreq->value * g_studio.time ) * 4000.0f;
g_studio.chrome_origin[2] = cos( r_glowshellfreq->value * g_studio.time * 0.33f ) * 4000.0f;
// FIXME VK: pass this to model color
if( e->curstate.rendercolor.r || e->curstate.rendercolor.g || e->curstate.rendercolor.b )
TriColor4ub( e->curstate.rendercolor.r, e->curstate.rendercolor.g, e->curstate.rendercolor.b, 255 );
else TriColor4ub( 255, 255, 255, 255 );
}
/*
====================
StudioGenerateNormals
NOTE: m_pSubModel must be set
g_studio.verts must be computed
====================
*/
void R_StudioGenerateNormals( void )
{
int v0, v1, v2;
vec3_t e0, e1, norm, tangent;
mstudiomesh_t *pmesh;
int i, j;
ASSERT( m_pSubModel != NULL );
for( i = 0; i < m_pSubModel->numverts; i++ ) {
VectorClear( g_studio.norms[i] );
VectorClear( g_studio.tangents[i] );
}
for( j = 0; j < m_pSubModel->nummesh; j++ )
{
short *ptricmds;
pmesh = (mstudiomesh_t *)((byte *)m_pStudioHeader + m_pSubModel->meshindex) + j;
ptricmds = (short *)((byte *)m_pStudioHeader + pmesh->triindex);
while(( i = *( ptricmds++ )))
{
if( i < 0 )
{
i = -i;
if( i > 2 )
{
// TODO should we get uv (for tangents) for STUDIO_NF_CHROME differently?
const vec2_t uv0 = {ptricmds[2], ptricmds[3]};
v0 = ptricmds[0]; ptricmds += 4;
vec2_t uv1 = {ptricmds[2], ptricmds[3]};
v1 = ptricmds[0]; ptricmds += 4;
for( i -= 2; i > 0; i--, ptricmds += 4 )
{
const vec2_t uv2 = {ptricmds[2], ptricmds[3]};
v2 = ptricmds[0];
VectorSubtract( g_studio.verts[v1], g_studio.verts[v0], e0 );
VectorSubtract( g_studio.verts[v2], g_studio.verts[v0], e1 );
CrossProduct( e1, e0, norm );
VectorAdd( g_studio.norms[v0], norm, g_studio.norms[v0] );
VectorAdd( g_studio.norms[v1], norm, g_studio.norms[v1] );
VectorAdd( g_studio.norms[v2], norm, g_studio.norms[v2] );
computeTangent(tangent, g_studio.verts[v0], g_studio.verts[v1], g_studio.verts[v2], uv0, uv1, uv2);
VectorAdd( g_studio.tangents[v0], tangent, g_studio.tangents[v0] );
VectorAdd( g_studio.tangents[v1], tangent, g_studio.tangents[v1] );
VectorAdd( g_studio.tangents[v2], tangent, g_studio.tangents[v2] );
v1 = v2;
Vector2Copy(uv2, uv1);
}
}
else
{
ptricmds += i;
}
}
else
{
if( i > 2 )
{
qboolean odd = false;
// TODO should we get uv (for tangents) for STUDIO_NF_CHROME differently?
vec2_t uv0 = {ptricmds[2], ptricmds[3]};
v0 = ptricmds[0]; ptricmds += 4;
vec2_t uv1 = {ptricmds[2], ptricmds[3]};
v1 = ptricmds[0]; ptricmds += 4;
for( i -= 2; i > 0; i--, ptricmds += 4 )
{
const vec2_t uv2 = {ptricmds[2], ptricmds[3]};
v2 = ptricmds[0];
VectorSubtract( g_studio.verts[v1], g_studio.verts[v0], e0 );
VectorSubtract( g_studio.verts[v2], g_studio.verts[v0], e1 );
CrossProduct( e1, e0, norm );
VectorAdd( g_studio.norms[v0], norm, g_studio.norms[v0] );
VectorAdd( g_studio.norms[v1], norm, g_studio.norms[v1] );
VectorAdd( g_studio.norms[v2], norm, g_studio.norms[v2] );
computeTangent(tangent, g_studio.verts[v0], g_studio.verts[v1], g_studio.verts[v2], uv0, uv1, uv2);
VectorAdd( g_studio.tangents[v0], tangent, g_studio.tangents[v0] );
VectorAdd( g_studio.tangents[v1], tangent, g_studio.tangents[v1] );
VectorAdd( g_studio.tangents[v2], tangent, g_studio.tangents[v2] );
if( odd ) {
v1 = v2;
Vector2Copy(uv2, uv1);
} else {
v0 = v2;
Vector2Copy(uv2, uv0);
}
odd = !odd;
}
}
else
{
ptricmds += i;
}
}
}
}
for( i = 0; i < m_pSubModel->numverts; i++ ) {
VectorNormalize( g_studio.norms[i] );
VectorNormalize( g_studio.tangents[i] );
}
}
/*
====================
StudioSetupChrome
====================
*/
void R_StudioSetupChrome( float *pchrome, int bone, vec3_t normal )
{
float n;
if( g_studio.chromeage[bone] != g_studio.framecount )
{
// calculate vectors from the viewer to the bone. This roughly adjusts for position
vec3_t chromeupvec; // g_studio.chrome t vector in world reference frame
vec3_t chromerightvec; // g_studio.chrome s vector in world reference frame
vec3_t tmp; // vector pointing at bone in world reference frame
VectorNegate( g_studio.chrome_origin, tmp );
tmp[0] += g_studio.bonestransform[bone][0][3];
tmp[1] += g_studio.bonestransform[bone][1][3];
tmp[2] += g_studio.bonestransform[bone][2][3];
VectorNormalize( tmp );
CrossProduct( tmp, g_camera.vright, chromeupvec );
VectorNormalize( chromeupvec );
CrossProduct( tmp, chromeupvec, chromerightvec );
VectorNormalize( chromerightvec );
Matrix3x4_VectorIRotate( g_studio.bonestransform[bone], chromeupvec, g_studio.chromeup[bone] );
Matrix3x4_VectorIRotate( g_studio.bonestransform[bone], chromerightvec, g_studio.chromeright[bone] );
g_studio.chromeage[bone] = g_studio.framecount;
}
// calc s coord
n = DotProduct( normal, g_studio.chromeright[bone] );
pchrome[0] = (n + 1.0f) * 32.0f;
// calc t coord
n = DotProduct( normal, g_studio.chromeup[bone] );
pchrome[1] = (n + 1.0f) * 32.0f;
}
/*
====================
StudioCalcAttachments
====================
*/
static void R_StudioCalcAttachments( void )
{
mstudioattachment_t *pAtt;
int i;
// calculate attachment points
pAtt = (mstudioattachment_t *)((byte *)m_pStudioHeader + m_pStudioHeader->attachmentindex);
for( i = 0; i < Q_min( MAXSTUDIOATTACHMENTS, m_pStudioHeader->numattachments ); i++ )
{
Matrix3x4_VectorTransform( g_studio.lighttransform[pAtt[i].bone], pAtt[i].org, RI.currententity->attachment[i] );
}
}
/*
===============
pfnStudioSetupModel
===============
*/
static void R_StudioSetupModel( int bodypart, void **ppbodypart, void **ppsubmodel )
{
int index;
if( bodypart > m_pStudioHeader->numbodyparts )
bodypart = 0;
m_pBodyPart = (mstudiobodyparts_t *)((byte *)m_pStudioHeader + m_pStudioHeader->bodypartindex) + bodypart;
index = RI.currententity->curstate.body / m_pBodyPart->base;
index = index % m_pBodyPart->nummodels;
m_pSubModel = (mstudiomodel_t *)((byte *)m_pStudioHeader + m_pBodyPart->modelindex) + index;
if( ppbodypart ) *ppbodypart = m_pBodyPart;
if( ppsubmodel ) *ppsubmodel = m_pSubModel;
}
/*
===============
R_StudioCheckBBox
===============
*/
static int R_StudioCheckBBox( void )
{
if( !RI.currententity || !RI.currentmodel )
return false;
return R_StudioComputeBBox( NULL );
}
/*
===============
R_StudioDynamicLight
===============
*/
void R_StudioDynamicLight( cl_entity_t *ent, alight_t *plight )
{
movevars_t *mv = gEngine.pfnGetMoveVars();
vec3_t lightDir, vecSrc, vecEnd;
vec3_t origin, dist, finalLight;
float add, radius, total;
colorVec light;
uint lnum;
dlight_t *dl;
if( !plight || !ent || !ent->model )
return;
if( !RI.drawWorld /* FIXME VK NOT IMPLEMENTED || r_fullbright->value */ || FBitSet( ent->curstate.effects, EF_FULLBRIGHT ))
{
plight->shadelight = 0;
plight->ambientlight = 192;
VectorSet( plight->plightvec, 0.0f, 0.0f, -1.0f );
VectorSet( plight->color, 1.0f, 1.0f, 1.0f );
return;
}
// determine plane to get lightvalues from: ceil or floor
if( FBitSet( ent->curstate.effects, EF_INVLIGHT ))
VectorSet( lightDir, 0.0f, 0.0f, 1.0f );
else VectorSet( lightDir, 0.0f, 0.0f, -1.0f );
VectorCopy( ent->origin, origin );
VectorSet( vecSrc, origin[0], origin[1], origin[2] - lightDir[2] * 8.0f );
light.r = light.g = light.b = light.a = 0;
if(( mv->skycolor_r + mv->skycolor_g + mv->skycolor_b ) != 0 )
{
msurface_t *psurf = NULL;
pmtrace_t trace;
if( FBitSet( ENGINE_GET_PARM( PARM_FEATURES ), ENGINE_WRITE_LARGE_COORD ))
{
vecEnd[0] = origin[0] - mv->skyvec_x * 65536.0f;
vecEnd[1] = origin[1] - mv->skyvec_y * 65536.0f;
vecEnd[2] = origin[2] - mv->skyvec_z * 65536.0f;
}
else
{
vecEnd[0] = origin[0] - mv->skyvec_x * 8192.0f;
vecEnd[1] = origin[1] - mv->skyvec_y * 8192.0f;
vecEnd[2] = origin[2] - mv->skyvec_z * 8192.0f;
}
trace = gEngine.CL_TraceLine( vecSrc, vecEnd, PM_WORLD_ONLY );
if( trace.ent > 0 ) psurf = gEngine.EV_TraceSurface( trace.ent, vecSrc, vecEnd );
else psurf = gEngine.EV_TraceSurface( 0, vecSrc, vecEnd );
if( FBitSet( ent->model->flags, STUDIO_FORCE_SKYLIGHT ) || ( psurf && FBitSet( psurf->flags, SURF_DRAWSKY )))
{
VectorSet( lightDir, mv->skyvec_x, mv->skyvec_y, mv->skyvec_z );
light.r = gEngine.LightToTexGamma( bound( 0, mv->skycolor_r, 255 ));
light.g = gEngine.LightToTexGamma( bound( 0, mv->skycolor_g, 255 ));
light.b = gEngine.LightToTexGamma( bound( 0, mv->skycolor_b, 255 ));
}
}
if(( light.r + light.g + light.b ) < 16 ) // TESTTEST
{
colorVec gcolor;
float grad[4];
VectorScale( lightDir, 2048.0f, vecEnd );
VectorAdd( vecEnd, vecSrc, vecEnd );
light = R_LightVec( vecSrc, vecEnd, g_studio.lightspot, g_studio.lightvec );
if( VectorIsNull( g_studio.lightvec ))
{
vecSrc[0] -= 16.0f;
vecSrc[1] -= 16.0f;
vecEnd[0] -= 16.0f;
vecEnd[1] -= 16.0f;
gcolor = R_LightVec( vecSrc, vecEnd, NULL, NULL );
grad[0] = ( gcolor.r + gcolor.g + gcolor.b ) / 768.0f;
vecSrc[0] += 32.0f;
vecEnd[0] += 32.0f;
gcolor = R_LightVec( vecSrc, vecEnd, NULL, NULL );
grad[1] = ( gcolor.r + gcolor.g + gcolor.b ) / 768.0f;
vecSrc[1] += 32.0f;
vecEnd[1] += 32.0f;
gcolor = R_LightVec( vecSrc, vecEnd, NULL, NULL );
grad[2] = ( gcolor.r + gcolor.g + gcolor.b ) / 768.0f;
vecSrc[0] -= 32.0f;
vecEnd[0] -= 32.0f;
gcolor = R_LightVec( vecSrc, vecEnd, NULL, NULL );
grad[3] = ( gcolor.r + gcolor.g + gcolor.b ) / 768.0f;
lightDir[0] = grad[0] - grad[1] - grad[2] + grad[3];
lightDir[1] = grad[1] + grad[0] - grad[2] - grad[3];
VectorNormalize( lightDir );
}
else
{
VectorCopy( g_studio.lightvec, lightDir );
}
}
VectorSet( finalLight, light.r, light.g, light.b );
ent->cvFloorColor = light;
total = Q_max( Q_max( light.r, light.g ), light.b );
if( total == 0.0f ) total = 1.0f;
// scale lightdir by light intentsity
VectorScale( lightDir, total, lightDir );
for( lnum = 0; lnum < MAX_DLIGHTS; lnum++ )
{
dl = gEngine.GetDynamicLight( lnum );
if( dl->die < g_studio.time) // VK FIXME || !r_dynamic->value )
continue;
VectorSubtract( ent->origin, dl->origin, dist );
radius = VectorLength( dist );
add = (dl->radius - radius);
if( add > 0.0f )
{
total += add;
if( radius > 1.0f )
VectorScale( dist, ( add / radius ), dist );
else VectorScale( dist, add, dist );
VectorAdd( lightDir, dist, lightDir );
finalLight[0] += gEngine.LightToTexGamma( dl->color.r ) * ( add / 256.0f ) * 2.0f;
finalLight[1] += gEngine.LightToTexGamma( dl->color.g ) * ( add / 256.0f ) * 2.0f;
finalLight[2] += gEngine.LightToTexGamma( dl->color.b ) * ( add / 256.0f ) * 2.0f;
}
}
if( FBitSet( ent->model->flags, STUDIO_AMBIENT_LIGHT ))
add = 0.6f;
else add = 0.9f;
VectorScale( lightDir, add, lightDir );
plight->shadelight = VectorLength( lightDir );
plight->ambientlight = total - plight->shadelight;
total = Q_max( Q_max( finalLight[0], finalLight[1] ), finalLight[2] );
if( total > 0.0f )
{
plight->color[0] = finalLight[0] * ( 1.0f / total );
plight->color[1] = finalLight[1] * ( 1.0f / total );
plight->color[2] = finalLight[2] * ( 1.0f / total );
}
else VectorSet( plight->color, 1.0f, 1.0f, 1.0f );
if( plight->ambientlight > 128 )
plight->ambientlight = 128;
if( plight->ambientlight + plight->shadelight > 255 )
plight->shadelight = 255 - plight->ambientlight;
VectorNormalize2( lightDir, plight->plightvec );
}
/*
===============
pfnStudioEntityLight
===============
*/
void R_StudioEntityLight( alight_t *lightinfo )
{
int lnum, i, j, k;
float minstrength, dist2, f, r2;
float lstrength[MAX_LOCALLIGHTS];
cl_entity_t *ent = RI.currententity;
vec3_t mid, origin, pos;
dlight_t *el;
g_studio.numlocallights = 0;
if( !ent ) // VK FIXME || !r_dynamic->value )
return;
for( i = 0; i < MAX_LOCALLIGHTS; i++ )
lstrength[i] = 0;
Matrix3x4_OriginFromMatrix( g_studio.rotationmatrix, origin );
dist2 = 1000000.0f;
k = 0;
for( lnum = 0; lnum < MAX_ELIGHTS; lnum++ )
{
el = gEngine.GetEntityLight( lnum );
if( el->die < g_studio.time || el->radius <= 0.0f )
continue;
if(( el->key & 0xFFF ) == ent->index )
{
int att = (el->key >> 12) & 0xF;
if( att ) VectorCopy( ent->attachment[att], el->origin );
else VectorCopy( ent->origin, el->origin );
}
VectorCopy( el->origin, pos );
VectorSubtract( origin, el->origin, mid );
f = DotProduct( mid, mid );
r2 = el->radius * el->radius;
if( f > r2 ) minstrength = r2 / f;
else minstrength = 1.0f;
if( minstrength > 0.05f )
{
if( g_studio.numlocallights >= MAX_LOCALLIGHTS )
{
for( j = 0, k = -1; j < g_studio.numlocallights; j++ )
{
if( lstrength[j] < dist2 && lstrength[j] < minstrength )
{
dist2 = lstrength[j];
k = j;
}
}
}
else k = g_studio.numlocallights;
if( k != -1 )
{
g_studio.locallightcolor[k].r = gEngine.LightToTexGamma( el->color.r );
g_studio.locallightcolor[k].g = gEngine.LightToTexGamma( el->color.g );
g_studio.locallightcolor[k].b = gEngine.LightToTexGamma( el->color.b );
g_studio.locallightR2[k] = r2;
g_studio.locallight[k] = el;
lstrength[k] = minstrength;
if( k >= g_studio.numlocallights )
g_studio.numlocallights = k + 1;
}
}
}
}
/*
===============
R_StudioSetupLighting
===============
*/
void R_StudioSetupLighting( alight_t *plight )
{
float scale = 1.0f;
int i;
if( !m_pStudioHeader || !plight )
return;
if( RI.currententity != NULL )
scale = RI.currententity->curstate.scale;
g_studio.ambientlight = plight->ambientlight;
g_studio.shadelight = plight->shadelight;
VectorCopy( plight->plightvec, g_studio.lightvec );
for( i = 0; i < m_pStudioHeader->numbones; i++ )
{
Matrix3x4_VectorIRotate( g_studio.lighttransform[i], plight->plightvec, g_studio.blightvec[i] );
if( scale > 1.0f ) VectorNormalize( g_studio.blightvec[i] ); // in case model may be scaled
}
VectorCopy( plight->color, g_studio.lightcolor );
}
/*
===============
R_StudioLighting
===============
*/
void R_StudioLighting( float *lv, int bone, int flags, vec3_t normal )
{
float illum;
if( FBitSet( flags, STUDIO_NF_FULLBRIGHT ))
{
*lv = 1.0f;
return;
}
illum = g_studio.ambientlight;
if( FBitSet( flags, STUDIO_NF_FLATSHADE ))
{
illum += g_studio.shadelight * 0.8f;
}
else
{
float r, lightcos;
if( bone != -1 ) lightcos = DotProduct( normal, g_studio.blightvec[bone] );
else lightcos = DotProduct( normal, g_studio.lightvec ); // -1 colinear, 1 opposite
if( lightcos > 1.0f ) lightcos = 1.0f;
illum += g_studio.shadelight;
// TODO VK what
#define SHADE_LAMBERT 1.495f
r = SHADE_LAMBERT;
// do modified hemispherical lighting
if( r <= 1.0f )
{
r += 1.0f;
lightcos = (( r - 1.0f ) - lightcos) / r;
if( lightcos > 0.0f )
illum += g_studio.shadelight * lightcos;
}
else
{
lightcos = (lightcos + ( r - 1.0f )) / r;
if( lightcos > 0.0f )
illum -= g_studio.shadelight * lightcos;
}
illum = Q_max( illum, 0.0f );
}
illum = Q_min( illum, 255.0f );
*lv = illum * (1.0f / 255.0f);
}
static void R_LightLambert( vec4_t light[MAX_LOCALLIGHTS], const vec3_t normal, const vec3_t color, byte *out )
{
vec3_t finalLight;
int i;
if( !g_studio.numlocallights )
{
VectorScale( color, 255.0f, out );
return;
}
VectorCopy( color, finalLight );
for( i = 0; i < g_studio.numlocallights; i++ )
{
float r;
r = DotProduct( normal, light[i] );
#if 0 // VKTODO
if( likely( !tr.fFlipViewModel ))
r = -r;
#endif
if( r > 0.0f )
{
vec3_t localLight;
float temp;
if( light[i][3] == 0.0f )
{
float r2 = DotProduct( light[i], light[i] );
if( r2 > 0.0f )
light[i][3] = g_studio.locallightR2[i] / ( r2 * sqrt( r2 ));
else light[i][3] = 0.0001f;
}
temp = Q_min( r * light[i][3] / 255.0f, 1.0f );
localLight[0] = (float)g_studio.locallightcolor[i].r * temp;
localLight[1] = (float)g_studio.locallightcolor[i].g * temp;
localLight[2] = (float)g_studio.locallightcolor[i].b * temp;
VectorAdd( finalLight, localLight, finalLight );
}
}
VectorScale( finalLight, 255.0f, finalLight );
out[0] = Q_min( (int)( finalLight[0] ), 255 );
out[1] = Q_min( (int)( finalLight[1] ), 255 );
out[2] = Q_min( (int)( finalLight[2] ), 255 );
}
static void R_StudioSetColorArray(const short *ptricmds, const vec3_t *pstudionorms, byte *color )
{
float *lv = (float *)g_studio.lightvalues[ptricmds[1]];
color[3] = g_studio.blend * 255;
R_LightLambert( g_studio.lightpos[ptricmds[0]], pstudionorms[ptricmds[1]], lv, color );
}
static void R_StudioSetColorBegin(const short *ptricmds, const vec3_t *pstudionorms, rgba_t out_color )
{
R_StudioSetColorArray( ptricmds, pstudionorms, out_color );
}
void R_LightStrength( int bone, const vec3_t localpos, vec4_t light[MAX_LOCALLIGHTS] )
{
int i;
if( g_studio.lightage[bone] != g_studio.framecount )
{
for( i = 0; i < g_studio.numlocallights; i++ )
{
dlight_t *el = g_studio.locallight[i];
Matrix3x4_VectorITransform( g_studio.lighttransform[bone], el->origin, g_studio.lightbonepos[bone][i] );
}
g_studio.lightage[bone] = g_studio.framecount;
}
for( i = 0; i < g_studio.numlocallights; i++ )
{
VectorSubtract( localpos, g_studio.lightbonepos[bone][i], light[i] );
light[i][3] = 0.0f;
}
}
static int R_StudioSetupSkin( studiohdr_t *ptexturehdr, int index )
{
mstudiotexture_t *ptexture = NULL;
if( FBitSet( g_nForceFaceFlags, STUDIO_NF_CHROME ))
return -1;
if( ptexturehdr == NULL )
return -1;
// NOTE: user may ignore to call StudioRemapColors and remap_info will be unavailable
if( m_fDoRemap ) ptexture = gEngine.CL_GetRemapInfoForEntity( RI.currententity )->ptexture;
if( !ptexture ) ptexture = (mstudiotexture_t *)((byte *)ptexturehdr + ptexturehdr->textureindex); // fallback
/* FIXME VK
if( r_lightmap->value && !r_fullbright->value )
GL_Bind( XASH_TEXTURE0, tr.whiteTexture );
else GL_Bind( XASH_TEXTURE0, ptexture[index].index );
*/
return ptexture[index].index;
}
/*
===============
R_StudioGetTexture
Doesn't changes studio global state at all
===============
*/
mstudiotexture_t *R_StudioGetTexture( cl_entity_t *e )
{
mstudiotexture_t *ptexture;
studiohdr_t *phdr, *thdr;
if(( phdr = gEngine.Mod_Extradata( mod_studio, e->model )) == NULL )
return NULL;
thdr = m_pStudioHeader;
if( !thdr ) return NULL;
if( m_fDoRemap ) ptexture = gEngine.CL_GetRemapInfoForEntity( e )->ptexture;
else ptexture = (mstudiotexture_t *)((byte *)thdr + thdr->textureindex);
return ptexture;
}
// TODO where does this need to be declared and defined? currently it's in vk_scene.c
extern int CL_FxBlend( cl_entity_t *e );
void R_StudioSetRenderamt( int iRenderamt )
{
if( !RI.currententity ) return;
RI.currententity->curstate.renderamt = iRenderamt;
g_studio.blend = CL_FxBlend( RI.currententity ) / 255.0f;
}
/*
===============
R_StudioSetCullState
sets true for enable backculling (for left-hand viewmodel)
===============
*/
void R_StudioSetCullState( int iCull )
{
g_iBackFaceCull = iCull;
}
/*
===============
R_StudioRenderShadow
just a prefab for render shadow
===============
*/
void R_StudioRenderShadow( int iSprite, float *p1, float *p2, float *p3, float *p4 )
{
PRINT_NOT_IMPLEMENTED();
/*
if( !p1 || !p2 || !p3 || !p4 )
return;
if( TriSpriteTexture( gEngine.pfnGetModelByIndex( iSprite ), 0 ))
{
TriRenderMode( kRenderTransAlpha );
TriColor4f( 0.0f, 0.0f, 0.0f, 1.0f );
pglBegin( GL_QUADS );
pglTexCoord2f( 0.0f, 0.0f );
pglVertex3fv( p1 );
pglTexCoord2f( 0.0f, 1.0f );
pglVertex3fv( p2 );
pglTexCoord2f( 1.0f, 1.0f );
pglVertex3fv( p3 );
pglTexCoord2f( 1.0f, 0.0f );
pglVertex3fv( p4 );
pglEnd();
TriRenderMode( kRenderNormal );
}
*/
}
/*
===============
R_StudioMeshCompare
Sorting opaque entities by model type
===============
*/
static int R_StudioMeshCompare( const void *a, const void *b )
{
if( FBitSet( ((const sortedmesh_t*)a)->flags, STUDIO_NF_ADDITIVE ))
return 1;
if( FBitSet( ((const sortedmesh_t*)a)->flags, STUDIO_NF_MASKED ))
return -1;
return 0;
}
static void addVerticesIndicesCounts( const short *ptricmds, int *num_vertices, int *num_indices ) {
int i;
while(( i = *( ptricmds++ ))) {
enum { FAN, STRIP } mode = i < 0 ? FAN : STRIP;
const int vertices = mode == FAN ? -i : i;
ASSERT(vertices > 2);
*num_vertices += vertices;
*num_indices += (vertices-2) * 3;
ptricmds += 4 * vertices;
}
}
typedef struct {
const short *ptricmds;
const vec3_t *pstudionorms;
float s, t;
int texture;
uint32_t vertices_offset;
uint32_t indices_offset;
vk_vertex_t *dst_vertices;
uint16_t *dst_indices;
vk_render_geometry_t *out_geometry;
int *out_vertices_count;
int *out_indices_count;
} build_submodel_mesh_t;
static void buildSubmodelMeshGeometry( build_submodel_mesh_t args ) {
float *lv;
int i;
uint32_t vertex_offset = 0, index_offset = 0;
int num_vertices = 0, num_indices = 0;
addVerticesIndicesCounts(args.ptricmds, &num_vertices, &num_indices);
ASSERT(num_vertices > 0);
ASSERT(num_indices > 0);
vk_vertex_t *dst_vtx = args.dst_vertices;
uint16_t *dst_idx = args.dst_indices;
// Restore ptricmds and upload vertices
while(( i = *( args.ptricmds++ )))
{
enum { FAN, STRIP } mode = i < 0 ? FAN : STRIP;
const int vertices = mode == FAN ? -i : i;
uint32_t elements = 0;
for(int j = 0; j < vertices ; ++j, ++dst_vtx, args.ptricmds += 4 )
{
*dst_vtx = (vk_vertex_t){0};
VectorCopy(g_studio.verts[args.ptricmds[0]], dst_vtx->pos);
VectorCopy(g_studio.prev_verts[args.ptricmds[0]], dst_vtx->prev_pos);
VectorCopy(g_studio.norms[args.ptricmds[0]], dst_vtx->normal);
VectorCopy(g_studio.tangents[args.ptricmds[0]], dst_vtx->tangent);
dst_vtx->lm_tc[0] = dst_vtx->lm_tc[1] = 0.f;
if (FBitSet( g_nFaceFlags, STUDIO_NF_CHROME ))
{
// FIXME also support glow mode
const int idx = args.ptricmds[1];
dst_vtx->gl_tc[0] = g_studio.chrome[idx][0] * args.s;
dst_vtx->gl_tc[1] = g_studio.chrome[idx][1] * args.t;
} else {
dst_vtx->gl_tc[0] = args.ptricmds[2] * args.s;
dst_vtx->gl_tc[1] = args.ptricmds[3] * args.t;
}
R_StudioSetColorBegin( args.ptricmds, args.pstudionorms, dst_vtx->color );
if (j > 1) {
switch (mode) {
case FAN:
dst_idx[elements++] = vertex_offset + 0;
dst_idx[elements++] = vertex_offset + j - 1;
dst_idx[elements++] = vertex_offset + j;
break;
case STRIP:
// flip triangles between clockwise and counter clockwise
if( j & 1 )
{
// draw triangle [n-1 n-2 n]
dst_idx[elements++] = vertex_offset + j - 1;
dst_idx[elements++] = vertex_offset + j - 2;
dst_idx[elements++] = vertex_offset + j;
}
else
{
// draw triangle [n-2 n-1 n]
dst_idx[elements++] = vertex_offset + j - 2;
dst_idx[elements++] = vertex_offset + j - 1;
dst_idx[elements++] = vertex_offset + j;
}
break;
}
}
}
ASSERT(elements == (vertices-2)*3);
index_offset += elements;
vertex_offset += vertices;
dst_idx += elements;
}
ASSERT(vertex_offset < UINT16_MAX);
ASSERT(index_offset == num_indices);
ASSERT(vertex_offset == num_vertices);
*args.out_geometry = (vk_render_geometry_t){
//.lightmap = tglob.whiteTexture,
.texture = args.texture,
.material = FBitSet( g_nFaceFlags, STUDIO_NF_CHROME ) ? kXVkMaterialChrome : kXVkMaterialRegular,
.vertex_offset = args.vertices_offset,
.max_vertex = num_vertices,
.index_offset = args.indices_offset,
.element_count = num_indices,
.emissive = {0, 0, 0},
};
*args.out_vertices_count += num_vertices;
*args.out_indices_count += num_indices;
}
/* FIXME VK
static void R_StudioDrawFloatMesh( short *ptricmds, vec3_t *pstudionorms )
{
float *lv;
int i;
while(( i = *( ptricmds++ )))
{
if( i < 0 )
{
pglBegin( GL_TRIANGLE_FAN );
i = -i;
}
else pglBegin( GL_TRIANGLE_STRIP );
for( ; i > 0; i--, ptricmds += 4 )
{
R_StudioSetColorBegin( ptricmds, pstudionorms );
pglTexCoord2f( HalfToFloat( ptricmds[2] ), HalfToFloat( ptricmds[3] ));
pglVertex3fv( g_studio.verts[ptricmds[0]] );
}
pglEnd();
}
}
*/
/* FIXME VK
_inline void R_StudioDrawChromeMesh( short *ptricmds, vec3_t *pstudionorms, float s, float t, float scale )
{
float *lv, *av;
int i, idx;
qboolean glowShell = (scale > 0.0f) ? true : false;
vec3_t vert;
while(( i = *( ptricmds++ )))
{
if( i < 0 )
{
pglBegin( GL_TRIANGLE_FAN );
i = -i;
}
else pglBegin( GL_TRIANGLE_STRIP );
for( ; i > 0; i--, ptricmds += 4 )
{
if( glowShell )
{
color24 *clr = &RI.currententity->curstate.rendercolor;
idx = g_studio.normaltable[ptricmds[0]];
av = g_studio.verts[ptricmds[0]];
lv = g_studio.norms[ptricmds[0]];
VectorMA( av, scale, lv, vert );
pglColor4ub( clr->r, clr->g, clr->b, 255 );
pglTexCoord2f( g_studio.chrome[idx][0] * s, g_studio.chrome[idx][1] * t );
pglVertex3fv( vert );
}
else
{
idx = ptricmds[1];
lv = (float *)g_studio.lightvalues[ptricmds[1]];
R_StudioSetColorBegin( ptricmds, pstudionorms );
pglTexCoord2f( g_studio.chrome[idx][0] * s, g_studio.chrome[idx][1] * t );
pglVertex3fv( g_studio.verts[ptricmds[0]] );
}
}
pglEnd();
}
}
*/
static vk_render_type_e studioRenderModeToRenderType( int render_mode ) {
switch (render_mode) {
case kRenderNormal: return kVkRenderTypeSolid;
case kRenderTransColor: return kVkRenderType_A_1mA_RW;
case kRenderTransTexture: return kVkRenderType_A_1mA_RW;
case kRenderGlow: return kVkRenderType_A_1mA_RW;
case kRenderTransAlpha: return kVkRenderType_A_1mA_RW;
case kRenderTransAdd: return kVkRenderType_1_1_R;
default: ASSERT(!"Unxpected render_mode");
}
return kVkRenderTypeSolid;
}
static const r_studio_model_cache_entry_t *buildCachedStudioSubModel( const mstudiomodel_t *submodel ) {
if (g_studio_cache.entries_count == MAX_CACHED_STUDIO_MODELS) {
PRINT_NOT_IMPLEMENTED_ARGS("Studio submodel cache overflow at %d", MAX_CACHED_STUDIO_MODELS);
return NULL;
}
const mstudiomesh_t *const pmesh = (mstudiomesh_t *)((byte *)m_pStudioHeader + m_pSubModel->meshindex);
int vertex_count = 0, index_count = 0;
for( int i = 0; i < submodel->nummesh; i++ ) {
const short* const ptricmds = (short *)((byte *)m_pStudioHeader + pmesh[i].triindex);
addVerticesIndicesCounts(ptricmds, &vertex_count, &index_count);
}
ASSERT(vertex_count > 0);
ASSERT(index_count > 0);
const r_geometry_range_t geometry = R_GeometryRangeAlloc(vertex_count, index_count);
if (geometry.block_handle.size == 0) {
gEngine.Con_Printf(S_ERROR "Unable to allocate %d vertices %d indices for submodel %s",
vertex_count, index_count, submodel->name);
return NULL;
}
const r_geometry_range_lock_t geom_lock = R_GeometryRangeLock(&geometry);
if (!geom_lock.vertices) {
gEngine.Con_Printf(S_ERROR "Unable to lock staging for %d vertices %d indices for submodel %s",
vertex_count, index_count, submodel->name);
R_GeometryRangeFree(&geometry);
return NULL;
}
vk_render_geometry_t *const geometries = Mem_Malloc(vk_core.pool, submodel->nummesh * sizeof(*geometries));
ASSERT(geometries);
g_studio.numverts = g_studio.numelems = 0;
// safety bounding the skinnum
const int m_skinnum = bound( 0, RI.currententity->curstate.skin, ( m_pStudioHeader->numskinfamilies - 1 ));
const mstudiotexture_t *const ptexture = (const mstudiotexture_t *)((const byte *)m_pStudioHeader + m_pStudioHeader->textureindex);
const byte *const pvertbone = ((const byte *)m_pStudioHeader + m_pSubModel->vertinfoindex);
const byte *pnormbone = ((const byte *)m_pStudioHeader + m_pSubModel->norminfoindex);
const vec3_t *pstudioverts = (const vec3_t *)((const byte *)m_pStudioHeader + m_pSubModel->vertindex);
const vec3_t *pstudionorms = (const vec3_t *)((const byte *)m_pStudioHeader + m_pSubModel->normindex);
const short *pskinref = (short *)((byte *)m_pStudioHeader + m_pStudioHeader->skinindex);
if( m_skinnum != 0 ) pskinref += (m_skinnum * m_pStudioHeader->numskinref);
// Compute inverse entity matrix, as we need vertices to be in local model space instead of global world space.
// Ideally, we'd just avoid multiplying vertices by entity matrix in R_StudioMergeBones and friends. But unfortunately games themselves seem to be premultiplying bone matrices by entity matrix, so we need to manually undo this multiplication here.
matrix4x4 rotationmatrix = {0}, rotationmatrix_inv = {0};
Matrix3x4_Copy(rotationmatrix, g_studio.rotationmatrix);
Matrix4x4_Invert_Simple(rotationmatrix_inv, rotationmatrix);
if( FBitSet( m_pStudioHeader->flags, STUDIO_HAS_BONEWEIGHTS ) && m_pSubModel->blendvertinfoindex != 0 && m_pSubModel->blendnorminfoindex != 0 )
{
const mstudioboneweight_t *const pvertweight = (mstudioboneweight_t *)((byte *)m_pStudioHeader + m_pSubModel->blendvertinfoindex);
const mstudioboneweight_t *const pnormweight = (mstudioboneweight_t *)((byte *)m_pStudioHeader + m_pSubModel->blendnorminfoindex);
matrix3x4 skinMat;
for( int i = 0; i < m_pSubModel->numverts; i++ )
{
R_StudioComputeSkinMatrix( &pvertweight[i], g_studio.worldtransform, skinMat );
vec3_t v;
Matrix3x4_VectorTransform( skinMat, pstudioverts[i], v);
Matrix3x4_VectorTransform( rotationmatrix_inv, v, g_studio.verts[i] );
R_LightStrength( pvertbone[i], pstudioverts[i], g_studio.lightpos[i] );
}
R_PrevFrame_SaveCurrentBoneTransforms( RI.currententity->index, g_studio.worldtransform, rotationmatrix_inv);
matrix3x4* prev_bones_transforms = R_PrevFrame_BoneTransforms( RI.currententity->index );
for( int i = 0; i < m_pSubModel->numverts; i++ )
{
R_StudioComputeSkinMatrix( &pvertweight[i], prev_bones_transforms, skinMat );
Matrix3x4_VectorTransform( skinMat, pstudioverts[i], g_studio.prev_verts[i] );
}
for( int i = 0; i < m_pSubModel->numnorms; i++ )
{
R_StudioComputeSkinMatrix( &pnormweight[i], g_studio.worldtransform, skinMat );
vec3_t v;
Matrix3x4_VectorRotate( skinMat, pstudionorms[i], v);
Matrix3x4_VectorRotate( rotationmatrix_inv, v, g_studio.norms[i] );
}
}
else
{
R_PrevFrame_SaveCurrentBoneTransforms( RI.currententity->index, g_studio.bonestransform, rotationmatrix_inv );
matrix3x4* prev_bones_transforms = R_PrevFrame_BoneTransforms( RI.currententity->index );
for( int i = 0; i < m_pSubModel->numverts; i++ )
{
vec3_t v;
Matrix3x4_VectorTransform( g_studio.bonestransform[pvertbone[i]], pstudioverts[i], v);
Matrix3x4_VectorTransform( rotationmatrix_inv, v, g_studio.verts[i] );
R_LightStrength( pvertbone[i], pstudioverts[i], g_studio.lightpos[i] );
Matrix3x4_VectorTransform( prev_bones_transforms[pvertbone[i]], pstudioverts[i], g_studio.prev_verts[i] );
}
}
// generate shared normals for properly scaling glowing shell
float shellscale = 0.0f;
if( RI.currententity->curstate.renderfx == kRenderFxGlowShell )
{
float factor = (1.0f / 128.0f);
shellscale = Q_max( factor, RI.currententity->curstate.renderamt * factor );
R_StudioBuildNormalTable();
}
R_StudioGenerateNormals();
qboolean need_sort = false;
for( int j = 0, k = 0; j < m_pSubModel->nummesh; j++ )
{
g_nFaceFlags = ptexture[pskinref[pmesh[j].skinref]].flags | g_nForceFaceFlags;
// fill in sortedmesh info
g_studio.meshes[j].flags = g_nFaceFlags;
g_studio.meshes[j].mesh = &pmesh[j];
if( FBitSet( g_nFaceFlags, STUDIO_NF_MASKED|STUDIO_NF_ADDITIVE ))
need_sort = true;
if( RI.currententity->curstate.rendermode == kRenderTransAdd )
{
for( int i = 0; i < pmesh[j].numnorms; i++, k++, pstudionorms++, pnormbone++ )
{
// FIXME VK
const struct { float blend; } tr = {1.f};
if( FBitSet( g_nFaceFlags, STUDIO_NF_CHROME ))
R_StudioSetupChrome( g_studio.chrome[k], *pnormbone, (float *)pstudionorms );
VectorSet( g_studio.lightvalues[k], g_studio.blend, g_studio.blend, g_studio.blend );
}
}
else
{
for( int i = 0; i < pmesh[j].numnorms; i++, k++, pstudionorms++, pnormbone++ ) {
float lv_tmp;
if( FBitSet( m_pStudioHeader->flags, STUDIO_HAS_BONEWEIGHTS ))
R_StudioLighting( &lv_tmp, -1, g_nFaceFlags, g_studio.norms[k] );
else R_StudioLighting( &lv_tmp, *pnormbone, g_nFaceFlags, (float *)pstudionorms );
if( FBitSet( g_nFaceFlags, STUDIO_NF_CHROME ))
R_StudioSetupChrome( g_studio.chrome[k], *pnormbone, (float *)pstudionorms );
VectorScale( g_studio.lightcolor, lv_tmp, g_studio.lightvalues[k] );
}
}
}
/* FIXME VK
* this might potentially break blas update topology
if( need_sort )
{
// resort opaque and translucent meshes draw order
qsort( g_studio.meshes, m_pSubModel->nummesh, sizeof( sortedmesh_t ), R_StudioMeshCompare );
}
*/
// NOTE: rewind normals at start
pstudionorms = (const vec3_t *)((const byte *)m_pStudioHeader + m_pSubModel->normindex);
int vertices_offset = 0, indices_offset = 0;
for( int j = 0; j < m_pSubModel->nummesh; j++ ) {
const mstudiomesh_t *const pmesh = g_studio.meshes[j].mesh;
const short *const ptricmds = (short *)((byte *)m_pStudioHeader + pmesh->triindex);
g_nFaceFlags = ptexture[pskinref[pmesh->skinref]].flags | g_nForceFaceFlags;
const float s = 1.0f / (float)ptexture[pskinref[pmesh->skinref]].width;
const float t = 1.0f / (float)ptexture[pskinref[pmesh->skinref]].height;
/* FIXME VK
const float oldblend = g_studio.blend;
if( FBitSet( g_nFaceFlags, STUDIO_NF_MASKED ))
{
pglEnable( GL_ALPHA_TEST );
pglAlphaFunc( GL_GREATER, 0.5f );
pglDepthMask( GL_TRUE );
if( R_ModelOpaque( RI.currententity->curstate.rendermode ))
g_studio.blend = 1.0f;
}
else if( FBitSet( g_nFaceFlags, STUDIO_NF_ADDITIVE ))
{
if( R_ModelOpaque( RI.currententity->curstate.rendermode ))
{
pglBlendFunc( GL_ONE, GL_ONE );
pglDepthMask( GL_FALSE );
pglEnable( GL_BLEND );
R_AllowFog( false );
}
else pglBlendFunc( GL_SRC_ALPHA, GL_ONE );
}
*/
const int texture = R_StudioSetupSkin( m_pStudioHeader, pskinref[pmesh->skinref] );
/* FIXME VK if( FBitSet( g_nFaceFlags, STUDIO_NF_CHROME ))
R_StudioDrawChromeMesh( ptricmds, pstudionorms, s, t, shellscale );
else if( FBitSet( g_nFaceFlags, STUDIO_NF_UV_COORDS ))
R_StudioDrawFloatMesh( ptricmds, pstudionorms );
else*/
buildSubmodelMeshGeometry((build_submodel_mesh_t){
.ptricmds = ptricmds,
.pstudionorms = pstudionorms,
.s = s,
.t = t,
.texture = texture,
.vertices_offset = geometry.vertices.unit_offset + vertices_offset,
.indices_offset = geometry.indices.unit_offset + indices_offset,
.dst_vertices = geom_lock.vertices + vertices_offset,
.dst_indices = geom_lock.indices + indices_offset,
.out_geometry = geometries + j,
.out_vertices_count = &vertices_offset,
.out_indices_count = &indices_offset,
});
ASSERT(vertices_offset <= vertex_count);
ASSERT(indices_offset <= index_count);
/* FIXME VK
if( FBitSet( g_nFaceFlags, STUDIO_NF_MASKED ))
{
pglAlphaFunc( GL_GREATER, DEFAULT_ALPHATEST );
pglDisable( GL_ALPHA_TEST );
}
else if( FBitSet( g_nFaceFlags, STUDIO_NF_ADDITIVE ) && R_ModelOpaque( RI.currententity->curstate.rendermode ))
{
pglDepthMask( GL_TRUE );
pglDisable( GL_BLEND );
R_AllowFog( true );
}
r_stats.c_studio_polys += pmesh->numtris;
g_studio.blend = oldblend;
*/
}
R_GeometryRangeUnlock(&geom_lock);
r_studio_model_cache_entry_t *const entry = g_studio_cache.entries + g_studio_cache.entries_count;
*entry = (r_studio_model_cache_entry_t){
.key_submodel = submodel,
.geometries = geometries,
.geometries_count = submodel->nummesh,
.geometry_range = geometry,
};
if (!VK_RenderModelCreate( &entry->render_model, (vk_render_model_init_t){
.name = submodel->name,
.geometries = geometries,
.geometries_count = submodel->nummesh,
})) {
gEngine.Con_Printf(S_ERROR "Unable to create render model for studio submodel %s", submodel->name);
Mem_Free(geometries);
// FIXME everything else leaks ;_;
// FIXME sync up with staging and free
return NULL;
}
++g_studio_cache.entries_count;
return entry;
}
static const r_studio_model_cache_entry_t *findSubModelInCache(const mstudiomodel_t *submodel) {
for (int i = 0; i < g_studio_cache.entries_count; ++i) {
const r_studio_model_cache_entry_t *const entry = g_studio_cache.entries + i;
if (entry->key_submodel == submodel)
return entry;
}
return NULL;
}
static void R_StudioDrawPoints( void ) {
if( !m_pStudioHeader || !m_pSubModel || !m_pSubModel->nummesh)
return;
const r_studio_model_cache_entry_t *cached_model = findSubModelInCache( m_pSubModel );
if (!cached_model)
cached_model = buildCachedStudioSubModel(m_pSubModel);
if (!cached_model)
return;
vec4_t color = {1, 1, 1, g_studio.blend};
if (g_studio.rendermode2 == kRenderTransAdd) {
Vector4Set(color, g_studio.blend, g_studio.blend, g_studio.blend, 1.f);
}
// TODO r_model_draw_t.transform should be matrix3x4
matrix4x4 transform;
Matrix4x4_LoadIdentity(transform);
Matrix3x4_Copy(transform, g_studio.rotationmatrix);
R_RenderModelDraw(&cached_model->render_model, (r_model_draw_t){
.render_type = RI.currententity->curstate.rendermode,
.color = &color,
.transform = &transform,
.prev_transform = /* FIXME */ &transform,
.geometries_changed = NULL,
.geometries_changed_count = 0,
.textures_override = -1,
});
}
static void R_StudioSetRemapColors( int newTop, int newBottom )
{
gEngine.CL_AllocRemapInfo( RI.currententity, RI.currentmodel, newTop, newBottom );
if( gEngine.CL_GetRemapInfoForEntity( RI.currententity ))
{
gEngine.CL_UpdateRemapInfo( RI.currententity, newTop, newBottom );
m_fDoRemap = true;
}
}
void R_StudioResetPlayerModels( void )
{
memset( g_studio.player_models, 0, sizeof( g_studio.player_models ));
}
static model_t *R_StudioSetupPlayerModel( int index )
{
player_info_t *info = gEngine.pfnPlayerInfo( index );
player_model_t *state;
state = &g_studio.player_models[index];
// g-cont: force for "dev-mode", non-local games and menu preview
if(( gpGlobals->developer || !ENGINE_GET_PARM( PARM_LOCAL_GAME ) || !RI.drawWorld ) && info->model[0] )
{
if( Q_strcmp( state->name, info->model ))
{
Q_strncpy( state->name, info->model, sizeof( state->name ));
state->name[sizeof( state->name ) - 1] = 0;
Q_snprintf( state->modelname, sizeof( state->modelname ), "models/player/%s/%s.mdl", info->model, info->model );
if( gEngine.fsapi->FileExists( state->modelname, false ))
state->model = gEngine.Mod_ForName( state->modelname, false, true );
else state->model = NULL;
if( !state->model )
state->model = RI.currententity->model;
}
}
else
{
if( state->model != RI.currententity->model )
state->model = RI.currententity->model;
state->name[0] = 0;
}
return state->model;
}
/*
================
R_GetEntityRenderMode
check for texture flags
================
*/
int R_GetEntityRenderMode( cl_entity_t *ent )
{
int i, opaque, trans;
mstudiotexture_t *ptexture;
cl_entity_t *oldent;
model_t *model;
studiohdr_t *phdr;
oldent = RI.currententity;
RI.currententity = ent;
if( ent->player ) // check it for real playermodel
model = R_StudioSetupPlayerModel( ent->curstate.number - 1 );
else model = ent->model;
RI.currententity = oldent;
if(( phdr = gEngine.Mod_Extradata( mod_studio, model )) == NULL )
{
if( ent->curstate.rendermode == kRenderNormal )
{
// forcing to choose right sorting type
if(( model && model->type == mod_brush ) && FBitSet( model->flags, MODEL_TRANSPARENT ))
return kRenderTransAlpha;
}
return ent->curstate.rendermode;
}
ptexture = (mstudiotexture_t *)((byte *)phdr + phdr->textureindex);
for( opaque = trans = i = 0; i < phdr->numtextures; i++, ptexture++ )
{
// ignore chrome & additive it's just a specular-like effect
if( FBitSet( ptexture->flags, STUDIO_NF_ADDITIVE ) && !FBitSet( ptexture->flags, STUDIO_NF_CHROME ))
trans++;
else opaque++;
}
// if model is more additive than opaque
if( trans > opaque )
return kRenderTransAdd;
return ent->curstate.rendermode;
}
/*
===============
R_StudioClientEvents
===============
*/
static void R_StudioClientEvents( void )
{
mstudioseqdesc_t *pseqdesc;
mstudioevent_t *pevent;
cl_entity_t *e = RI.currententity;
int i, sequence;
float end, start;
if( g_studio.frametime == 0.0 )
return; // gamepaused
// fill attachments with interpolated origin
if( m_pStudioHeader->numattachments <= 0 )
{
Matrix3x4_OriginFromMatrix( g_studio.rotationmatrix, e->attachment[0] );
Matrix3x4_OriginFromMatrix( g_studio.rotationmatrix, e->attachment[1] );
Matrix3x4_OriginFromMatrix( g_studio.rotationmatrix, e->attachment[2] );
Matrix3x4_OriginFromMatrix( g_studio.rotationmatrix, e->attachment[3] );
}
if( FBitSet( e->curstate.effects, EF_MUZZLEFLASH ))
{
dlight_t *el = gEngine.CL_AllocElight( 0 );
ClearBits( e->curstate.effects, EF_MUZZLEFLASH );
VectorCopy( e->attachment[0], el->origin );
el->die = gpGlobals->time + 0.05f;
el->color.r = 255;
el->color.g = 192;
el->color.b = 64;
el->decay = 320;
el->radius = 24;
}
sequence = bound( 0, e->curstate.sequence, m_pStudioHeader->numseq - 1 );
pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + sequence;
// no events for this animation
if( pseqdesc->numevents == 0 )
return;
end = R_StudioEstimateFrame( e, pseqdesc, g_studio.time );
start = end - e->curstate.framerate * gpGlobals->frametime * pseqdesc->fps;
pevent = (mstudioevent_t *)((byte *)m_pStudioHeader + pseqdesc->eventindex);
if( e->latched.sequencetime == e->curstate.animtime )
{
if( !FBitSet( pseqdesc->flags, STUDIO_LOOPING ))
start = -0.01f;
}
for( i = 0; i < pseqdesc->numevents; i++ )
{
// ignore all non-client-side events
if( pevent[i].event < EVENT_CLIENT )
continue;
if( (float)pevent[i].frame > start && pevent[i].frame <= end )
gEngine.pfnStudioEvent( &pevent[i], e );
}
}
/*
===============
R_StudioGetForceFaceFlags
===============
*/
int R_StudioGetForceFaceFlags( void )
{
return g_nForceFaceFlags;
}
/*
===============
R_StudioSetForceFaceFlags
===============
*/
void R_StudioSetForceFaceFlags( int flags )
{
g_nForceFaceFlags = flags;
}
/*
===============
pfnStudioSetHeader
===============
*/
void R_StudioSetHeader( studiohdr_t *pheader )
{
m_pStudioHeader = pheader;
m_fDoRemap = false;
}
/*
===============
R_StudioSetRenderModel
===============
*/
void R_StudioSetRenderModel( model_t *model )
{
RI.currentmodel = model;
}
/*
===============
R_StudioSetupRenderer
===============
*/
static void R_StudioSetupRenderer( int rendermode )
{
studiohdr_t *phdr = m_pStudioHeader;
int i;
if( rendermode > kRenderTransAdd ) rendermode = 0;
g_studio.rendermode = bound( 0, rendermode, kRenderTransAdd );
/* FIXME VK
pglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );
pglDisable( GL_ALPHA_TEST );
pglShadeModel( GL_SMOOTH );
*/
// a point to setup local to world transform for boneweighted models
if( phdr && FBitSet( phdr->flags, STUDIO_HAS_BONEINFO ))
{
// NOTE: extended boneinfo goes immediately after bones
mstudioboneinfo_t *boneinfo = (mstudioboneinfo_t *)((byte *)phdr + phdr->boneindex + phdr->numbones * sizeof( mstudiobone_t ));
for( i = 0; i < phdr->numbones; i++ )
Matrix3x4_ConcatTransforms( g_studio.worldtransform[i], g_studio.bonestransform[i], boneinfo[i].poseToBone );
}
}
static void R_StudioRestoreRenderer( void )
{
/* FIXME VK
if( g_studio.rendermode != kRenderNormal )
pglDisable( GL_BLEND );
pglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE );
pglShadeModel( GL_FLAT );
*/
m_fDoRemap = false;
}
void R_StudioSetChromeOrigin( void )
{
VectorCopy( g_camera.vieworg, g_studio.chrome_origin );
}
static int pfnIsHardware( void )
{
return 3; // 0 is Software, 1 is OpenGL, 2 is Direct3D, 3 is Vulkan
}
static void R_StudioDrawPointsShadow( void )
{
float *av, height;
float vec_x, vec_y;
mstudiomesh_t *pmesh;
vec3_t point;
int i, k;
if( FBitSet( RI.currententity->curstate.effects, EF_NOSHADOW ))
return;
/* FIXME VK
if( glState.stencilEnabled )
pglEnable( GL_STENCIL_TEST );
*/
height = g_studio.lightspot[2] + 1.0f;
vec_x = -g_studio.lightvec[0] * 8.0f;
vec_y = -g_studio.lightvec[1] * 8.0f;
for( k = 0; k < m_pSubModel->nummesh; k++ )
{
short *ptricmds;
pmesh = (mstudiomesh_t *)((byte *)m_pStudioHeader + m_pSubModel->meshindex) + k;
ptricmds = (short *)((byte *)m_pStudioHeader + pmesh->triindex);
/* FIXME VK
r_stats.c_studio_polys += pmesh->numtris;
while(( i = *( ptricmds++ )))
{
if( i < 0 )
{
pglBegin( GL_TRIANGLE_FAN );
i = -i;
}
else
{
pglBegin( GL_TRIANGLE_STRIP );
}
for( ; i > 0; i--, ptricmds += 4 )
{
av = g_studio.verts[ptricmds[0]];
point[0] = av[0] - (vec_x * ( av[2] - g_studio.lightspot[2] ));
point[1] = av[1] - (vec_y * ( av[2] - g_studio.lightspot[2] ));
point[2] = g_studio.lightspot[2] + 1.0f;
pglVertex3fv( point );
}
pglEnd();
}
*/
}
/* FIXME VK
if( glState.stencilEnabled )
pglDisable( GL_STENCIL_TEST );
*/
}
void GL_StudioSetRenderMode( int rendermode )
{
g_studio.rendermode2 = rendermode;
}
/*
===============
GL_StudioDrawShadow
g-cont: don't modify this code it's 100% matched with
original GoldSrc code and used in some mods to enable
studio shadows with some asm tricks
===============
*/
static void GL_StudioDrawShadow( void )
{
/* FIXME VK
pglDepthMask( GL_TRUE );
*/
if( r_shadows.value && g_studio.rendermode != kRenderTransAdd && !FBitSet( RI.currentmodel->flags, STUDIO_AMBIENT_LIGHT ))
{
float color = 1.0f - (g_studio.blend * 0.5f);
/* FIXME VK
pglDisable( GL_TEXTURE_2D );
pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
pglEnable( GL_BLEND );
pglColor4f( 0.0f, 0.0f, 0.0f, 1.0f - color );
pglDepthFunc( GL_LESS );
*/
R_StudioDrawPointsShadow();
/* FIXME VK
pglDepthFunc( GL_LEQUAL );
pglEnable( GL_TEXTURE_2D );
pglDisable( GL_BLEND );
pglColor4f( 1.0f, 1.0f, 1.0f, 1.0f );
pglShadeModel( GL_SMOOTH );
*/
}
}
static void R_StudioRenderFinal( void )
{
int i, rendermode;
rendermode = R_StudioGetForceFaceFlags() ? kRenderTransAdd : RI.currententity->curstate.rendermode;
R_StudioSetupRenderer( rendermode );
VK_RenderDebugLabelBegin( RI.currentmodel->name );
for( i = 0; i < m_pStudioHeader->numbodyparts; i++ )
{
R_StudioSetupModel( i, (void**)&m_pBodyPart, (void**)&m_pSubModel );
// TODO does literally nothing GL_StudioSetRenderMode( rendermode );
R_StudioDrawPoints();
GL_StudioDrawShadow();
}
VK_RenderDebugLabelEnd();
R_StudioRestoreRenderer();
}
void R_StudioRenderModel( void )
{
R_StudioSetChromeOrigin();
R_StudioSetForceFaceFlags( 0 );
/* FIXME VK
if( RI.currententity->curstate.renderfx == kRenderFxGlowShell )
{
RI.currententity->curstate.renderfx = kRenderFxNone;
R_StudioRenderFinal( );
R_StudioSetForceFaceFlags( STUDIO_NF_CHROME );
TriSpriteTexture( R_GetChromeSprite(), 0 );
RI.currententity->curstate.renderfx = kRenderFxGlowShell;
R_StudioRenderFinal( );
}
else */
{
R_StudioRenderFinal( );
}
}
void R_StudioEstimateGait( entity_state_t *pplayer )
{
vec3_t est_velocity;
float dt;
dt = bound( 0.0f, g_studio.frametime, 1.0f );
if( dt == 0.0f) // FIXME VK || m_pPlayerInfo->renderframe == tr.realframecount )
{
m_flGaitMovement = 0;
return;
}
VectorSubtract( RI.currententity->origin, m_pPlayerInfo->prevgaitorigin, est_velocity );
VectorCopy( RI.currententity->origin, m_pPlayerInfo->prevgaitorigin );
m_flGaitMovement = VectorLength( est_velocity );
if( dt <= 0.0f || m_flGaitMovement / dt < 5.0f )
{
m_flGaitMovement = 0.0f;
est_velocity[0] = 0.0f;
est_velocity[1] = 0.0f;
}
if( est_velocity[1] == 0.0f && est_velocity[0] == 0.0f )
{
float flYawDiff = RI.currententity->angles[YAW] - m_pPlayerInfo->gaityaw;
flYawDiff = flYawDiff - (int)(flYawDiff / 360) * 360;
if( flYawDiff > 180.0f ) flYawDiff -= 360.0f;
if( flYawDiff < -180.0f ) flYawDiff += 360.0f;
if( dt < 0.25f )
flYawDiff *= dt * 4.0f;
else flYawDiff *= dt;
m_pPlayerInfo->gaityaw += flYawDiff;
m_pPlayerInfo->gaityaw = m_pPlayerInfo->gaityaw - (int)(m_pPlayerInfo->gaityaw / 360) * 360;
m_flGaitMovement = 0.0f;
}
else
{
m_pPlayerInfo->gaityaw = ( atan2( est_velocity[1], est_velocity[0] ) * 180 / M_PI_F );
if( m_pPlayerInfo->gaityaw > 180.0f ) m_pPlayerInfo->gaityaw = 180.0f;
if( m_pPlayerInfo->gaityaw < -180.0f ) m_pPlayerInfo->gaityaw = -180.0f;
}
}
void R_StudioProcessGait( entity_state_t *pplayer )
{
mstudioseqdesc_t *pseqdesc;
int iBlend;
float dt, flYaw; // view direction relative to movement
if( RI.currententity->curstate.sequence >= m_pStudioHeader->numseq )
RI.currententity->curstate.sequence = 0;
dt = bound( 0.0f, g_studio.frametime, 1.0f );
pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + RI.currententity->curstate.sequence;
R_StudioPlayerBlend( pseqdesc, &iBlend, &RI.currententity->angles[PITCH] );
RI.currententity->latched.prevangles[PITCH] = RI.currententity->angles[PITCH];
RI.currententity->curstate.blending[0] = iBlend;
RI.currententity->latched.prevblending[0] = RI.currententity->curstate.blending[0];
RI.currententity->latched.prevseqblending[0] = RI.currententity->curstate.blending[0];
R_StudioEstimateGait( pplayer );
// calc side to side turning
flYaw = RI.currententity->angles[YAW] - m_pPlayerInfo->gaityaw;
flYaw = flYaw - (int)(flYaw / 360) * 360;
if( flYaw < -180.0f ) flYaw = flYaw + 360.0f;
if( flYaw > 180.0f ) flYaw = flYaw - 360.0f;
if( flYaw > 120.0f )
{
m_pPlayerInfo->gaityaw = m_pPlayerInfo->gaityaw - 180.0f;
m_flGaitMovement = -m_flGaitMovement;
flYaw = flYaw - 180.0f;
}
else if( flYaw < -120.0f )
{
m_pPlayerInfo->gaityaw = m_pPlayerInfo->gaityaw + 180.0f;
m_flGaitMovement = -m_flGaitMovement;
flYaw = flYaw + 180.0f;
}
// adjust torso
RI.currententity->curstate.controller[0] = ((flYaw / 4.0f) + 30.0f) / (60.0f / 255.0f);
RI.currententity->curstate.controller[1] = ((flYaw / 4.0f) + 30.0f) / (60.0f / 255.0f);
RI.currententity->curstate.controller[2] = ((flYaw / 4.0f) + 30.0f) / (60.0f / 255.0f);
RI.currententity->curstate.controller[3] = ((flYaw / 4.0f) + 30.0f) / (60.0f / 255.0f);
RI.currententity->latched.prevcontroller[0] = RI.currententity->curstate.controller[0];
RI.currententity->latched.prevcontroller[1] = RI.currententity->curstate.controller[1];
RI.currententity->latched.prevcontroller[2] = RI.currententity->curstate.controller[2];
RI.currententity->latched.prevcontroller[3] = RI.currententity->curstate.controller[3];
RI.currententity->angles[YAW] = m_pPlayerInfo->gaityaw;
if( RI.currententity->angles[YAW] < -0 ) RI.currententity->angles[YAW] += 360.0f;
RI.currententity->latched.prevangles[YAW] = RI.currententity->angles[YAW];
if( pplayer->gaitsequence >= m_pStudioHeader->numseq )
pplayer->gaitsequence = 0;
pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + pplayer->gaitsequence;
// calc gait frame
if( pseqdesc->linearmovement[0] > 0 )
m_pPlayerInfo->gaitframe += (m_flGaitMovement / pseqdesc->linearmovement[0]) * pseqdesc->numframes;
else m_pPlayerInfo->gaitframe += pseqdesc->fps * dt;
// do modulo
m_pPlayerInfo->gaitframe = m_pPlayerInfo->gaitframe - (int)(m_pPlayerInfo->gaitframe / pseqdesc->numframes) * pseqdesc->numframes;
if( m_pPlayerInfo->gaitframe < 0 ) m_pPlayerInfo->gaitframe += pseqdesc->numframes;
}
static int R_StudioDrawPlayer( int flags, entity_state_t *pplayer )
{
int m_nPlayerIndex;
alight_t lighting;
vec3_t dir;
m_nPlayerIndex = pplayer->number - 1;
if( m_nPlayerIndex < 0 || m_nPlayerIndex >= ENGINE_GET_PARM( PARM_MAX_CLIENTS ) )
return 0;
RI.currentmodel = R_StudioSetupPlayerModel( m_nPlayerIndex );
if( RI.currentmodel == NULL )
return 0;
R_StudioSetHeader((studiohdr_t *)gEngine.Mod_Extradata( mod_studio, RI.currentmodel ));
if( pplayer->gaitsequence )
{
vec3_t orig_angles;
m_pPlayerInfo = pfnPlayerInfo( m_nPlayerIndex );
VectorCopy( RI.currententity->angles, orig_angles );
R_StudioProcessGait( pplayer );
m_pPlayerInfo->gaitsequence = pplayer->gaitsequence;
m_pPlayerInfo = NULL;
R_StudioSetUpTransform( RI.currententity );
VectorCopy( orig_angles, RI.currententity->angles );
}
else
{
RI.currententity->curstate.controller[0] = 127;
RI.currententity->curstate.controller[1] = 127;
RI.currententity->curstate.controller[2] = 127;
RI.currententity->curstate.controller[3] = 127;
RI.currententity->latched.prevcontroller[0] = RI.currententity->curstate.controller[0];
RI.currententity->latched.prevcontroller[1] = RI.currententity->curstate.controller[1];
RI.currententity->latched.prevcontroller[2] = RI.currententity->curstate.controller[2];
RI.currententity->latched.prevcontroller[3] = RI.currententity->curstate.controller[3];
m_pPlayerInfo = pfnPlayerInfo( m_nPlayerIndex );
m_pPlayerInfo->gaitsequence = 0;
R_StudioSetUpTransform( RI.currententity );
}
if( flags & STUDIO_RENDER )
{
// see if the bounding box lets us trivially reject, also sets
if( !R_StudioCheckBBox( ))
return 0;
// FIXME VK r_stats.c_studio_models_drawn++;
g_studio.framecount++; // render data cache cookie
if( m_pStudioHeader->numbodyparts == 0 )
return 1;
}
m_pPlayerInfo = pfnPlayerInfo( m_nPlayerIndex );
R_StudioSetupBones( RI.currententity );
R_StudioSaveBones( );
// FIXME VK m_pPlayerInfo->renderframe = tr.realframecount;
m_pPlayerInfo = NULL;
if( flags & STUDIO_EVENTS )
{
R_StudioCalcAttachments( );
R_StudioClientEvents( );
// copy attachments into global entity array
if( RI.currententity->index > 0 )
{
cl_entity_t *ent = gEngine.GetEntityByIndex( RI.currententity->index );
memcpy( ent->attachment, RI.currententity->attachment, sizeof( vec3_t ) * 4 );
}
}
if( flags & STUDIO_RENDER )
{
if( cl_himodels->value && RI.currentmodel != RI.currententity->model )
{
// show highest resolution multiplayer model
RI.currententity->curstate.body = 255;
}
if( !( !gpGlobals->developer && ENGINE_GET_PARM( PARM_MAX_CLIENTS ) == 1 ) && ( RI.currentmodel == RI.currententity->model ))
RI.currententity->curstate.body = 1; // force helmet
lighting.plightvec = dir;
R_StudioDynamicLight( RI.currententity, &lighting );
R_StudioEntityLight( &lighting );
// model and frame independant
R_StudioSetupLighting( &lighting );
m_pPlayerInfo = pfnPlayerInfo( m_nPlayerIndex );
// get remap colors
g_nTopColor = m_pPlayerInfo->topcolor;
g_nBottomColor = m_pPlayerInfo->bottomcolor;
if( g_nTopColor < 0 ) g_nTopColor = 0;
if( g_nTopColor > 360 ) g_nTopColor = 360;
if( g_nBottomColor < 0 ) g_nBottomColor = 0;
if( g_nBottomColor > 360 ) g_nBottomColor = 360;
R_StudioSetRemapColors( g_nTopColor, g_nBottomColor );
R_StudioRenderModel( );
m_pPlayerInfo = NULL;
if( pplayer->weaponmodel )
{
cl_entity_t saveent = *RI.currententity;
model_t *pweaponmodel = gEngine.pfnGetModelByIndex( pplayer->weaponmodel );
m_pStudioHeader = (studiohdr_t *)gEngine.Mod_Extradata( mod_studio, pweaponmodel );
R_StudioMergeBones( RI.currententity, pweaponmodel );
R_StudioSetupLighting( &lighting );
R_StudioRenderModel( );
R_StudioCalcAttachments( );
*RI.currententity = saveent;
}
}
return 1;
}
static int R_StudioDrawModel( int flags )
{
alight_t lighting;
vec3_t dir;
if( RI.currententity->curstate.renderfx == kRenderFxDeadPlayer )
{
entity_state_t deadplayer;
int result;
if( RI.currententity->curstate.renderamt <= 0 ||
RI.currententity->curstate.renderamt > ENGINE_GET_PARM( PARM_MAX_CLIENTS ) )
return 0;
// get copy of player
deadplayer = *R_StudioGetPlayerState( RI.currententity->curstate.renderamt - 1 );
// clear weapon, movement state
deadplayer.number = RI.currententity->curstate.renderamt;
deadplayer.weaponmodel = 0;
deadplayer.gaitsequence = 0;
deadplayer.movetype = MOVETYPE_NONE;
VectorCopy( RI.currententity->curstate.angles, deadplayer.angles );
VectorCopy( RI.currententity->curstate.origin, deadplayer.origin );
g_studio.interpolate = false;
result = R_StudioDrawPlayer( flags, &deadplayer ); // draw as though it were a player
g_studio.interpolate = true;
return result;
}
R_StudioSetHeader((studiohdr_t *)gEngine.Mod_Extradata( mod_studio, RI.currentmodel ));
R_StudioSetUpTransform( RI.currententity );
if( flags & STUDIO_RENDER )
{
// see if the bounding box lets us trivially reject, also sets
if( !R_StudioCheckBBox( ))
return 0;
// FIXME VK r_stats.c_studio_models_drawn++;
g_studio.framecount++; // render data cache cookie
if( m_pStudioHeader->numbodyparts == 0 )
return 1;
}
if( RI.currententity->curstate.movetype == MOVETYPE_FOLLOW )
R_StudioMergeBones( RI.currententity, RI.currentmodel );
else R_StudioSetupBones( RI.currententity );
R_StudioSaveBones();
if( flags & STUDIO_EVENTS )
{
R_StudioCalcAttachments( );
R_StudioClientEvents( );
// copy attachments into global entity array
if( RI.currententity->index > 0 )
{
cl_entity_t *ent = gEngine.GetEntityByIndex( RI.currententity->index );
memcpy( ent->attachment, RI.currententity->attachment, sizeof( vec3_t ) * 4 );
}
}
if( flags & STUDIO_RENDER )
{
lighting.plightvec = dir;
R_StudioDynamicLight( RI.currententity, &lighting );
R_StudioEntityLight( &lighting );
// model and frame independant
R_StudioSetupLighting( &lighting );
// get remap colors
g_nTopColor = RI.currententity->curstate.colormap & 0xFF;
g_nBottomColor = (RI.currententity->curstate.colormap & 0xFF00) >> 8;
R_StudioSetRemapColors( g_nTopColor, g_nBottomColor );
R_StudioRenderModel();
}
return 1;
}
static void R_StudioDrawModelInternal( cl_entity_t *e, int flags )
{
VK_RenderDebugLabelBegin( e->model->name );
++g_studio_stats.models_count;
if( !RI.drawWorld )
{
if( e->player )
R_StudioDrawPlayer( flags, &e->curstate );
else R_StudioDrawModel( flags );
}
else
{
// select the properly method
if( e->player )
pStudioDraw->StudioDrawPlayer( flags, R_StudioGetPlayerState( e->index - 1 ));
else pStudioDraw->StudioDrawModel( flags );
}
VK_RenderDebugLabelEnd();
}
static void R_DrawStudioModel( cl_entity_t *e )
{
/* FIXME VK
if( FBitSet( RI.params, RP_ENVVIEW ))
return;
*/
R_StudioSetupTimings();
if( e->player )
{
R_StudioDrawModelInternal( e, STUDIO_RENDER|STUDIO_EVENTS );
}
else
{
if( e->curstate.movetype == MOVETYPE_FOLLOW && e->curstate.aiment > 0 )
{
cl_entity_t *parent = gEngine.GetEntityByIndex( e->curstate.aiment );
if( parent && parent->model && parent->model->type == mod_studio )
{
RI.currententity = parent;
R_StudioDrawModelInternal( RI.currententity, 0 );
VectorCopy( parent->curstate.origin, e->curstate.origin );
VectorCopy( parent->origin, e->origin );
RI.currententity = e;
}
}
R_StudioDrawModelInternal( e, STUDIO_RENDER|STUDIO_EVENTS );
}
}
void R_RunViewmodelEvents( void )
{
int i;
vec3_t simorg;
if( r_drawviewmodel->value == 0 )
return;
if( ENGINE_GET_PARM( PARM_THIRDPERSON ))
return;
/* FIXME VK
// ignore in thirdperson, camera view or client is died
if( !RP_NORMALPASS() || ENGINE_GET_PARM( PARM_LOCAL_HEALTH ) <= 0 || !CL_IsViewEntityLocalPlayer())
return;
*/
RI.currententity = gEngine.GetViewModel();
if( !RI.currententity->model || RI.currententity->model->type != mod_studio )
return;
R_StudioSetupTimings();
gEngine.GetPredictedOrigin( simorg );
for( i = 0; i < 4; i++ )
VectorCopy( simorg, RI.currententity->attachment[i] );
RI.currentmodel = RI.currententity->model;
R_StudioDrawModelInternal( RI.currententity, STUDIO_EVENTS );
}
void R_GatherPlayerLight( void )
{
cl_entity_t *view = gEngine.GetViewModel();
colorVec c;
/* FIXME VK
tr.ignore_lightgamma = true;
c = R_LightPoint( view->origin );
tr.ignore_lightgamma = false;
gEngine.SetLocalLightLevel( ( c.r + c.g + c.b ) / 3 );
*/
}
void R_DrawViewModel( void )
{
cl_entity_t *view = gEngine.GetViewModel();
R_GatherPlayerLight();
if( r_drawviewmodel->value == 0 )
return;
if( ENGINE_GET_PARM( PARM_THIRDPERSON ))
return;
/* FIXME VK
// ignore in thirdperson, camera view or client is died
if( !RP_NORMALPASS() || ENGINE_GET_PARM( PARM_LOCAL_HEALTH ) <= 0 || !CL_IsViewEntityLocalPlayer())
return;
g_studio.blend = CL_FxBlend( view ) / 255.0f;
if( !R_ModelOpaque( view->curstate.rendermode ) && g_studio.blend <= 0.0f )
return; // invisible ?
*/
RI.currententity = view;
if( !RI.currententity->model )
return;
RI.currentmodel = RI.currententity->model;
/* FIXME VK
// adjust the depth range to prevent view model from poking into walls
pglDepthRange( gldepthmin, gldepthmin + 0.3f * ( gldepthmax - gldepthmin ));
// backface culling for left-handed weapons
if( R_AllowFlipViewModel( RI.currententity ) || g_iBackFaceCull )
{
tr.fFlipViewModel = true;
pglFrontFace( GL_CW );
}
*/
switch( RI.currententity->model->type )
{
case mod_alias:
/* FIXME VK
R_DrawAliasModel( RI.currententity );
break;
*/
case mod_studio:
R_StudioSetupTimings();
R_StudioDrawModelInternal( RI.currententity, STUDIO_RENDER );
break;
case mod_sprite:
case mod_brush:
case mod_bad:
// Complain, impossible
break;
}
RI.currententity = NULL;
RI.currentmodel = NULL;
/* FIXME VK
// restore depth range
pglDepthRange( gldepthmin, gldepthmax );
// backface culling for left-handed weapons
if( R_AllowFlipViewModel( RI.currententity ) || g_iBackFaceCull )
{
tr.fFlipViewModel = false;
pglFrontFace( GL_CCW );
}
*/
}
/*
====================
R_StudioLoadTexture
load model texture with unique name
====================
*/
static void R_StudioLoadTexture( model_t *mod, studiohdr_t *phdr, mstudiotexture_t *ptexture )
{
size_t size;
int flags = 0;
char texname[128], name[128], mdlname[128];
texture_t *tx = NULL;
if( ptexture->flags & STUDIO_NF_NORMALMAP )
flags |= (TF_NORMALMAP);
// store some textures for remapping
if( !Q_strnicmp( ptexture->name, "DM_Base", 7 ) || !Q_strnicmp( ptexture->name, "remap", 5 ))
{
int i, size;
char val[6];
byte *pixels;
i = mod->numtextures;
mod->textures = (texture_t **)Mem_Realloc( mod->mempool, mod->textures, ( i + 1 ) * sizeof( texture_t* ));
size = ptexture->width * ptexture->height + 768;
tx = Mem_Calloc( mod->mempool, sizeof( *tx ) + size );
mod->textures[i] = tx;
// store ranges into anim_min, anim_max etc
if( !Q_strnicmp( ptexture->name, "DM_Base", 7 ))
{
Q_strncpy( tx->name, "DM_Base", sizeof( tx->name ));
tx->anim_min = PLATE_HUE_START; // topcolor start
tx->anim_max = PLATE_HUE_END; // topcolor end
// bottomcolor start always equal is (topcolor end + 1)
tx->anim_total = SUIT_HUE_END;// bottomcolor end
}
else
{
Q_strncpy( tx->name, "DM_User", sizeof( tx->name )); // custom remapped
Q_strncpy( val, ptexture->name + 7, 4 );
tx->anim_min = bound( 0, Q_atoi( val ), 255 ); // topcolor start
Q_strncpy( val, ptexture->name + 11, 4 );
tx->anim_max = bound( 0, Q_atoi( val ), 255 ); // topcolor end
// bottomcolor start always equal is (topcolor end + 1)
Q_strncpy( val, ptexture->name + 15, 4 );
tx->anim_total = bound( 0, Q_atoi( val ), 255 ); // bottomcolor end
}
tx->width = ptexture->width;
tx->height = ptexture->height;
// the pixels immediately follow the structures
pixels = (byte *)phdr + ptexture->index;
memcpy( tx+1, pixels, size );
ptexture->flags |= STUDIO_NF_COLORMAP; // yes, this is colormap image
flags |= TF_FORCE_COLOR;
mod->numtextures++; // done
}
Q_strncpy( mdlname, mod->name, sizeof( mdlname ));
COM_FileBase( ptexture->name, name, sizeof( name ));
COM_StripExtension( mdlname );
if( FBitSet( ptexture->flags, STUDIO_NF_NOMIPS ))
SetBits( flags, TF_NOMIPMAP );
// NOTE: replace index with pointer to start of imagebuffer, ImageLib expected it
//ptexture->index = (int)((byte *)phdr) + ptexture->index;
gEngine.Image_SetMDLPointer((byte *)phdr + ptexture->index);
size = sizeof( mstudiotexture_t ) + ptexture->width * ptexture->height + 768;
if( FBitSet( ENGINE_GET_PARM( PARM_FEATURES ), ENGINE_IMPROVED_LINETRACE ) && FBitSet( ptexture->flags, STUDIO_NF_MASKED ))
flags |= TF_KEEP_SOURCE; // Paranoia2 texture alpha-tracing
// build the texname
Q_snprintf( texname, sizeof( texname ), "#%s/%s.mdl", mdlname, name );
ptexture->index = VK_LoadTexture( texname, (byte *)ptexture, size, flags );
if( !ptexture->index )
{
ptexture->index = tglob.defaultTexture;
}
else if( tx )
{
// duplicate texnum for easy acess
tx->gl_texturenum = ptexture->index;
}
}
void Mod_StudioLoadTextures( model_t *mod, void *data )
{
studiohdr_t *phdr = (studiohdr_t *)data;
mstudiotexture_t *ptexture;
int i;
if( !phdr )
return;
ptexture = (mstudiotexture_t *)(((byte *)phdr) + phdr->textureindex);
if( phdr->textureindex > 0 && phdr->numtextures <= MAXSTUDIOSKINS )
{
for( i = 0; i < phdr->numtextures; i++ )
R_StudioLoadTexture( mod, phdr, &ptexture[i] );
}
}
void Mod_StudioUnloadTextures( void *data )
{
studiohdr_t *phdr = (studiohdr_t *)data;
mstudiotexture_t *ptexture;
int i;
if( !phdr )
return;
ptexture = (mstudiotexture_t *)(((byte *)phdr) + phdr->textureindex);
// release all textures
for( i = 0; i < phdr->numtextures; i++ )
{
if( ptexture[i].index == tglob.defaultTexture )
continue;
VK_FreeTexture( ptexture[i].index );
}
}
static model_t *pfnModelHandle( int modelindex )
{
return gEngine.pfnGetModelByIndex( modelindex );
}
static void *pfnMod_CacheCheck( struct cache_user_s *c )
{
return gEngine.Mod_CacheCheck( c );
}
static void *pfnMod_StudioExtradata( model_t *mod )
{
return gEngine.Mod_Extradata( mod_studio, mod );
}
static void pfnMod_LoadCacheFile( const char *path, struct cache_user_s *cu )
{
gEngine.Mod_LoadCacheFile( path, cu );
}
static cvar_t *pfnGetCvarPointer( const char *name )
{
return (cvar_t*)gEngine.pfnGetCvarPointer( name, 0 );
}
static void *pfnMod_Calloc( int number, size_t size )
{
return gEngine.Mod_Calloc( number, size );
}
static void R_StudioDrawHulls( void )
{
PRINT_NOT_IMPLEMENTED();
}
static void R_StudioDrawAbsBBox( void )
{
PRINT_NOT_IMPLEMENTED();
}
static void R_StudioDrawBones( void )
{
PRINT_NOT_IMPLEMENTED();
}
static engine_studio_api_t gStudioAPI =
{
pfnMod_Calloc,
pfnMod_CacheCheck,
pfnMod_LoadCacheFile,
pfnMod_ForName,
pfnMod_StudioExtradata,
pfnModelHandle,
pfnGetCurrentEntity,
pfnPlayerInfo,
R_StudioGetPlayerState,
pfnGetViewEntity,
pfnGetEngineTimes,
pfnGetCvarPointer,
pfnGetViewInfo,
R_GetChromeSprite,
pfnGetModelCounters,
pfnGetAliasScale,
pfnStudioGetBoneTransform,
pfnStudioGetLightTransform,
pfnStudioGetAliasTransform,
pfnStudioGetRotationMatrix,
R_StudioSetupModel,
R_StudioCheckBBox,
R_StudioDynamicLight,
R_StudioEntityLight,
R_StudioSetupLighting,
R_StudioDrawPoints,
R_StudioDrawHulls,
R_StudioDrawAbsBBox,
R_StudioDrawBones,
(void*)R_StudioSetupSkin,
R_StudioSetRemapColors,
R_StudioSetupPlayerModel,
R_StudioClientEvents,
R_StudioGetForceFaceFlags,
R_StudioSetForceFaceFlags,
(void*)R_StudioSetHeader,
R_StudioSetRenderModel,
R_StudioSetupRenderer,
R_StudioRestoreRenderer,
R_StudioSetChromeOrigin,
pfnIsHardware,
GL_StudioDrawShadow,
GL_StudioSetRenderMode,
R_StudioSetRenderamt,
R_StudioSetCullState,
R_StudioRenderShadow,
};
static r_studio_interface_t gStudioDraw =
{
STUDIO_INTERFACE_VERSION,
R_StudioDrawModel,
R_StudioDrawPlayer,
};
void CL_InitStudioAPI( void )
{
pStudioDraw = &gStudioDraw;
// trying to grab them from client.dll
cl_righthand = gEngine.pfnGetCvarPointer( "cl_righthand", 0 );
if( cl_righthand == NULL )
cl_righthand = gEngine.Cvar_Get( "cl_righthand", "0", FCVAR_ARCHIVE, "flip viewmodel (left to right)" );
// Xash will be used internal StudioModelRenderer
if( gEngine.pfnGetStudioModelInterface( STUDIO_INTERFACE_VERSION, &pStudioDraw, &gStudioAPI ))
return;
// NOTE: we always return true even if game interface was not correct
// because we need Draw our StudioModels
// just restore pointer to builtin function
pStudioDraw = &gStudioDraw;
}
void VK_StudioInit( void )
{
R_StudioInit();
}
void VK_StudioShutdown( void )
{
R_StudioCacheClear();
}
void Mod_LoadStudioModel( model_t *mod, const void *buffer, qboolean *loaded )
{
PRINT_NOT_IMPLEMENTED_ARGS("(%s)", mod->name);
}
void VK_StudioDrawModel( cl_entity_t *ent, int render_mode, float blend )
{
RI.currententity = ent;
RI.currentmodel = ent->model;
RI.drawWorld = true;
g_studio.blend = blend;
R_DrawStudioModel( ent );
RI.currentmodel = NULL;
RI.currententity = NULL;
}