//========= Copyright (c) 1996-2002, Valve LLC, All rights reserved. ============ // // Purpose: // // $NoKeywords: $ //============================================================================= // studio_model.cpp // routines for setting up to draw 3DStudio models #include "hud.h" #include "cl_util.h" #include "const.h" #include "com_model.h" #include "studio.h" #include "entity_state.h" #include "cl_entity.h" #include "dlight.h" #include "triangleapi.h" #include #include #include "studio_util.h" #include "r_studioint.h" #include "StudioModelRenderer.h" #include "GameStudioModelRenderer.h" // Global engine <-> studio model rendering code interface engine_studio_api_t IEngineStudio; ///////////////////// // Implementation of CStudioModelRenderer.h #define LEGS_BONES_COUNT 8 // enumerate all the bones that used for gait animation const char *legs_bones[] = { "Bip01", "Bip01 Pelvis", "Bip01 L Leg", "Bip01 L Leg1", "Bip01 L Foot", "Bip01 R Leg", "Bip01 R Leg1", "Bip01 R Foot" }; /* ==================== Init ==================== */ void CStudioModelRenderer::Init( void ) { // Set up some variables shared with engine m_pCvarHiModels = IEngineStudio.GetCvar( "cl_himodels" ); m_pCvarDeveloper = IEngineStudio.GetCvar( "developer" ); m_pCvarDrawEntities = IEngineStudio.GetCvar( "r_drawentities" ); m_pChromeSprite = IEngineStudio.GetChromeSprite(); IEngineStudio.GetModelCounters( &m_pStudioModelCount, &m_pModelsDrawn ); // Get pointers to engine data structures m_pbonetransform = (float (*)[MAXSTUDIOBONES][3][4])IEngineStudio.StudioGetBoneTransform(); m_plighttransform = (float (*)[MAXSTUDIOBONES][3][4])IEngineStudio.StudioGetLightTransform(); m_paliastransform = (float (*)[3][4])IEngineStudio.StudioGetAliasTransform(); m_protationmatrix = (float (*)[3][4])IEngineStudio.StudioGetRotationMatrix(); } /* ==================== CStudioModelRenderer ==================== */ CStudioModelRenderer::CStudioModelRenderer( void ) { m_fDoInterp = 1; m_fGaitEstimation = 1; m_pCurrentEntity = NULL; m_pCvarHiModels = NULL; m_pCvarDeveloper = NULL; m_pCvarDrawEntities = NULL; m_pChromeSprite = NULL; m_pStudioModelCount = NULL; m_pModelsDrawn = NULL; m_protationmatrix = NULL; m_paliastransform = NULL; m_pbonetransform = NULL; m_plighttransform = NULL; m_pStudioHeader = NULL; m_pBodyPart = NULL; m_pSubModel = NULL; m_pPlayerInfo = NULL; m_pRenderModel = NULL; } /* ==================== ~CStudioModelRenderer ==================== */ CStudioModelRenderer::~CStudioModelRenderer( void ) { } /* ==================== StudioCalcBoneAdj ==================== */ void CStudioModelRenderer::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 <= 3 ) { // check for 360% wrapping if( pbonecontroller[j].type & STUDIO_RLOOP ) { if( abs( pcontroller1[i] - pcontroller2[i] ) > 128) { int a, b; a = ( pcontroller1[j] + 128 ) % 256; b = ( pcontroller2[j] + 128 ) % 256; value = ( ( a * dadt ) + ( b * ( 1.0f - dadt ) ) - 128.0f ) * ( 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; if( value < 0.0f ) value = 0.0f; if( value > 1.0f ) value = 1.0f; value = ( 1.0f - value ) * pbonecontroller[j].start + value * pbonecontroller[j].end; } // Con_DPrintf( "%d %d %f : %f\n", m_pCurrentEntity->curstate.controller[j], m_pCurrentEntity->latched.prevcontroller[j], value, dadt ); } else { value = mouthopen / 64.0f; if( value > 1.0f ) value = 1.0f; value = ( 1.0f - value ) * pbonecontroller[j].start + value * pbonecontroller[j].end; // Con_DPrintf( "%d %f\n", mouthopen, value ); } switch( pbonecontroller[j].type & STUDIO_TYPES ) { case STUDIO_XR: case STUDIO_YR: case STUDIO_ZR: adj[j] = value * ( M_PI_F / 180.0f ); break; case STUDIO_X: case STUDIO_Y: case STUDIO_Z: adj[j] = value; break; } } } /* ==================== StudioCalcBoneQuaterion ==================== */ void CStudioModelRenderer::StudioCalcBoneQuaterion( int frame, float s, mstudiobone_t *pbone, mstudioanim_t *panim, float *adj, float *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 CStudioModelRenderer::StudioCalcBonePosition( int frame, float s, mstudiobone_t *pbone, mstudioanim_t *panim, float *adj, float *pos ) { int j, k; mstudioanimvalue_t *panimvalue; 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] ); /* if( i == 0 && j == 0 ) Con_DPrintf( "%d %d:%d %f\n", frame, panimvalue->num.valid, panimvalue->num.total, s ); */ 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 CStudioModelRenderer::StudioSlerpBones( vec4_t q1[], float pos1[][3], vec4_t q2[], float pos2[][3], float s ) { int i; vec4_t q3; float s1; if( s < 0.0f ) s = 0.0f; else if( s > 1.0f ) s = 1.0f; s1 = 1.0f - s; 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; } } /* ==================== StudioGetAnim ==================== */ mstudioanim_t *CStudioModelRenderer::StudioGetAnim( model_t *m_pSubModel, mstudioseqdesc_t *pseqdesc ) { mstudioseqgroup_t *pseqgroup; cache_user_t *paSequences; pseqgroup = (mstudioseqgroup_t *)( (byte *)m_pStudioHeader + m_pStudioHeader->seqgroupindex ) + pseqdesc->seqgroup; if( pseqdesc->seqgroup == 0 ) { return (mstudioanim_t *)( (byte *)m_pStudioHeader + pseqdesc->animindex ); } paSequences = (cache_user_t *)m_pSubModel->submodels; if( paSequences == NULL ) { paSequences = (cache_user_t *)IEngineStudio.Mem_Calloc( 16, sizeof(cache_user_t) ); // UNDONE: leak! m_pSubModel->submodels = (dmodel_t *)paSequences; } if( !IEngineStudio.Cache_Check( (struct cache_user_s *)&( paSequences[pseqdesc->seqgroup] ) ) ) { gEngfuncs.Con_DPrintf("loading %s\n", pseqgroup->name ); IEngineStudio.LoadCacheFile( pseqgroup->name, (struct cache_user_s *)&paSequences[pseqdesc->seqgroup] ); } return (mstudioanim_t *)( (byte *)paSequences[pseqdesc->seqgroup].data + pseqdesc->animindex ); } /* ==================== StudioPlayerBlend ==================== */ void CStudioModelRenderer::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 * ( *pBlend - pseqdesc->blendstart[0] ) / ( pseqdesc->blendend[0] - pseqdesc->blendstart[0] ); *pPitch = 0; } } /* ==================== StudioSetUpTransform ==================== */ void CStudioModelRenderer::StudioSetUpTransform( int trivial_accept ) { int i; vec3_t angles; vec3_t modelpos; // tweek model origin //for( i = 0; i < 3; i++ ) // modelpos[i] = m_pCurrentEntity->origin[i]; VectorCopy( m_pCurrentEntity->origin, modelpos ); // TODO: should really be stored with the entity instead of being reconstructed // TODO: should use a look-up table // TODO: could cache lazily, stored in the entity angles[ROLL] = m_pCurrentEntity->curstate.angles[ROLL]; angles[PITCH] = m_pCurrentEntity->curstate.angles[PITCH]; angles[YAW] = m_pCurrentEntity->curstate.angles[YAW]; //Con_DPrintf( "Angles %4.2f prev %4.2f for %i\n", angles[PITCH], m_pCurrentEntity->index ); //Con_DPrintf( "movetype %d %d\n", m_pCurrentEntity->movetype, m_pCurrentEntity->aiment ); if( m_pCurrentEntity->curstate.movetype == MOVETYPE_STEP ) { float f = 0; float d; // 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_clTime < m_pCurrentEntity->curstate.animtime + 1.0f ) && ( m_pCurrentEntity->curstate.animtime != m_pCurrentEntity->latched.prevanimtime ) ) { f = ( m_clTime - m_pCurrentEntity->curstate.animtime ) / ( m_pCurrentEntity->curstate.animtime - m_pCurrentEntity->latched.prevanimtime ); //Con_DPrintf( "%4.2f %.2f %.2f\n", f, m_pCurrentEntity->curstate.animtime, m_clTime ); } if( m_fDoInterp ) { // ugly hack to interpolate angle, position. current is reached 0.1 seconds after being set f = f - 1.0f; } else { f = 0.0f; } for( i = 0; i < 3; i++ ) { modelpos[i] += ( m_pCurrentEntity->origin[i] - m_pCurrentEntity->latched.prevorigin[i] ) * f; } // NOTE: Because multiplayer lag can be relatively large, we don't want to cap // f at 1.5 anymore. //if( f > -1.0f && f < 1.5f ) {} //Con_DPrintf( "%.0f %.0f\n",m_pCurrentEntity->msg_angles[0][YAW], m_pCurrentEntity->msg_angles[1][YAW] ); for( i = 0; i < 3; i++ ) { float ang1, ang2; ang1 = m_pCurrentEntity->angles[i]; ang2 = m_pCurrentEntity->latched.prevangles[i]; d = ang1 - ang2; if( d > 180.0f ) { d -= 360.0f; } else if( d < -180.0f ) { d += 360.0f; } angles[i] += d * f; } //Con_DPrintf( "%.3f \n", f ); } else if( m_pCurrentEntity->curstate.movetype != MOVETYPE_NONE ) { VectorCopy( m_pCurrentEntity->angles, angles ); } //Con_DPrintf( "%.0f %0.f %0.f\n", modelpos[0], modelpos[1], modelpos[2] ); //Con_DPrintf( "%.0f %0.f %0.f\n", angles[0], angles[1], angles[2] ); angles[PITCH] = -angles[PITCH]; AngleMatrix( angles, ( *m_protationmatrix ) ); if( !IEngineStudio.IsHardware() ) { static float viewmatrix[3][4]; VectorCopy( m_vRight, viewmatrix[0] ); VectorCopy( m_vUp, viewmatrix[1] ); VectorInverse( viewmatrix[1] ); VectorCopy( m_vNormal, viewmatrix[2] ); (*m_protationmatrix)[0][3] = modelpos[0] - m_vRenderOrigin[0]; (*m_protationmatrix)[1][3] = modelpos[1] - m_vRenderOrigin[1]; (*m_protationmatrix)[2][3] = modelpos[2] - m_vRenderOrigin[2]; ConcatTransforms( viewmatrix, (*m_protationmatrix), (*m_paliastransform) ); // do the scaling up of x and y to screen coordinates as part of the transform // for the unclipped case (it would mess up clipping in the clipped case). // Also scale down z, so 1/z is scaled 31 bits for free, and scale down x and y // correspondingly so the projected x and y come out right // FIXME: make this work for clipped case too? if( trivial_accept ) { for( i = 0; i < 4; i++ ) { (*m_paliastransform)[0][i] *= m_fSoftwareXScale * ( 1.0f / ( ZISCALE * 0x10000 ) ); (*m_paliastransform)[1][i] *= m_fSoftwareYScale * ( 1.0f / ( ZISCALE * 0x10000 ) ); (*m_paliastransform)[2][i] *= 1.0f / ( ZISCALE * 0x10000 ); } } } (*m_protationmatrix)[0][3] = modelpos[0]; (*m_protationmatrix)[1][3] = modelpos[1]; (*m_protationmatrix)[2][3] = modelpos[2]; //LRC - apply scale to models! if (m_pCurrentEntity->curstate.scale != 0) { int j; for (i = 0; i < 3; i++) for (j = 0; j < 3; j++) { (*m_protationmatrix)[i][j] *= m_pCurrentEntity->curstate.scale; } } } /* ==================== StudioEstimateInterpolant ==================== */ float CStudioModelRenderer::StudioEstimateInterpolant( void ) { float dadt = 1.0f; if( m_fDoInterp && ( m_pCurrentEntity->curstate.animtime >= m_pCurrentEntity->latched.prevanimtime + 0.01f ) ) { dadt = ( m_clTime - m_pCurrentEntity->curstate.animtime ) / 0.1f; if( dadt > 2.0f ) { dadt = 2.0f; } } return dadt; } /* ==================== StudioCalcRotations ==================== */ void CStudioModelRenderer::StudioCalcRotations( float pos[][3], vec4_t *q, mstudioseqdesc_t *pseqdesc, mstudioanim_t *panim, float f ) { int i; int frame; mstudiobone_t *pbone; float s; float adj[MAXSTUDIOCONTROLLERS]; float dadt; if( f > pseqdesc->numframes - 1 ) { f = 0.0f; // bah, fix this bug with changing sequences too fast } // 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 else if( f < -0.01f ) { f = -0.01f; } frame = (int)f; // Con_DPrintf( "%d %.4f %.4f %.4f %.4f %d\n", m_pCurrentEntity->curstate.sequence, m_clTime, m_pCurrentEntity->animtime, m_pCurrentEntity->frame, f, frame ); // Con_DPrintf( "%f %f %f\n", m_pCurrentEntity->angles[ROLL], m_pCurrentEntity->angles[PITCH], m_pCurrentEntity->angles[YAW] ); // Con_DPrintf( "frame %d %d\n", frame1, frame2 ); dadt = StudioEstimateInterpolant(); s = ( f - frame ); // add in programtic controllers pbone = (mstudiobone_t *)( (byte *)m_pStudioHeader + m_pStudioHeader->boneindex ); StudioCalcBoneAdj( dadt, adj, m_pCurrentEntity->curstate.controller, m_pCurrentEntity->latched.prevcontroller, m_pCurrentEntity->mouth.mouthopen ); for( i = 0; i < m_pStudioHeader->numbones; i++, pbone++, panim++ ) { StudioCalcBoneQuaterion( frame, s, pbone, panim, adj, q[i] ); StudioCalcBonePosition( frame, s, pbone, panim, adj, pos[i] ); // if( 0 && i == 0 ) // Con_DPrintf( "%d %d %d %d\n", m_pCurrentEntity->curstate.sequence, frame, j, k ); } 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 ) ) * m_pCurrentEntity->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]; } } /* ==================== Studio_FxTransform ==================== */ void CStudioModelRenderer::StudioFxTransform( cl_entity_t *ent, float transform[3][4] ) { switch( ent->curstate.renderfx ) { case kRenderFxDistort: case kRenderFxHologram: if( gEngfuncs.pfnRandomLong( 0, 49 ) == 0 ) { int axis = gEngfuncs.pfnRandomLong( 0, 1 ); if( axis == 1 ) // Choose between x & z axis = 2; VectorScale( transform[axis], gEngfuncs.pfnRandomFloat( 1.0f, 1.484f ), transform[axis] ); } else if( gEngfuncs.pfnRandomLong( 0, 49 ) == 0 ) { float offset; /*int axis = gEngfuncs.pfnRandomLong(0,1); if( axis == 1 ) // Choose between x & z axis = 2;*/ offset = gEngfuncs.pfnRandomFloat( -10.0f, 10.0f ); transform[gEngfuncs.pfnRandomLong( 0, 2 )][3] += offset; } break; case kRenderFxExplode: { float scale; scale = 1.0f + ( m_clTime - ent->curstate.animtime ) * 10.0f; if( scale > 2 ) // Don't blow up more than 200% scale = 2; transform[0][1] *= scale; transform[1][1] *= scale; transform[2][1] *= scale; } break; } } /* ==================== StudioEstimateFrame ==================== */ float CStudioModelRenderer::StudioEstimateFrame( mstudioseqdesc_t *pseqdesc ) { double dfdt, f; if( m_fDoInterp ) { if( m_clTime < m_pCurrentEntity->curstate.animtime ) { dfdt = 0; } else { dfdt = ( m_clTime - m_pCurrentEntity->curstate.animtime ) * m_pCurrentEntity->curstate.framerate * pseqdesc->fps; } } else { dfdt = 0; } if( pseqdesc->numframes <= 1 ) { f = 0.0; } else { f = ( m_pCurrentEntity->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; } /* ==================== StudioSetupBones ==================== */ void CStudioModelRenderer::StudioSetupBones( void ) { int i, j; double f; mstudiobone_t *pbones; mstudioseqdesc_t *pseqdesc; mstudioanim_t *panim; static float pos[MAXSTUDIOBONES][3]; static vec4_t q[MAXSTUDIOBONES]; float bonematrix[3][4]; static float pos2[MAXSTUDIOBONES][3]; static vec4_t q2[MAXSTUDIOBONES]; static float pos3[MAXSTUDIOBONES][3]; static vec4_t q3[MAXSTUDIOBONES]; static float pos4[MAXSTUDIOBONES][3]; static vec4_t q4[MAXSTUDIOBONES]; if( m_pCurrentEntity->curstate.sequence >= m_pStudioHeader->numseq ) { m_pCurrentEntity->curstate.sequence = 0; } pseqdesc = (mstudioseqdesc_t *)( (byte *)m_pStudioHeader + m_pStudioHeader->seqindex ) + m_pCurrentEntity->curstate.sequence; f = StudioEstimateFrame( pseqdesc ); if( m_pCurrentEntity->latched.prevframe > f ) { //Con_DPrintf( "%f %f\n", m_pCurrentEntity->prevframe, f ); } panim = StudioGetAnim( m_pRenderModel, pseqdesc ); StudioCalcRotations( pos, q, pseqdesc, panim, f ); if( pseqdesc->numblends > 1 ) { float s; float dadt; panim += m_pStudioHeader->numbones; StudioCalcRotations( pos2, q2, pseqdesc, panim, f ); dadt = StudioEstimateInterpolant(); s = ( m_pCurrentEntity->curstate.blending[0] * dadt + m_pCurrentEntity->latched.prevblending[0] * ( 1.0 - dadt ) ) / 255.0; StudioSlerpBones( q, pos, q2, pos2, s ); if( pseqdesc->numblends == 4 ) { panim += m_pStudioHeader->numbones; StudioCalcRotations( pos3, q3, pseqdesc, panim, f ); panim += m_pStudioHeader->numbones; StudioCalcRotations( pos4, q4, pseqdesc, panim, f ); s = ( m_pCurrentEntity->curstate.blending[0] * dadt + m_pCurrentEntity->latched.prevblending[0] * ( 1.0 - dadt ) ) / 255.0; StudioSlerpBones( q3, pos3, q4, pos4, s ); s = ( m_pCurrentEntity->curstate.blending[1] * dadt + m_pCurrentEntity->latched.prevblending[1] * ( 1.0 - dadt ) ) / 255.0; StudioSlerpBones( q, pos, q3, pos3, s ); } } if( m_fDoInterp && m_pCurrentEntity->latched.sequencetime && ( m_pCurrentEntity->latched.sequencetime + 0.2 > m_clTime ) && ( m_pCurrentEntity->latched.prevsequence < m_pStudioHeader->numseq ) ) { // blend from last sequence static float pos1b[MAXSTUDIOBONES][3]; static vec4_t q1b[MAXSTUDIOBONES]; float s; pseqdesc = (mstudioseqdesc_t *)( (byte *)m_pStudioHeader + m_pStudioHeader->seqindex ) + m_pCurrentEntity->latched.prevsequence; panim = StudioGetAnim( m_pRenderModel, pseqdesc ); // clip prevframe StudioCalcRotations( pos1b, q1b, pseqdesc, panim, m_pCurrentEntity->latched.prevframe ); if( pseqdesc->numblends > 1 ) { panim += m_pStudioHeader->numbones; StudioCalcRotations( pos2, q2, pseqdesc, panim, m_pCurrentEntity->latched.prevframe ); s = (m_pCurrentEntity->latched.prevseqblending[0]) / 255.0; StudioSlerpBones( q1b, pos1b, q2, pos2, s ); if( pseqdesc->numblends == 4 ) { panim += m_pStudioHeader->numbones; StudioCalcRotations( pos3, q3, pseqdesc, panim, m_pCurrentEntity->latched.prevframe ); panim += m_pStudioHeader->numbones; StudioCalcRotations( pos4, q4, pseqdesc, panim, m_pCurrentEntity->latched.prevframe ); s = ( m_pCurrentEntity->latched.prevseqblending[0] ) / 255.0; StudioSlerpBones( q3, pos3, q4, pos4, s ); s = ( m_pCurrentEntity->latched.prevseqblending[1] ) / 255.0; StudioSlerpBones( q1b, pos1b, q3, pos3, s ); } } s = 1.0 - ( m_clTime - m_pCurrentEntity->latched.sequencetime ) / 0.2; StudioSlerpBones( q, pos, q1b, pos1b, s ); } else { //Con_DPrintf("prevframe = %4.2f\n", f); m_pCurrentEntity->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 = StudioGetAnim( m_pRenderModel, pseqdesc ); StudioCalcRotations( pos2, q2, pseqdesc, panim, m_pPlayerInfo->gaitframe ); for( i = 0; i < m_pStudioHeader->numbones; i++ ) { for( j = 0; j < LEGS_BONES_COUNT; j++ ) { if( !strcmp( pbones[i].name, legs_bones[j] ) ) break; } if( j == LEGS_BONES_COUNT ) continue; // not used for legs memcpy( pos[i], pos2[i], sizeof(pos[i]) ); memcpy( q[i], q2[i], sizeof(q[i]) ); } } for( i = 0; i < m_pStudioHeader->numbones; i++ ) { QuaternionMatrix( q[i], bonematrix ); bonematrix[0][3] = pos[i][0]; bonematrix[1][3] = pos[i][1]; bonematrix[2][3] = pos[i][2]; if( pbones[i].parent == -1 ) { if( IEngineStudio.IsHardware() ) { ConcatTransforms( (*m_protationmatrix), bonematrix, (*m_pbonetransform)[i] ); // MatrixCopy should be faster... //ConcatTransforms( (*m_protationmatrix), bonematrix, (*m_plighttransform)[i] ); MatrixCopy( (*m_pbonetransform)[i], (*m_plighttransform)[i] ); } else { ConcatTransforms( (*m_paliastransform), bonematrix, (*m_pbonetransform)[i] ); ConcatTransforms( (*m_protationmatrix), bonematrix, (*m_plighttransform)[i] ); } // Apply client-side effects to the transformation matrix StudioFxTransform( m_pCurrentEntity, (*m_pbonetransform)[i] ); } else { ConcatTransforms( (*m_pbonetransform)[pbones[i].parent], bonematrix, (*m_pbonetransform)[i] ); ConcatTransforms( (*m_plighttransform)[pbones[i].parent], bonematrix, (*m_plighttransform)[i] ); } } } /* ==================== StudioSaveBones ==================== */ void CStudioModelRenderer::StudioSaveBones( void ) { int i; mstudiobone_t *pbones; pbones = (mstudiobone_t *)( (byte *)m_pStudioHeader + m_pStudioHeader->boneindex ); m_nCachedBones = m_pStudioHeader->numbones; for( i = 0; i < m_pStudioHeader->numbones; i++ ) { strcpy( m_nCachedBoneNames[i], pbones[i].name ); MatrixCopy( (*m_pbonetransform)[i], m_rgCachedBoneTransform[i] ); MatrixCopy( (*m_plighttransform)[i], m_rgCachedLightTransform[i] ); } } /* ==================== StudioMergeBones ==================== */ void CStudioModelRenderer::StudioMergeBones( model_t *m_pSubModel ) { int i, j; double f; int do_hunt = true; mstudiobone_t *pbones; mstudioseqdesc_t *pseqdesc; mstudioanim_t *panim; static float pos[MAXSTUDIOBONES][3]; float bonematrix[3][4]; static vec4_t q[MAXSTUDIOBONES]; if( m_pCurrentEntity->curstate.sequence >= m_pStudioHeader->numseq ) { m_pCurrentEntity->curstate.sequence = 0; } pseqdesc = (mstudioseqdesc_t *)( (byte *)m_pStudioHeader + m_pStudioHeader->seqindex ) + m_pCurrentEntity->curstate.sequence; f = StudioEstimateFrame( pseqdesc ); if( m_pCurrentEntity->latched.prevframe > f ) { //Con_DPrintf( "%f %f\n", m_pCurrentEntity->prevframe, f ); } panim = StudioGetAnim( m_pSubModel, pseqdesc ); StudioCalcRotations( 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 < m_nCachedBones; j++ ) { if( stricmp( pbones[i].name, m_nCachedBoneNames[j] ) == 0 ) { MatrixCopy( m_rgCachedBoneTransform[j], (*m_pbonetransform)[i] ); MatrixCopy( m_rgCachedLightTransform[j], (*m_plighttransform)[i] ); break; } } if( j >= m_nCachedBones ) { QuaternionMatrix( q[i], bonematrix ); bonematrix[0][3] = pos[i][0]; bonematrix[1][3] = pos[i][1]; bonematrix[2][3] = pos[i][2]; if( pbones[i].parent == -1 ) { if( IEngineStudio.IsHardware() ) { ConcatTransforms( (*m_protationmatrix), bonematrix, (*m_pbonetransform)[i] ); // MatrixCopy should be faster... //ConcatTransforms( (*m_protationmatrix), bonematrix, (*m_plighttransform)[i] ); MatrixCopy( (*m_pbonetransform)[i], (*m_plighttransform)[i] ); } else { ConcatTransforms( (*m_paliastransform), bonematrix, (*m_pbonetransform)[i] ); ConcatTransforms( (*m_protationmatrix), bonematrix, (*m_plighttransform)[i] ); } // Apply client-side effects to the transformation matrix StudioFxTransform( m_pCurrentEntity, (*m_pbonetransform)[i] ); } else { ConcatTransforms( (*m_pbonetransform)[pbones[i].parent], bonematrix, (*m_pbonetransform)[i] ); ConcatTransforms( (*m_plighttransform)[pbones[i].parent], bonematrix, (*m_plighttransform)[i] ); } } } } /* ==================== StudioDrawModel ==================== */ int CStudioModelRenderer::StudioDrawModel( int flags ) { alight_t lighting; vec3_t dir; m_pCurrentEntity = IEngineStudio.GetCurrentEntity(); IEngineStudio.GetTimes( &m_nFrameCount, &m_clTime, &m_clOldTime ); IEngineStudio.GetViewInfo( m_vRenderOrigin, m_vUp, m_vRight, m_vNormal ); IEngineStudio.GetAliasScale( &m_fSoftwareXScale, &m_fSoftwareYScale ); if( m_pCurrentEntity->curstate.renderfx == kRenderFxDeadPlayer ) { entity_state_t deadplayer; int result; int save_interp; if( m_pCurrentEntity->curstate.renderamt <= 0 || m_pCurrentEntity->curstate.renderamt > gEngfuncs.GetMaxClients() ) return 0; // get copy of player deadplayer = *( IEngineStudio.GetPlayerState( m_pCurrentEntity->curstate.renderamt - 1 ) ); //cl.frames[cl.parsecount & CL_UPDATE_MASK].playerstate[m_pCurrentEntity->curstate.renderamt - 1]; // clear weapon, movement state deadplayer.number = m_pCurrentEntity->curstate.renderamt; deadplayer.weaponmodel = 0; deadplayer.gaitsequence = 0; deadplayer.movetype = MOVETYPE_NONE; VectorCopy( m_pCurrentEntity->curstate.angles, deadplayer.angles ); VectorCopy( m_pCurrentEntity->curstate.origin, deadplayer.origin ); save_interp = m_fDoInterp; m_fDoInterp = 0; // draw as though it were a player result = StudioDrawPlayer( flags, &deadplayer ); m_fDoInterp = save_interp; return result; } m_pRenderModel = m_pCurrentEntity->model; m_pStudioHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata( m_pRenderModel ); IEngineStudio.StudioSetHeader( m_pStudioHeader ); IEngineStudio.SetRenderModel( m_pRenderModel ); StudioSetUpTransform( 0 ); if( flags & STUDIO_RENDER ) { // see if the bounding box lets us trivially reject, also sets if( !IEngineStudio.StudioCheckBBox() ) return 0; ( *m_pModelsDrawn )++; ( *m_pStudioModelCount )++; // render data cache cookie if( m_pStudioHeader->numbodyparts == 0 ) return 1; } if( m_pCurrentEntity->curstate.movetype == MOVETYPE_FOLLOW ) { StudioMergeBones( m_pRenderModel ); } else { StudioSetupBones(); } StudioSaveBones(); if( flags & STUDIO_EVENTS ) { StudioCalcAttachments(); IEngineStudio.StudioClientEvents(); // copy attachments into global entity array if( m_pCurrentEntity->index > 0 ) { cl_entity_t *ent = gEngfuncs.GetEntityByIndex( m_pCurrentEntity->index ); memcpy( ent->attachment, m_pCurrentEntity->attachment, sizeof(vec3_t) * 4 ); } } if( flags & STUDIO_RENDER ) { lighting.plightvec = dir; IEngineStudio.StudioDynamicLight( m_pCurrentEntity, &lighting ); IEngineStudio.StudioEntityLight( &lighting ); // model and frame independant IEngineStudio.StudioSetupLighting( &lighting ); // get remap colors m_nTopColor = m_pCurrentEntity->curstate.colormap & 0xFF; m_nBottomColor = ( m_pCurrentEntity->curstate.colormap & 0xFF00 ) >> 8; IEngineStudio.StudioSetRemapColors( m_nTopColor, m_nBottomColor ); StudioRenderModel(); } //LRC if (m_pCurrentEntity->curstate.renderfx == kRenderFxReflection ) { m_pCurrentEntity->curstate.renderfx = kRenderFxNone; (*m_protationmatrix)[2][2] *= -1; gEngfuncs.pTriAPI->CullFace( TRI_NONE ); // we're turning the model inside out, so we need to draw // the opposite faces of the tris. This should really be TRI_BACK, but that's not available. :( // glCullFace(GL_BACK); if (flags & STUDIO_RENDER) { // see if the bounding box lets us trivially reject, also sets if (!IEngineStudio.StudioCheckBBox ()) return 0; (*m_pModelsDrawn)++; (*m_pStudioModelCount)++; // render data cache cookie if (m_pStudioHeader->numbodyparts == 0) return 1; } if (m_pCurrentEntity->curstate.movetype == MOVETYPE_FOLLOW) { StudioMergeBones( m_pRenderModel ); } else { StudioSetupBones( ); } StudioSaveBones( ); if (flags & STUDIO_EVENTS) { StudioCalcAttachments( ); IEngineStudio.StudioClientEvents( ); // copy attachments into global entity array if ( m_pCurrentEntity->index > 0 ) { cl_entity_t *ent = gEngfuncs.GetEntityByIndex( m_pCurrentEntity->index ); memcpy( ent->attachment, m_pCurrentEntity->attachment, sizeof( vec3_t ) * 4 ); } } if (flags & STUDIO_RENDER) { lighting.plightvec = dir; IEngineStudio.StudioDynamicLight(m_pCurrentEntity, &lighting ); IEngineStudio.StudioEntityLight( &lighting ); // model and frame independant IEngineStudio.StudioSetupLighting (&lighting); // get remap colors m_nTopColor = m_pCurrentEntity->curstate.colormap & 0xFF; m_nBottomColor = (m_pCurrentEntity->curstate.colormap & 0xFF00) >> 8; IEngineStudio.StudioSetRemapColors( m_nTopColor, m_nBottomColor ); StudioRenderModel( ); } // glCullFace(GL_FRONT); gEngfuncs.pTriAPI->CullFace( TRI_FRONT ); m_pCurrentEntity->curstate.renderfx = kRenderFxReflection; } return 1; } /* ==================== StudioEstimateGait ==================== */ void CStudioModelRenderer::StudioEstimateGait( entity_state_t *pplayer ) { float dt; vec3_t est_velocity; dt = ( m_clTime - m_clOldTime ); if( dt < 0.0f ) dt = 0.0f; else if( dt > 1.0f ) dt = 1.0f; if( dt == 0 || m_pPlayerInfo->renderframe == m_nFrameCount ) { m_flGaitMovement = 0; return; } // VectorAdd( pplayer->velocity, pplayer->prediction_error, est_velocity ); if( m_fGaitEstimation ) { VectorSubtract( m_pCurrentEntity->origin, m_pPlayerInfo->prevgaitorigin, est_velocity ); VectorCopy( m_pCurrentEntity->origin, m_pPlayerInfo->prevgaitorigin ); m_flGaitMovement = Length( est_velocity ); if( dt <= 0 || m_flGaitMovement / dt < 5 ) { m_flGaitMovement = 0; est_velocity[0] = 0; est_velocity[1] = 0; } } else { VectorCopy( pplayer->velocity, est_velocity ); m_flGaitMovement = Length( est_velocity ) * dt; } if( est_velocity[1] == 0 && est_velocity[0] == 0 ) { float flYawDiff = m_pCurrentEntity->angles[YAW] - m_pPlayerInfo->gaityaw; flYawDiff = flYawDiff - (int)( flYawDiff / 360.0f ) * 360.0f; 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.0f ) * 360.0f; m_flGaitMovement = 0; } else { m_pPlayerInfo->gaityaw = ( atan2( est_velocity[1], est_velocity[0] ) * 180.0f / 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; } } /* ==================== StudioProcessGait ==================== */ void CStudioModelRenderer::StudioProcessGait( entity_state_t *pplayer ) { mstudioseqdesc_t *pseqdesc; float dt; int iBlend; float flYaw; // view direction relative to movement if( m_pCurrentEntity->curstate.sequence >= m_pStudioHeader->numseq ) { m_pCurrentEntity->curstate.sequence = 0; } pseqdesc = (mstudioseqdesc_t *)( (byte *)m_pStudioHeader + m_pStudioHeader->seqindex ) + m_pCurrentEntity->curstate.sequence; StudioPlayerBlend( pseqdesc, &iBlend, &m_pCurrentEntity->angles[PITCH] ); m_pCurrentEntity->latched.prevangles[PITCH] = m_pCurrentEntity->angles[PITCH]; m_pCurrentEntity->curstate.blending[0] = iBlend; m_pCurrentEntity->latched.prevblending[0] = m_pCurrentEntity->curstate.blending[0]; m_pCurrentEntity->latched.prevseqblending[0] = m_pCurrentEntity->curstate.blending[0]; // Con_DPrintf( "%f %d\n", m_pCurrentEntity->angles[PITCH], m_pCurrentEntity->blending[0] ); dt = ( m_clTime - m_clOldTime ); if( dt < 0.0f ) dt = 0.0f; else if( dt > 1.0f ) dt = 1.0f; StudioEstimateGait( pplayer ); // Con_DPrintf( "%f %f\n", m_pCurrentEntity->angles[YAW], m_pPlayerInfo->gaityaw ); // calc side to side turning flYaw = m_pCurrentEntity->angles[YAW] - m_pPlayerInfo->gaityaw; flYaw = flYaw - (int)( flYaw / 360.0f ) * 360.0f; 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 m_pCurrentEntity->curstate.controller[0] = ( ( flYaw / 4.0f ) + 30.0f ) / ( 60.0f / 255.0f ); m_pCurrentEntity->curstate.controller[1] = ( ( flYaw / 4.0f ) + 30.0f ) / ( 60.0f / 255.0f ); m_pCurrentEntity->curstate.controller[2] = ( ( flYaw / 4.0f ) + 30.0f ) / ( 60.0f / 255.0f ); m_pCurrentEntity->curstate.controller[3] = ( ( flYaw / 4.0f ) + 30.0f ) / ( 60.0f / 255.0f ); m_pCurrentEntity->latched.prevcontroller[0] = m_pCurrentEntity->curstate.controller[0]; m_pCurrentEntity->latched.prevcontroller[1] = m_pCurrentEntity->curstate.controller[1]; m_pCurrentEntity->latched.prevcontroller[2] = m_pCurrentEntity->curstate.controller[2]; m_pCurrentEntity->latched.prevcontroller[3] = m_pCurrentEntity->curstate.controller[3]; m_pCurrentEntity->angles[YAW] = m_pPlayerInfo->gaityaw; if( m_pCurrentEntity->angles[YAW] < -0.0f ) m_pCurrentEntity->angles[YAW] += 360.0f; m_pCurrentEntity->latched.prevangles[YAW] = m_pCurrentEntity->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; } /* ==================== StudioDrawPlayer ==================== */ int CStudioModelRenderer::StudioDrawPlayer( int flags, entity_state_t *pplayer ) { alight_t lighting; vec3_t dir; m_pCurrentEntity = IEngineStudio.GetCurrentEntity(); IEngineStudio.GetTimes( &m_nFrameCount, &m_clTime, &m_clOldTime ); IEngineStudio.GetViewInfo( m_vRenderOrigin, m_vUp, m_vRight, m_vNormal ); IEngineStudio.GetAliasScale( &m_fSoftwareXScale, &m_fSoftwareYScale ); // Con_DPrintf( "DrawPlayer %d\n", m_pCurrentEntity->blending[0] ); // Con_DPrintf( "DrawPlayer %d %d (%d)\n", m_nFrameCount, pplayer->player_index, m_pCurrentEntity->curstate.sequence ); // Con_DPrintf("Player %.2f %.2f %.2f\n", pplayer->velocity[0], pplayer->velocity[1], pplayer->velocity[2] ); m_nPlayerIndex = pplayer->number - 1; if( m_nPlayerIndex < 0 || m_nPlayerIndex >= gEngfuncs.GetMaxClients() ) return 0; m_pRenderModel = IEngineStudio.SetupPlayerModel( m_nPlayerIndex ); if( m_pRenderModel == NULL ) return 0; m_pStudioHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata( m_pRenderModel ); IEngineStudio.StudioSetHeader( m_pStudioHeader ); IEngineStudio.SetRenderModel( m_pRenderModel ); if( pplayer->gaitsequence ) { vec3_t orig_angles; m_pPlayerInfo = IEngineStudio.PlayerInfo( m_nPlayerIndex ); VectorCopy( m_pCurrentEntity->angles, orig_angles ); StudioProcessGait( pplayer ); m_pPlayerInfo->gaitsequence = pplayer->gaitsequence; m_pPlayerInfo = NULL; StudioSetUpTransform( 0 ); VectorCopy( orig_angles, m_pCurrentEntity->angles ); } else { m_pCurrentEntity->curstate.controller[0] = 127; m_pCurrentEntity->curstate.controller[1] = 127; m_pCurrentEntity->curstate.controller[2] = 127; m_pCurrentEntity->curstate.controller[3] = 127; m_pCurrentEntity->latched.prevcontroller[0] = m_pCurrentEntity->curstate.controller[0]; m_pCurrentEntity->latched.prevcontroller[1] = m_pCurrentEntity->curstate.controller[1]; m_pCurrentEntity->latched.prevcontroller[2] = m_pCurrentEntity->curstate.controller[2]; m_pCurrentEntity->latched.prevcontroller[3] = m_pCurrentEntity->curstate.controller[3]; m_pPlayerInfo = IEngineStudio.PlayerInfo( m_nPlayerIndex ); m_pPlayerInfo->gaitsequence = 0; StudioSetUpTransform( 0 ); } if( flags & STUDIO_RENDER ) { // see if the bounding box lets us trivially reject, also sets if( !IEngineStudio.StudioCheckBBox () ) return 0; ( *m_pModelsDrawn )++; ( *m_pStudioModelCount )++; // render data cache cookie if( m_pStudioHeader->numbodyparts == 0 ) return 1; } m_pPlayerInfo = IEngineStudio.PlayerInfo( m_nPlayerIndex ); StudioSetupBones(); StudioSaveBones(); m_pPlayerInfo->renderframe = m_nFrameCount; m_pPlayerInfo = NULL; if( flags & STUDIO_EVENTS ) { StudioCalcAttachments(); IEngineStudio.StudioClientEvents(); // copy attachments into global entity array if( m_pCurrentEntity->index > 0 ) { cl_entity_t *ent = gEngfuncs.GetEntityByIndex( m_pCurrentEntity->index ); memcpy( ent->attachment, m_pCurrentEntity->attachment, sizeof(vec3_t) * 4 ); } } if( flags & STUDIO_RENDER ) { if( m_pCvarHiModels->value && m_pRenderModel != m_pCurrentEntity->model ) { // show highest resolution multiplayer model m_pCurrentEntity->curstate.body = 255; } if( !( m_pCvarDeveloper->value == 0 && gEngfuncs.GetMaxClients() == 1 ) && ( m_pRenderModel == m_pCurrentEntity->model ) ) { m_pCurrentEntity->curstate.body = 1; // force helmet } lighting.plightvec = dir; IEngineStudio.StudioDynamicLight( m_pCurrentEntity, &lighting ); IEngineStudio.StudioEntityLight( &lighting ); // model and frame independant IEngineStudio.StudioSetupLighting( &lighting ); m_pPlayerInfo = IEngineStudio.PlayerInfo( m_nPlayerIndex ); // get remap colors m_nTopColor = m_pPlayerInfo->topcolor; m_nBottomColor = m_pPlayerInfo->bottomcolor; if( m_nTopColor < 0 ) m_nTopColor = 0; if( m_nTopColor > 360 ) m_nTopColor = 360; if( m_nBottomColor < 0 ) m_nBottomColor = 0; if( m_nBottomColor > 360 ) m_nBottomColor = 360; IEngineStudio.StudioSetRemapColors( m_nTopColor, m_nBottomColor ); StudioRenderModel(); m_pPlayerInfo = NULL; if( pplayer->weaponmodel ) { cl_entity_t saveent = *m_pCurrentEntity; model_t *pweaponmodel = IEngineStudio.GetModelByIndex( pplayer->weaponmodel ); m_pStudioHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata( pweaponmodel ); IEngineStudio.StudioSetHeader( m_pStudioHeader ); StudioMergeBones( pweaponmodel ); IEngineStudio.StudioSetupLighting( &lighting ); StudioRenderModel(); StudioCalcAttachments(); *m_pCurrentEntity = saveent; } } return 1; } /* ==================== StudioCalcAttachments ==================== */ void CStudioModelRenderer::StudioCalcAttachments( void ) { int i; mstudioattachment_t *pattachment; if( m_pStudioHeader->numattachments > 4 ) { gEngfuncs.Con_DPrintf( "Too many attachments on %s\n", m_pCurrentEntity->model->name ); exit( -1 ); } // calculate attachment points pattachment = (mstudioattachment_t *)( (byte *)m_pStudioHeader + m_pStudioHeader->attachmentindex ); for( i = 0; i < m_pStudioHeader->numattachments; i++ ) { VectorTransform( pattachment[i].org, (*m_plighttransform)[pattachment[i].bone], m_pCurrentEntity->attachment[i] ); } } /* ==================== StudioRenderModel ==================== */ void CStudioModelRenderer::StudioRenderModel( void ) { IEngineStudio.SetChromeOrigin(); IEngineStudio.SetForceFaceFlags( 0 ); if( m_pCurrentEntity->curstate.renderfx == kRenderFxGlowShell ) { m_pCurrentEntity->curstate.renderfx = kRenderFxNone; StudioRenderFinal(); if( !IEngineStudio.IsHardware() ) { gEngfuncs.pTriAPI->RenderMode( kRenderTransAdd ); } IEngineStudio.SetForceFaceFlags( STUDIO_NF_CHROME ); gEngfuncs.pTriAPI->SpriteTexture( m_pChromeSprite, 0 ); m_pCurrentEntity->curstate.renderfx = kRenderFxGlowShell; StudioRenderFinal(); if( !IEngineStudio.IsHardware() ) { gEngfuncs.pTriAPI->RenderMode( kRenderNormal ); } } else { StudioRenderFinal(); } } /* ==================== StudioRenderFinal_Software ==================== */ void CStudioModelRenderer::StudioRenderFinal_Software( void ) { int i; // Note, rendermode set here has effect in SW IEngineStudio.SetupRenderer( 0 ); if( m_pCvarDrawEntities->value == 2 ) { IEngineStudio.StudioDrawBones(); } else if( m_pCvarDrawEntities->value == 3 ) { IEngineStudio.StudioDrawHulls(); } else { for( i = 0; i < m_pStudioHeader->numbodyparts; i++ ) { IEngineStudio.StudioSetupModel( i, (void **)&m_pBodyPart, (void **)&m_pSubModel ); IEngineStudio.StudioDrawPoints(); } } if( m_pCvarDrawEntities->value == 4 ) { gEngfuncs.pTriAPI->RenderMode( kRenderTransAdd ); IEngineStudio.StudioDrawHulls(); gEngfuncs.pTriAPI->RenderMode( kRenderNormal ); } if( m_pCvarDrawEntities->value == 5 ) { IEngineStudio.StudioDrawAbsBBox(); } IEngineStudio.RestoreRenderer(); } /* ==================== StudioRenderFinal_Hardware ==================== */ void CStudioModelRenderer::StudioRenderFinal_Hardware( void ) { int i; int rendermode; rendermode = IEngineStudio.GetForceFaceFlags() ? kRenderTransAdd : m_pCurrentEntity->curstate.rendermode; IEngineStudio.SetupRenderer( rendermode ); if( m_pCvarDrawEntities->value == 2 ) { IEngineStudio.StudioDrawBones(); } else if( m_pCvarDrawEntities->value == 3 ) { IEngineStudio.StudioDrawHulls(); } else { for( i = 0; i < m_pStudioHeader->numbodyparts; i++ ) { IEngineStudio.StudioSetupModel( i, (void **)&m_pBodyPart, (void **)&m_pSubModel ); if( m_fDoInterp ) { // interpolation messes up bounding boxes. m_pCurrentEntity->trivial_accept = 0; } IEngineStudio.GL_SetRenderMode( rendermode ); IEngineStudio.StudioDrawPoints(); IEngineStudio.GL_StudioDrawShadow(); } } if( m_pCvarDrawEntities->value == 4 ) { gEngfuncs.pTriAPI->RenderMode( kRenderTransAdd ); IEngineStudio.StudioDrawHulls(); gEngfuncs.pTriAPI->RenderMode( kRenderNormal ); } if( m_pCvarDrawEntities->value == 5 ) { IEngineStudio.StudioDrawAbsBBox(); } IEngineStudio.RestoreRenderer(); } /* ==================== StudioRenderFinal ==================== */ void CStudioModelRenderer::StudioRenderFinal( void ) { if( IEngineStudio.IsHardware() ) { StudioRenderFinal_Hardware(); } else { StudioRenderFinal_Software(); } }