/* gl_studio.c - studio model renderer Copyright (C) 2010 Uncle Mike This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ #include "common.h" #include "client.h" #include "mathlib.h" #include "const.h" #include "r_studioint.h" #include "studio.h" #include "pm_local.h" #include "gl_local.h" #include "cl_tent.h" #include "cl_tent.h" // NOTE: enable this if you want merge both 'model' and 'modelT' files into one model slot. // otherwise it's uses two slots in models[] array for models with external textures #define STUDIO_MERGE_TEXTURES #define EVENT_CLIENT 5000 // less than this value it's a server-side studio events #define MAXARRAYVERTS 8192 // used for draw shadows static vec3_t hullcolor[8] = { { 1.0f, 1.0f, 1.0f }, { 1.0f, 0.5f, 0.5f }, { 0.5f, 1.0f, 0.5f }, { 1.0f, 1.0f, 0.5f }, { 0.5f, 0.5f, 1.0f }, { 1.0f, 0.5f, 1.0f }, { 0.5f, 1.0f, 1.0f }, { 1.0f, 1.0f, 1.0f }, }; typedef struct studiolight_s { vec3_t lightvec; // light vector vec3_t lightcolor; // ambient light color vec3_t lightspot; // potential coords where placed lightsource vec3_t blightvec[MAXSTUDIOBONES]; // ambient lightvectors per bone vec3_t dlightvec[MAX_DLIGHTS][MAXSTUDIOBONES]; vec3_t dlightcolor[MAX_DLIGHTS]; // ambient dynamic light colors vec3_t elightvec[MAX_ELIGHTS][MAXSTUDIOBONES]; vec3_t elightcolor[MAX_ELIGHTS]; // ambient entity light colors int numdlights; int numelights; } studiolight_t; convar_t *r_studio_lerping; convar_t *r_studio_lambert; convar_t *r_studio_lighting; convar_t *r_drawviewmodel; convar_t *r_customdraw_playermodel; convar_t *cl_himodels; cvar_t r_shadows = { "r_shadows", "0", 0, 0 }; // dead cvar. especially disabled cvar_t r_shadowalpha = { "r_shadowalpha", "0.5", 0, 0.8f }; static r_studio_interface_t *pStudioDraw; static float aliasXscale, aliasYscale; // software renderer scale static matrix3x4 g_aliastransform; // software renderer transform static matrix3x4 g_rotationmatrix; static vec3_t g_chrome_origin; static vec2_t g_chrome[MAXSTUDIOVERTS]; // texture coords for surface normals static matrix3x4 g_bonestransform[MAXSTUDIOBONES]; static matrix3x4 g_lighttransform[MAXSTUDIOBONES]; static matrix3x4 g_rgCachedBonesTransform[MAXSTUDIOBONES]; static matrix3x4 g_rgCachedLightTransform[MAXSTUDIOBONES]; static vec3_t g_chromeright[MAXSTUDIOBONES];// chrome vector "right" in bone reference frames static vec3_t g_chromeup[MAXSTUDIOBONES]; // chrome vector "up" in bone reference frames static int g_chromeage[MAXSTUDIOBONES]; // last time chrome vectors were updated static vec3_t g_xformverts[MAXSTUDIOVERTS]; static vec3_t g_xformnorms[MAXSTUDIOVERTS]; static vec3_t g_xarrayverts[MAXARRAYVERTS]; static uint g_xarrayelems[MAXARRAYVERTS*6]; static uint g_nNumArrayVerts; static uint g_nNumArrayElems; static vec3_t g_lightvalues[MAXSTUDIOVERTS]; static studiolight_t g_studiolight; char g_nCachedBoneNames[MAXSTUDIOBONES][32]; int g_nCachedBones; // number of bones in cache int g_nStudioCount; // for chrome update int g_iRenderMode; // currentmodel rendermode vec3_t studio_mins, studio_maxs; float studio_radius; // global variables qboolean m_fDoInterp; qboolean m_fDoRemap; mstudiomodel_t *m_pSubModel; mstudiobodyparts_t *m_pBodyPart; player_info_t *m_pPlayerInfo; studiohdr_t *m_pStudioHeader; studiohdr_t *m_pTextureHeader; float m_flGaitMovement; pmtrace_t g_shadowTrace; vec3_t g_mvShadowVec; int g_nTopColor, g_nBottomColor; // remap colors int g_nFaceFlags, g_nForceFaceFlags; /* ==================== R_StudioInit ==================== */ void R_StudioInit( void ) { float pixelAspect; r_studio_lambert = Cvar_Get( "r_studio_lambert", "2", CVAR_ARCHIVE, "bonelighting lambert value" ); r_studio_lerping = Cvar_Get( "r_studio_lerping", "1", CVAR_ARCHIVE, "enables studio animation lerping" ); r_drawviewmodel = Cvar_Get( "r_drawviewmodel", "1", 0, "draw firstperson weapon model" ); cl_himodels = Cvar_Get( "cl_himodels", "1", CVAR_ARCHIVE, "draw high-resolution player models in multiplayer" ); r_studio_lighting = Cvar_Get( "r_studio_lighting", "1", CVAR_ARCHIVE, "studio lighting models ( 0 - normal, 1 - extended, 2 - experimental )" ); // NOTE: some mods with custom studiomodel renderer may cause error when menu trying draw player model out of the loaded game r_customdraw_playermodel = Cvar_Get( "r_customdraw_playermodel", "0", CVAR_ARCHIVE, "allow to drawing playermodel in menu with client renderer" ); // recalc software X and Y alias scale (this stuff is used only by HL software renderer but who knews...) pixelAspect = ((float)scr_height->integer / (float)scr_width->integer); if( scr_width->integer < 640 ) pixelAspect *= (320.0f / 240.0f); else pixelAspect *= (640.0f / 480.0f); aliasXscale = (float)scr_width->integer / RI.refdef.fov_y; aliasYscale = aliasXscale * pixelAspect; Matrix3x4_LoadIdentity( g_aliastransform ); Matrix3x4_LoadIdentity( g_rotationmatrix ); g_nStudioCount = 0; m_fDoRemap = false; } /* =============== R_StudioTexName extract texture filename from modelname =============== */ const char *R_StudioTexName( model_t *mod ) { static char texname[64]; Q_strncpy( texname, mod->name, sizeof( texname )); FS_StripExtension( texname ); Q_strncat( texname, "T.mdl", sizeof( texname )); return texname; } /* ================ R_StudioBodyVariations calc studio body variations ================ */ static int R_StudioBodyVariations( model_t *mod ) { studiohdr_t *pstudiohdr; mstudiobodyparts_t *pbodypart; int i, count; pstudiohdr = (studiohdr_t *)Mod_Extradata( mod ); if( !pstudiohdr ) return 0; count = 1; pbodypart = (mstudiobodyparts_t *)((byte *)pstudiohdr + pstudiohdr->bodypartindex); // each body part has nummodels variations so there are as many total variations as there // are in a matrix of each part by each other part for( i = 0; i < pstudiohdr->numbodyparts; i++ ) count = count * pbodypart[i].nummodels; return count; } /* ================ R_StudioExtractBbox Extract bbox from current sequence ================ */ int R_StudioExtractBbox( cl_entity_t *e, studiohdr_t *phdr, int sequence, float *mins, float *maxs ) { mstudioseqdesc_t *pseqdesc; float scale = 1.0f; if( !phdr ) return 0; pseqdesc = (mstudioseqdesc_t *)((byte *)phdr + phdr->seqindex); if( sequence == -1 ) return 0; if( e->curstate.scale > 0.0f ) scale = e->curstate.scale; VectorScale( pseqdesc[sequence].bbmin, scale, mins ); VectorScale( pseqdesc[sequence].bbmax, scale, maxs ); return 1; } /* ================ R_StudioComputeBBox Compute a full bounding box for current sequence ================ */ static qboolean R_StudioComputeBBox( cl_entity_t *e, vec3_t bbox[8] ) { vec3_t tmp_mins, tmp_maxs; vec3_t vectors[3], angles, p1, p2; int i, seq = e->curstate.sequence; float scale = 1.0f; if( !R_StudioExtractBbox( e, m_pStudioHeader, seq, tmp_mins, tmp_maxs )) return false; if( e->curstate.scale > 0.0f ) scale = e->curstate.scale; // copy original bbox VectorScale( m_pStudioHeader->bbmin, scale, studio_mins ); VectorScale( m_pStudioHeader->bbmin, scale, studio_mins ); // rotate the bounding box VectorCopy( e->angles, angles ); if( e->player ) angles[PITCH] = 0; // don't rotate player model, only aim AngleVectors( angles, vectors[0], vectors[1], vectors[2] ); // compute a full bounding box for( i = 0; i < 8; i++ ) { p1[0] = ( i & 1 ) ? tmp_mins[0] : tmp_maxs[0]; p1[1] = ( i & 2 ) ? tmp_mins[1] : tmp_maxs[1]; p1[2] = ( i & 4 ) ? tmp_mins[2] : tmp_maxs[2]; // rotate by YAW p2[0] = DotProduct( p1, vectors[0] ); p2[1] = DotProduct( p1, vectors[1] ); p2[2] = DotProduct( p1, vectors[2] ); if( bbox ) VectorAdd( p2, e->origin, bbox[i] ); if( p2[0] < studio_mins[0] ) studio_mins[0] = p2[0]; if( p2[0] > studio_maxs[0] ) studio_maxs[0] = p2[0]; if( p2[1] < studio_mins[1] ) studio_mins[1] = p2[1]; if( p2[1] > studio_maxs[1] ) studio_maxs[1] = p2[1]; if( p2[2] < studio_mins[2] ) studio_mins[2] = p2[2]; if( p2[2] > studio_maxs[2] ) studio_maxs[2] = p2[2]; } studio_radius = RadiusFromBounds( studio_mins, studio_maxs ); return true; } /* =============== pfnGetCurrentEntity =============== */ static cl_entity_t *pfnGetCurrentEntity( void ) { return RI.currententity; } /* =============== pfnPlayerInfo =============== */ static player_info_t *pfnPlayerInfo( int index ) { if( cls.key_dest == key_menu && !index ) return &menu.playerinfo; if( index < 0 || index > cl.maxclients ) return NULL; return &cl.players[index]; } /* =============== pfnGetPlayerState =============== */ entity_state_t *R_StudioGetPlayerState( int index ) { if( index < 0 || index > cl.maxclients ) return NULL; return &cl.frame.playerstate[index]; } /* =============== pfnGetViewEntity =============== */ static cl_entity_t *pfnGetViewEntity( void ) { return &clgame.viewent; } /* =============== pfnGetEngineTimes =============== */ static void pfnGetEngineTimes( int *framecount, double *current, double *old ) { if( framecount ) *framecount = tr.framecount; if( current ) *current = cl.time; if( old ) *old = cl.oldtime; } /* =============== pfnGetViewInfo =============== */ static void pfnGetViewInfo( float *origin, float *upv, float *rightv, float *forwardv ) { if( origin ) VectorCopy( RI.vieworg, origin ); if( forwardv ) VectorCopy( RI.vforward, forwardv ); if( rightv ) VectorCopy( RI.vright, rightv ); if( upv ) VectorCopy( RI.vup, upv ); } /* =============== R_GetChromeSprite =============== */ static model_t *R_GetChromeSprite( void ) { if( cls.hChromeSprite <= 0 || cls.hChromeSprite > ( MAX_IMAGES - 1 )) return NULL; // bad sprite return &clgame.sprites[cls.hChromeSprite]; } /* =============== pfnGetModelCounters =============== */ static void pfnGetModelCounters( int **s, int **a ) { *s = &g_nStudioCount; *a = &r_stats.c_studio_models_drawn; } /* =============== pfnGetAliasScale =============== */ static void pfnGetAliasScale( float *x, float *y ) { if( x ) *x = aliasXscale; if( y ) *y = aliasYscale; } /* =============== pfnStudioGetBoneTransform =============== */ static float ****pfnStudioGetBoneTransform( void ) { return (float ****)g_bonestransform; } /* =============== pfnStudioGetLightTransform =============== */ static float ****pfnStudioGetLightTransform( void ) { return (float ****)g_lighttransform; } /* =============== pfnStudioGetAliasTransform =============== */ static float ***pfnStudioGetAliasTransform( void ) { return (float ***)g_aliastransform; } /* =============== pfnStudioGetRotationMatrix =============== */ static float ***pfnStudioGetRotationMatrix( void ) { return (float ***)g_rotationmatrix; } /* ==================== CullStudioModel ==================== */ qboolean R_CullStudioModel( cl_entity_t *e ) { vec3_t origin; if( !e->model->cache.data ) return true; if( e == &clgame.viewent && r_lefthand->integer >= 2 ) return true; // hidden if( !R_StudioComputeBBox( e, NULL )) return true; // invalid sequence // NOTE: extract real drawing origin from rotation matrix Matrix3x4_OriginFromMatrix( g_rotationmatrix, origin ); return R_CullModel( e, origin, studio_mins, studio_maxs, studio_radius ); } /* ==================== StudioPlayerBlend ==================== */ void R_StudioPlayerBlend( mstudioseqdesc_t *pseqdesc, int *pBlend, float *pPitch ) { // calc up/down pointing *pBlend = (*pPitch * 3); 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.0f * (*pBlend - pseqdesc->blendstart[0]) / (pseqdesc->blendend[0] - pseqdesc->blendstart[0]); *pPitch = 0; } } /* ==================== StudioSetUpTransform ==================== */ void R_StudioSetUpTransform( cl_entity_t *e ) { vec3_t origin, angles; float scale = 1.0f; VectorCopy( e->origin, origin ); VectorCopy( e->angles, angles ); // interpolate monsters position if( e->curstate.movetype == MOVETYPE_STEP ) { float d, f = 0.0f; int i; // 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( m_fDoInterp && ( RI.refdef.time < e->curstate.animtime + 1.0f ) && ( e->curstate.animtime != e->latched.prevanimtime )) { f = ( RI.refdef.time - e->curstate.animtime ) / ( e->curstate.animtime - e->latched.prevanimtime ); // Msg( "%4.2f %.2f %.2f\n", f, e->curstate.animtime, RI.refdef.time ); } if( m_fDoInterp ) { // ugly hack to interpolate angle, position. // current is reached 0.1 seconds after being set f = f - 1.0f; } origin[0] += ( e->curstate.origin[0] - e->latched.prevorigin[0] ) * f; origin[1] += ( e->curstate.origin[1] - e->latched.prevorigin[1] ) * f; origin[2] += ( e->curstate.origin[2] - e->latched.prevorigin[2] ) * f; for( i = 0; i < 3; i++ ) { float ang1, ang2; ang1 = e->curstate.angles[i]; ang2 = e->latched.prevangles[i]; d = ang1 - ang2; if( d > 180 ) d -= 360; else if( d < -180 ) d += 360; angles[i] += d * f; } } // stupid Half-Life bug angles[PITCH] = -angles[PITCH]; // don't rotate clients, only aim if( e->player ) angles[PITCH] = 0; if( clgame.movevars.studio_scale && e->curstate.scale > 0.0f ) scale = e->curstate.scale; Matrix3x4_CreateFromEntity( g_rotationmatrix, angles, origin, scale ); if( e == &clgame.viewent && r_lefthand->integer == 1 ) { // inverse the right vector (should work in Opposing Force) g_rotationmatrix[0][1] = -g_rotationmatrix[0][1]; g_rotationmatrix[1][1] = -g_rotationmatrix[1][1]; g_rotationmatrix[2][1] = -g_rotationmatrix[2][1]; } } /* ==================== StudioEstimateFrame ==================== */ float R_StudioEstimateFrame( cl_entity_t *e, mstudioseqdesc_t *pseqdesc ) { double dfdt, f; if( m_fDoInterp ) { if( RI.refdef.time < e->curstate.animtime ) dfdt = 0; else dfdt = (RI.refdef.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.0; 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( m_fDoInterp && ( e->curstate.animtime >= e->latched.prevanimtime + 0.01f )) { dadt = ( RI.refdef.time - e->curstate.animtime ) / 0.1f; if( dadt > 2.0f ) dadt = 2.0f; } return dadt; } /* ==================== StudioGetAnim ==================== */ mstudioanim_t *R_StudioGetAnim( model_t *m_pSubModel, mstudioseqdesc_t *pseqdesc ) { mstudioseqgroup_t *pseqgroup; cache_user_t *paSequences; size_t filesize; byte *buf; ASSERT( m_pSubModel ); pseqgroup = (mstudioseqgroup_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqgroupindex) + pseqdesc->seqgroup; if( pseqdesc->seqgroup == 0 ) return (mstudioanim_t *)((byte *)m_pStudioHeader + pseqgroup->data + pseqdesc->animindex); paSequences = (cache_user_t *)m_pSubModel->submodels; if( paSequences == NULL ) { paSequences = (cache_user_t *)Mem_Alloc( com_studiocache, MAXSTUDIOGROUPS * sizeof( cache_user_t )); m_pSubModel->submodels = (void *)paSequences; } // check for already loaded if( !Mod_CacheCheck(( cache_user_t *)&( paSequences[pseqdesc->seqgroup] ))) { string filepath, modelname, modelpath; FS_FileBase( m_pSubModel->name, modelname ); FS_ExtractFilePath( m_pSubModel->name, modelpath ); // NOTE: here we build real sub-animation filename because stupid user may rename model without recompile Q_snprintf( filepath, sizeof( filepath ), "%s/%s%i%i.mdl", modelpath, modelname, pseqdesc->seqgroup / 10, pseqdesc->seqgroup % 10 ); buf = FS_LoadFile( filepath, &filesize, false ); if( !buf || !filesize ) Host_Error( "StudioGetAnim: can't load %s\n", filepath ); if( IDSEQGRPHEADER != *(uint *)buf ) Host_Error( "StudioGetAnim: %s is corrupted\n", filepath ); MsgDev( D_INFO, "loading: %s\n", filepath ); paSequences[pseqdesc->seqgroup].data = Mem_Alloc( com_studiocache, filesize ); Q_memcpy( paSequences[pseqdesc->seqgroup].data, buf, filesize ); Mem_Free( buf ); } return (mstudioanim_t *)((byte *)paSequences[pseqdesc->seqgroup].data + pseqdesc->animindex); } /* ==================== StudioFxTransform ==================== */ void R_StudioFxTransform( cl_entity_t *ent, matrix3x4 transform ) { switch( ent->curstate.renderfx ) { case kRenderFxDistort: case kRenderFxHologram: if( !Com_RandomLong( 0, 49 )) { int axis = Com_RandomLong( 0, 1 ); if( axis == 1 ) axis = 2; // choose between x & z VectorScale( transform[axis], Com_RandomFloat( 1.0f, 1.484f ), transform[axis] ); } else if( !Com_RandomLong( 0, 49 )) { float offset; int axis = Com_RandomLong( 0, 1 ); if( axis == 1 ) axis = 2; // choose between x & z offset = Com_RandomFloat( -10, 10 ); transform[Com_RandomLong( 0, 2 )][3] += offset; } break; case kRenderFxExplode: { float scale; scale = 1.0f + ( RI.refdef.time - ent->curstate.animtime ) * 10.0f; if( scale > 2 ) scale = 2; // 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 ) { int i, j; float value; mstudiobonecontroller_t *pbonecontroller; 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 = mouthopen / 64.0f; if( value > 1.0f ) value = 1.0f; value = (1.0f - value) * pbonecontroller[j].start + value * pbonecontroller[j].end; } else if( i <= MAXSTUDIOCONTROLLERS ) { // check for 360% wrapping if( pbonecontroller[j].type & STUDIO_RLOOP ) { if( abs( pcontroller1[i] - pcontroller2[i] ) > 128 ) { float a, b; a = fmod(( pcontroller1[j] + 128 ), 256 ); b = fmod(( pcontroller2[j] + 128 ), 256 ); value = ((a * dadt) + (b * (1 - 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] = value * (M_PI / 180.0); break; case STUDIO_X: case STUDIO_Y: case STUDIO_Z: adj[j] = value; break; } } } /* ==================== StudioCalcBoneQuaterion ==================== */ void R_StudioCalcBoneQuaterion( int frame, float s, mstudiobone_t *pbone, mstudioanim_t *panim, float *adj, vec4_t q ) { int j, k; vec4_t q1, q2; vec3_t angle1, angle2; mstudioanimvalue_t *panimvalue; for( j = 0; j < 3; j++ ) { if( panim->offset[j+3] == 0 ) { angle2[j] = angle1[j] = pbone->value[j+3]; // default; } else { panimvalue = (mstudioanimvalue_t *)((byte *)panim + panim->offset[j+3]); k = frame; // debug if( panimvalue->num.total < panimvalue->num.valid ) k = 0; while( panimvalue->num.total <= k ) { k -= panimvalue->num.total; panimvalue += panimvalue->num.valid + 1; // debug if( panimvalue->num.total < panimvalue->num.valid ) k = 0; } // bah, missing blend! if( panimvalue->num.valid > k ) { angle1[j] = panimvalue[k+1].value; if( panimvalue->num.valid > k + 1 ) { angle2[j] = panimvalue[k+2].value; } else { if( panimvalue->num.total > k + 1 ) angle2[j] = angle1[j]; else angle2[j] = panimvalue[panimvalue->num.valid+2].value; } } else { angle1[j] = panimvalue[panimvalue->num.valid].value; if( panimvalue->num.total > k + 1 ) { angle2[j] = angle1[j]; } else { angle2[j] = panimvalue[panimvalue->num.valid + 2].value; } } angle1[j] = pbone->value[j+3] + angle1[j] * pbone->scale[j+3]; angle2[j] = pbone->value[j+3] + angle2[j] * pbone->scale[j+3]; } if( pbone->bonecontroller[j+3] != -1 ) { angle1[j] += adj[pbone->bonecontroller[j+3]]; angle2[j] += adj[pbone->bonecontroller[j+3]]; } } if( !VectorCompare( angle1, angle2 )) { AngleQuaternion( angle1, q1 ); AngleQuaternion( angle2, q2 ); QuaternionSlerp( q1, q2, s, q ); } else { AngleQuaternion( angle1, q ); } } /* ==================== StudioCalcBonePosition ==================== */ void R_StudioCalcBonePosition( int frame, float s, mstudiobone_t *pbone, mstudioanim_t *panim, vec3_t adj, vec3_t pos ) { mstudioanimvalue_t *panimvalue; int j, k; for( j = 0; j < 3; j++ ) { pos[j] = pbone->value[j]; // default; if( panim->offset[j] != 0 ) { panimvalue = (mstudioanimvalue_t *)((byte *)panim + panim->offset[j]); k = frame; // debug if( panimvalue->num.total < panimvalue->num.valid ) k = 0; // find span of values that includes the frame we want while( panimvalue->num.total <= k ) { k -= panimvalue->num.total; panimvalue += panimvalue->num.valid + 1; // debug if( panimvalue->num.total < panimvalue->num.valid ) k = 0; } // if we're inside the span if( panimvalue->num.valid > k ) { // and there's more data in the span if( panimvalue->num.valid > k + 1 ) pos[j] += (panimvalue[k+1].value * (1.0f - s) + s * panimvalue[k+2].value) * pbone->scale[j]; else pos[j] += panimvalue[k+1].value * pbone->scale[j]; } else { // are we at the end of the repeating values section and there's another section with data? if( panimvalue->num.total <= k + 1 ) pos[j] += (panimvalue[panimvalue->num.valid].value * (1.0f - s) + s * panimvalue[panimvalue->num.valid + 2].value) * pbone->scale[j]; else pos[j] += panimvalue[panimvalue->num.valid].value * pbone->scale[j]; } } if( pbone->bonecontroller[j] != -1 && adj ) pos[j] += adj[pbone->bonecontroller[j]]; } } /* ==================== StudioSlerpBones ==================== */ void R_StudioSlerpBones( vec4_t q1[], float pos1[][3], vec4_t q2[], float pos2[][3], float s ) { int i; vec4_t q3; float s1; s = bound( 0.0f, s, 1.0f ); s1 = 1.0f - s; // backlerp for( i = 0; i < m_pStudioHeader->numbones; i++ ) { QuaternionSlerp( q1[i], q2[i], s, q3 ); q1[i][0] = q3[0]; q1[i][1] = q3[1]; q1[i][2] = q3[2]; q1[i][3] = q3[3]; pos1[i][0] = pos1[i][0] * s1 + pos2[i][0] * s; pos1[i][1] = pos1[i][1] * s1 + pos2[i][1] * s; pos1[i][2] = pos1[i][2] * s1 + pos2[i][2] * s; } } /* ==================== 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; mstudiobone_t *pbone; float adj[MAXSTUDIOCONTROLLERS]; float s, dadt; if( f > pseqdesc->numframes - 1 ) { f = 0.0f; // bah, fix this bug with changing sequences too fast } else if( f < -0.01f ) { // BUGBUG ( 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 MsgDev( D_ERROR, "StudioCalcRotations: f = %g\n", f ); 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_StudioCalcBoneQuaterion( 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; s = 0 * ((1.0f - (f - (int)(f))) / (pseqdesc->numframes)) * e->curstate.framerate; if( pseqdesc->motiontype & STUDIO_LX ) pos[pseqdesc->motionbone][0] += s * pseqdesc->linearmovement[0]; if( pseqdesc->motiontype & STUDIO_LY ) pos[pseqdesc->motionbone][1] += s * pseqdesc->linearmovement[1]; if( pseqdesc->motiontype & STUDIO_LZ ) pos[pseqdesc->motionbone][2] += s * pseqdesc->linearmovement[2]; } /* ==================== 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]; double 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 ); panim = R_StudioGetAnim( 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_nCachedBones; j++ ) { if( !Q_stricmp( pbones[i].name, g_nCachedBoneNames[j] )) { Matrix3x4_Copy( g_bonestransform[i], g_rgCachedBonesTransform[j] ); Matrix3x4_Copy( g_lighttransform[i], g_rgCachedLightTransform[j] ); break; } } if( j >= g_nCachedBones ) { Matrix3x4_FromOriginQuat( bonematrix, q[i], pos[i] ); if( pbones[i].parent == -1 ) { Matrix3x4_ConcatTransforms( g_bonestransform[i], g_rotationmatrix, bonematrix ); Matrix3x4_Copy( g_lighttransform[i], g_bonestransform[i] ); // apply client-side effects to the transformation matrix R_StudioFxTransform( e, g_bonestransform[i] ); } else { Matrix3x4_ConcatTransforms( g_bonestransform[i], g_bonestransform[pbones[i].parent], bonematrix ); Matrix3x4_ConcatTransforms( g_lighttransform[i], g_lighttransform[pbones[i].parent], bonematrix ); } } } } /* ==================== StudioSetupBones ==================== */ void R_StudioSetupBones( cl_entity_t *e ) { double 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 ); panim = R_StudioGetAnim( e->model, 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( 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( q3, pos3, q4, pos4, s ); s = (e->curstate.blending[1] * dadt + e->latched.prevblending[1] * (1.0f - dadt)) / 255.0f; R_StudioSlerpBones( q, pos, q3, pos3, s ); } } if( m_fDoInterp && e->latched.sequencetime && ( e->latched.sequencetime + 0.2f > RI.refdef.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 = R_StudioGetAnim( e->model, 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( 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( q3, pos3, q4, pos4, s ); s = (e->latched.prevseqblending[1]) / 255.0f; R_StudioSlerpBones( q1b, pos1b, q3, pos3, s ); } } s = 1.0f - ( RI.refdef.time - e->latched.sequencetime ) / 0.2f; R_StudioSlerpBones( 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 ) { if( m_pPlayerInfo->gaitsequence >= m_pStudioHeader->numseq ) m_pPlayerInfo->gaitsequence = 0; pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + m_pPlayerInfo->gaitsequence; panim = R_StudioGetAnim( e->model, 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" )) break; 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_bonestransform[i], g_rotationmatrix, bonematrix ); Matrix3x4_Copy( g_lighttransform[i], g_bonestransform[i] ); // apply client-side effects to the transformation matrix R_StudioFxTransform( e, g_bonestransform[i] ); } else { Matrix3x4_ConcatTransforms( g_bonestransform[i], g_bonestransform[pbones[i].parent], bonematrix ); Matrix3x4_ConcatTransforms( g_lighttransform[i], g_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_nCachedBones = m_pStudioHeader->numbones; for( i = 0; i < m_pStudioHeader->numbones; i++ ) { Q_strcpy( g_nCachedBoneNames[i], pbones[i].name ); Matrix3x4_Copy( g_rgCachedBonesTransform[i], g_bonestransform[i] ); Matrix3x4_Copy( g_rgCachedLightTransform[i], g_lighttransform[i] ); } } /* ==================== StudioSetupChrome ==================== */ void R_StudioSetupChrome( float *pchrome, int bone, vec3_t normal ) { float n; if( g_chromeage[bone] != g_nStudioCount ) { // calculate vectors from the viewer to the bone. This roughly adjusts for position vec3_t chromeupvec; // g_chrome t vector in world reference frame vec3_t chromerightvec; // g_chrome s vector in world reference frame vec3_t tmp, v_left; // vector pointing at bone in world reference frame VectorCopy( g_chrome_origin, tmp ); tmp[0] += g_bonestransform[bone][0][3]; tmp[1] += g_bonestransform[bone][1][3]; tmp[2] += g_bonestransform[bone][2][3]; VectorNormalize( tmp ); VectorNegate( RI.vright, v_left ); if( g_nForceFaceFlags & STUDIO_NF_CHROME ) { float angle, sr, cr; int i; angle = anglemod( RI.refdef.time * 40 ) * (M_PI * 2.0f / 360.0f); SinCos( angle, &sr, &cr ); for( i = 0; i < 3; i++ ) { chromerightvec[i] = (v_left[i] * cr + RI.vup[i] * sr); chromeupvec[i] = v_left[i] * -sr + RI.vup[i] * cr; } } else { CrossProduct( tmp, v_left, chromeupvec ); VectorNormalize( chromeupvec ); CrossProduct( tmp, chromeupvec, chromerightvec ); VectorNormalize( chromerightvec ); } Matrix3x4_VectorIRotate( g_bonestransform[bone], chromeupvec, g_chromeup[bone] ); Matrix3x4_VectorIRotate( g_bonestransform[bone], chromerightvec, g_chromeright[bone] ); g_chromeage[bone] = g_nStudioCount; } // calc s coord n = DotProduct( normal, g_chromeright[bone] ); pchrome[0] = (n + 1.0f) * 32.0f; // calc t coord n = DotProduct( normal, g_chromeup[bone] ); pchrome[1] = (n + 1.0f) * 32.0f; } /* ==================== StudioCalcAttachments ==================== */ static void R_StudioCalcAttachments( void ) { mstudioattachment_t *pAtt; vec3_t forward, bonepos; vec3_t localOrg, localAng; int i; if( m_pStudioHeader->numattachments <= 0 ) { // clear attachments for( i = 0; i < MAXSTUDIOATTACHMENTS; i++ ) VectorClear( RI.currententity->attachment[i] ); return; } else if( m_pStudioHeader->numattachments > MAXSTUDIOATTACHMENTS ) { m_pStudioHeader->numattachments = MAXSTUDIOATTACHMENTS; // reduce it MsgDev( D_WARN, "R_StudioCalcAttahments: too many attachments on %s\n", RI.currentmodel->name ); } // calculate attachment points pAtt = (mstudioattachment_t *)((byte *)m_pStudioHeader + m_pStudioHeader->attachmentindex); for( i = 0; i < m_pStudioHeader->numattachments; i++ ) { Matrix3x4_VectorTransform( g_lighttransform[pAtt[i].bone], pAtt[i].org, RI.currententity->attachment[i] ); VectorSubtract( RI.currententity->attachment[i], RI.currententity->origin, localOrg ); Matrix3x4_OriginFromMatrix( g_lighttransform[pAtt[i].bone], bonepos ); VectorSubtract( localOrg, bonepos, forward ); // make forward VectorNormalizeFast( forward ); VectorAngles( forward, localAng ); } } /* =============== 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( R_CullStudioModel( RI.currententity )) return false; return true; } /* =============== R_StudioGetShadowImpactAndDir =============== */ void R_StudioGetShadowImpactAndDir( void ) { float angle; vec3_t skyAngles, origin, end; if( !RI.refdef.movevars ) return; // e.g. in menu // convert skyvec into angles then back into vector to avoid 0 0 0 direction VectorAngles( (float *)&RI.refdef.movevars->skyvec_x, skyAngles ); angle = skyAngles[YAW] / 180 * M_PI; Matrix3x4_OriginFromMatrix( g_bonestransform[0], origin ); SinCos( angle, &g_mvShadowVec[1], &g_mvShadowVec[0] ); // g-cont. looks ugly. disabled // R_LightDir( origin, g_mvShadowVec, 256.0f ); VectorSet( g_mvShadowVec, -g_mvShadowVec[0], -g_mvShadowVec[1], -1.0f ); VectorNormalizeFast( g_mvShadowVec ); VectorMA( origin, 256.0f, g_mvShadowVec, end ); g_shadowTrace = PM_PlayerTrace( clgame.pmove, origin, end, PM_STUDIO_IGNORE, 2, -1, NULL ); } /* =============== R_StudioDynamicLight =============== */ void R_StudioDynamicLight( cl_entity_t *ent, alight_t *lightinfo ) { uint lnum, i; studiolight_t *plight; qboolean invLight; color24 ambient; float dist, radius2; vec3_t direction, origin; dlight_t *dl; if( !lightinfo ) return; plight = &g_studiolight; plight->numdlights = 0; // clear previous dlights if( r_studio_lighting->integer == 2 ) Matrix3x4_OriginFromMatrix( g_lighttransform[0], origin ); else Matrix3x4_OriginFromMatrix( g_rotationmatrix, origin ); // setup light dir if( RI.refdef.movevars ) { // pre-defined light vector plight->lightvec[0] = RI.refdef.movevars->skyvec_x; plight->lightvec[1] = RI.refdef.movevars->skyvec_y; plight->lightvec[2] = RI.refdef.movevars->skyvec_z; } else VectorSet( plight->lightvec, 0.0f, 0.0f, -1.0f ); VectorCopy( plight->lightvec, lightinfo->plightvec ); // setup ambient lighting invLight = (ent->curstate.effects & EF_INVLIGHT) ? true : false; R_LightForPoint( origin, &ambient, invLight, true, 0.0f ); // ignore dlights R_GetLightSpot( plight->lightspot ); // shadow stuff plight->lightcolor[0] = ambient.r * (1.0f / 255.0f); plight->lightcolor[1] = ambient.g * (1.0f / 255.0f); plight->lightcolor[2] = ambient.b * (1.0f / 255.0f); VectorCopy( plight->lightcolor, lightinfo->color ); lightinfo->shadelight = (ambient.r + ambient.g + ambient.b) / 3; lightinfo->ambientlight = lightinfo->shadelight; if( !ent || !ent->model || !r_dynamic->integer ) return; for( lnum = 0, dl = cl_dlights; lnum < MAX_DLIGHTS; lnum++, dl++ ) { if( dl->die < RI.refdef.time || !dl->radius ) continue; VectorSubtract( dl->origin, origin, direction ); dist = VectorLength( direction ); if( !dist || dist > dl->radius + ent->model->radius ) continue; radius2 = dl->radius * dl->radius; // squared radius for( i = 0; i < m_pStudioHeader->numbones; i++ ) { vec3_t vec, org; float dist, atten; Matrix3x4_OriginFromMatrix( g_lighttransform[i], org ); VectorSubtract( org, dl->origin, vec ); dist = DotProduct( vec, vec ); atten = (dist / radius2 - 1) * -1; if( atten < 0 ) atten = 0; dist = sqrt( dist ); if( dist ) { dist = 1.0f / dist; VectorScale( vec, dist, vec ); lightinfo->ambientlight += atten; lightinfo->shadelight += atten; } Matrix3x4_VectorIRotate( g_lighttransform[i], vec, plight->dlightvec[plight->numdlights][i] ); VectorScale( plight->dlightvec[plight->numdlights][i], atten, plight->dlightvec[plight->numdlights][i] ); } plight->dlightcolor[plight->numdlights][0] = dl->color.r * (1.0f / 255.0f); plight->dlightcolor[plight->numdlights][1] = dl->color.g * (1.0f / 255.0f); plight->dlightcolor[plight->numdlights][2] = dl->color.b * (1.0f / 255.0f); plight->numdlights++; } // clamp lighting so it doesn't overbright as much if( lightinfo->ambientlight > 128 ) lightinfo->ambientlight = 128; if( lightinfo->ambientlight + lightinfo->shadelight > 192 ) lightinfo->shadelight = 192 - lightinfo->ambientlight; } /* =============== pfnStudioEntityLight =============== */ void R_StudioEntityLight( alight_t *lightinfo ) { uint lnum, i; studiolight_t *plight; float dist, radius2; vec3_t direction, origin; cl_entity_t *ent; dlight_t *el; ent = RI.currententity; if( !ent || !ent->model || !r_dynamic->integer ) return; plight = &g_studiolight; plight->numelights = 0; // clear previous elights if( r_studio_lighting->integer == 2 ) Matrix3x4_OriginFromMatrix( g_lighttransform[0], origin ); else Matrix3x4_OriginFromMatrix( g_rotationmatrix, origin ); for( lnum = 0, el = cl_elights; lnum < MAX_ELIGHTS; lnum++, el++ ) { if( el->die < RI.refdef.time || !el->radius ) continue; VectorSubtract( el->origin, origin, direction ); dist = VectorLength( direction ); if( !dist || dist > el->radius + ent->model->radius ) continue; radius2 = el->radius * el->radius; // squared radius for( i = 0; i < m_pStudioHeader->numbones; i++ ) { vec3_t vec, org; float dist, atten; Matrix3x4_OriginFromMatrix( g_lighttransform[i], org ); VectorSubtract( org, origin, vec ); dist = DotProduct( vec, vec ); atten = (dist / radius2 - 1) * -1; if( atten < 0 ) atten = 0; dist = sqrt( dist ); if( dist ) { dist = 1.0f / dist; VectorScale( vec, dist, vec ); lightinfo->ambientlight += atten; lightinfo->shadelight += atten; } Matrix3x4_VectorIRotate( g_lighttransform[i], vec, plight->elightvec[plight->numelights][i] ); VectorScale( plight->elightvec[plight->numelights][i], atten, plight->elightvec[plight->numelights][i] ); } plight->elightcolor[plight->numelights][0] = el->color.r * (1.0f / 255.0f); plight->elightcolor[plight->numelights][1] = el->color.g * (1.0f / 255.0f); plight->elightcolor[plight->numelights][2] = el->color.b * (1.0f / 255.0f); plight->numelights++; } // clamp lighting so it doesn't overbright as much if( lightinfo->ambientlight > 128 ) lightinfo->ambientlight = 128; if( lightinfo->ambientlight + lightinfo->shadelight > 192 ) lightinfo->shadelight = 192 - lightinfo->ambientlight; } /* =============== R_StudioSetupLighting =============== */ void R_StudioSetupLighting( alight_t *lightinfo ) { studiolight_t *plight; int i; plight = &g_studiolight; for( i = 0; i < m_pStudioHeader->numbones; i++ ) Matrix3x4_VectorIRotate( g_lighttransform[i], lightinfo->plightvec, plight->blightvec[i] ); R_StudioGetShadowImpactAndDir(); } /* =============== R_StudioLighting =============== */ void R_StudioLighting( float *lv, int bone, int flags, vec3_t normal ) { float max, ambient; vec3_t illum; studiolight_t *plight; if( !RI.drawWorld || RI.currententity->curstate.effects & EF_FULLBRIGHT ) { VectorSet( lv, 1.0f, 1.0f, 1.0f ); return; } plight = &g_studiolight; ambient = max( 0.1f, r_lighting_ambient->value ); // to avoid divison by zero VectorScale( plight->lightcolor, ambient, illum ); if( flags & STUDIO_NF_FLATSHADE ) { VectorMA( illum, 0.8f, plight->lightcolor, illum ); } else { float r, lightcos; int i; lightcos = DotProduct( normal, plight->blightvec[bone] ); // -1 colinear, 1 opposite if( lightcos > 1.0f ) lightcos = 1; VectorAdd( illum, plight->lightcolor, illum ); r = r_studio_lambert->value; if( r < 1.0f ) r = 1.0f; lightcos = (lightcos + ( r - 1.0f )) / r; // do modified hemispherical lighting if( lightcos > 0.0f ) VectorMA( illum, -lightcos, plight->lightcolor, illum ); if( illum[0] <= 0 ) illum[0] = 0; if( illum[1] <= 0 ) illum[1] = 0; if( illum[2] <= 0 ) illum[2] = 0; // now add all dynamic lights for( i = 0; i < plight->numdlights; i++) { lightcos = -DotProduct( normal, plight->dlightvec[i][bone] ); if( lightcos > 0 ) VectorMA( illum, lightcos, plight->dlightcolor[i], illum ); } // now add all entity lights for( i = 0; i < plight->numelights; i++) { lightcos = -DotProduct( normal, plight->elightvec[i][bone] ); if( lightcos > 0 ) VectorMA( illum, lightcos, plight->elightcolor[i], illum ); } } max = VectorMax( illum ); if( max > 1.0f ) VectorScale( illum, ( 1.0f / max ), lv ); else VectorCopy( illum, lv ); } /* =============== R_StudioSetupTextureHeader =============== */ void R_StudioSetupTextureHeader( void ) { #ifndef STUDIO_MERGE_TEXTURES if( !m_pStudioHeader->numtextures || !m_pStudioHeader->textureindex ) { string texturename; model_t *textures = NULL; Q_strncpy( texturename, R_StudioTexName( RI.currentmodel ), sizeof( texturename )); COM_FixSlashes( texturename ); if( FS_FileExists( texturename, false )) textures = Mod_ForName( texturename, false ); if( !textures ) { m_pTextureHeader = NULL; return; } m_pTextureHeader = (studiohdr_t *)Mod_Extradata( textures ); } else #endif { m_pTextureHeader = m_pStudioHeader; } } /* =============== R_StudioSetupSkin =============== */ static void R_StudioSetupSkin( mstudiotexture_t *ptexture, int index ) { short *pskinref; int m_skinnum; R_StudioSetupTextureHeader (); if( !m_pTextureHeader ) return; // NOTE: user can comment call StudioRemapColors and remap_info will be unavailable if( m_fDoRemap ) ptexture = CL_GetRemapInfoForEntity( RI.currententity )->ptexture; m_skinnum = RI.currententity->curstate.skin; pskinref = (short *)((byte *)m_pTextureHeader + m_pTextureHeader->skinindex); if( m_skinnum != 0 && m_skinnum < m_pTextureHeader->numskinfamilies ) pskinref += (m_skinnum * m_pTextureHeader->numskinref); GL_Bind( GL_TEXTURE0, ptexture[pskinref[index]].index ); } /* =============== R_StudioDrawPoints =============== */ static void R_StudioDrawPoints( void ) { int i, j, m_skinnum; byte *pvertbone; byte *pnormbone; vec3_t *pstudioverts; vec3_t *pstudionorms; mstudiotexture_t *ptexture; mstudiomesh_t *pmesh; short *pskinref; float *av, *lv, *nv, scale; R_StudioSetupTextureHeader (); g_nNumArrayVerts = g_nNumArrayElems = 0; if( !m_pTextureHeader ) return; if( RI.currententity->curstate.renderfx == kRenderFxGlowShell ) g_nStudioCount++; m_skinnum = RI.currententity->curstate.skin; pvertbone = ((byte *)m_pStudioHeader + m_pSubModel->vertinfoindex); pnormbone = ((byte *)m_pStudioHeader + m_pSubModel->norminfoindex); // NOTE: user can comment call StudioRemapColors and remap_info will be unavailable if( m_fDoRemap ) ptexture = CL_GetRemapInfoForEntity( RI.currententity )->ptexture; else ptexture = (mstudiotexture_t *)((byte *)m_pTextureHeader + m_pTextureHeader->textureindex); ASSERT( ptexture != NULL ); pmesh = (mstudiomesh_t *)((byte *)m_pStudioHeader + m_pSubModel->meshindex); pstudioverts = (vec3_t *)((byte *)m_pStudioHeader + m_pSubModel->vertindex); pstudionorms = (vec3_t *)((byte *)m_pStudioHeader + m_pSubModel->normindex); pskinref = (short *)((byte *)m_pTextureHeader + m_pTextureHeader->skinindex); if( m_skinnum != 0 && m_skinnum < m_pTextureHeader->numskinfamilies ) pskinref += (m_skinnum * m_pTextureHeader->numskinref); for( i = 0; i < m_pSubModel->numverts; i++ ) Matrix3x4_VectorTransform( g_bonestransform[pvertbone[i]], pstudioverts[i], g_xformverts[i] ); if( g_nForceFaceFlags & STUDIO_NF_CHROME ) { scale = 1.0f + RI.currententity->curstate.renderamt * (1.0f / 255.0f); for( i = 0; i < m_pSubModel->numnorms; i++ ) Matrix3x4_VectorRotate( g_bonestransform[pnormbone[i]], pstudionorms[i], g_xformnorms[i] ); } lv = (float *)g_lightvalues; for( j = 0; j < m_pSubModel->nummesh; j++ ) { g_nFaceFlags = ptexture[pskinref[pmesh[j].skinref]].flags; for( i = 0; i < pmesh[j].numnorms; i++, lv += 3, pstudionorms++, pnormbone++ ) { R_StudioLighting( lv, *pnormbone, g_nFaceFlags, (float *)pstudionorms ); if(( g_nFaceFlags & STUDIO_NF_CHROME ) || ( g_nForceFaceFlags & STUDIO_NF_CHROME )) R_StudioSetupChrome( g_chrome[(float (*)[3])lv - g_lightvalues], *pnormbone, (float *)pstudionorms ); } } for( j = 0; j < m_pSubModel->nummesh; j++ ) { float s, t, alpha; short *ptricmds; pmesh = (mstudiomesh_t *)((byte *)m_pStudioHeader + m_pSubModel->meshindex) + j; ptricmds = (short *)((byte *)m_pStudioHeader + pmesh->triindex); g_nFaceFlags = ptexture[pskinref[pmesh->skinref]].flags; s = 1.0f / (float)ptexture[pskinref[pmesh->skinref]].width; t = 1.0f / (float)ptexture[pskinref[pmesh->skinref]].height; // check bounds if( ptexture[pskinref[pmesh->skinref]].index < 0 || ptexture[pskinref[pmesh->skinref]].index > MAX_TEXTURES ) ptexture[pskinref[pmesh->skinref]].index = tr.defaultTexture; if( g_nForceFaceFlags & STUDIO_NF_CHROME ) { color24 *clr; clr = &RI.currententity->curstate.rendercolor; pglColor4ub( clr->r, clr->g, clr->b, 255 ); alpha = 1.0f; } else if( g_nFaceFlags & STUDIO_NF_TRANSPARENT ) { GL_SetRenderMode( kRenderTransAlpha ); alpha = 1.0f; } else if( g_nFaceFlags & STUDIO_NF_ADDITIVE ) { GL_SetRenderMode( kRenderTransAdd ); alpha = RI.currententity->curstate.renderamt * (1.0f / 255.0f); } else { GL_SetRenderMode( g_iRenderMode ); if( g_iRenderMode == kRenderNormal ) { pglTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); alpha = 1.0f; } else alpha = RI.currententity->curstate.renderamt * (1.0f / 255.0f); } if(!( g_nForceFaceFlags & STUDIO_NF_CHROME )) { GL_Bind( GL_TEXTURE0, ptexture[pskinref[pmesh->skinref]].index ); } while( i = *( ptricmds++ )) { int vertexState = 0; qboolean tri_strip; if( i < 0 ) { pglBegin( GL_TRIANGLE_FAN ); tri_strip = false; i = -i; } else { pglBegin( GL_TRIANGLE_STRIP ); tri_strip = true; } r_stats.c_studio_polys++; for( ; i > 0; i--, ptricmds += 4 ) { // build in indices if( vertexState++ < 3 ) { g_xarrayelems[g_nNumArrayElems++] = g_nNumArrayVerts; } else if( tri_strip ) { // flip triangles between clockwise and counter clockwise if( vertexState & 1 ) { // draw triangle [n-2 n-1 n] g_xarrayelems[g_nNumArrayElems++] = g_nNumArrayVerts - 2; g_xarrayelems[g_nNumArrayElems++] = g_nNumArrayVerts - 1; g_xarrayelems[g_nNumArrayElems++] = g_nNumArrayVerts; } else { // draw triangle [n-1 n-2 n] g_xarrayelems[g_nNumArrayElems++] = g_nNumArrayVerts - 1; g_xarrayelems[g_nNumArrayElems++] = g_nNumArrayVerts - 2; g_xarrayelems[g_nNumArrayElems++] = g_nNumArrayVerts; } } else { // draw triangle fan [0 n-1 n] g_xarrayelems[g_nNumArrayElems++] = g_nNumArrayVerts - ( vertexState - 1 ); g_xarrayelems[g_nNumArrayElems++] = g_nNumArrayVerts - 1; g_xarrayelems[g_nNumArrayElems++] = g_nNumArrayVerts; } if( g_nFaceFlags & STUDIO_NF_CHROME || ( g_nForceFaceFlags & STUDIO_NF_CHROME )) pglTexCoord2f( g_chrome[ptricmds[1]][0] * s, g_chrome[ptricmds[1]][1] * t ); else pglTexCoord2f( ptricmds[2] * s, ptricmds[3] * t ); if(!( g_nForceFaceFlags & STUDIO_NF_CHROME )) { if( g_iRenderMode == kRenderTransAdd ) { pglColor4f( 1.0f, 1.0f, 1.0f, alpha ); } else if( g_iRenderMode == kRenderTransColor ) { color24 *clr; clr = &RI.currententity->curstate.rendercolor; pglColor4ub( clr->r, clr->g, clr->b, alpha * 255 ); } else { lv = (float *)g_lightvalues[ptricmds[1]]; pglColor4f( lv[0], lv[1], lv[2], alpha ); } } av = g_xformverts[ptricmds[0]]; if( g_nForceFaceFlags & STUDIO_NF_CHROME ) { vec3_t scaled_vertex; nv = (float *)g_xformnorms[ptricmds[1]]; VectorMA( av, scale, nv, scaled_vertex ); pglVertex3fv( scaled_vertex ); } else { pglVertex3f( av[0], av[1], av[2] ); ASSERT( g_nNumArrayVerts < MAXARRAYVERTS ); VectorCopy( av, g_xarrayverts[g_nNumArrayVerts] ); // store off vertex g_nNumArrayVerts++; } } pglEnd(); } } } /* =============== R_StudioDrawHulls =============== */ static void R_StudioDrawHulls( void ) { int i, j; float alpha; if( r_drawentities->integer == 4 ) alpha = 0.5f; else alpha = 1.0f; pglDisable( GL_TEXTURE_2D ); if( r_drawentities->integer == 4 ) pglDisable( GL_DEPTH_TEST ); for( i = 0; i < m_pStudioHeader->numhitboxes; i++ ) { mstudiobbox_t *pbboxes = (mstudiobbox_t *)((byte *)m_pStudioHeader + m_pStudioHeader->hitboxindex); vec3_t v[8], v2[8], bbmin, bbmax; VectorCopy( pbboxes[i].bbmin, bbmin ); VectorCopy( pbboxes[i].bbmax, bbmax ); v[0][0] = bbmin[0]; v[0][1] = bbmax[1]; v[0][2] = bbmin[2]; v[1][0] = bbmin[0]; v[1][1] = bbmin[1]; v[1][2] = bbmin[2]; v[2][0] = bbmax[0]; v[2][1] = bbmax[1]; v[2][2] = bbmin[2]; v[3][0] = bbmax[0]; v[3][1] = bbmin[1]; v[3][2] = bbmin[2]; v[4][0] = bbmax[0]; v[4][1] = bbmax[1]; v[4][2] = bbmax[2]; v[5][0] = bbmax[0]; v[5][1] = bbmin[1]; v[5][2] = bbmax[2]; v[6][0] = bbmin[0]; v[6][1] = bbmax[1]; v[6][2] = bbmax[2]; v[7][0] = bbmin[0]; v[7][1] = bbmin[1]; v[7][2] = bbmax[2]; Matrix3x4_VectorTransform( g_bonestransform[pbboxes[i].bone], v[0], v2[0] ); Matrix3x4_VectorTransform( g_bonestransform[pbboxes[i].bone], v[1], v2[1] ); Matrix3x4_VectorTransform( g_bonestransform[pbboxes[i].bone], v[2], v2[2] ); Matrix3x4_VectorTransform( g_bonestransform[pbboxes[i].bone], v[3], v2[3] ); Matrix3x4_VectorTransform( g_bonestransform[pbboxes[i].bone], v[4], v2[4] ); Matrix3x4_VectorTransform( g_bonestransform[pbboxes[i].bone], v[5], v2[5] ); Matrix3x4_VectorTransform( g_bonestransform[pbboxes[i].bone], v[6], v2[6] ); Matrix3x4_VectorTransform( g_bonestransform[pbboxes[i].bone], v[7], v2[7] ); j = (pbboxes[i].group % 8); // set properly color for hull pglColor4f( hullcolor[j][0], hullcolor[j][1], hullcolor[j][2], alpha ); pglBegin( GL_QUAD_STRIP ); for( j = 0; j < 10; j++ ) pglVertex3fv( v2[j & 7] ); pglEnd( ); pglBegin( GL_QUAD_STRIP ); pglVertex3fv( v2[6] ); pglVertex3fv( v2[0] ); pglVertex3fv( v2[4] ); pglVertex3fv( v2[2] ); pglEnd( ); pglBegin( GL_QUAD_STRIP ); pglVertex3fv( v2[1] ); pglVertex3fv( v2[7] ); pglVertex3fv( v2[3] ); pglVertex3fv( v2[5] ); pglEnd( ); } pglEnable( GL_TEXTURE_2D ); if( r_drawentities->integer == 4 ) pglEnable( GL_DEPTH_TEST ); } /* =============== R_StudioDrawAbsBBox =============== */ static void R_StudioDrawAbsBBox( void ) { vec3_t bbox[8]; int i; // looks ugly, skip if( RI.currententity == &clgame.viewent ) return; if( !R_StudioComputeBBox( RI.currententity, bbox )) return; pglDisable( GL_TEXTURE_2D ); pglDisable( GL_DEPTH_TEST ); pglColor4f( 1.0f, 0.0f, 0.0f, 1.0f ); // red bboxes for studiomodels pglBegin( GL_LINES ); for( i = 0; i < 2; i += 1 ) { pglVertex3fv( bbox[i+0] ); pglVertex3fv( bbox[i+2] ); pglVertex3fv( bbox[i+4] ); pglVertex3fv( bbox[i+6] ); pglVertex3fv( bbox[i+0] ); pglVertex3fv( bbox[i+4] ); pglVertex3fv( bbox[i+2] ); pglVertex3fv( bbox[i+6] ); pglVertex3fv( bbox[i*2+0] ); pglVertex3fv( bbox[i*2+1] ); pglVertex3fv( bbox[i*2+4] ); pglVertex3fv( bbox[i*2+5] ); } pglEnd(); pglEnable( GL_TEXTURE_2D ); pglEnable( GL_DEPTH_TEST ); } /* =============== R_StudioDrawBones =============== */ static void R_StudioDrawBones( void ) { mstudiobone_t *pbones = (mstudiobone_t *) ((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); vec3_t point; int i; pglDisable( GL_TEXTURE_2D ); for( i = 0; i < m_pStudioHeader->numbones; i++ ) { if( pbones[i].parent >= 0 ) { pglPointSize( 3.0f ); pglColor3f( 1, 0.7f, 0 ); pglBegin( GL_LINES ); Matrix3x4_OriginFromMatrix( g_bonestransform[pbones[i].parent], point ); pglVertex3fv( point ); Matrix3x4_OriginFromMatrix( g_bonestransform[i], point ); pglVertex3fv( point ); pglEnd(); pglColor3f( 0, 0, 0.8f ); pglBegin( GL_POINTS ); if( pbones[pbones[i].parent].parent != -1 ) { Matrix3x4_OriginFromMatrix( g_bonestransform[pbones[i].parent], point ); pglVertex3fv( point ); } Matrix3x4_OriginFromMatrix( g_bonestransform[i], point ); pglVertex3fv( point ); pglEnd(); } else { // draw parent bone node pglPointSize( 5.0f ); pglColor3f( 0.8f, 0, 0 ); pglBegin( GL_POINTS ); Matrix3x4_OriginFromMatrix( g_bonestransform[i], point ); pglVertex3fv( point ); pglEnd(); } } pglPointSize( 1.0f ); pglEnable( GL_TEXTURE_2D ); } static void R_StudioDrawAttachments( void ) { int i; pglDisable( GL_TEXTURE_2D ); pglDisable( GL_DEPTH_TEST ); for( i = 0; i < m_pStudioHeader->numattachments; i++ ) { mstudioattachment_t *pattachments; vec3_t v[4]; pattachments = (mstudioattachment_t *) ((byte *)m_pStudioHeader + m_pStudioHeader->attachmentindex); Matrix3x4_VectorTransform( g_bonestransform[pattachments[i].bone], pattachments[i].org, v[0] ); Matrix3x4_VectorTransform( g_bonestransform[pattachments[i].bone], pattachments[i].vectors[0], v[1] ); Matrix3x4_VectorTransform( g_bonestransform[pattachments[i].bone], pattachments[i].vectors[1], v[2] ); Matrix3x4_VectorTransform( g_bonestransform[pattachments[i].bone], pattachments[i].vectors[2], v[3] ); pglBegin( GL_LINES ); pglColor3f( 1, 0, 0 ); pglVertex3fv( v[0] ); pglColor3f( 1, 1, 1 ); pglVertex3fv (v[1] ); pglColor3f( 1, 0, 0 ); pglVertex3fv (v[0] ); pglColor3f( 1, 1, 1 ); pglVertex3fv (v[2] ); pglColor3f( 1, 0, 0 ); pglVertex3fv (v[0] ); pglColor3f( 1, 1, 1 ); pglVertex3fv( v[3] ); pglEnd(); pglPointSize( 5.0f ); pglColor3f( 0, 1, 0 ); pglBegin( GL_POINTS ); pglVertex3fv( v[0] ); pglEnd(); pglPointSize( 1.0f ); } pglEnable( GL_TEXTURE_2D ); pglEnable( GL_DEPTH_TEST ); } /* =============== R_StudioSetRemapColors =============== */ void R_StudioSetRemapColors( int newTop, int newBottom ) { // update colors for viewentity if( RI.currententity == &clgame.viewent ) { player_info_t *pLocalPlayer; // copy top and bottom colors for viewmodel if(( pLocalPlayer = pfnPlayerInfo( clgame.viewent.curstate.number - 1 )) != NULL ) { newTop = bound( 0, pLocalPlayer->topcolor, 360 ); newBottom = bound( 0, pLocalPlayer->bottomcolor, 360 ); } } CL_AllocRemapInfo( newTop, newBottom ); if( CL_GetRemapInfoForEntity( RI.currententity )) { CL_UpdateRemapInfo( newTop, newBottom ); m_fDoRemap = true; } } /* =============== R_StudioSetupPlayerModel =============== */ static model_t *R_StudioSetupPlayerModel( int index ) { player_info_t *info; string modelpath; if( cls.key_dest == key_menu && !index ) { // we are in menu. info = &menu.playerinfo; } else { if( index < 0 || index > cl.maxclients ) return NULL; // bad client ? info = &cl.players[index]; } if( !info->model[0] ) return NULL; if( !Q_stricmp( info->model, "player" )) Q_strncpy( modelpath, "models/player.mdl", sizeof( modelpath )); else Q_snprintf( modelpath, sizeof( modelpath ), "models/player/%s/%s.mdl", info->model, info->model ); if( !FS_FileExists( modelpath, false )) return NULL; return Mod_ForName( modelpath, false ); } /* =============== R_StudioClientEvents =============== */ static void R_StudioClientEvents( void ) { mstudioseqdesc_t *pseqdesc; mstudioevent_t *pevent; float flEventFrame; qboolean bLooped = false; cl_entity_t *e = RI.currententity; int i; pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + e->curstate.sequence; pevent = (mstudioevent_t *)((byte *)m_pStudioHeader + pseqdesc->eventindex); // curstate.frame not used for viewmodel animating flEventFrame = e->latched.prevframe; if( pseqdesc->numevents == 0 ) return; if( e->syncbase == -0.01f ) flEventFrame = 0.0f; // stalled? if( flEventFrame == e->syncbase ) return; //Msg( "(seq %d cycle %.3f ) evframe %.3f prevevframe %.3f (time %.3f)\n", e->curstate.sequence, e->latched.prevframe, flEventFrame, e->syncbase, RI.refdef.time ); // check for looping if( flEventFrame <= e->syncbase ) { if( e->syncbase - flEventFrame > 0.5f ) { bLooped = true; } else { // things have backed up, which is bad since it'll probably result in a hitch in the animation playback // but, don't play events again for the same time slice return; } } for( i = 0; i < pseqdesc->numevents; i++ ) { // ignore all non-client-side events if( pevent[i].event < EVENT_CLIENT ) continue; // looped if( bLooped ) { if(( pevent[i].frame > e->syncbase || pevent[i].frame <= flEventFrame )) clgame.dllFuncs.pfnStudioEvent( &pevent[i], e ); } else { if(( pevent[i].frame > e->syncbase && pevent[i].frame <= flEventFrame )) clgame.dllFuncs.pfnStudioEvent( &pevent[i], e ); } } e->syncbase = flEventFrame; } /* =============== 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; VectorClear( g_chrome_origin ); m_fDoRemap = false; } /* =============== R_StudioSetRenderModel =============== */ void R_StudioSetRenderModel( model_t *model ) { RI.currentmodel = model; } /* =============== R_StudioSetupRenderer =============== */ static void R_StudioSetupRenderer( int rendermode ) { g_iRenderMode = bound( 0, rendermode, kRenderTransAdd ); pglShadeModel( GL_SMOOTH ); // enable gouraud shading if( clgame.ds.cullMode != GL_NONE ) GL_Cull( GL_FRONT ); // enable depthmask on studiomodels if( glState.drawTrans ) pglDepthMask( GL_TRUE ); } /* =============== R_StudioRestoreRenderer =============== */ static void R_StudioRestoreRenderer( void ) { pglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE ); pglShadeModel( GL_FLAT ); // restore depthmask state for sprites etc if( glState.drawTrans ) pglDepthMask( GL_FALSE ); m_fDoRemap = false; } /* =============== R_StudioSetChromeOrigin =============== */ void R_StudioSetChromeOrigin( void ) { VectorNegate( RI.vieworg, g_chrome_origin ); } /* =============== pfnIsHardware Xash3D is always works in hardware mode =============== */ static int pfnIsHardware( void ) { return 1; // 0 is Software, 1 is OpenGL, 2 is Direct3D } /* =============== R_StudioDeformShadow Deform vertices by specified lightdir =============== */ void R_StudioDeformShadow( void ) { float *verts, dist, dist2; int numVerts; dist = g_shadowTrace.plane.dist + 1.0f; dist2 = -1.0f / DotProduct( g_mvShadowVec, g_shadowTrace.plane.normal ); VectorScale( g_mvShadowVec, dist2, g_mvShadowVec ); verts = g_xarrayverts[0]; numVerts = g_nNumArrayVerts; for( numVerts = 0; numVerts < g_nNumArrayVerts; numVerts++, verts += 3 ) { dist2 = DotProduct( verts, g_shadowTrace.plane.normal ) - dist; if( dist2 > 0 ) VectorMA( verts, dist2, g_mvShadowVec, verts ); } } static void R_StudioDrawPlanarShadow( void ) { if( RI.currententity->curstate.effects & EF_NOSHADOW ) return; R_StudioDeformShadow (); if( glState.stencilEnabled ) pglEnable( GL_STENCIL_TEST ); pglEnableClientState( GL_VERTEX_ARRAY ); pglVertexPointer( 3, GL_FLOAT, 12, g_xarrayverts ); if( GL_Support( GL_DRAW_RANGEELEMENTS_EXT )) pglDrawRangeElementsEXT( GL_TRIANGLES, 0, g_nNumArrayVerts, g_nNumArrayElems, GL_UNSIGNED_INT, g_xarrayelems ); else pglDrawElements( GL_TRIANGLES, g_nNumArrayElems, GL_UNSIGNED_INT, g_xarrayelems ); if( glState.stencilEnabled ) pglDisable( GL_STENCIL_TEST ); pglDisableClientState( GL_VERTEX_ARRAY ); } /* =============== GL_StudioDrawShadow NOTE: this code sucessfully working with ShadowHack only in Release build =============== */ static void GL_StudioDrawShadow( void ) { int rendermode; float shadow_alpha; float shadow_alpha2; GLenum depthmode; GLenum depthmode2; pglDepthMask( GL_TRUE ); if( r_shadows.value != 0.0f ) { if( RI.currententity->baseline.movetype != MOVETYPE_FLY ) { rendermode = RI.currententity->baseline.rendermode; if( rendermode == kRenderNormal && RI.currententity != &clgame.viewent ) { shadow_alpha = 1.0f - r_shadowalpha.value * 0.5f; pglDisable( GL_TEXTURE_2D ); pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); pglEnable( GL_BLEND ); pglShadeModel( GL_FLAT ); shadow_alpha2 = 1.0 - shadow_alpha; pglColor4f( 0.0f, 0.0f, 0.0f, shadow_alpha2 ); depthmode = GL_LESS; pglDepthFunc( depthmode ); R_StudioDrawPlanarShadow(); depthmode2 = GL_LEQUAL; pglDepthFunc( depthmode2 ); pglEnable( GL_TEXTURE_2D ); pglDisable( GL_BLEND ); pglColor4f( 1.0f, 1.0f, 1.0f, 1.0f ); pglShadeModel( GL_SMOOTH ); } } } } /* ==================== StudioRenderFinal ==================== */ void R_StudioRenderFinal( void ) { int i, rendermode; rendermode = R_StudioGetForceFaceFlags() ? kRenderTransAdd : RI.currententity->curstate.rendermode; R_StudioSetupRenderer( rendermode ); if( r_drawentities->integer == 2 ) { R_StudioDrawBones(); } else if( r_drawentities->integer == 3 ) { R_StudioDrawHulls(); } else { for( i = 0; i < m_pStudioHeader->numbodyparts; i++ ) { R_StudioSetupModel( i, &m_pBodyPart, &m_pSubModel ); if( m_fDoInterp ) { // interpolation messes up bounding boxes. RI.currententity->trivial_accept = 0; } GL_SetRenderMode( rendermode ); R_StudioDrawPoints(); GL_StudioDrawShadow(); } } if( r_drawentities->integer == 4 ) { GL_SetRenderMode( kRenderTransAdd ); R_StudioDrawHulls( ); GL_SetRenderMode( kRenderNormal ); } if( r_drawentities->integer == 5 ) { R_StudioDrawAbsBBox( ); } if( r_drawentities->integer == 6 ) { R_StudioDrawAttachments(); } R_StudioRestoreRenderer(); } /* ==================== StudioRenderModel ==================== */ void R_StudioRenderModel( void ) { R_StudioSetChromeOrigin(); R_StudioSetForceFaceFlags( 0 ); 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( ); } } /* ==================== StudioEstimateGait ==================== */ void R_StudioEstimateGait( entity_state_t *pplayer ) { vec3_t est_velocity; float dt; dt = bound( 0.0f, (RI.refdef.time - cl.oldtime), 1.0f ); if( dt == 0 || m_pPlayerInfo->renderframe == tr.framecount ) { 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 || m_flGaitMovement / dt < 5 ) { m_flGaitMovement = 0; est_velocity[0] = 0; est_velocity[1] = 0; } 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 ) flYawDiff -= 360; if( flYawDiff < -180 ) flYawDiff += 360; if( dt < 0.25 ) flYawDiff *= dt * 4; else flYawDiff *= dt; m_pPlayerInfo->gaityaw += flYawDiff; m_pPlayerInfo->gaityaw = m_pPlayerInfo->gaityaw - (int)(m_pPlayerInfo->gaityaw / 360) * 360; m_flGaitMovement = 0; } else { m_pPlayerInfo->gaityaw = ( atan2( est_velocity[1], est_velocity[0] ) * 180 / M_PI ); if( m_pPlayerInfo->gaityaw > 180 ) m_pPlayerInfo->gaityaw = 180; if( m_pPlayerInfo->gaityaw < -180 ) m_pPlayerInfo->gaityaw = -180; } } /* ==================== StudioProcessGait ==================== */ 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; 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]; dt = bound( 0.0f, (RI.refdef.time - cl.oldtime), 1.0f ); 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 ) flYaw = flYaw + 360; if( flYaw > 180 ) flYaw = flYaw - 360; if( flYaw > 120 ) { m_pPlayerInfo->gaityaw = m_pPlayerInfo->gaityaw - 180; m_flGaitMovement = -m_flGaitMovement; flYaw = flYaw - 180; } else if( flYaw < -120 ) { m_pPlayerInfo->gaityaw = m_pPlayerInfo->gaityaw + 180; m_flGaitMovement = -m_flGaitMovement; flYaw = flYaw + 180; } // adjust torso RI.currententity->curstate.controller[0] = ((flYaw / 4.0) + 30) / (60.0 / 255.0); RI.currententity->curstate.controller[1] = ((flYaw / 4.0) + 30) / (60.0 / 255.0); RI.currententity->curstate.controller[2] = ((flYaw / 4.0) + 30) / (60.0 / 255.0); RI.currententity->curstate.controller[3] = ((flYaw / 4.0) + 30) / (60.0 / 255.0); 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; 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; } /* =============== R_StudioDrawPlayer =============== */ 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 >= cl.maxclients ) return 0; RI.currentmodel = R_StudioSetupPlayerModel( m_nPlayerIndex ); if( RI.currentmodel == NULL ) return 0; R_StudioSetHeader((studiohdr_t *)Mod_Extradata( 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; r_stats.c_studio_models_drawn++; g_nStudioCount++; // render data cache cookie if( m_pStudioHeader->numbodyparts == 0 ) return 1; } m_pPlayerInfo = pfnPlayerInfo( m_nPlayerIndex ); R_StudioSetupBones( RI.currententity ); R_StudioSaveBones( ); m_pPlayerInfo->renderframe = tr.framecount; 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 = CL_GetEntityByIndex( RI.currententity->index ); Q_memcpy( ent->attachment, RI.currententity->attachment, sizeof( vec3_t ) * 4 ); } } if( flags & STUDIO_RENDER ) { if( cl_himodels->integer && RI.currentmodel != RI.currententity->model ) { // show highest resolution multiplayer model RI.currententity->curstate.body = 255; } if(!( host.developer == 0 && cl.maxclients == 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 = Mod_Handle( pplayer->weaponmodel ); m_pStudioHeader = (studiohdr_t *)Mod_Extradata( pweaponmodel ); R_StudioMergeBones( RI.currententity, pweaponmodel ); R_StudioSetupLighting( &lighting ); R_StudioRenderModel( ); R_StudioCalcAttachments( ); *RI.currententity = saveent; } } return 1; } /* =============== R_StudioDrawModel =============== */ static int R_StudioDrawModel( int flags ) { alight_t lighting; vec3_t dir; if( RI.currententity->curstate.renderfx == kRenderFxDeadPlayer ) { entity_state_t deadplayer; int save_interp; int result; if( RI.currententity->curstate.renderamt <= 0 || RI.currententity->curstate.renderamt > cl.maxclients ) 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 ); save_interp = m_fDoInterp; m_fDoInterp = 0; // draw as though it were a player result = R_StudioDrawPlayer( flags, &deadplayer ); m_fDoInterp = save_interp; return result; } R_StudioSetHeader((studiohdr_t *)Mod_Extradata( 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; r_stats.c_studio_models_drawn++; g_nStudioCount++; // 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 = CL_GetEntityByIndex( RI.currententity->index ); Q_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; } /* ================= R_DrawStudioModel ================= */ void R_DrawStudioModelInternal( cl_entity_t *e, qboolean follow_entity ) { int i, flags, result; float prevFrame; if( RI.params & RP_ENVVIEW ) return; if( !Mod_Extradata( e->model )) return; ASSERT( pStudioDraw != NULL ); if( e == &clgame.viewent ) flags = STUDIO_RENDER; else flags = STUDIO_RENDER|STUDIO_EVENTS; if( e == &clgame.viewent ) m_fDoInterp = true; // viewmodel can't properly animate without lerping else if( r_studio_lerping->integer ) m_fDoInterp = (e->curstate.effects & EF_NOINTERP) ? false : true; else m_fDoInterp = false; prevFrame = e->latched.prevframe; // prevent to crash some mods like HLFX in menu Customize if( !RI.drawWorld && !r_customdraw_playermodel->integer ) { if( e->player ) result = R_StudioDrawPlayer( flags, &e->curstate ); else result = R_StudioDrawModel( flags ); } else { // select the properly method if( e->player ) result = pStudioDraw->StudioDrawPlayer( flags, &e->curstate ); else result = pStudioDraw->StudioDrawModel( flags ); } // old frame must be restored if( !RP_NORMALPASS( )) e->latched.prevframe = prevFrame; if( !result || follow_entity ) return; // NOTE: we must draw all followed entities // immediately after drawing parent when cached bones is valid for( i = 0; i < tr.num_child_entities; i++ ) { if( CL_GetEntityByIndex( tr.child_entities[i]->curstate.aiment ) == e ) { // copy the parent origin for right frustum culling VectorCopy( e->origin, tr.child_entities[i]->origin ); RI.currententity = tr.child_entities[i]; RI.currentmodel = RI.currententity->model; R_DrawStudioModelInternal( RI.currententity, true ); } } } /* ================= R_DrawStudioModel ================= */ void R_DrawStudioModel( cl_entity_t *e ) { R_DrawStudioModelInternal( e, false ); } /* ================= R_RunViewmodelEvents ================= */ void R_RunViewmodelEvents( void ) { if( cl.refdef.nextView || cl.thirdperson || RI.params & RP_NONVIEWERREF ) return; if( !Mod_Extradata( clgame.viewent.model )) return; RI.currententity = &clgame.viewent; RI.currentmodel = RI.currententity->model; if( !RI.currentmodel ) return; pStudioDraw->StudioDrawModel( STUDIO_EVENTS ); RI.currententity = NULL; RI.currentmodel = NULL; } /* ================= R_DrawViewModel ================= */ void R_DrawViewModel( void ) { if( RI.refdef.onlyClientDraw || r_drawviewmodel->integer == 0 ) return; // ignore in thirdperson, camera view or client is died if( cl.thirdperson || cl.refdef.health <= 0 || cl.refdef.viewentity != ( cl.playernum + 1 )) return; if( RI.params & RP_NONVIEWERREF ) return; if( !Mod_Extradata( clgame.viewent.model )) return; RI.currententity = &clgame.viewent; RI.currentmodel = RI.currententity->model; if( !RI.currentmodel ) return; RI.currententity->curstate.renderamt = R_ComputeFxBlend( RI.currententity ); // hack 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_lefthand->integer == 1 ) GL_FrontFace( !glState.frontFace ); pStudioDraw->StudioDrawModel( STUDIO_RENDER ); // restore depth range pglDepthRange( gldepthmin, gldepthmax ); // backface culling for left-handed weapons if( r_lefthand->integer == 1 ) GL_FrontFace( !glState.frontFace ); RI.currententity = NULL; RI.currentmodel = NULL; } /* ==================== 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; qboolean load_external = false; char texname[128], name[128], mdlname[128]; texture_t *tx = NULL; if( ptexture->flags & STUDIO_NF_TRANSPARENT ) flags |= (TF_CLAMP|TF_NOMIPMAP); if( ptexture->flags & ( STUDIO_NF_NORMALMAP|STUDIO_NF_HEIGHTMAP )) 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_Alloc( mod->mempool, sizeof( *tx ) + size ); mod->textures[i] = tx; // parse ranges and store it // HACKHACK: 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; Q_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 )); FS_FileBase( ptexture->name, name ); FS_StripExtension( mdlname ); // NOTE: colormaps must have the palette for properly work. Ignore it. if( host_allow_materials->integer && !( ptexture->flags & STUDIO_NF_COLORMAP )) { int gl_texturenum = 0; Q_snprintf( texname, sizeof( texname ), "materials/%s/%s.tga", mdlname, name ); if( FS_FileExists( texname, false )) gl_texturenum = GL_LoadTexture( texname, NULL, 0, flags ); if( gl_texturenum ) { ptexture->index = gl_texturenum; load_external = true; // sucessfully loaded } } if( !load_external ) { // NOTE: replace index with pointer to start of imagebuffer, ImageLib expected it ptexture->index = (int)((byte *)phdr) + ptexture->index; size = sizeof( mstudiotexture_t ) + ptexture->width * ptexture->height + 768; // build the texname Q_snprintf( texname, sizeof( texname ), "#%s/%s.mdl", mdlname, name ); ptexture->index = GL_LoadTexture( texname, (byte *)ptexture, size, flags ); } if( !ptexture->index ) { MsgDev( D_WARN, "%s has null texture %s\n", mod->name, ptexture->name ); ptexture->index = tr.defaultTexture; } else { // duplicate texnum for easy acess if( tx ) tx->gl_texturenum = ptexture->index; GL_SetTextureType( ptexture->index, TEX_STUDIO ); } } /* ================= R_StudioLoadHeader ================= */ studiohdr_t *R_StudioLoadHeader( model_t *mod, const void *buffer ) { byte *pin; studiohdr_t *phdr; mstudiotexture_t *ptexture; int i; if( !buffer ) return NULL; pin = (byte *)buffer; phdr = (studiohdr_t *)pin; i = phdr->version; if( i != STUDIO_VERSION ) { MsgDev( D_ERROR, "%s has wrong version number (%i should be %i)\n", mod->name, i, STUDIO_VERSION ); return NULL; } if( host.type != HOST_DEDICATED ) { 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] ); } } return (studiohdr_t *)buffer; } /* ================= Mod_LoadStudioModel ================= */ void Mod_LoadStudioModel( model_t *mod, const void *buffer, qboolean *loaded ) { studiohdr_t *phdr; if( loaded ) *loaded = false; loadmodel->mempool = Mem_AllocPool( va( "^2%s^7", loadmodel->name )); loadmodel->type = mod_studio; phdr = R_StudioLoadHeader( mod, buffer ); if( !phdr ) return; // bad model #ifdef STUDIO_MERGE_TEXTURES if( phdr->numtextures == 0 ) { studiohdr_t *thdr; byte *in, *out; void *buffer2 = NULL; size_t size1, size2; buffer2 = FS_LoadFile( R_StudioTexName( mod ), NULL, false ); thdr = R_StudioLoadHeader( mod, buffer2 ); if( !thdr ) { MsgDev( D_WARN, "Mod_LoadStudioModel: %s missing textures file\n", mod->name ); if( buffer2 ) Mem_Free( buffer2 ); } else { // give space for textures and skinrefs size1 = thdr->numtextures * sizeof( mstudiotexture_t ); size2 = thdr->numskinfamilies * thdr->numskinref * sizeof( short ); mod->cache.data = Mem_Alloc( loadmodel->mempool, phdr->length + size1 + size2 ); Q_memcpy( loadmodel->cache.data, buffer, phdr->length ); // copy main mdl buffer phdr = (studiohdr_t *)loadmodel->cache.data; // get the new pointer on studiohdr phdr->numskinfamilies = thdr->numskinfamilies; phdr->numtextures = thdr->numtextures; phdr->numskinref = thdr->numskinref; phdr->textureindex = phdr->length; phdr->skinindex = phdr->textureindex + size1; in = (byte *)thdr + thdr->textureindex; out = (byte *)phdr + phdr->textureindex; Q_memcpy( out, in, size1 + size2 ); // copy textures + skinrefs phdr->length += size1 + size2; Mem_Free( buffer2 ); // release T.mdl } } else { // NOTE: we wan't keep raw textures in memory. just cutoff model pointer above texture base loadmodel->cache.data = Mem_Alloc( loadmodel->mempool, phdr->texturedataindex ); Q_memcpy( loadmodel->cache.data, buffer, phdr->texturedataindex ); phdr->length = phdr->texturedataindex; // update model size } #else // just copy model into memory loadmodel->cache.data = Mem_Alloc( loadmodel->mempool, phdr->length ); Q_memcpy( loadmodel->cache.data, buffer, phdr->length ); #endif // setup bounding box VectorCopy( phdr->bbmin, loadmodel->mins ); VectorCopy( phdr->bbmax, loadmodel->maxs ); loadmodel->numframes = R_StudioBodyVariations( loadmodel ); loadmodel->radius = RadiusFromBounds( loadmodel->mins, loadmodel->maxs ); loadmodel->flags = phdr->flags; // copy header flags if( loaded ) *loaded = true; } /* ================= Mod_UnloadStudioModel ================= */ void Mod_UnloadStudioModel( model_t *mod ) { studiohdr_t *pstudio; mstudiotexture_t *ptexture; int i; ASSERT( mod != NULL ); if( mod->type != mod_studio ) return; // not a studio pstudio = mod->cache.data; if( !pstudio ) return; // already freed ptexture = (mstudiotexture_t *)(((byte *)pstudio) + pstudio->textureindex); // release all textures for( i = 0; i < pstudio->numtextures; i++ ) { if( ptexture[i].index == tr.defaultTexture ) continue; GL_FreeTexture( ptexture[i].index ); } Mem_FreePool( &mod->mempool ); Q_memset( mod, 0, sizeof( *mod )); } static engine_studio_api_t gStudioAPI = { Mod_Calloc, Mod_CacheCheck, Mod_LoadCacheFile, Mod_ForName, Mod_Extradata, Mod_Handle, pfnGetCurrentEntity, pfnPlayerInfo, R_StudioGetPlayerState, pfnGetViewEntity, pfnGetEngineTimes, pfnCVarGetPointer, 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, R_StudioSetupSkin, R_StudioSetRemapColors, R_StudioSetupPlayerModel, R_StudioClientEvents, R_StudioGetForceFaceFlags, R_StudioSetForceFaceFlags, R_StudioSetHeader, R_StudioSetRenderModel, R_StudioSetupRenderer, R_StudioRestoreRenderer, R_StudioSetChromeOrigin, pfnIsHardware, GL_StudioDrawShadow, GL_SetRenderMode, }; static r_studio_interface_t gStudioDraw = { STUDIO_INTERFACE_VERSION, R_StudioDrawModel, R_StudioDrawPlayer, }; /* =============== CL_InitStudioAPI Initialize client studio =============== */ qboolean CL_InitStudioAPI( void ) { pStudioDraw = &gStudioDraw; // Xash will be used internal StudioModelRenderer if( !clgame.dllFuncs.pfnGetStudioModelInterface ) return true; if( clgame.dllFuncs.pfnGetStudioModelInterface( STUDIO_INTERFACE_VERSION, &pStudioDraw, &gStudioAPI )) return true; // 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; return true; }