/* gl_studio_draw.cpp - rendering studio models Copyright (C) 2019 Uncle Mike This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ #include "hud.h" #include "cl_util.h" #include "gl_local.h" #include "com_model.h" #include "r_studioint.h" #include "pm_movevars.h" #include "gl_studio.h" #include "gl_sprite.h" #include "event_api.h" #include #include "pm_defs.h" #include "stringlib.h" #include "triangleapi.h" #include "entity_types.h" #include "gl_shader.h" #include "gl_world.h" #define LIGHT_INTERP_UPDATE 0.1f #define LIGHT_INTERP_FACTOR (1.0f / LIGHT_INTERP_UPDATE) /* ================= SortSolidMeshes sort by shaders to reduce state switches ================= */ int CStudioModelRenderer :: SortSolidMeshes( const CSolidEntry *a, const CSolidEntry *b ) { if( a->m_hProgram > b->m_hProgram ) return -1; if( a->m_hProgram < b->m_hProgram ) return 1; return 0; } /* ================ HeadShieldThink process client-side animations ================ */ int CStudioModelRenderer :: HeadShieldThink( void ) { switch( gHUD.m_iHeadShieldState ) { case SHIELD_ON: return 1; case SHIELD_TURNING_ON: if( tr.time > gHUD.m_flHeadShieldSwitchTime ) { gHUD.m_iHeadShieldState = SHIELD_ON; gHUD.m_pHeadShieldEnt->curstate.animtime = tr.time; gHUD.m_pHeadShieldEnt->curstate.sequence = SHIELDANIM_IDLE; } return 1; case SHIELD_TURNING_OFF: if( tr.time > gHUD.m_flHeadShieldSwitchTime ) { gHUD.m_iHeadShieldState = SHIELD_OFF; return 0; } else { return 1; } case SHIELD_OFF: return 0; default: ALERT( at_error, "HeadShield: invalid state %d\n", gHUD.m_iHeadShieldState ); return 0; } } /* ================ StudioExtractBbox Extract bbox from current sequence ================ */ int CStudioModelRenderer :: StudioExtractBbox( studiohdr_t *phdr, int sequence, Vector &mins, Vector &maxs ) { if( !phdr || sequence < 0 || sequence >= phdr->numseq ) return 0; mstudioseqdesc_t *pseqdesc = (mstudioseqdesc_t *)((byte *)phdr + phdr->seqindex) + sequence; mins = pseqdesc->bbmin; maxs = pseqdesc->bbmax; return 1; } /* ================ StudioGetBounds Get bounds for a current sequence ================ */ int CStudioModelRenderer :: StudioGetBounds( cl_entity_t *e, Vector bounds[2] ) { if( !e || e->modelhandle == INVALID_HANDLE ) return 0; ModelInstance_t *inst = &m_ModelInstances[e->modelhandle]; bounds[0] = inst->absmin; bounds[1] = inst->absmax; return 1; } /* ================ StudioGetBounds Get bounds for a given mesh ================ */ int CStudioModelRenderer :: StudioGetBounds( CSolidEntry *entry, Vector bounds[2] ) { if( !entry || entry->m_bDrawType != DRAWTYPE_MESH ) return 0; if( !entry->m_pParentEntity || entry->m_pParentEntity->modelhandle == INVALID_HANDLE ) return 0; vbomesh_t *vbo = entry->m_pMesh; if( !vbo || vbo->parentbone == 0xFF ) return 0; ModelInstance_t *inst = &m_ModelInstances[entry->m_pParentEntity->modelhandle]; TransformAABB( inst->m_pbones[vbo->parentbone], vbo->mins, vbo->maxs, bounds[0], bounds[1] ); return 1; } /* ================ UpdateLatchedVars lerp custom values ================ */ void CStudioModelRenderer :: UpdateLatchedVars( cl_entity_t *e, qboolean reset ) { if( !g_fRenderInitialized ) return; if( !StudioSetEntity( e )) return; // store old values to right lerping from m_pModelInstance->m_oldposeparams[0] = e->prevstate.vuser1[0]; m_pModelInstance->m_oldposeparams[1] = e->prevstate.vuser1[1]; m_pModelInstance->m_oldposeparams[2] = e->prevstate.vuser1[2]; m_pModelInstance->m_oldposeparams[3] = e->prevstate.vuser2[0]; m_pModelInstance->m_oldposeparams[4] = e->prevstate.vuser2[1]; m_pModelInstance->m_oldposeparams[5] = e->prevstate.vuser2[2]; m_pModelInstance->m_oldposeparams[6] = e->prevstate.vuser3[0]; m_pModelInstance->m_oldposeparams[7] = e->prevstate.vuser3[1]; m_pModelInstance->m_oldposeparams[8] = e->prevstate.vuser3[2]; m_pModelInstance->m_oldposeparams[ 9] = e->prevstate.vuser4[0]; m_pModelInstance->m_oldposeparams[10] = e->prevstate.vuser4[1]; m_pModelInstance->m_oldposeparams[11] = e->prevstate.vuser4[2]; } /* ================ StudioComputeBBox Compute a full bounding box for current sequence ================ */ int CStudioModelRenderer :: StudioComputeBBox( void ) { Vector p1, p2, scale = Vector( 1.0f, 1.0f, 1.0f ); int sequence = RI->currententity->curstate.sequence; Vector origin, mins, maxs; if( FBitSet( m_pModelInstance->info_flags, MF_STATIC_BOUNDS )) return true; // bounds already computed if( !StudioExtractBbox( m_pStudioHeader, sequence, mins, maxs )) return false; // FIXME: some problems in studiomdl compiler? if( BoundsIsCleared( mins, maxs ) || BoundsIsNull( mins, maxs )) { mstudioseqdesc_t *pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + sequence; ALERT( at_aiconsole, "%s: sequence: %s has invalid bbox\n", RI->currentmodel->name, pseqdesc->label ); // some seqeunces can have invalid bbox size, so we need to start from default size AddPointToBounds( RI->currentmodel->mins, mins, maxs ); AddPointToBounds( RI->currentmodel->maxs, mins, maxs ); } // prevent to compute env_static bounds every frame if( FBitSet( RI->currententity->curstate.iuser1, CF_STATIC_ENTITY ) && RI->currententity->curstate.renderfx != SKYBOX_ENTITY ) SetBits( m_pModelInstance->info_flags, MF_STATIC_BOUNDS ); if( FBitSet( RI->currententity->curstate.iuser1, CF_STATIC_ENTITY )) { if( RI->currententity->curstate.startpos != g_vecZero ) scale = RI->currententity->curstate.startpos; } else if( RI->currententity->curstate.scale > 0.0f && RI->currententity->curstate.scale <= 16.0f ) { // apply studiomodel scale (clamp scale to prevent too big sizes on some HL maps) scale = Vector( RI->currententity->curstate.scale ); } Vector angles = RI->currententity->angles; angles[PITCH] = -angles[PITCH]; // stupid quakebug // don't rotate player model, only aim if( RI->currententity->player ) angles[PITCH] = 0; origin = StudioGetOrigin(); matrix3x4 transform = matrix3x4( g_vecZero, angles, scale ); // rotate and scale bbox for env_static TransformAABB( transform, mins, maxs, mins, maxs ); if( m_pModelInstance->origin != origin ) SetBits( m_pModelInstance->info_flags, MF_POSITION_CHANGED ); // compute abs box m_pModelInstance->absmin = mins + origin; m_pModelInstance->absmax = maxs + origin; m_pModelInstance->radius = RadiusFromBounds( mins, maxs ); m_pModelInstance->origin = origin; return true; } /* ==================== AddBlendSequence multiple blends ==================== */ void CStudioModelRenderer :: AddBlendSequence( int oldseq, int newseq, float prevframe, bool gaitseq ) { mstudioseqdesc_t *poldseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + oldseq; mstudioseqdesc_t *pnewseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + newseq; // sequence has changed, hold the previous sequence info if( oldseq != newseq && !FBitSet( pnewseqdesc->flags, STUDIO_SNAP )) { mstudioblendseq_t *pseqblending; // move current sequence into circular buffer m_pModelInstance->m_current_seqblend = (m_pModelInstance->m_current_seqblend + 1) & MASK_SEQBLENDS; pseqblending = &m_pModelInstance->m_seqblend[m_pModelInstance->m_current_seqblend]; pseqblending->blendtime = tr.time; pseqblending->sequence = oldseq; pseqblending->cycle = prevframe / m_boneSetup.LocalMaxFrame( oldseq ); pseqblending->gaitseq = gaitseq; pseqblending->fadeout = Q_min( poldseqdesc->fadeouttime / 100.0f, pnewseqdesc->fadeintime / 100.0f ); if( pseqblending->fadeout <= 0.0f ) pseqblending->fadeout = 0.2f; // force to default } } /* ==================== CalcStairSmoothValue smoothing stepsize height ==================== */ float CStudioModelRenderer :: CalcStairSmoothValue( float oldz, float newz, float smoothtime, float smoothvalue ) { if( oldz < newz ) return bound( newz - tr.movevars->stepsize, oldz + smoothtime * smoothvalue, newz ); if( oldz > newz ) return bound( newz, oldz - smoothtime * smoothvalue, newz + tr.movevars->stepsize ); return 0.0f; } /* ==================== StudioCheckLOD check special bodygroup ==================== */ int CStudioModelRenderer :: StudioCheckLOD( void ) { mstudiobodyparts_t *m_pBodyPart; for( int i = 0; i < m_pStudioHeader->numbodyparts; i++ ) { m_pBodyPart = (mstudiobodyparts_t *)((byte *)m_pStudioHeader + m_pStudioHeader->bodypartindex) + i; if( !Q_stricmp( m_pBodyPart->name, "studioLOD" )) return m_pBodyPart->nummodels; } return 0; // no lod-levels for this model } const Vector CStudioModelRenderer :: StudioGetOrigin( void ) { Vector origin = RI->currententity->origin; if( RI->currententity->curstate.renderfx == SKYBOX_ENTITY ) { // calc skybox origin Vector trans = GetVieworg() - tr.sky_origin; if( tr.sky_speed ) trans -= (GetVieworg() - tr.sky_world_origin) / tr.sky_speed; origin += trans; } return origin; } /* ==================== StudioSetUpTransform ==================== */ void CStudioModelRenderer :: StudioSetUpTransform( void ) { cl_entity_t *e = RI->currententity; bool disable_smooth = false; float step, smoothtime; int numLods; if( !m_fShootDecal && RI->currententity->curstate.renderfx != kRenderFxDeadPlayer ) { if( m_iDrawModelType == DRAWSTUDIO_HEADSHIELD || m_iDrawModelType == DRAWSTUDIO_RUNEVENTS ) e = gEngfuncs.GetLocalPlayer(); // calculate how much time has passed since the last V_CalcRefdef smoothtime = bound( 0.0f, tr.time - m_pModelInstance->lerp.stairtime, 0.1f ); m_pModelInstance->lerp.stairtime = tr.time; if( e->curstate.onground == -1 || FBitSet( e->curstate.effects, EF_NOINTERP )) disable_smooth = true; if( e->curstate.movetype != MOVETYPE_STEP && e->curstate.movetype != MOVETYPE_WALK ) disable_smooth = true; if( !FBitSet( m_pModelInstance->info_flags, MF_INIT_SMOOTHSTAIRS )) { SetBits( m_pModelInstance->info_flags, MF_INIT_SMOOTHSTAIRS ); disable_smooth = true; } if( disable_smooth ) { m_pModelInstance->lerp.stairoldz = e->origin[2]; } else { step = CalcStairSmoothValue( m_pModelInstance->lerp.stairoldz, e->origin[2], smoothtime, STAIR_INTERP_TIME ); if( step ) m_pModelInstance->lerp.stairoldz = e->origin[2] = step; } e = RI->currententity; } Vector origin = StudioGetOrigin(); Vector angles = RI->currententity->angles; Vector scale = Vector( 1.0f, 1.0f, 1.0f ); // apply lodnum to model if(( numLods = StudioCheckLOD( )) != 0 ) { float radius = Q_max( m_pModelInstance->radius, 1.0f ); // to avoid division by zero #if 1 float screenSize = ComputePixelWidthOfSphere( origin, 0.5f ); float lodMetric = screenSize != 0.0f ? (100.0f / screenSize) : 0.0f; int lodnum = (int)( lodMetric / radius * 2.0f ); #else float lodDist = (origin - RI->view.origin).Length() * RI->view.lodScale; int lodnum = (int)( lodDist / radius ); if( CVAR_TO_BOOL( m_pCvarLodScale )) lodnum /= (int)fabs( m_pCvarLodScale->value ); #endif if( CVAR_TO_BOOL( m_pCvarLodBias )) lodnum += (int)fabs( m_pCvarLodBias->value ); // set derived LOD e->curstate.body = Q_min( lodnum, numLods - 1 ); } angles[PITCH] = -angles[PITCH]; // stupid quake bug! if( e->curstate.renderfx != kRenderFxDeadPlayer ) { int gaitsequence = 0; // don't rotate clients, only aim if( e->player ) angles[PITCH] = 0.0f; if( e->curstate.gaitsequence != m_pModelInstance->lerp.gaitsequence ) { AddBlendSequence( m_pModelInstance->lerp.gaitsequence, e->curstate.gaitsequence, m_pModelInstance->lerp.gaitframe, true ); m_pModelInstance->lerp.gaitsequence = e->curstate.gaitsequence; } } if( FBitSet( RI->currententity->curstate.effects, EF_NOINTERP ) || ( tr.realframecount - m_pModelInstance->cached_frame ) > 1 ) { m_pModelInstance->lerp.sequence = RI->currententity->curstate.sequence; } else if( RI->currententity->curstate.sequence != m_pModelInstance->lerp.sequence ) { AddBlendSequence( m_pModelInstance->lerp.sequence, RI->currententity->curstate.sequence, m_pModelInstance->lerp.frame ); m_pModelInstance->lerp.sequence = RI->currententity->curstate.sequence; } // store prevseqblending manually, engine doesn't it e->latched.prevseqblending[0] = e->curstate.blending[0]; e->latched.prevseqblending[1] = e->curstate.blending[1]; // don't blend sequences for a dead player or a viewmodel, faceprotect if( m_iDrawModelType > DRAWSTUDIO_NORMAL || RI->currententity->curstate.renderfx == kRenderFxDeadPlayer ) memset( &m_pModelInstance->m_seqblend, 0, sizeof( m_pModelInstance->m_seqblend )); if( RI->currententity->curstate.iuser1 & CF_STATIC_ENTITY ) { if( RI->currententity->curstate.startpos != g_vecZero ) scale = RI->currententity->curstate.startpos; } else if( RI->currententity->curstate.scale > 0.0f && RI->currententity->curstate.scale <= 16.0f ) { // apply studiomodel scale (clamp scale to prevent too big sizes on some HL maps) scale = Vector( RI->currententity->curstate.scale, RI->currententity->curstate.scale, RI->currententity->curstate.scale ); } if( RP_LOCALCLIENT( RI->currententity ) && !FBitSet( RI->params, RP_THIRDPERSON )) { // offset only for legs or water reflection if(( RI->currentmodel == m_pPlayerLegsModel ) || FBitSet( RI->params, RP_SHADOWVIEW|RP_MIRRORVIEW ) || ( GetVForward().z == 1.0f )) { Vector ang, forward; ang = tr.cached_viewangles; ang[PITCH] = ang[ROLL] = 0; // yaw only AngleVectors( ang, forward, NULL, NULL ); origin += forward * -m_pCvarLegsOffset->value; } } // build the rotation matrix m_pModelInstance->m_protationmatrix = matrix3x4( origin, angles, scale ); if( RI->currententity == GET_VIEWMODEL() && CVAR_TO_BOOL( m_pCvarHand )) { // inverse the right vector m_pModelInstance->m_protationmatrix.SetRight( -m_pModelInstance->m_protationmatrix.GetRight() ); } StudioFxTransform( e, m_pModelInstance->m_protationmatrix ); } /* ==================== StudioEstimateFrame ==================== */ float CStudioModelRenderer :: StudioEstimateFrame( mstudioseqdesc_t *pseqdesc ) { int numframes = m_boneSetup.LocalMaxFrame( RI->currententity->curstate.sequence ); double fps = m_boneSetup.LocalFPS( RI->currententity->curstate.sequence ); double dfdt = 0, f = 0; if( !m_fShootDecal && tr.time >= RI->currententity->curstate.animtime ) dfdt = (tr.time - RI->currententity->curstate.animtime) * RI->currententity->curstate.framerate * fps; if( numframes > 1 ) f = (RI->currententity->curstate.frame * numframes) / 256.0; f += dfdt; if( pseqdesc->flags & STUDIO_LOOPING ) { if( numframes > 1 ) { f -= (int)(f / numframes) * numframes; } if( f < 0.0 ) { f += numframes; } } else { if( f >= numframes ) { f = numframes; } if( f < 0.0 ) { f = 0.0; } } return f; } /* ==================== StudioEstimateFrame ==================== */ float CStudioModelRenderer :: StudioEstimateGaitFrame( mstudioseqdesc_t *pseqdesc ) { cl_entity_t *e = RI->currententity; int numframes = m_boneSetup.LocalMaxFrame( e->curstate.gaitsequence ); double dfdt = 0, f = 0; // FIXME: gaitinterp is broken if( !m_fShootDecal && tr.time >= RI->currententity->curstate.animtime ) dfdt = (tr.time - RI->currententity->curstate.animtime) * tr.frametime; if( numframes > 1 ) f = RI->currententity->curstate.fuser1; f += dfdt; if( pseqdesc->flags & STUDIO_LOOPING ) { if( numframes > 1 ) { f -= (int)(f / numframes) * numframes; } if( f < 0.0 ) { f += numframes; } } else { if( f >= numframes - 1.001 ) { f = numframes - 1.001; } if( f < 0.0 ) { f = 0.0; } } return f; } /* ==================== StudioEstimateInterpolant ==================== */ float CStudioModelRenderer :: StudioEstimateInterpolant( void ) { cl_entity_t *e = RI->currententity; double rate = 0.1; // monster think float dadt = 1.0f; if( e->player ) { rate = atof( gEngfuncs.PlayerInfo_ValueForKey( e->index, "cl_updaterate" )); if( rate != 0.0 ) rate = 1.0 / rate; if( gEngfuncs.GetMaxClients() == 1 ) rate = 1.0f; // it's single player and has unlimited update rate } if( !m_fShootDecal && ( e->curstate.animtime >= e->latched.prevanimtime + 0.01f )) { dadt = (tr.time - e->curstate.animtime) / (e->player) ? 0.01f : 0.1f; // think interval if( dadt > 1.0f ) dadt = 1.0f; } return dadt; } /* ==================== StudioInterpolatePoseParams ==================== */ void CStudioModelRenderer :: StudioInterpolatePoseParams( cl_entity_t *e, float dadt ) { if( !m_boneSetup.CountPoseParameters( )) { // interpolate blends m_pModelInstance->m_poseparameter[0] = (e->curstate.blending[0] * dadt + e->latched.prevblending[0] * (1.0f - dadt)) / 255.0f; m_pModelInstance->m_poseparameter[1] = (e->curstate.blending[1] * dadt + e->latched.prevblending[1] * (1.0f - dadt)) / 255.0f; } else { m_pModelInstance->m_poseparameter[0] = (e->curstate.vuser1[0] * dadt + m_pModelInstance->m_oldposeparams[0] * (1.0f - dadt)); m_pModelInstance->m_poseparameter[1] = (e->curstate.vuser1[1] * dadt + m_pModelInstance->m_oldposeparams[1] * (1.0f - dadt)); m_pModelInstance->m_poseparameter[2] = (e->curstate.vuser1[2] * dadt + m_pModelInstance->m_oldposeparams[2] * (1.0f - dadt)); m_pModelInstance->m_poseparameter[3] = (e->curstate.vuser2[0] * dadt + m_pModelInstance->m_oldposeparams[3] * (1.0f - dadt)); m_pModelInstance->m_poseparameter[4] = (e->curstate.vuser2[1] * dadt + m_pModelInstance->m_oldposeparams[4] * (1.0f - dadt)); m_pModelInstance->m_poseparameter[5] = (e->curstate.vuser2[2] * dadt + m_pModelInstance->m_oldposeparams[5] * (1.0f - dadt)); m_pModelInstance->m_poseparameter[6] = (e->curstate.vuser3[0] * dadt + m_pModelInstance->m_oldposeparams[6] * (1.0f - dadt)); m_pModelInstance->m_poseparameter[7] = (e->curstate.vuser3[1] * dadt + m_pModelInstance->m_oldposeparams[7] * (1.0f - dadt)); m_pModelInstance->m_poseparameter[8] = (e->curstate.vuser3[2] * dadt + m_pModelInstance->m_oldposeparams[8] * (1.0f - dadt)); m_pModelInstance->m_poseparameter[ 9] = (e->curstate.vuser4[0] * dadt + m_pModelInstance->m_oldposeparams[ 9] * (1.0f - dadt)); m_pModelInstance->m_poseparameter[10] = (e->curstate.vuser4[1] * dadt + m_pModelInstance->m_oldposeparams[10] * (1.0f - dadt)); m_pModelInstance->m_poseparameter[11] = (e->curstate.vuser4[2] * dadt + m_pModelInstance->m_oldposeparams[11] * (1.0f - dadt)); } } /* ==================== StudioInterpolateControllers ==================== */ void CStudioModelRenderer :: StudioInterpolateControllers( cl_entity_t *e, float dadt ) { // buz: хак, позволяющий не интерполировать контроллеры для стационарного пулемета if( RI->currententity->curstate.renderfx == 51 ) dadt = 1.0f; mstudiobonecontroller_t *pbonecontroller = (mstudiobonecontroller_t *)((byte *)m_pStudioHeader + m_pStudioHeader->bonecontrollerindex); // interpolate controllers for( int j = 0; j < m_pStudioHeader->numbonecontrollers; j++ ) { int i = pbonecontroller[j].index; float value; if( i <= 3 ) { // check for 360% wrapping if( FBitSet( pbonecontroller[j].type, STUDIO_RLOOP )) { if( abs( e->curstate.controller[i] - e->latched.prevcontroller[i] ) > 128 ) { int a = (e->curstate.controller[j] + 128) % 256; int b = (e->latched.prevcontroller[j] + 128) % 256; value = ((a * dadt) + (b * (1.0f - dadt)) - 128); } else { value = ((e->curstate.controller[i] * dadt + (e->latched.prevcontroller[i]) * (1.0f - dadt))); } } else { value = (e->curstate.controller[i] * dadt + e->latched.prevcontroller[i] * (1.0 - dadt)); } m_pModelInstance->m_controller[i] = bound( 0, Q_rint( value ), 255 ); } } } /* ==================== StudioGetAnim ==================== */ mstudioanim_t *CStudioModelRenderer :: StudioGetAnim( model_t *pModel, 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 + pseqgroup->data + pseqdesc->animindex); paSequences = (cache_user_t *)pModel->submodels; if( paSequences == NULL ) { paSequences = (cache_user_t *)IEngineStudio.Mem_Calloc( MAXSTUDIOGROUPS, sizeof( cache_user_t )); pModel->submodels = (dmodel_t *)paSequences; } // check for already loaded if( !IEngineStudio.Cache_Check(( struct cache_user_s *)&(paSequences[pseqdesc->seqgroup] ))) { char filepath[128], modelpath[128], modelname[64]; COM_FileBase( pModel->name, modelname ); COM_ExtractFilePath( pModel->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 ); ALERT( at_console, "loading: %s\n", filepath ); IEngineStudio.LoadCacheFile( filepath, (struct cache_user_s *)&paSequences[pseqdesc->seqgroup] ); } return (mstudioanim_t *)((byte *)paSequences[pseqdesc->seqgroup].data + pseqdesc->animindex); } /* ==================== Studio_FxTransform ==================== */ void CStudioModelRenderer :: StudioFxTransform( cl_entity_t *ent, matrix3x4 &transform ) { switch( ent->curstate.renderfx ) { case kRenderFxDistort: case kRenderFxHologram: if( RANDOM_LONG( 0, 49 ) == 0 ) { // choose between x & z switch( RANDOM_LONG( 0, 1 )) { case 0: transform.SetForward( transform.GetForward() * RANDOM_FLOAT( 1.0f, 1.484f )); break; case 1: transform.SetUp( transform.GetUp() * RANDOM_FLOAT( 1.0f, 1.484f )); break; } } else if( RANDOM_LONG( 0, 49 ) == 0 ) { transform[3][RANDOM_LONG( 0, 2 )] += RANDOM_FLOAT( -10.0f, 10.0f ); } break; case kRenderFxExplode: { float scale = 1.0f + ( tr.time - ent->curstate.animtime ) * 10.0f; if( scale > 2 ) scale = 2; // don't blow up more than 200% transform.SetRight( transform.GetRight() * scale ); } break; } } void CStudioModelRenderer :: BlendSequence( Vector pos[], Vector4D q[], mstudioblendseq_t *pseqblend ) { float m_flGaitBoneWeights[MAXSTUDIOBONES]; CIKContext *pIK = NULL; // to prevent division by zero if( pseqblend->fadeout <= 0.0f ) pseqblend->fadeout = 0.2f; if( m_boneSetup.GetNumIKChains( )) pIK = &m_pModelInstance->m_ik; if( pseqblend->blendtime && ( pseqblend->blendtime + pseqblend->fadeout > tr.time ) && ( pseqblend->sequence < m_pStudioHeader->numseq )) { float s = 1.0f - (tr.time - pseqblend->blendtime) / pseqblend->fadeout; if( s > 0 && s <= 1.0 ) { // do a nice spline curve s = 3.0f * s * s - 2.0f * s * s * s; } else if( s > 1.0f ) { // Shouldn't happen, but maybe curtime is behind animtime? s = 1.0f; } if( pseqblend->gaitseq ) { mstudiobone_t *pbones = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); bool copy = true; for( int i = 0; i < m_pStudioHeader->numbones; i++) { if( !Q_strcmp( pbones[i].name, "Bip01 Spine" )) copy = false; else if( !Q_strcmp( pbones[pbones[i].parent].name, "Bip01 Pelvis" )) copy = true; m_flGaitBoneWeights[i] = (copy) ? 1.0f : 0.0f; } m_boneSetup.SetBoneWeights( m_flGaitBoneWeights ); // install weightlist for gait sequence } m_boneSetup.AccumulatePose( pIK, pos, q, pseqblend->sequence, pseqblend->cycle, s ); m_boneSetup.SetBoneWeights( NULL ); // back to default rules } } //----------------------------------------------------------------------------- // Purpose: update latched IK contacts if they're in a moving reference frame. //----------------------------------------------------------------------------- void CStudioModelRenderer :: UpdateIKLocks( CIKContext *pIK ) { if( !pIK ) return; int targetCount = pIK->m_target.Count(); if( targetCount == 0 ) return; for( int i = 0; i < targetCount; i++ ) { CIKTarget *pTarget = &pIK->m_target[i]; if( !pTarget->IsActive( )) continue; if( pTarget->GetOwner() != -1 ) { cl_entity_t *pOwner = GET_ENTITY( pTarget->GetOwner() ); if( pOwner != NULL ) { pTarget->UpdateOwner( pOwner->index, pOwner->origin, pOwner->angles ); } } } } //----------------------------------------------------------------------------- // Purpose: Find the ground or external attachment points needed by IK rules //----------------------------------------------------------------------------- void CStudioModelRenderer :: CalculateIKLocks( CIKContext *pIK ) { if( !pIK ) return; int targetCount = pIK->m_target.Count(); if( targetCount == 0 ) return; Vector up = Vector( 0.0f, 0.0f, 1.0f ); // FIXME: check number of slots? float minHeight = FLT_MAX; float maxHeight = -FLT_MAX; for( int i = 0, j = 0; i < targetCount; i++ ) { pmtrace_t *trace; CIKTarget *pTarget = &pIK->m_target[i]; float flDist = pTarget->est.radius; if( !pTarget->IsActive( )) continue; switch( pTarget->type ) { case IK_GROUND: { Vector estGround; Vector p1, p2; // adjust ground to original ground position estGround = (pTarget->est.pos - RI->currententity->origin); estGround = estGround - (estGround * up) * up; estGround = RI->currententity->origin + estGround + pTarget->est.floor * up; p1 = estGround + up * pTarget->est.height; p2 = estGround - up * pTarget->est.height; float r = Q_max( pTarget->est.radius, 1 ); Vector mins = Vector( -r, -r, 0.0f ); Vector maxs = Vector( r, r, r * 2.0f ); // don't IK to other characters gEngfuncs.pEventAPI->EV_SetTraceHull( 2 ); gEngfuncs.pEventAPI->EV_PushTraceBounds( 2, mins, maxs ); trace = gEngfuncs.pEventAPI->EV_VisTraceLine( p1, p2, PM_STUDIO_IGNORE ); physent_t *ve = gEngfuncs.pEventAPI->EV_GetVisent( trace->ent ); cl_entity_t *m_pGround = (ve) ? GET_ENTITY( ve->info ) : NULL; gEngfuncs.pEventAPI->EV_PopTraceBounds(); if( m_pGround != NULL && m_pGround->curstate.movetype == MOVETYPE_PUSH ) { pTarget->SetOwner( m_pGround->index, m_pGround->origin, m_pGround->angles ); } else { pTarget->ClearOwner(); } if( trace->startsolid ) { // trace from back towards hip Vector tmp = (estGround - pTarget->trace.closest).Normalize(); p1 = estGround - tmp * pTarget->est.height; p2 = estGround; mins = Vector( -r, -r, 0.0f ); maxs = Vector( r, r, 1.0f ); gEngfuncs.pEventAPI->EV_SetTraceHull( 2 ); gEngfuncs.pEventAPI->EV_PushTraceBounds( 2, mins, maxs ); trace = gEngfuncs.pEventAPI->EV_VisTraceLine( p1, p2, PM_STUDIO_IGNORE ); ve = gEngfuncs.pEventAPI->EV_GetVisent( trace->ent ); m_pGround = (ve) ? GET_ENTITY( ve->info ) : NULL; gEngfuncs.pEventAPI->EV_PopTraceBounds(); if( !trace->startsolid ) { p1 = trace->endpos; p2 = p1 - up * pTarget->est.height; gEngfuncs.pEventAPI->EV_SetTraceHull( 2 ); trace = gEngfuncs.pEventAPI->EV_VisTraceLine( p1, p2, PM_STUDIO_IGNORE ); ve = gEngfuncs.pEventAPI->EV_GetVisent( trace->ent ); m_pGround = (ve) ? GET_ENTITY( ve->info ) : NULL; } } if( !trace->startsolid ) { if( m_pGround == GET_ENTITY( 0 )) { // clamp normal to 33 degrees const float limit = 0.832; float dot = DotProduct( trace->plane.normal, up ); if( dot < limit ) { ASSERT( dot >= 0 ); // subtract out up component Vector diff = trace->plane.normal - up * dot; // scale remainder such that it and the up vector are a unit vector float d = sqrt(( 1.0f - limit * limit ) / DotProduct( diff, diff ) ); trace->plane.normal = up * limit + d * diff; } // FIXME: this is wrong with respect to contact position and actual ankle offset pTarget->SetPosWithNormalOffset( trace->endpos, trace->plane.normal ); pTarget->SetNormal( trace->plane.normal ); pTarget->SetOnWorld( true ); // only do this on forward tracking or commited IK ground rules if( pTarget->est.release < 0.1f ) { // keep track of ground height float offset = DotProduct( pTarget->est.pos, up ); if( minHeight > offset ) minHeight = offset; if( maxHeight < offset ) maxHeight = offset; } // FIXME: if we don't drop legs, running down hills looks horrible /* if (DotProduct( pTarget->est.pos, up ) < DotProduct( estGround, up )) { pTarget->est.pos = estGround; } */ } else if( m_pGround != NULL ) { pTarget->SetPos( trace->endpos ); pTarget->SetAngles( RI->currententity->angles ); // only do this on forward tracking or commited IK ground rules if( pTarget->est.release < 0.1f ) { float offset = DotProduct( pTarget->est.pos, up ); if( minHeight > offset ) minHeight = offset; if( maxHeight < offset ) maxHeight = offset; } // FIXME: if we don't drop legs, running down hills looks horrible /* if (DotProduct( pTarget->est.pos, up ) < DotProduct( estGround, up )) { pTarget->est.pos = estGround; } */ } else { pTarget->IKFailed(); } } else { if( m_pGround != GET_ENTITY( 0 )) { pTarget->IKFailed( ); } else { pTarget->SetPos( trace->endpos ); pTarget->SetAngles( RI->currententity->angles ); pTarget->SetOnWorld( true ); } } } break; case IK_ATTACHMENT: flDist = pTarget->est.radius; for( j = 1; j < RENDER_GET_PARM( PARM_MAX_ENTITIES, 0 ); j++ ) { cl_entity_t *m_pEntity = GET_ENTITY( j ); float flRadius = 4096.0f; // (64.0f * 64.0f) if( !m_pEntity || m_pEntity->modelhandle == INVALID_HANDLE ) continue; // not a studiomodel or not in PVS ModelInstance_t *inst = &m_ModelInstances[m_pEntity->modelhandle]; float distSquared = 0.0f, eorg; for( int k = 0; k < 3 && distSquared <= flRadius; k++ ) { if( pTarget->est.pos[j] < inst->absmin[j] ) eorg = pTarget->est.pos[j] - inst->absmin[j]; else if( pTarget->est.pos[j] > inst->absmax[j] ) eorg = pTarget->est.pos[j] - inst->absmax[j]; else eorg = 0.0f; distSquared += eorg * eorg; } if( distSquared >= flRadius ) continue; // not in radius // Extract the bone index from the name if( pTarget->offset.attachmentIndex >= inst->numattachments ) continue; // FIXME: how to validate a index? Vector origin = inst->attachment[pTarget->offset.attachmentIndex].origin; Vector angles = inst->attachment[pTarget->offset.attachmentIndex].angles; float d = (pTarget->est.pos - origin).Length(); if( d >= flDist ) continue; flDist = d; pTarget->SetPos( origin ); pTarget->SetAngles( angles ); } if( flDist >= pTarget->est.radius ) { // no solution, disable ik rule pTarget->IKFailed( ); } break; } } } /* ================ CheckBoneCache validate cache ================ */ bool CStudioModelRenderer :: CheckBoneCache( float f ) { if( !m_pModelInstance ) return false; ModelInstance_t *inst = m_pModelInstance; BoneCache_t *cache = &inst->bonecache; cl_entity_t *e = RI->currententity; if(( tr.time - m_pModelInstance->lighttimecheck ) > LIGHT_INTERP_UPDATE ) { // shuffle states m_pModelInstance->oldlight = m_pModelInstance->newlight; m_pModelInstance->lighttimecheck = tr.time; m_pModelInstance->light_update = true; } // no cache for local player if( RP_LOCALCLIENT( RI->currententity ) && !FBitSet( RI->params, RP_THIRDPERSON )) return false; bool pos_valid = (cache->transform == inst->m_protationmatrix) ? true : false; bool param_valid = !memcmp( cache->poseparam, m_pModelInstance->m_poseparameter, sizeof( float ) * MAXSTUDIOPOSEPARAM ); // make sure what all cached values are unchanged if( cache->frame == f && cache->sequence == e->curstate.sequence && pos_valid && !memcmp( cache->blending, e->curstate.blending, 2 ) && !memcmp( cache->controller, e->curstate.controller, 4 ) && cache->mouthopen == e->mouth.mouthopen && param_valid ) { if( cache->gaitsequence == e->curstate.gaitsequence && cache->gaitframe == e->curstate.fuser1 ) return true; } // time to check cubemaps if( cache->transform.GetOrigin() != inst->m_protationmatrix.GetOrigin() ) { // search for center of bbox Vector pos = ( inst->absmin + inst->absmax ) * 0.5f; CL_FindTwoNearestCubeMap( pos, &inst->cubemap[0], &inst->cubemap[1] ); float dist0 = ( inst->cubemap[0]->origin - pos ).Length(); float dist1 = ( inst->cubemap[1]->origin - pos ).Length(); inst->lerpFactor = dist0 / (dist0 + dist1); } // save rotationmatrix to GL-style array inst->m_protationmatrix.CopyToArray( inst->m_glmatrix ); // update bonecache cache->frame = f; cache->mouthopen = e->mouth.mouthopen; cache->sequence = e->curstate.sequence; cache->transform = inst->m_protationmatrix; memcpy( cache->blending, e->curstate.blending, 2 ); memcpy( cache->controller, e->curstate.controller, 4 ); memcpy( cache->poseparam, m_pModelInstance->m_poseparameter, sizeof( float ) * MAXSTUDIOPOSEPARAM ); cache->gaitsequence = e->curstate.gaitsequence; cache->gaitframe = e->curstate.fuser1; return false; } /* ==================== StudioSetupBones ==================== */ void CStudioModelRenderer :: StudioSetupBones( void ) { float adj[MAXSTUDIOCONTROLLERS]; cl_entity_t *e = RI->currententity; // for more readability CIKContext *pIK = NULL; mstudioboneinfo_t *pboneinfo; mstudioseqdesc_t *pseqdesc; matrix3x4 bonematrix; mstudiobone_t *pbones; int i; static Vector pos[MAXSTUDIOBONES]; static Vector4D q[MAXSTUDIOBONES]; if( e->curstate.sequence < 0 || e->curstate.sequence >= m_pStudioHeader->numseq ) { int sequence = (short)e->curstate.sequence; ALERT( at_aiconsole, "StudioSetupBones: sequence %i/%i out of range for model %s\n", sequence, m_pStudioHeader->numseq, RI->currentmodel->name ); e->curstate.sequence = 0; } pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + e->curstate.sequence; float f = StudioEstimateFrame( pseqdesc ); float dadt = StudioEstimateInterpolant(); StudioInterpolatePoseParams( e, dadt ); if( CheckBoneCache( f )) return; // using a cached bones no need transformations if( m_boneSetup.GetNumIKChains( )) { if( FBitSet( e->curstate.effects, EF_NOINTERP )) m_pModelInstance->m_ik.ClearTargets(); m_pModelInstance->m_ik.Init( &m_boneSetup, e->angles, e->origin, tr.time, tr.realframecount ); pIK = &m_pModelInstance->m_ik; } float cycle = f / m_boneSetup.LocalMaxFrame( e->curstate.sequence ); StudioInterpolateControllers( e, dadt ); m_boneSetup.InitPose( pos, q ); m_boneSetup.UpdateRealTime( tr.time ); if( CVAR_TO_BOOL( m_pCvarCompatible )) m_boneSetup.CalcBoneAdj( adj, m_pModelInstance->m_controller, e->mouth.mouthopen ); m_boneSetup.AccumulatePose( pIK, pos, q, e->curstate.sequence, cycle, 1.0 ); m_pModelInstance->lerp.frame = f; pbones = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); pboneinfo = (mstudioboneinfo_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex + m_pStudioHeader->numbones * sizeof( mstudiobone_t )); if( e->curstate.gaitsequence < 0 || e->curstate.gaitsequence >= m_pStudioHeader->numseq ) e->curstate.gaitsequence = 0; // calc gait animation if( e->curstate.gaitsequence != 0 ) { float m_flGaitBoneWeights[MAXSTUDIOBONES]; bool copy = true; for( int i = 0; i < m_pStudioHeader->numbones; i++) { if( !Q_strcmp( pbones[i].name, "Bip01 Spine" )) copy = false; else if( !Q_strcmp( pbones[pbones[i].parent].name, "Bip01 Pelvis" )) copy = true; m_flGaitBoneWeights[i] = (copy) ? 1.0f : 0.0f; } pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + e->curstate.gaitsequence; f = StudioEstimateGaitFrame( pseqdesc ); // convert gaitframe to cycle cycle = f / m_boneSetup.LocalMaxFrame( e->curstate.gaitsequence ); m_boneSetup.SetBoneWeights( m_flGaitBoneWeights ); // install weightlist for gait sequence m_boneSetup.AccumulatePose( pIK, pos, q, e->curstate.gaitsequence, cycle, 1.0 ); m_boneSetup.SetBoneWeights( NULL ); // back to default rules m_pModelInstance->lerp.gaitframe = f; } // run blends from previous sequences for( i = 0; i < MAX_SEQBLENDS; i++ ) BlendSequence( pos, q, &m_pModelInstance->m_seqblend[i] ); CIKContext auto_ik; auto_ik.Init( &m_boneSetup, e->angles, e->origin, 0.0f, 0 ); m_boneSetup.CalcAutoplaySequences( &auto_ik, pos, q ); if( !CVAR_TO_BOOL( m_pCvarCompatible )) m_boneSetup.CalcBoneAdj( pos, q, m_pModelInstance->m_controller, e->mouth.mouthopen ); byte boneComputed[MAXSTUDIOBONES]; memset( boneComputed, 0, sizeof( boneComputed )); // don't calculate IK on ragdolls if( pIK != NULL ) { UpdateIKLocks( pIK ); pIK->UpdateTargets( pos, q, m_pModelInstance->m_pbones, boneComputed ); CalculateIKLocks( pIK ); pIK->SolveDependencies( pos, q, m_pModelInstance->m_pbones, boneComputed ); } for( i = 0; i < m_pStudioHeader->numbones; i++ ) { // animate all non-simulated bones if( CalcProceduralBone( m_pStudioHeader, i, m_pModelInstance->m_pbones )) continue; // initialize bonematrix bonematrix = matrix3x4( pos[i], q[i] ); if( FBitSet( pbones[i].flags, BONE_JIGGLE_PROCEDURAL ) && FBitSet( m_pStudioHeader->flags, STUDIO_HAS_BONEINFO )) { // Physics-based "jiggle" bone // Bone is assumed to be along the Z axis // Pitch around X, yaw around Y // compute desired bone orientation matrix3x4 goalMX; if( pbones[i].parent == -1 ) goalMX = m_pModelInstance->m_protationmatrix.ConcatTransforms( bonematrix ); else goalMX = m_pModelInstance->m_pbones[pbones[i].parent].ConcatTransforms( bonematrix ); // get jiggle properties from QC data mstudiojigglebone_t *jiggleInfo = (mstudiojigglebone_t *)((byte *)m_pStudioHeader + pboneinfo[i].procindex); if( !m_pModelInstance->m_pJiggleBones ) m_pModelInstance->m_pJiggleBones = new CJiggleBones; // do jiggle physics if( pboneinfo[i].proctype == STUDIO_PROC_JIGGLE ) m_pModelInstance->m_pJiggleBones->BuildJiggleTransformations( i, tr.time, jiggleInfo, goalMX, m_pModelInstance->m_pbones[i] ); else m_pModelInstance->m_pbones[i] = goalMX; // fallback } else { if( pbones[i].parent == -1 ) m_pModelInstance->m_pbones[i] = m_pModelInstance->m_protationmatrix.ConcatTransforms( bonematrix ); else m_pModelInstance->m_pbones[i] = m_pModelInstance->m_pbones[pbones[i].parent].ConcatTransforms( bonematrix ); } } mposetobone_t *m = m_pModelInstance->m_pModel->poseToBone; // convert bones into compacted GLSL array if( m != NULL ) { for( int i = 0; i < m_pStudioHeader->numbones; i++ ) { matrix3x4 out = m_pModelInstance->m_pbones[i].ConcatTransforms( m->posetobone[i] ); out.CopyToArray4x3( &m_pModelInstance->m_glstudiobones[i*3] ); m_pModelInstance->m_studioquat[i] = out.GetQuaternion(); m_pModelInstance->m_studiopos[i] = out.GetOrigin(); } } else { for( int i = 0; i < m_pStudioHeader->numbones; i++ ) { m_pModelInstance->m_pbones[i].CopyToArray4x3( &m_pModelInstance->m_glstudiobones[i*3] ); m_pModelInstance->m_studioquat[i] = m_pModelInstance->m_pbones[i].GetQuaternion(); m_pModelInstance->m_studiopos[i] = m_pModelInstance->m_pbones[i].GetOrigin(); } } } /* ==================== StudioMergeBones ==================== */ void CStudioModelRenderer :: StudioMergeBones( matrix3x4 &transform, matrix3x4 bones[], matrix3x4 cached_bones[], model_t *pModel, model_t *pParentModel ) { matrix3x4 bonematrix; static Vector pos[MAXSTUDIOBONES]; static Vector4D q[MAXSTUDIOBONES]; float poseparams[MAXSTUDIOPOSEPARAM]; int sequence = RI->currententity->curstate.sequence; model_t *oldmodel = RI->currentmodel; studiohdr_t *oldheader = m_pStudioHeader; bool weapon_model = false; ASSERT( pModel != NULL && pModel->type == mod_studio ); ASSERT( pParentModel != NULL && pParentModel->type == mod_studio ); RI->currentmodel = pModel; m_pStudioHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata( RI->currentmodel ); // tell the bonesetup about current model m_boneSetup.SetStudioPointers( m_pStudioHeader, poseparams ); // don't touch original parameters m_boneSetup.CalcDefaultPoseParameters( poseparams ); // just to have something valid here if( pModel == IEngineStudio.GetModelByIndex( RI->currententity->curstate.weaponmodel )) weapon_model = true; // FIXME: how to specify custom sequence for p_model? if( weapon_model ) sequence = 0; mstudioseqdesc_t *pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + sequence; float f = StudioEstimateFrame( pseqdesc ); float cycle = f / m_boneSetup.LocalMaxFrame( sequence ); m_boneSetup.InitPose( pos, q ); m_boneSetup.AccumulatePose( NULL, pos, q, sequence, cycle, 1.0 ); studiohdr_t *m_pParentHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata( pParentModel ); ASSERT( m_pParentHeader != NULL ); mstudiobone_t *pchildbones = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); mstudiobone_t *pparentbones = (mstudiobone_t *)((byte *)m_pParentHeader + m_pParentHeader->boneindex); for( int i = 0; i < m_pStudioHeader->numbones; i++ ) { int j; for( j = 0; j < m_pParentHeader->numbones; j++ ) { if( !Q_stricmp( pchildbones[i].name, pparentbones[j].name )) { bones[i] = cached_bones[j]; break; } } if( j >= m_pParentHeader->numbones ) { // initialize bonematrix bonematrix = matrix3x4( pos[i], q[i] ); if( pchildbones[i].parent == -1 ) bones[i] = transform.ConcatTransforms( bonematrix ); else bones[i] = bones[pchildbones[i].parent].ConcatTransforms( bonematrix ); } } RI->currentmodel = oldmodel; m_pStudioHeader = oldheader; // restore the bonesetup pointers m_boneSetup.SetStudioPointers( m_pStudioHeader, m_pModelInstance->m_poseparameter ); } /* ==================== StudioCalcAttachments ==================== */ void CStudioModelRenderer :: StudioCalcAttachments( matrix3x4 bones[] ) { if( RI->currententity->modelhandle == INVALID_HANDLE || !m_pModelInstance ) return; // too early ? if( FBitSet( m_pModelInstance->info_flags, MF_ATTACHMENTS_DONE )) return; // already computed StudioAttachment_t *att = m_pModelInstance->attachment; mstudioattachment_t *pattachment; cl_entity_t *e = RI->currententity; // prevent to compute env_static bounds every frame if( FBitSet( e->curstate.iuser1, CF_STATIC_ENTITY )) SetBits( m_pModelInstance->info_flags, MF_ATTACHMENTS_DONE ); if( m_pStudioHeader->numattachments <= 0 ) { // clear attachments for( int i = 0; i < MAXSTUDIOATTACHMENTS; i++ ) { if( i < 4 ) e->attachment[i] = e->origin; att[i].angles = e->angles; att[i].origin = e->origin; } return; } else if( m_pStudioHeader->numattachments > MAXSTUDIOATTACHMENTS ) { m_pStudioHeader->numattachments = MAXSTUDIOATTACHMENTS; // reduce it ALERT( at_error, "Too many attachments on %s\n", e->model->name ); } // calculate attachment points pattachment = (mstudioattachment_t *)((byte *)m_pStudioHeader + m_pStudioHeader->attachmentindex); for( int i = 0; i < m_pStudioHeader->numattachments; i++ ) { matrix3x4 world = bones[pattachment[i].bone].ConcatTransforms( att[i].local ); Vector p1 = bones[pattachment[i].bone].GetOrigin(); att[i].origin = world.GetOrigin(); att[i].angles = world.GetAngles(); // merge attachments position for viewmodel if( m_iDrawModelType == DRAWSTUDIO_VIEWMODEL || m_iDrawModelType == DRAWSTUDIO_RUNEVENTS ) { StudioFormatAttachment( att[i].origin ); StudioFormatAttachment( p1 ); } if( i < 4 ) e->attachment[i] = att[i].origin; att[i].dir = (att[i].origin - p1).Normalize(); // forward vec } } /* ==================== StudioGetAttachment ==================== */ void CStudioModelRenderer :: StudioGetAttachment( const cl_entity_t *ent, int iAttachment, Vector *pos, Vector *ang, Vector *dir ) { if( !ent || !ent->model || ( !pos && !ang )) return; studiohdr_t *phdr = (studiohdr_t *)IEngineStudio.Mod_Extradata( ent->model ); if( !phdr ) return; if( ent->modelhandle == INVALID_HANDLE ) return; // too early ? ModelInstance_t *inst = &m_ModelInstances[ent->modelhandle]; // make sure we not overflow iAttachment = bound( 0, iAttachment, phdr->numattachments - 1 ); if( pos ) *pos = inst->attachment[iAttachment].origin; if( ang ) *ang = inst->attachment[iAttachment].angles; if( dir ) *dir = inst->attachment[iAttachment].dir; } /* ==================== StudioSetupModel used only by decals code ==================== */ int CStudioModelRenderer :: StudioSetupModel( int bodypart, mstudiomodel_t **ppsubmodel, msubmodel_t **ppvbomodel ) { mstudiobodyparts_t *pbodypart; mstudiomodel_t *psubmodel; mbodypart_t *pCachedParts; msubmodel_t *pvbomodel; int index; if( bodypart > m_pStudioHeader->numbodyparts ) bodypart = 0; if( m_pModelInstance->m_VlCache != NULL ) pCachedParts = &m_pModelInstance->m_VlCache->bodyparts[bodypart]; else pCachedParts = &RI->currentmodel->studiocache->bodyparts[bodypart]; pbodypart = (mstudiobodyparts_t *)((byte *)m_pStudioHeader + m_pStudioHeader->bodypartindex) + bodypart; index = RI->currententity->curstate.body / pbodypart->base; index = index % pbodypart->nummodels; psubmodel = (mstudiomodel_t *)((byte *)m_pStudioHeader + pbodypart->modelindex) + index; pvbomodel = pCachedParts->models[index]; *ppsubmodel = psubmodel; *ppvbomodel = pvbomodel; return index; } /* =============== StudioLighting used for lighting decals =============== */ void CStudioModelRenderer :: StudioLighting( float *lv, int bone, int flags, const Vector &normal ) { mstudiolight_t *light = &m_pModelInstance->light; if( FBitSet( flags, STUDIO_NF_FULLBRIGHT ) || FBitSet( RI->currententity->curstate.effects, EF_FULLBRIGHT ) || R_FullBright( )) { *lv = 1.0f; return; } float illum = light->ambientlight; if( FBitSet( flags, STUDIO_NF_FLATSHADE )) { illum += light->shadelight * 0.8f; } else { float lightcos; if( bone != -1 ) lightcos = DotProduct( normal, m_bonelightvecs[bone] ); else lightcos = DotProduct( normal, -light->normal ); // -1 colinear, 1 opposite if( lightcos > 1.0f ) lightcos = 1.0f; illum += light->shadelight; // do modified hemispherical lighting lightcos = (lightcos + ( SHADE_LAMBERT - 1.0f )) / SHADE_LAMBERT; if( lightcos > 0.0f ) illum -= light->shadelight * lightcos; illum = Q_max( illum, 0.0f ); } illum = Q_min( illum, 255.0f ); *lv = illum * (1.0f / 255.0f); } /* =============== CacheVertexLight =============== */ void CStudioModelRenderer :: CacheVertexLight( cl_entity_t *ent ) { if( FBitSet( ent->curstate.iuser1, CF_STATIC_ENTITY ) && ( ent->curstate.colormap > 0 ) && world->vertex_lighting != NULL ) { Vector org = m_pModelInstance->m_protationmatrix[3]; // model origin int cacheID = ent->curstate.colormap - 1; dvlightlump_t *vl = world->vertex_lighting; if( FBitSet( m_pModelInstance->info_flags, MF_VERTEX_LIGHTING )) { // does nothing } else if( !FBitSet( m_pModelInstance->info_flags, MF_VL_BAD_CACHE )) { // first initialization if( cacheID < vl->nummodels && vl->dataofs[cacheID] != -1 ) { dmodelvertlight_t *dml = (dmodelvertlight_t *)((byte *)world->vertex_lighting + vl->dataofs[cacheID]); byte *vislight = ((byte *)world->vertex_lighting + vl->dataofs[cacheID]); // we have ID of vertex light cache and cache is present CreateStudioCacheVL( dml, cacheID ); vislight += sizeof( *dml ) - ( sizeof( dvertlight_t ) * 3 ) + sizeof( dvertlight_t ) * dml->numverts; Mod_FindStaticLights( vislight, m_pModelInstance->lights, org ); } } } } /* =============== CacheSurfaceLight =============== */ void CStudioModelRenderer :: CacheSurfaceLight( cl_entity_t *ent ) { if( FBitSet( ent->curstate.iuser1, CF_STATIC_ENTITY ) && ( ent->curstate.colormap > 0 ) && world->surface_lighting != NULL ) { Vector org = m_pModelInstance->m_protationmatrix[3]; // model origin int cacheID = ent->curstate.colormap - 1; dvlightlump_t *vl = world->surface_lighting; if( FBitSet( m_pModelInstance->info_flags, MF_SURFACE_LIGHTING )) { if( m_pModelInstance->m_FlCache && m_pModelInstance->m_FlCache->update_light ) { for( int j = 0; j < m_pModelInstance->m_FlCache->numsurfaces; j++ ) { mstudiosurface_t *surf = &m_pModelInstance->m_FlCache->surfaces[j]; R_UpdateSurfaceParams( surf ); } m_pModelInstance->m_FlCache->update_light = false; } } else if( !FBitSet( m_pModelInstance->info_flags, MF_VL_BAD_CACHE )) { // first initialization if( cacheID < vl->nummodels && vl->dataofs[cacheID] != -1 ) { dmodelfacelight_t *dml = (dmodelfacelight_t *)((byte *)world->surface_lighting + vl->dataofs[cacheID]); byte *vislight = ((byte *)world->surface_lighting + vl->dataofs[cacheID]); // we have ID of vertex light cache and cache is present CreateStudioCacheFL( dml, cacheID ); vislight += sizeof( *dml ) - ( sizeof( dfacelight_t ) * 3 ) + sizeof( dfacelight_t ) * dml->numfaces; Mod_FindStaticLights( vislight, m_pModelInstance->lights, org ); } } } } /* =============== StudioStaticLight =============== */ void CStudioModelRenderer :: StudioStaticLight( cl_entity_t *ent, mstudiolight_t *light ) { bool ambient_light = false; bool skipZCheck = true; if( FBitSet( ent->model->flags, STUDIO_AMBIENT_LIGHT )) ambient_light = true; #if 0 if( ent->curstate.movetype == MOVETYPE_STEP ) skipZCheck = false; #endif if( FBitSet( ent->curstate.iuser1, CF_STATIC_LIGHTMAPPED )) CacheSurfaceLight( ent ); else CacheVertexLight( ent ); if( m_pModelInstance->oldlight.nointerp ) { m_pModelInstance->light_update = true; m_pModelInstance->lighttimecheck = 0.0f; } // refresh lighting every 0.1 secs if( m_pModelInstance->light_update ) { if( ent->curstate.renderfx == SKYBOX_ENTITY ) { R_LightForSky( ent->origin, &m_pModelInstance->newlight ); } else if( FBitSet( ent->curstate.iuser1, CF_STATIC_ENTITY ) && FBitSet( m_pModelInstance->info_flags, MF_VL_BAD_CACHE )) { float dynamic = r_dynamic->value; alight_t lighting; Vector dir; lighting.plightvec = dir; // setup classic Half-Life lighting r_dynamic->value = 0.0f; // ignore dlights IEngineStudio.StudioDynamicLight( ent, &lighting ); r_dynamic->value = dynamic; m_pModelInstance->newlight.ambientlight = lighting.ambientlight; m_pModelInstance->newlight.shadelight = lighting.shadelight; m_pModelInstance->newlight.diffuse = lighting.color; m_pModelInstance->newlight.normal = -Vector( lighting.plightvec ); R_PointAmbientFromLeaf( ent->origin, &m_pModelInstance->newlight ); } else { R_LightForStudio( ent->origin, &m_pModelInstance->newlight, ambient_light ); } m_pModelInstance->light_update = false; // init state if was not in frustum if( Q_abs( m_pModelInstance->cached_frame - tr.realframecount ) > 2 ) m_pModelInstance->oldlight = m_pModelInstance->newlight; } float frac = (tr.time - m_pModelInstance->lighttimecheck) * LIGHT_INTERP_FACTOR; frac = bound( 0.0f, frac, 1.0f ); memset( light, 0, sizeof( *light )); InterpolateOrigin( m_pModelInstance->oldlight.diffuse, m_pModelInstance->newlight.diffuse, light->diffuse, frac ); InterpolateOrigin( m_pModelInstance->oldlight.normal, m_pModelInstance->newlight.normal, light->normal, frac ); light->ambientlight = Lerp( frac, m_pModelInstance->oldlight.ambientlight, m_pModelInstance->newlight.ambientlight ); light->shadelight = Lerp( frac, m_pModelInstance->oldlight.shadelight, m_pModelInstance->newlight.shadelight ); for( int i = 0; i < 6; i++ ) InterpolateOrigin( m_pModelInstance->oldlight.ambient[i], m_pModelInstance->newlight.ambient[i], light->ambient[i], frac ); // find worldlights for dynamic lighting if( FBitSet( m_pModelInstance->info_flags, MF_POSITION_CHANGED ) && !FBitSet( ent->curstate.iuser1, CF_STATIC_ENTITY )) { Vector origin = (m_pModelInstance->absmin + m_pModelInstance->absmax) * 0.5f; Vector mins = m_pModelInstance->absmin - origin; Vector maxs = m_pModelInstance->absmax - origin; // search for worldlights that affected to model bbox R_FindWorldLights( origin, mins, maxs, m_pModelInstance->lights, skipZCheck ); ClearBits( m_pModelInstance->info_flags, MF_POSITION_CHANGED ); } // use inverted light vector for head shield (hack) if( m_iDrawModelType == DRAWSTUDIO_HEADSHIELD ) light->normal = -light->normal; } /* =============== StudioClientEvents =============== */ void CStudioModelRenderer :: StudioClientEvents( void ) { mstudioseqdesc_t *pseqdesc; mstudioevent_t *pevent; pseqdesc = (mstudioseqdesc_t *)((byte *)m_pStudioHeader + m_pStudioHeader->seqindex) + RI->currententity->curstate.sequence; pevent = (mstudioevent_t *)((byte *)m_pStudioHeader + pseqdesc->eventindex); // no events for this animation or gamepaused if( pseqdesc->numevents == 0 || tr.frametime == 0 ) return; // don't playing events from thirdperson model if( RP_LOCALCLIENT( RI->currententity ) && !FBitSet( RI->params, RP_THIRDPERSON )) return; float frame = StudioEstimateFrame( pseqdesc ); // get start offset float start = frame - RI->currententity->curstate.framerate * tr.frametime * pseqdesc->fps; float end = frame; for( int i = 0; i < pseqdesc->numevents; i++ ) { // ignore all non-client-side events if( pevent[i].event < EVENT_CLIENT ) continue; if( (float)pevent[i].frame > start && (float)pevent[i].frame <= end ) HUD_StudioEvent( &pevent[i], RI->currententity ); } } /* =============== StudioDrawBones =============== */ void CStudioModelRenderer :: StudioDrawBones( void ) { mstudiobone_t *pbones = (mstudiobone_t *) ((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); Vector point; GL_BindTexture( GL_TEXTURE0, tr.whiteTexture ); pglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); for( int i = 0; i < m_pStudioHeader->numbones; i++ ) { if( pbones[i].parent >= 0 ) { pglPointSize( 3.0f ); pglColor3f( 1, 0.7f, 0 ); pglBegin( GL_LINES ); m_pModelInstance->m_pbones[pbones[i].parent].GetOrigin( point ); pglVertex3fv( point ); m_pModelInstance->m_pbones[i].GetOrigin( point ); pglVertex3fv( point ); pglEnd(); pglColor3f( 0, 0, 0.8f ); pglBegin( GL_POINTS ); if( pbones[pbones[i].parent].parent != -1 ) { m_pModelInstance->m_pbones[pbones[i].parent].GetOrigin( point ); pglVertex3fv( point ); } m_pModelInstance->m_pbones[i].GetOrigin( point ); pglVertex3fv( point ); pglEnd(); } else { // draw parent bone node pglPointSize( 5.0f ); pglColor3f( 0.8f, 0, 0 ); pglBegin( GL_POINTS ); m_pModelInstance->m_pbones[i].GetOrigin( point ); pglVertex3fv( point ); pglEnd(); } } pglPointSize( 1.0f ); } /* =============== StudioDrawHulls =============== */ void CStudioModelRenderer :: StudioDrawHulls( int iHitbox ) { float alpha, lv; int i, j; if( r_drawentities->value == 4 ) alpha = 0.5f; else alpha = 1.0f; // setup bone lighting for( i = 0; i < m_pStudioHeader->numbones; i++ ) m_bonelightvecs[i] = m_pModelInstance->m_pbones[i].VectorIRotate( -m_pModelInstance->light.normal ); GL_BindTexture( GL_TEXTURE0, tr.whiteTexture ); pglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); for( i = 0; i < m_pStudioHeader->numhitboxes; i++ ) { mstudiobbox_t *pbbox = (mstudiobbox_t *)((byte *)m_pStudioHeader + m_pStudioHeader->hitboxindex); vec3_t tmp, p[8]; if( iHitbox >= 0 && iHitbox != i ) continue; for( j = 0; j < 8; j++ ) { tmp[0] = (j & 1) ? pbbox[i].bbmin[0] : pbbox[i].bbmax[0]; tmp[1] = (j & 2) ? pbbox[i].bbmin[1] : pbbox[i].bbmax[1]; tmp[2] = (j & 4) ? pbbox[i].bbmin[2] : pbbox[i].bbmax[2]; p[j] = m_pModelInstance->m_pbones[pbbox[i].bone].VectorTransform( tmp ); } j = (pbbox[i].group % ARRAYSIZE( g_hullcolor )); gEngfuncs.pTriAPI->Begin( TRI_QUADS ); gEngfuncs.pTriAPI->Color4f( g_hullcolor[j][0], g_hullcolor[j][1], g_hullcolor[j][2], alpha ); for( j = 0; j < 6; j++ ) { tmp = g_vecZero; tmp[j % 3] = (j < 3) ? 1.0f : -1.0f; StudioLighting( &lv, pbbox[i].bone, 0, tmp ); gEngfuncs.pTriAPI->Brightness( Q_max( lv, tr.ambientFactor )); gEngfuncs.pTriAPI->Vertex3fv( p[g_boxpnt[j][0]] ); gEngfuncs.pTriAPI->Vertex3fv( p[g_boxpnt[j][1]] ); gEngfuncs.pTriAPI->Vertex3fv( p[g_boxpnt[j][2]] ); gEngfuncs.pTriAPI->Vertex3fv( p[g_boxpnt[j][3]] ); } gEngfuncs.pTriAPI->End(); } } /* =============== StudioDrawAbsBBox =============== */ void CStudioModelRenderer :: StudioDrawAbsBBox( void ) { Vector p[8], tmp; float lv; int i; // looks ugly, skip if( RI->currententity == GET_VIEWMODEL( )) return; // compute a full bounding box for( i = 0; i < 8; i++ ) { p[i][0] = ( i & 1 ) ? m_pModelInstance->absmin[0] : m_pModelInstance->absmax[0]; p[i][1] = ( i & 2 ) ? m_pModelInstance->absmin[1] : m_pModelInstance->absmax[1]; p[i][2] = ( i & 4 ) ? m_pModelInstance->absmin[2] : m_pModelInstance->absmax[2]; } GL_BindTexture( GL_TEXTURE0, tr.whiteTexture ); gEngfuncs.pTriAPI->Color4f( 0.5f, 0.5f, 1.0f, 0.5f ); gEngfuncs.pTriAPI->RenderMode( kRenderTransAdd ); gEngfuncs.pTriAPI->Begin( TRI_QUADS ); for( i = 0; i < 6; i++ ) { tmp = g_vecZero; tmp[i % 3] = (i < 3) ? 1.0f : -1.0f; StudioLighting( &lv, -1, 0, tmp ); gEngfuncs.pTriAPI->Brightness( Q_max( lv, tr.ambientFactor )); gEngfuncs.pTriAPI->Vertex3fv( p[g_boxpnt[i][0]] ); gEngfuncs.pTriAPI->Vertex3fv( p[g_boxpnt[i][1]] ); gEngfuncs.pTriAPI->Vertex3fv( p[g_boxpnt[i][2]] ); gEngfuncs.pTriAPI->Vertex3fv( p[g_boxpnt[i][3]] ); } gEngfuncs.pTriAPI->End(); } void CStudioModelRenderer :: StudioDrawAttachments( bool bCustomFov ) { GL_BindTexture( GL_TEXTURE0, tr.whiteTexture ); pglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); pglDisable( GL_DEPTH_TEST ); for( int i = 0; i < m_pStudioHeader->numattachments; i++ ) { mstudioattachment_t *pattachments; Vector v[4]; pattachments = (mstudioattachment_t *) ((byte *)m_pStudioHeader + m_pStudioHeader->attachmentindex); v[0] = m_pModelInstance->m_pbones[pattachments[i].bone].VectorTransform( pattachments[i].org ); v[1] = m_pModelInstance->m_pbones[pattachments[i].bone].VectorTransform( g_vecZero ); v[2] = m_pModelInstance->m_pbones[pattachments[i].bone].VectorTransform( g_vecZero ); v[3] = m_pModelInstance->m_pbones[pattachments[i].bone].VectorTransform( g_vecZero ); for( int j = 0; j < 4 && bCustomFov; j++ ) StudioFormatAttachment( v[j] ); 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_DEPTH_TEST ); } void CStudioModelRenderer :: StudioDrawDebug( cl_entity_t *e ) { matrix4x4 projMatrix, worldViewProjMatrix; bool bCustomFov = false; // don't reflect this entity in mirrors if( e->curstate.effects & EF_NOREFLECT && FBitSet( RI->params, RP_MIRRORVIEW )) return; // draw only in mirrors if( e->curstate.effects & EF_REFLECTONLY && !FBitSet( RI->params, RP_MIRRORVIEW )) return; // ignore in thirdperson, camera view or client is died if( e == GET_VIEWMODEL() || e == gHUD.m_pHeadShieldEnt ) { if( FBitSet( RI->params, RP_THIRDPERSON ) || CL_IsDead() || !UTIL_IsLocal( RI->view.entity )) return; if( !RP_NORMALPASS( )) return; bCustomFov = ComputeCustomFov( projMatrix, worldViewProjMatrix ); } if( !StudioSetEntity( e )) return; GL_Blend( GL_FALSE ); GL_AlphaTest( GL_FALSE ); GL_BindShader( NULL ); switch( (int)r_drawentities->value ) { case 2: StudioDrawBones(); break; case 3: StudioDrawHulls (); break; case 4: gEngfuncs.pTriAPI->RenderMode( kRenderTransAdd ); StudioDrawHulls (); gEngfuncs.pTriAPI->RenderMode( kRenderNormal ); break; case 5: StudioDrawAbsBBox(); break; case 6: StudioDrawAttachments( bCustomFov ); break; } if( bCustomFov ) RestoreNormalFov( projMatrix, worldViewProjMatrix ); } /* ==================== StudioFormatAttachment ==================== */ void CStudioModelRenderer :: StudioFormatAttachment( Vector &point ) { float worldx = tan( (float)RI->view.fov_x * M_PI / 360.0 ); float viewx = tan( m_flViewmodelFov * M_PI / 360.0 ); // BUGBUG: workaround if( viewx == 0.0f ) viewx = 1.0f; // aspect ratio cancels out, so only need one factor // the difference between the screen coordinates of the 2 systems is the ratio // of the coefficients of the projection matrices (tan (fov/2) is that coefficient) float factor = worldx / viewx; // get the coordinates in the viewer's space. Vector tmp = point - GetVieworg(); Vector vTransformed; vTransformed.x = DotProduct( GetVLeft(), tmp ); vTransformed.y = DotProduct( GetVUp(), tmp ); vTransformed.z = DotProduct( GetVForward(), tmp ); vTransformed.x *= factor; vTransformed.y *= factor; // Transform back to world space. Vector vOut = (GetVLeft() * vTransformed.x) + (GetVUp() * vTransformed.y) + (GetVForward() * vTransformed.z); point = GetVieworg() + vOut; } /* =============== ChooseStudioProgram Select the program for mesh (diffuse\bump\debug) =============== */ word CStudioModelRenderer :: ChooseStudioProgram( studiohdr_t *phdr, mstudiomaterial_t *mat, bool lightpass ) { bool bone_weights = FBitSet( m_pStudioHeader->flags, STUDIO_HAS_BONEWEIGHTS ) ? true : false; int lightmode = LIGHTSTATIC_NONE; if( FBitSet( m_pModelInstance->info_flags, MF_SURFACE_LIGHTING )) lightmode = LIGHTSTATIC_SURFACE; else if( FBitSet( m_pModelInstance->info_flags, MF_VERTEX_LIGHTING )) lightmode = LIGHTSTATIC_VERTEX; if( FBitSet( RI->params, RP_SHADOWVIEW )) return ShaderSceneDepth( mat, bone_weights, phdr->numbones ); if( lightpass ) { return ShaderLightForward( RI->currentlight, mat, bone_weights, phdr->numbones ); } else { if( FBitSet( RI->params, RP_DEFERREDSCENE|RP_DEFERREDLIGHT )) { ShaderSceneDeferred( mat, bone_weights, phdr->numbones ); return ShaderLightDeferred( mat, bone_weights, phdr->numbones ); } return ShaderSceneForward( mat, lightmode, bone_weights, phdr->numbones ); } } /* ==================== AddMeshToDrawList ==================== */ void CStudioModelRenderer :: AddMeshToDrawList( studiohdr_t *phdr, vbomesh_t *mesh, bool lightpass ) { // static entities allows to cull each part individually if( FBitSet( RI->currententity->curstate.iuser1, CF_STATIC_ENTITY ) && RI->currententity->curstate.renderfx != SKYBOX_ENTITY ) { Vector absmin, absmax; TransformAABB( m_pModelInstance->m_pbones[mesh->parentbone], mesh->mins, mesh->maxs, absmin, absmax ); if( !Mod_CheckBoxVisible( absmin, absmax )) return; // occulded if( R_CullBox( absmin, absmax )) return; // culled } int m_skinnum = bound( 0, RI->currententity->curstate.skin, phdr->numskinfamilies - 1 ); short *pskinref = (short *)((byte *)phdr + phdr->skinindex); if( m_skinnum != 0 && m_skinnum < phdr->numskinfamilies ) pskinref += (m_skinnum * phdr->numskinref); mstudiomaterial_t *mat = NULL; bool cache_from_model = false; if( RI->currentmodel == m_pPlayerLegsModel ) cache_from_model = true; if( RI->currentmodel == IEngineStudio.GetModelByIndex( RI->currententity->curstate.weaponmodel )) cache_from_model = true; if( cache_from_model ) mat = &RI->currentmodel->materials[pskinref[mesh->skinref]]; else mat = &m_pModelInstance->materials[pskinref[mesh->skinref]]; // NOTE: use local copy for right cache shadernums bool solid = ( R_OpaqueEntity( RI->currententity ) && !FBitSet( mat->flags, STUDIO_NF_ADDITIVE )) ? true : false; // goes into regular arrays if( FBitSet( RI->params, RP_SHADOWVIEW )) { if( FBitSet( mat->flags, STUDIO_NF_ADDITIVE ) && FBitSet( mat->flags, STUDIO_NF_HAS_ALPHA )) solid = true; // add alpha-glasses to shadowlist lightpass = false; } if( lightpass ) { if( RI->currententity->curstate.renderfx == SKYBOX_ENTITY ) return; // skybox entities can't lighting by dynlights if( FBitSet( mat->flags, STUDIO_NF_FULLBRIGHT )) return; // can't light fullbrights if( RI->currentlight->type == LIGHT_DIRECTIONAL ) { if( FBitSet( mat->flags, STUDIO_NF_NOSUNLIGHT )) return; // shader was failed to compile } else { if( FBitSet( mat->flags, STUDIO_NF_NODLIGHT )) return; // shader was failed to compile } } else { if( FBitSet( mat->flags, STUDIO_NF_NODRAW )) return; // shader was failed to compile } word hProgram = 0; if( !( hProgram = ChooseStudioProgram( phdr, mat, lightpass ))) return; // failed to build shader, don't draw this surface glsl_program_t *shader = &glsl_programs[hProgram]; if( lightpass ) { CSolidEntry entry; entry.SetRenderMesh( mesh, hProgram ); RI->frame.light_meshes.AddToTail( entry ); } else if( solid ) { CSolidEntry entry; entry.SetRenderMesh( mesh, hProgram ); RI->frame.solid_meshes.AddToTail( entry ); } else { CTransEntry entry; entry.SetRenderMesh( mesh, hProgram ); if( ScreenCopyRequired( shader ) && mesh->parentbone != 0xFF ) { Vector mins, maxs; TransformAABB( m_pModelInstance->m_pbones[mesh->parentbone], mesh->mins, mesh->maxs, mins, maxs ); // create sentinel border for refractions ExpandBounds( mins, maxs, 0.5f ); entry.ComputeScissor( mins, maxs ); } RI->frame.trans_list.AddToTail( entry ); } } /* ==================== AddBodyPartToDrawList ==================== */ void CStudioModelRenderer :: AddBodyPartToDrawList( studiohdr_t *phdr, mbodypart_s *bodyparts, int bodypart, bool lightpass ) { if( !bodyparts ) bodyparts = RI->currentmodel->studiocache->bodyparts; if( !bodyparts ) HOST_ERROR( "%s missed cache\n", RI->currententity->model->name ); bodypart = bound( 0, bodypart, phdr->numbodyparts ); mbodypart_t *pBodyPart = &bodyparts[bodypart]; int index = RI->currententity->curstate.body / pBodyPart->base; index = index % pBodyPart->nummodels; msubmodel_t *pSubModel = pBodyPart->models[index]; if( !pSubModel ) return; // blank submodel, just ignore for( int i = 0; i < pSubModel->nummesh; i++ ) AddMeshToDrawList( phdr, &pSubModel->meshes[i], lightpass ); } /* ================= AddStudioModelToDrawList do culling, compute bones, add meshes to list ================= */ void CStudioModelRenderer :: AddStudioModelToDrawList( cl_entity_t *e, bool update ) { // no shadows for skybox ents if( FBitSet( RI->params, RP_SHADOWVIEW ) && e->curstate.renderfx == SKYBOX_ENTITY ) return; if( !StudioSetEntity( e )) return; if( !StudioComputeBBox( )) return; // invalid sequence if( !Mod_CheckBoxVisible( m_pModelInstance->absmin, m_pModelInstance->absmax )) { r_stats.c_culled_entities++; return; } if( R_CullModel( RI->currententity, m_pModelInstance->absmin, m_pModelInstance->absmax )) { r_stats.c_culled_entities++; return; // culled } // no cache for local player in firstperson if( RP_LOCALCLIENT( RI->currententity ) && !FBitSet( RI->params, RP_THIRDPERSON )) m_pModelInstance->cached_frame = -1; m_pModelInstance->visframe = tr.realframecount; // visible if( m_pModelInstance->cached_frame != tr.realframecount ) { StudioSetUpTransform( ); if( RI->currententity->curstate.movetype == MOVETYPE_FOLLOW && RI->currententity->curstate.aiment > 0 ) { cl_entity_t *parent = gEngfuncs.GetEntityByIndex( RI->currententity->curstate.aiment ); if( parent != NULL && parent->modelhandle != INVALID_HANDLE ) { ModelInstance_t *inst = &m_ModelInstances[parent->modelhandle]; StudioMergeBones( m_pModelInstance->m_protationmatrix, m_pModelInstance->m_pbones, inst->m_pbones, RI->currentmodel, parent->model ); mposetobone_t *m = m_pModelInstance->m_pModel->poseToBone; // convert bones into compacted GLSL array if( m != NULL ) { for( int i = 0; i < m_pStudioHeader->numbones; i++ ) { matrix3x4 out = m_pModelInstance->m_pbones[i].ConcatTransforms( m->posetobone[i] ); out.CopyToArray4x3( &m_pModelInstance->m_glstudiobones[i*3] ); m_pModelInstance->m_studioquat[i] = out.GetQuaternion(); m_pModelInstance->m_studiopos[i] = out.GetOrigin(); } } else { for( int i = 0; i < m_pStudioHeader->numbones; i++ ) { m_pModelInstance->m_pbones[i].CopyToArray4x3( &m_pModelInstance->m_glstudiobones[i*3] ); m_pModelInstance->m_studioquat[i] = m_pModelInstance->m_pbones[i].GetQuaternion(); m_pModelInstance->m_studiopos[i] = m_pModelInstance->m_pbones[i].GetOrigin(); } } } else { ALERT( at_error, "FollowEntity: %i with model %s has missed parent!\n", RI->currententity->index, RI->currentmodel->name ); return; } } else StudioSetupBones( ); // calc attachments only once per frame StudioCalcAttachments( m_pModelInstance->m_pbones ); StudioClientEvents( ); if( RI->currententity->index > 0 ) { // because RI->currententity may be not equal his index e.g. for viewmodel cl_entity_t *ent = GET_ENTITY( RI->currententity->index ); memcpy( ent->attachment, RI->currententity->attachment, sizeof( Vector ) * 4 ); } // grab the static lighting from world StudioStaticLight( RI->currententity, &m_pModelInstance->light ); model_t *pweaponmodel = NULL; if( RI->currententity->curstate.weaponmodel ) pweaponmodel = IEngineStudio.GetModelByIndex( RI->currententity->curstate.weaponmodel ); // don't draw p_model for firstperson legs if( pweaponmodel && ( RI->currentmodel != m_pPlayerLegsModel )) { m_pStudioHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata( pweaponmodel ); StudioMergeBones( m_pModelInstance->m_protationmatrix, m_pModelInstance->m_pwpnbones, m_pModelInstance->m_pbones, pweaponmodel, RI->currentmodel ); mposetobone_t *m = pweaponmodel->poseToBone; // convert bones into compacted GLSL array if( m != NULL ) { for( int i = 0; i < m_pStudioHeader->numbones; i++ ) { matrix3x4 out = m_pModelInstance->m_pwpnbones[i].ConcatTransforms( m->posetobone[i] ); out.CopyToArray4x3( &m_pModelInstance->m_glweaponbones[i*3] ); m_pModelInstance->m_weaponquat[i] = out.GetQuaternion(); m_pModelInstance->m_weaponpos[i] = out.GetOrigin(); } } else { for( int i = 0; i < m_pStudioHeader->numbones; i++ ) { m_pModelInstance->m_pwpnbones[i].CopyToArray4x3( &m_pModelInstance->m_glweaponbones[i*3] ); m_pModelInstance->m_weaponquat[i] = m_pModelInstance->m_pwpnbones[i].GetQuaternion(); m_pModelInstance->m_weaponpos[i] = m_pModelInstance->m_pwpnbones[i].GetOrigin(); } } m_pStudioHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata( RI->currentmodel ); } // add visible lights to vislight matrix R_MarkVisibleLights( m_pModelInstance->lights ); // now this frame cached m_pModelInstance->cached_frame = tr.realframecount; } if(( m_iDrawModelType == DRAWSTUDIO_RUNEVENTS ) || r_drawentities->value == 2.0f ) return; model_t *pweaponmodel = NULL; mbodypart_t *pbodyparts = NULL; if( update ) { if( RI->currententity->curstate.weaponmodel ) pweaponmodel = IEngineStudio.GetModelByIndex( RI->currententity->curstate.weaponmodel ); m_pModelInstance = NULL; return; } // change shared model with instanced model for this entity (it has personal vertex light cache) if( m_pModelInstance->m_FlCache != NULL ) pbodyparts = m_pModelInstance->m_FlCache->bodyparts; else if( m_pModelInstance->m_VlCache != NULL ) pbodyparts = m_pModelInstance->m_VlCache->bodyparts; for( int i = 0 ; i < m_pStudioHeader->numbodyparts; i++ ) AddBodyPartToDrawList( m_pStudioHeader, pbodyparts, i, ( RI->currentlight != NULL )); if( RI->currententity->curstate.weaponmodel ) pweaponmodel = IEngineStudio.GetModelByIndex( RI->currententity->curstate.weaponmodel ); // don't draw p_model for firstperson legs if( pweaponmodel && ( RI->currentmodel != m_pPlayerLegsModel )) { RI->currentmodel = pweaponmodel; m_pStudioHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata( RI->currentmodel ); // add weaponmodel parts for( int i = 0 ; i < m_pStudioHeader->numbodyparts; i++ ) AddBodyPartToDrawList( m_pStudioHeader, NULL, i, ( RI->currentlight != NULL )); } m_pModelInstance = NULL; } /* ================= RunViewModelEvents ================= */ void CStudioModelRenderer :: RunViewModelEvents( void ) { if( !CVAR_TO_BOOL( m_pCvarDrawViewModel )) return; // ignore in thirdperson, camera view or client is died if( FBitSet( RI->params, RP_THIRDPERSON ) || CL_IsDead() || !UTIL_IsLocal( RI->view.entity )) return; if( !RP_NORMALPASS( )) return; if( !IEngineStudio.Mod_Extradata( GET_VIEWMODEL()->model )) return; RI->currententity = GET_VIEWMODEL(); RI->currentmodel = RI->currententity->model; if( !RI->currentmodel ) return; RI->currententity->curstate.renderamt = R_ComputeFxBlend( RI->currententity ); // tell the particle system about visibility RI->currententity->curstate.messagenum = r_currentMessageNum; m_iDrawModelType = DRAWSTUDIO_RUNEVENTS; SET_CURRENT_ENTITY( RI->currententity ); AddStudioModelToDrawList( RI->currententity ); SET_CURRENT_ENTITY( NULL ); m_iDrawModelType = DRAWSTUDIO_NORMAL; RI->currententity = NULL; RI->currentmodel = NULL; } bool CStudioModelRenderer :: ComputeCustomFov( matrix4x4 &projMatrix, matrix4x4 &worldViewProjMatrix ) { float flDesiredFOV = 90.0f; // bound FOV values if( m_pCvarViewmodelFov->value < 50 ) gEngfuncs.Cvar_SetValue( "cl_viewmodel_fov", 50 ); else if( m_pCvarViewmodelFov->value > 120 ) gEngfuncs.Cvar_SetValue( "cl_viewmodel_fov", 120 ); if( m_pCvarHeadShieldFov->value < 50 ) gEngfuncs.Cvar_SetValue( "cl_headshield_fov", 50 ); else if( m_pCvarHeadShieldFov->value > 120 ) gEngfuncs.Cvar_SetValue( "cl_headshield_fov", 120 ); // Find the offset our current FOV is from the default value float flFOVOffset = 90.0f - (float)RI->view.fov_x; switch( m_iDrawModelType ) { case DRAWSTUDIO_VIEWMODEL: // Adjust the viewmodel's FOV to move with any FOV offsets on the viewer's end m_flViewmodelFov = flDesiredFOV = m_pCvarViewmodelFov->value - flFOVOffset; break; case DRAWSTUDIO_HEADSHIELD: // Adjust the headshield's FOV to move with any FOV offsets on the viewer's end flDesiredFOV = m_pCvarHeadShieldFov->value - flFOVOffset; break; default: // failed case, unchanged flDesiredFOV = RI->view.fov_x; break; } // calc local FOV float fov_x = flDesiredFOV; float fov_y = V_CalcFov( fov_x, ScreenWidth, ScreenHeight ); // don't adjust FOV for viewmodel, faceprotect only if( m_iDrawModelType == DRAWSTUDIO_HEADSHIELD && RENDER_GET_PARM( PARM_WIDESCREEN, 0 )) V_AdjustFov( fov_x, fov_y, ScreenWidth, ScreenHeight, false ); if( fov_x != RI->view.fov_x ) { projMatrix = RI->view.projectionMatrix; worldViewProjMatrix = RI->view.worldProjectionMatrix; R_SetupProjectionMatrix( fov_x, fov_y, RI->view.projectionMatrix ); RI->view.worldProjectionMatrix = RI->view.projectionMatrix.Concat( RI->view.worldMatrix ); RI->view.worldProjectionMatrix.CopyToArray( RI->glstate.modelviewProjectionMatrix ); RI->view.projectionMatrix.CopyToArray( RI->glstate.projectionMatrix ); pglMatrixMode( GL_PROJECTION ); pglLoadMatrixf( RI->glstate.projectionMatrix ); return true; } return false; } void CStudioModelRenderer :: RestoreNormalFov( matrix4x4 &projMatrix, matrix4x4 &worldViewProjMatrix ) { // restore original matrix RI->view.projectionMatrix = projMatrix; RI->view.worldProjectionMatrix = worldViewProjMatrix; RI->view.worldProjectionMatrix.CopyToArray( RI->glstate.modelviewProjectionMatrix ); RI->view.projectionMatrix.CopyToArray( RI->glstate.projectionMatrix ); pglMatrixMode( GL_PROJECTION ); pglLoadMatrixf( RI->glstate.projectionMatrix ); } /* ================= DrawViewModel ================= */ void CStudioModelRenderer :: DrawViewModel( void ) { cl_entity_t *view = GET_VIEWMODEL(); if( !CVAR_TO_BOOL( m_pCvarDrawViewModel )) return; // ignore in thirdperson, camera view or client is died if( FBitSet( RI->params, RP_THIRDPERSON ) || CL_IsDead() || !UTIL_IsLocal( RI->view.entity )) return; if( !RP_NORMALPASS( ) || g_iGunMode == 3 ) return; // tell the particle system about visibility view->curstate.messagenum = r_currentMessageNum; // hack the depth range to prevent view model from poking into walls GL_DepthRange( gldepthmin, gldepthmin + 0.3f * ( gldepthmax - gldepthmin )); // backface culling for left-handed weapons if( CVAR_TO_BOOL( m_pCvarHand )) GL_FrontFace( !glState.frontFace ); m_iDrawModelType = DRAWSTUDIO_VIEWMODEL; RI->frame.solid_meshes.Purge(); RI->frame.trans_list.Purge(); view->curstate.weaponmodel = 0; matrix4x4 projMatrix, worldViewProjMatrix; bool bCustom = ComputeCustomFov( projMatrix, worldViewProjMatrix ); // prevent ugly blinking when weapon is changed view->model = MODEL_HANDLE( gHUD.m_iViewModelIndex ); // copy viewhands modelindx into weaponmodel if( view->index > 0 ) { cl_entity_t *ent = GET_ENTITY( view->index ); view->curstate.weaponmodel = ent->curstate.iuser3; } // we can't draw head shield and viewmodel for once call // because water blur separates them AddStudioModelToDrawList( view ); if( FBitSet( RI->params, RP_DEFERREDSCENE|RP_DEFERREDLIGHT )) { RenderDeferredStudioList(); } else { RenderSolidStudioList(); R_RenderTransList(); } if( bCustom ) RestoreNormalFov( projMatrix, worldViewProjMatrix ); // restore depth range GL_DepthRange( gldepthmin, gldepthmax ); // backface culling for left-handed weapons if( CVAR_TO_BOOL( m_pCvarHand )) GL_FrontFace( !glState.frontFace ); m_iDrawModelType = DRAWSTUDIO_NORMAL; GL_BindShader( NULL ); } /* ================= DrawHeadShield a some copy and paste from viewmodel code ================= */ void CStudioModelRenderer :: DrawHeadShield( void ) { // g-cont. head shield must thinking always if( !HeadShieldThink( )) return; // ignore in thirdperson, camera view or some special passes if( FBitSet( RI->params, RP_THIRDPERSON ) || !UTIL_IsLocal( RI->view.entity )) return; if( !RP_NORMALPASS( )) return; // load shield once only if( !IEngineStudio.Mod_Extradata( gHUD.m_pHeadShieldEnt->model )) return; RI->currententity = gHUD.m_pHeadShieldEnt; RI->currentmodel = gHUD.m_pHeadShieldEnt->model; if( !RI->currentmodel ) return; SET_CURRENT_ENTITY( RI->currententity ); RI->currententity->curstate.renderamt = R_ComputeFxBlend( RI->currententity ); // g-cont. current offset for headshield is match with original paranoia code gHUD.m_pHeadShieldEnt->origin = gHUD.m_pHeadShieldEnt->curstate.origin = GetVieworg() + GetVForward() * gHUD.m_pHeadShieldEnt->curstate.fuser2 + GetVUp() * 1.8f; gHUD.m_pHeadShieldEnt->angles = gHUD.m_pHeadShieldEnt->curstate.angles = RI->view.angles; // rotate face to player gHUD.m_pHeadShieldEnt->angles[PITCH] *= -1; gHUD.m_pHeadShieldEnt->curstate.angles[PITCH] *= -1; m_iDrawModelType = DRAWSTUDIO_HEADSHIELD; // don't affect headshield by any dynamic lights! ClearBits( RI->view.flags, RF_HASDYNLIGHTS ); RI->frame.solid_meshes.Purge(); RI->frame.trans_list.Purge(); // clearing depth buffer to draw headshield always on a top // like nextView = 1 in original code pglClear( GL_DEPTH_BUFFER_BIT ); pglDisable( GL_DEPTH_TEST ); matrix4x4 projMatrix, worldViewProjMatrix; bool bCustom = ComputeCustomFov( projMatrix, worldViewProjMatrix ); // we can't draw head shield and viewmodel for once call // because water blur separates them AddStudioModelToDrawList( RI->currententity ); if( FBitSet( RI->params, RP_DEFERREDSCENE|RP_DEFERREDLIGHT )) { RenderDeferredStudioList(); } else { RenderSolidStudioList(); R_RenderTransList(); } if( bCustom ) RestoreNormalFov( projMatrix, worldViewProjMatrix ); m_iDrawModelType = DRAWSTUDIO_NORMAL; pglEnable( GL_DEPTH_TEST ); SET_CURRENT_ENTITY( NULL ); RI->currententity = NULL; RI->currentmodel = NULL; GL_BindShader( NULL ); } void CStudioModelRenderer :: DrawMeshFromBuffer( const vbomesh_t *mesh ) { pglBindVertexArray( mesh->vao ); if( GL_Support( R_DRAW_RANGEELEMENTS_EXT )) pglDrawRangeElementsEXT( GL_TRIANGLES, 0, mesh->numVerts - 1, mesh->numElems, GL_UNSIGNED_INT, 0 ); else pglDrawElements( GL_TRIANGLES, mesh->numElems, GL_UNSIGNED_INT, 0 ); r_stats.c_total_tris += (mesh->numElems / 3); r_stats.num_flushes++; } void CStudioModelRenderer :: BuildMeshListForLight( CDynLight *pl, bool solid ) { RI->frame.light_meshes.Purge(); Vector bounds[2]; if( solid ) { // check solid meshes for light intersection for( int i = 0; i < RI->frame.solid_meshes.Count(); i++ ) { CSolidEntry *entry = &RI->frame.solid_meshes[i]; StudioGetBounds( entry, bounds ); if( pl->frustum.CullBox( bounds[0], bounds[1] )) continue; // no interaction // setup the global pointers if( !StudioSetEntity( entry )) continue; if( RI->currententity->curstate.renderfx == SKYBOX_ENTITY ) continue; // fast reject if( FBitSet( RI->currententity->curstate.effects, EF_FULLBRIGHT )) continue; AddMeshToDrawList( m_pStudioHeader, entry->m_pMesh, true ); } } else { // check trans meshes for light intersection for( int i = 0; i < RI->frame.trans_list.Count(); i++ ) { CTransEntry *entry = &RI->frame.trans_list[i]; if( entry->m_bDrawType != DRAWTYPE_MESH ) continue; StudioGetBounds( entry, bounds ); if( pl->frustum.CullBox( bounds[0], bounds[1] )) continue; // no interaction // setup the global pointers if( !StudioSetEntity( entry )) continue; if( RI->currententity->curstate.renderfx == SKYBOX_ENTITY ) continue; // fast reject if( FBitSet( RI->currententity->curstate.effects, EF_FULLBRIGHT )) continue; AddMeshToDrawList( m_pStudioHeader, entry->m_pMesh, true ); } } } void CStudioModelRenderer :: DrawLightForMeshList( CDynLight *pl ) { pglBlendFunc( GL_ONE, GL_ONE ); float y2 = (float)RI->view.port[3] - pl->h - pl->y; pglScissor( pl->x, y2, pl->w, pl->h ); // sorting list to reduce shader switches if( !CVAR_TO_BOOL( cv_nosort )) RI->frame.light_meshes.Sort( SortSolidMeshes ); pglAlphaFunc( GL_GEQUAL, 0.5f ); RI->currententity = NULL; RI->currentmodel = NULL; m_pCurrentMaterial = NULL; for( int i = 0; i < RI->frame.light_meshes.Count(); i++ ) { CSolidEntry *entry = &RI->frame.light_meshes[i]; DrawSingleMesh( entry, ( i == 0 )); } GL_Cull( GL_FRONT ); } void CStudioModelRenderer :: RenderDynLightList( bool solid ) { if( FBitSet( RI->params, RP_ENVVIEW|RP_SKYVIEW )) return; if( !FBitSet( RI->view.flags, RF_HASDYNLIGHTS )) return; if( R_FullBright( )) return; GL_Blend( GL_TRUE ); GL_AlphaTest( GL_FALSE ); GL_DepthMask( GL_FALSE ); CDynLight *pl = tr.dlights; for( int i = 0; i < MAX_DLIGHTS; i++, pl++ ) { if( pl->Expired( )) continue; if( pl->type == LIGHT_SPOT || pl->type == LIGHT_OMNI ) { if( !pl->Active( )) continue; if( !Mod_CheckBoxVisible( pl->absmin, pl->absmax )) continue; if( R_CullFrustum( &pl->frustum )) continue; pglEnable( GL_SCISSOR_TEST ); } else { // couldn't use scissor for sunlight pglDisable( GL_SCISSOR_TEST ); } RI->currentlight = pl; // draw world from light position BuildMeshListForLight( pl, solid ); if( !RI->frame.light_meshes.Count( )) continue; // no interaction with this light? DrawLightForMeshList( pl ); } GL_SelectTexture( glConfig.max_texture_units - 1 ); // force to cleanup all the units pglDisable( GL_SCISSOR_TEST ); GL_CleanUpTextureUnits( 0 ); RI->currentlight = NULL; } void CStudioModelRenderer :: RenderDeferredStudioList( void ) { if( !RI->frame.solid_meshes.Count() ) return; pglAlphaFunc( GL_GEQUAL, 0.5f ); GL_Blend( GL_FALSE ); GL_AlphaTest( GL_FALSE ); GL_DepthMask( GL_TRUE ); if( GL_Support( R_SEAMLESS_CUBEMAP )) pglEnable( GL_TEXTURE_CUBE_MAP_SEAMLESS ); // sorting list to reduce shader switches if( !CVAR_TO_BOOL( cv_nosort )) RI->frame.solid_meshes.Sort( SortSolidMeshes ); RI->currententity = NULL; RI->currentmodel = NULL; m_pCurrentMaterial = NULL; int i; for( i = 0; i < RI->frame.solid_meshes.Count(); i++ ) { CSolidEntry *entry = &RI->frame.solid_meshes[i]; if( entry->m_bDrawType != DRAWTYPE_MESH ) continue; if( m_iDrawModelType == DRAWSTUDIO_NORMAL ) { if( entry->m_pParentEntity->curstate.renderfx == SKYBOX_ENTITY ) { GL_DepthRange( 0.8f, 0.9f ); GL_ClipPlane( false ); } else { GL_DepthRange( gldepthmin, gldepthmax ); GL_ClipPlane( true ); } } DrawSingleMesh( entry, ( i == 0 )); } if( GL_Support( R_SEAMLESS_CUBEMAP )) pglDisable( GL_TEXTURE_CUBE_MAP_SEAMLESS ); if( m_iDrawModelType == DRAWSTUDIO_NORMAL ) GL_DepthRange( gldepthmin, gldepthmax ); GL_CleanupDrawState(); GL_AlphaTest( GL_FALSE ); GL_ClipPlane( true ); GL_Cull( GL_FRONT ); // now draw studio decals for( i = 0; i < RI->frame.solid_meshes.Count(); i++ ) { // DrawDecal( &RI->frame.solid_meshes[i] ); } } word CStudioModelRenderer :: ShaderSceneForward( mstudiomaterial_t *mat, int lightmode, bool bone_weighting, int numbones ) { char glname[64]; char options[MAX_OPTIONS_LENGTH]; bool shader_translucent = false; bool using_screenrect = false; bool using_cubemaps = false; if( mat->forwardScene.IsValid() && mat->lastRenderMode == RI->currententity->curstate.rendermode ) return mat->forwardScene.GetHandle(); // valid Q_strncpy( glname, "forward/scene_studio", sizeof( glname )); memset( options, 0, sizeof( options )); if( numbones == 1 ) GL_AddShaderDirective( options, "MAXSTUDIOBONES 1" ); if( bone_weighting ) GL_AddShaderDirective( options, "APPLY_BONE_WEIGHTING" ); if( FBitSet( mat->flags, STUDIO_NF_CHROME )) GL_AddShaderDirective( options, "HAS_CHROME" ); GL_CheckTextureAlpha( options, mat->gl_diffuse_id ); if( RI->currententity->curstate.rendermode == kRenderTransAdd || RI->currententity->curstate.rendermode == kRenderGlow ) { // additive and glow is always fullbright GL_AddShaderDirective( options, "LIGHTING_FULLBRIGHT" ); if( RI->currententity->curstate.rendermode == kRenderTransAdd ) shader_translucent = true; } else if( FBitSet( mat->flags, STUDIO_NF_FULLBRIGHT ) || R_FullBright() || FBitSet( RI->currententity->curstate.effects, EF_FULLBRIGHT )) { GL_AddShaderDirective( options, "LIGHTING_FULLBRIGHT" ); } else { if( CVAR_TO_BOOL( cv_brdf )) GL_AddShaderDirective( options, "APPLY_PBS" ); if( lightmode == LIGHTSTATIC_VERTEX ) GL_AddShaderDirective( options, "VERTEX_LIGHTING" ); else if( lightmode == LIGHTSTATIC_SURFACE ) GL_AddShaderDirective( options, "SURFACE_LIGHTING" ); else if( FBitSet( mat->flags, STUDIO_NF_FLATSHADE )) GL_AddShaderDirective( options, "LIGHTING_FLATSHADE" ); if( lightmode == LIGHTSTATIC_SURFACE ) { // process lightstyles for( int i = 0; i < MAXLIGHTMAPS && m_pModelInstance->styles[i] != LS_NONE; i++ ) { if( tr.sun_light_enabled && m_pModelInstance->styles[i] == LS_SKY ) continue; // skip the sunlight due realtime sun is enabled GL_AddShaderDirective( options, va( "APPLY_STYLE%i", i )); } if( FBitSet( world->features, WORLD_HAS_DELUXEMAP )) GL_AddShaderDirective( options, "HAS_DELUXEMAP" ); } // debug visualization if( r_lightmap->value > 0.0f && r_lightmap->value <= 2.0f ) { if( r_lightmap->value == 1.0f && worldmodel->lightdata ) GL_AddShaderDirective( options, "LIGHTMAP_DEBUG" ); else if( r_lightmap->value == 2.0f && FBitSet( world->features, WORLD_HAS_DELUXEMAP )) GL_AddShaderDirective( options, "LIGHTVEC_DEBUG" ); } // deluxemap required if( !RP_CUBEPASS() && ( CVAR_TO_BOOL( cv_bump ) && FBitSet( world->features, WORLD_HAS_DELUXEMAP ) && FBitSet( mat->flags, STUDIO_NF_NORMALMAP ))) { GL_AddShaderDirective( options, "HAS_NORMALMAP" ); GL_EncodeNormal( options, mat->gl_normalmap_id ); GL_AddShaderDirective( options, "COMPUTE_TBN" ); } // deluxemap required if( !RP_CUBEPASS() && ( CVAR_TO_BOOL( cv_specular ) && FBitSet( world->features, WORLD_HAS_DELUXEMAP ) && FBitSet( mat->flags, STUDIO_NF_GLOSSMAP ))) GL_AddShaderDirective( options, "HAS_GLOSSMAP" ); if( FBitSet( mat->flags, STUDIO_NF_LUMA )) GL_AddShaderDirective( options, "HAS_LUMA" ); } if( mat->aberrationScale > 0.0f && Q_stristr( options, "HAS_NORMALMAP" )) GL_AddShaderDirective( options, "APPLY_ABERRATION" ); if( mat->refractScale > 0.0f && Q_stristr( options, "HAS_NORMALMAP" )) GL_AddShaderDirective( options, "APPLY_REFRACTION" ); if(( world->num_cubemaps > 0 ) && CVAR_TO_BOOL( cv_cubemaps ) && (mat->reflectScale > 0.0f) && !RP_CUBEPASS( )) { GL_AddShaderDirective( options, "REFLECTION_CUBEMAP" ); using_cubemaps = true; } if( tr.fogEnabled ) GL_AddShaderDirective( options, "APPLY_FOG_EXP" ); // mixed mode: solid & transparent controlled by alpha-channel if( FBitSet( mat->flags, STUDIO_NF_ADDITIVE ) && RI->currententity->curstate.rendermode != kRenderGlow ) { if( FBitSet( mat->flags, STUDIO_NF_HAS_ALPHA )) GL_AddShaderDirective( options, "ALPHA_GLASS" ); shader_translucent = true; } if( RI->currententity->curstate.rendermode == kRenderTransColor || RI->currententity->curstate.rendermode == kRenderTransTexture ) shader_translucent = true; if( shader_translucent ) GL_AddShaderDirective( options, "TRANSLUCENT" ); if( FBitSet( mat->flags, STUDIO_NF_HAS_DETAIL ) && CVAR_TO_BOOL( r_detailtextures )) GL_AddShaderDirective( options, "HAS_DETAIL" ); word shaderNum = GL_FindUberShader( glname, options ); if( !shaderNum ) { SetBits( mat->flags, STUDIO_NF_NODRAW ); return 0; // something bad happens } if( shader_translucent ) GL_AddShaderFeature( shaderNum, SHADER_TRANSLUCENT|SHADER_USE_SCREENCOPY ); if( using_cubemaps ) GL_AddShaderFeature( shaderNum, SHADER_USE_CUBEMAPS ); // done mat->lastRenderMode = RI->currententity->curstate.rendermode; ClearBits( mat->flags, STUDIO_NF_NODRAW ); mat->forwardScene.SetShader( shaderNum ); return shaderNum; } word CStudioModelRenderer :: ShaderLightForward( CDynLight *dl, mstudiomaterial_t *mat, bool bone_weighting, int numbones ) { char glname[64]; char options[MAX_OPTIONS_LENGTH]; switch( dl->type ) { case LIGHT_SPOT: if( mat->forwardLightSpot.IsValid( )) return mat->forwardLightSpot.GetHandle(); // valid break; case LIGHT_OMNI: if( mat->forwardLightOmni.IsValid( )) return mat->forwardLightOmni.GetHandle(); // valid break; case LIGHT_DIRECTIONAL: if( mat->forwardLightProj.IsValid( )) return mat->forwardLightProj.GetHandle(); // valid break; } Q_strncpy( glname, "forward/light_studio", sizeof( glname )); memset( options, 0, sizeof( options )); switch( dl->type ) { case LIGHT_SPOT: GL_AddShaderDirective( options, "LIGHT_SPOT" ); break; case LIGHT_OMNI: GL_AddShaderDirective( options, "LIGHT_OMNI" ); break; case LIGHT_DIRECTIONAL: GL_AddShaderDirective( options, "LIGHT_PROJ" ); break; } if( numbones == 1 ) GL_AddShaderDirective( options, "MAXSTUDIOBONES 1" ); if( bone_weighting ) GL_AddShaderDirective( options, "APPLY_BONE_WEIGHTING" ); if( FBitSet( mat->flags, STUDIO_NF_CHROME )) GL_AddShaderDirective( options, "HAS_CHROME" ); if( CVAR_TO_BOOL( cv_brdf )) GL_AddShaderDirective( options, "APPLY_PBS" ); if( FBitSet( mat->flags, STUDIO_NF_HAS_DETAIL ) && CVAR_TO_BOOL( r_detailtextures )) GL_AddShaderDirective( options, "HAS_DETAIL" ); GL_CheckTextureAlpha( options, mat->gl_diffuse_id ); if( CVAR_TO_BOOL( cv_bump ) && FBitSet( mat->flags, STUDIO_NF_NORMALMAP ) && !FBitSet( dl->flags, DLF_NOBUMP )) { GL_AddShaderDirective( options, "HAS_NORMALMAP" ); GL_EncodeNormal( options, mat->gl_normalmap_id ); GL_AddShaderDirective( options, "COMPUTE_TBN" ); } if( CVAR_TO_BOOL( cv_specular ) && FBitSet( mat->flags, STUDIO_NF_GLOSSMAP )) GL_AddShaderDirective( options, "HAS_GLOSSMAP" ); if( CVAR_TO_BOOL( r_shadows ) && !FBitSet( dl->flags, DLF_NOSHADOWS )) { // shadow cubemaps only support if GL_EXT_gpu_shader4 is support if( dl->type == LIGHT_DIRECTIONAL && CVAR_TO_BOOL( r_sunshadows )) { GL_AddShaderDirective( options, "APPLY_SHADOW" ); } else if( dl->type == LIGHT_SPOT || GL_Support( R_EXT_GPU_SHADER4 )) { GL_AddShaderDirective( options, "APPLY_SHADOW" ); if( r_shadows->value == 2.0f ) GL_AddShaderDirective( options, "SHADOW_PCF2X2" ); else if( r_shadows->value >= 3.0f ) GL_AddShaderDirective( options, "SHADOW_PCF3X3" ); } } word shaderNum = GL_FindUberShader( glname, options ); if( !shaderNum ) { if( dl->type == LIGHT_DIRECTIONAL ) SetBits( mat->flags, STUDIO_NF_NOSUNLIGHT ); else SetBits( mat->flags, STUDIO_NF_NODLIGHT ); return 0; // something bad happens } // done switch( dl->type ) { case LIGHT_SPOT: mat->forwardLightSpot.SetShader( shaderNum ); ClearBits( mat->flags, STUDIO_NF_NODLIGHT ); break; case LIGHT_OMNI: mat->forwardLightOmni.SetShader( shaderNum ); ClearBits( mat->flags, STUDIO_NF_NODLIGHT ); break; case LIGHT_DIRECTIONAL: mat->forwardLightProj.SetShader( shaderNum ); ClearBits( mat->flags, STUDIO_NF_NOSUNLIGHT ); break; } return shaderNum; } word CStudioModelRenderer :: ShaderSceneDeferred( mstudiomaterial_t *mat, bool bone_weighting, int numbones ) { char glname[64]; char options[MAX_OPTIONS_LENGTH]; bool using_cubemaps = false; if( mat->deferredScene.IsValid( )) return mat->deferredScene.GetHandle(); // valid Q_strncpy( glname, "deferred/scene_studio", sizeof( glname )); memset( options, 0, sizeof( options )); if( numbones == 1 ) GL_AddShaderDirective( options, "MAXSTUDIOBONES 1" ); if( bone_weighting ) GL_AddShaderDirective( options, "APPLY_BONE_WEIGHTING" ); if( FBitSet( mat->flags, STUDIO_NF_CHROME )) GL_AddShaderDirective( options, "HAS_CHROME" ); if( FBitSet( mat->flags, STUDIO_NF_FULLBRIGHT ) || R_FullBright( )) GL_AddShaderDirective( options, "LIGHTING_FULLBRIGHT" ); if( FBitSet( mat->flags, STUDIO_NF_FLATSHADE )) GL_AddShaderDirective( options, "LIGHTING_FLATSHADE" ); if( FBitSet( mat->flags, STUDIO_NF_LUMA )) GL_AddShaderDirective( options, "HAS_LUMA" ); if( FBitSet( mat->flags, STUDIO_NF_HAS_DETAIL ) && CVAR_TO_BOOL( r_detailtextures ) && glConfig.max_varying_floats > 48 ) GL_AddShaderDirective( options, "HAS_DETAIL" ); if( !RP_CUBEPASS() && ( FBitSet( mat->flags, STUDIO_NF_NORMALMAP ) && CVAR_TO_BOOL( cv_bump ))) { GL_AddShaderDirective( options, "HAS_NORMALMAP" ); GL_EncodeNormal( options, mat->gl_normalmap_id ); GL_AddShaderDirective( options, "COMPUTE_TBN" ); } if( !RP_CUBEPASS() && ( CVAR_TO_BOOL( cv_specular ) && FBitSet( mat->flags, STUDIO_NF_GLOSSMAP ))) GL_AddShaderDirective( options, "HAS_GLOSSMAP" ); if(( world->num_cubemaps > 0 ) && CVAR_TO_BOOL( cv_cubemaps ) && (mat->reflectScale > 0.0f) && !RP_CUBEPASS( )) { GL_AddShaderDirective( options, "REFLECTION_CUBEMAP" ); using_cubemaps = true; } word shaderNum = GL_FindUberShader( glname, options ); if( !shaderNum ) { SetBits( mat->flags, STUDIO_NF_NODRAW ); return 0; // something bad happens } if( using_cubemaps ) GL_AddShaderFeature( shaderNum, SHADER_USE_CUBEMAPS ); // done ClearBits( mat->flags, STUDIO_NF_NODRAW ); mat->deferredScene.SetShader( shaderNum ); return shaderNum; } word CStudioModelRenderer :: ShaderLightDeferred( mstudiomaterial_t *mat, bool bone_weighting, int numbones ) { char glname[64]; char options[MAX_OPTIONS_LENGTH]; if( mat->deferredLight.IsValid( )) return mat->deferredLight.GetHandle(); // valid Q_strncpy( glname, "deferred/light_studio", sizeof( glname )); memset( options, 0, sizeof( options )); if( numbones == 1 ) GL_AddShaderDirective( options, "MAXSTUDIOBONES 1" ); if( bone_weighting ) GL_AddShaderDirective( options, "APPLY_BONE_WEIGHTING" ); if( FBitSet( mat->flags, STUDIO_NF_FULLBRIGHT ) || R_FullBright( )) GL_AddShaderDirective( options, "LIGHTING_FULLBRIGHT" ); if( FBitSet( mat->flags, STUDIO_NF_FLATSHADE )) GL_AddShaderDirective( options, "LIGHTING_FLATSHADE" ); if( FBitSet( mat->flags, STUDIO_NF_LUMA )) GL_AddShaderDirective( options, "HAS_LUMA" ); word shaderNum = GL_FindUberShader( glname, options ); if( !shaderNum ) { SetBits( mat->flags, STUDIO_NF_NODRAW ); return 0; // something bad happens } // done ClearBits( mat->flags, STUDIO_NF_NODRAW ); mat->deferredLight.SetShader( shaderNum ); return shaderNum; } word CStudioModelRenderer :: ShaderSceneDepth( mstudiomaterial_t *mat, bool bone_weighting, int numbones ) { char glname[64]; char options[MAX_OPTIONS_LENGTH]; if( mat->forwardDepth.IsValid( )) return mat->forwardDepth.GetHandle(); // valid Q_strncpy( glname, "forward/depth_studio", sizeof( glname )); memset( options, 0, sizeof( options )); if( numbones == 1 ) GL_AddShaderDirective( options, "MAXSTUDIOBONES 1" ); if( bone_weighting ) GL_AddShaderDirective( options, "APPLY_BONE_WEIGHTING" ); word shaderNum = GL_FindUberShader( glname, options ); if( !shaderNum ) return 0; // something bad happens mat->forwardDepth.SetShader( shaderNum ); return shaderNum; } void CStudioModelRenderer :: DrawSingleMesh( CSolidEntry *entry, bool force ) { bool cache_has_changed = false; static int cached_lightmap = -1; Vector4D lightstyles, lightdir; bool weapon_model = false; float r, g, b, a, *v; int map; if( entry->m_bDrawType != DRAWTYPE_MESH ) return; if( RI->currentmodel != entry->m_pRenderModel ) cache_has_changed = true; if( RI->currententity != entry->m_pParentEntity ) cache_has_changed = true; RI->currentmodel = entry->m_pRenderModel; cl_entity_t *e = RI->currententity = entry->m_pParentEntity; m_pStudioHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata( RI->currentmodel ); int m_skinnum = bound( 0, RI->currententity->curstate.skin, MAXSTUDIOSKINS - 1 ); ASSERT( RI->currententity->modelhandle != INVALID_HANDLE ); ModelInstance_t *inst = m_pModelInstance = &m_ModelInstances[e->modelhandle]; int num_bones = Q_min( m_pStudioHeader->numbones, glConfig.max_skinning_bones ); vbomesh_t *pMesh = entry->m_pMesh; mstudiomaterial_t *mat; if( pMesh->lightmapnum != cached_lightmap ) { cached_lightmap = pMesh->lightmapnum; cache_has_changed = true; } short *pskinref = (short *)((byte *)m_pStudioHeader + m_pStudioHeader->skinindex); if( m_skinnum != 0 && m_skinnum < m_pStudioHeader->numskinfamilies ) pskinref += (m_skinnum * m_pStudioHeader->numskinref); if( entry->m_pRenderModel == IEngineStudio.GetModelByIndex( e->curstate.weaponmodel )) weapon_model = true; if( weapon_model || RI->currentmodel == m_pPlayerLegsModel ) mat = &entry->m_pRenderModel->materials[pskinref[pMesh->skinref]]; else mat = &m_pModelInstance->materials[pskinref[pMesh->skinref]]; // NOTE: use local copy for right cache shadernums mstudiolight_t *light = &inst->light; if( mat != m_pCurrentMaterial ) cache_has_changed = true; m_pCurrentMaterial = mat; if( FBitSet( RI->params, RP_DEFERREDLIGHT|RP_DEFERREDSCENE )) { if( FBitSet( RI->params, RP_DEFERREDSCENE )) entry->m_hProgram = mat->deferredScene.GetHandle(); else if( FBitSet( RI->params, RP_DEFERREDLIGHT )) entry->m_hProgram = mat->deferredLight.GetHandle(); else entry->m_hProgram = 0; if( !entry->m_hProgram ) return; } if( force || ( RI->currentshader != &glsl_programs[entry->m_hProgram] )) { // force to bind new shader GL_BindShader( &glsl_programs[entry->m_hProgram] ); cache_has_changed = true; } if( FBitSet( RI->params, RP_SHADOWVIEW|RP_DEFERREDSCENE )) { if( FBitSet( mat->flags, STUDIO_NF_MASKED )) { pglAlphaFunc( GL_GEQUAL, 0.5f ); GL_AlphaTest( GL_TRUE ); } else if( FBitSet( mat->flags, STUDIO_NF_HAS_ALPHA )) { pglAlphaFunc( GL_GEQUAL, 0.999f ); GL_AlphaTest( GL_TRUE ); } else GL_AlphaTest( GL_FALSE ); } else { if( FBitSet( mat->flags, STUDIO_NF_MASKED )) GL_AlphaTest( GL_TRUE ); else GL_AlphaTest( GL_FALSE ); } if( !FBitSet( RI->params, RP_SHADOWVIEW )) { if( FBitSet( mat->flags, STUDIO_NF_TWOSIDE )) GL_Cull( GL_NONE ); else GL_Cull( GL_FRONT ); } // each transparent surfaces reqiured an actual screencopy if( ScreenCopyRequired( RI->currentshader ) && entry->IsTranslucent( )) { CTransEntry *trans = (CTransEntry *)entry; if( trans->m_bScissorReady ) { trans->RequestScreenColor(); cache_has_changed = true; // force to refresh uniforms r_stats.c_screen_copy++; } } glsl_program_t *shader = RI->currentshader; CDynLight *pl = RI->currentlight; // may be NULL // sometime we can't set the uniforms if( !cache_has_changed || !shader || !shader->numUniforms || !shader->uniforms ) { // just draw mesh and out DrawMeshFromBuffer( pMesh ); return; } // setup specified uniforms (and texture bindings) for( int i = 0; i < shader->numUniforms; i++ ) { uniform_t *u = &shader->uniforms[i]; switch( u->type ) { case UT_COLORMAP: u->SetValue( mat->gl_diffuse_id ); break; case UT_NORMALMAP: u->SetValue( mat->gl_normalmap_id ); break; case UT_GLOSSMAP: u->SetValue( mat->gl_specular_id ); break; case UT_DETAILMAP: u->SetValue( mat->gl_detailmap_id ); break; case UT_PROJECTMAP: if( pl && pl->type == LIGHT_SPOT ) u->SetValue( pl->spotlightTexture ); else u->SetValue( tr.whiteTexture ); break; case UT_SHADOWMAP: case UT_SHADOWMAP0: if( pl ) u->SetValue( pl->shadowTexture[0] ); else u->SetValue( tr.depthTexture ); break; case UT_SHADOWMAP1: if( pl ) u->SetValue( pl->shadowTexture[1] ); else u->SetValue( tr.depthTexture ); break; case UT_SHADOWMAP2: if( pl ) u->SetValue( pl->shadowTexture[2] ); else u->SetValue( tr.depthTexture ); break; case UT_SHADOWMAP3: if( pl ) u->SetValue( pl->shadowTexture[3] ); else u->SetValue( tr.depthTexture ); break; case UT_LIGHTMAP: if( pMesh->lightmapnum != -1 ) u->SetValue( tr.lightmaps[pMesh->lightmapnum].lightmap ); else u->SetValue( tr.whiteTexture ); break; case UT_DELUXEMAP: if( pMesh->lightmapnum != -1 ) u->SetValue( tr.lightmaps[pMesh->lightmapnum].deluxmap ); else u->SetValue( tr.whiteTexture ); break; case UT_DECALMAP: // unacceptable for studiomodels u->SetValue( tr.whiteTexture ); break; case UT_SCREENMAP: u->SetValue( tr.screen_color ); break; case UT_DEPTHMAP: u->SetValue( tr.screen_depth ); break; case UT_ENVMAP0: case UT_ENVMAP: if( inst->cubemap[0] != NULL ) u->SetValue( inst->cubemap[0]->texture ); else u->SetValue( tr.whiteCubeTexture ); break; case UT_ENVMAP1: if( inst->cubemap[1] != NULL ) u->SetValue( inst->cubemap[1]->texture ); else u->SetValue( tr.whiteCubeTexture ); break; case UT_GLOWMAP: u->SetValue( mat->gl_glowmap_id ); break; case UT_HEIGHTMAP: u->SetValue( mat->gl_heightmap_id ); break; case UT_BSPPLANESMAP: u->SetValue( tr.packed_planes_texture ); break; case UT_BSPNODESMAP: u->SetValue( tr.packed_nodes_texture ); break; case UT_BSPLIGHTSMAP: u->SetValue( tr.packed_lights_texture ); break; case UT_FITNORMALMAP: u->SetValue( tr.normalsFitting ); break; case UT_MODELMATRIX: u->SetValue( &inst->m_glmatrix[0] ); break; case UT_BONESARRAY: if( weapon_model ) u->SetValue( &inst->m_glweaponbones[0][0], num_bones * 3 ); else u->SetValue( &inst->m_glstudiobones[0][0], num_bones * 3 ); break; case UT_BONEQUATERNION: if( weapon_model ) u->SetValue( &inst->m_weaponquat[0][0], num_bones ); else u->SetValue( &inst->m_studioquat[0][0], num_bones ); break; case UT_BONEPOSITION: if( weapon_model ) u->SetValue( &inst->m_weaponpos[0][0], num_bones ); else u->SetValue( &inst->m_studiopos[0][0], num_bones ); break; case UT_SCREENSIZEINV: u->SetValue( 1.0f / (float)glState.width, 1.0f / (float)glState.height ); break; case UT_ZFAR: u->SetValue( -tr.farclip * 1.74f ); break; case UT_LIGHTSTYLES: for( map = 0; map < MAXLIGHTMAPS; map++ ) { if( inst->styles[map] != 255 ) lightstyles[map] = tr.lightstyle[inst->styles[map]]; else lightstyles[map] = 0.0f; } u->SetValue( lightstyles.x, lightstyles.y, lightstyles.z, lightstyles.w ); break; case UT_LIGHTSTYLEVALUES: u->SetValue( &tr.lightstyle[0], MAX_LIGHTSTYLES ); break; case UT_REALTIME: u->SetValue( (float)tr.time ); break; case UT_DETAILSCALE: u->SetValue( mat->detailScale[0], mat->detailScale[1] ); break; case UT_FOGPARAMS: if( e->curstate.renderfx == SKYBOX_ENTITY ) u->SetValue( tr.fogColor[0], tr.fogColor[1], tr.fogColor[2], tr.fogSkyDensity ); else u->SetValue( tr.fogColor[0], tr.fogColor[1], tr.fogColor[2], tr.fogDensity ); break; case UT_SHADOWPARMS: if( pl != NULL ) { float shadowWidth = 1.0f / (float)RENDER_GET_PARM( PARM_TEX_WIDTH, pl->shadowTexture[0] ); float shadowHeight = 1.0f / (float)RENDER_GET_PARM( PARM_TEX_HEIGHT, pl->shadowTexture[0] ); // depth scale and bias and shadowmap resolution u->SetValue( shadowWidth, shadowHeight, -pl->projectionMatrix[2][2], pl->projectionMatrix[3][2] ); } else u->SetValue( 0.0f, 0.0f, 0.0f, 0.0f ); break; case UT_TEXOFFSET: // FIXME: implement conveyors? u->SetValue( 0.0f, 0.0f ); break; case UT_VIEWORIGIN: u->SetValue( GetVieworg().x, GetVieworg().y, GetVieworg().z ); break; case UT_VIEWRIGHT: u->SetValue( GetVRight().x, GetVRight().y, GetVRight().z ); break; case UT_RENDERCOLOR: if( e->curstate.rendermode == kRenderNormal ) { r = g = b = a = 1.0f; } else { int sum = (e->curstate.rendercolor.r + e->curstate.rendercolor.g + e->curstate.rendercolor.b); if( sum > 0 ) { r = e->curstate.rendercolor.r / 255.0f; g = e->curstate.rendercolor.g / 255.0f; b = e->curstate.rendercolor.b / 255.0f; } else { r = g = b = 1.0f; } if( e->curstate.rendermode != kRenderTransAlpha ) a = e->curstate.renderamt / 255.0f; else a = 1.0f; } if( FBitSet( mat->flags, STUDIO_NF_ADDITIVE )) a = 0.65f; // FIXME: 0.5 looks ugly u->SetValue( r, g, b, a ); break; case UT_SMOOTHNESS: u->SetValue( mat->smoothness ); break; case UT_SHADOWMATRIX: if( pl ) u->SetValue( &pl->gl_shadowMatrix[0][0], MAX_SHADOWMAPS ); break; case UT_SHADOWSPLITDIST: v = RI->view.parallelSplitDistances; u->SetValue( v[0], v[1], v[2], v[3] ); break; case UT_TEXELSIZE: u->SetValue( 1.0f / (float)sunSize[0], 1.0f / (float)sunSize[1], 1.0f / (float)sunSize[2], 1.0f / (float)sunSize[3] ); break; case UT_GAMMATABLE: u->SetValue( &tr.gamma_table[0][0], 64 ); break; case UT_LIGHTDIR: if( pl ) { if( pl->type == LIGHT_DIRECTIONAL ) lightdir = -tr.sky_normal; else lightdir = pl->frustum.GetPlane( FRUSTUM_FAR )->normal; u->SetValue( lightdir.x, lightdir.y, lightdir.z, pl->fov ); } else u->SetValue( light->normal.x, light->normal.y, light->normal.z ); break; case UT_LIGHTDIFFUSE: if( pl ) u->SetValue( pl->color.x, pl->color.y, pl->color.z ); else u->SetValue( light->diffuse.x, light->diffuse.y, light->diffuse.z ); break; case UT_LIGHTSHADE: u->SetValue( (float)light->ambientlight / 255.0f, (float)light->shadelight / 255.0f ); break; case UT_LIGHTORIGIN: if( pl ) u->SetValue( pl->origin.x, pl->origin.y, pl->origin.z, ( 1.0f / pl->radius )); break; case UT_LIGHTVIEWPROJMATRIX: if( pl ) { GLfloat gl_lightViewProjMatrix[16]; pl->lightviewProjMatrix.CopyToArray( gl_lightViewProjMatrix ); u->SetValue( &gl_lightViewProjMatrix[0] ); } break; case UT_DIFFUSEFACTOR: u->SetValue( tr.diffuseFactor ); break; case UT_AMBIENTFACTOR: if( pl && pl->type == LIGHT_DIRECTIONAL ) u->SetValue( tr.sun_ambient ); else u->SetValue( tr.ambientFactor ); break; case UT_SUNREFRACT: u->SetValue( tr.sun_refract ); break; case UT_AMBIENTCUBE: u->SetValue( &light->ambient[0][0], 6 ); break; case UT_LERPFACTOR: u->SetValue( inst->lerpFactor ); break; case UT_REFRACTSCALE: u->SetValue( bound( 0.0f, mat->refractScale, 1.0f )); break; case UT_REFLECTSCALE: u->SetValue( bound( 0.0f, mat->reflectScale, 1.0f )); break; case UT_ABERRATIONSCALE: u->SetValue( bound( 0.0f, mat->aberrationScale, 1.0f )); break; case UT_BOXMINS: if( world->num_cubemaps > 0 ) { Vector mins[2]; mins[0] = inst->cubemap[0]->mins; mins[1] = inst->cubemap[1]->mins; u->SetValue( &mins[0][0], 2 ); } break; case UT_BOXMAXS: if( world->num_cubemaps > 0 ) { Vector maxs[2]; maxs[0] = inst->cubemap[0]->maxs; maxs[1] = inst->cubemap[1]->maxs; u->SetValue( &maxs[0][0], 2 ); } break; case UT_CUBEORIGIN: if( world->num_cubemaps > 0 ) { Vector origin[2]; origin[0] = inst->cubemap[0]->origin; origin[1] = inst->cubemap[1]->origin; u->SetValue( &origin[0][0], 2 ); } break; case UT_CUBEMIPCOUNT: if( world->num_cubemaps > 0 ) { r = Q_max( 1, inst->cubemap[0]->numMips - cv_cube_lod_bias->value ); g = Q_max( 1, inst->cubemap[1]->numMips - cv_cube_lod_bias->value ); u->SetValue( r, g ); } break; case UT_LIGHTNUMS0: u->SetValue( (float)inst->lights[0], (float)inst->lights[1], (float)inst->lights[2], (float)inst->lights[3] ); break; case UT_LIGHTNUMS1: u->SetValue( (float)inst->lights[4], (float)inst->lights[5], (float)inst->lights[6], (float)inst->lights[7] ); break; case UT_LIGHTGAMMA: u->SetValue( tr.light_gamma ); break; case UT_LIGHTSCALE: u->SetValue( tr.direct_scale ); break; case UT_LIGHTTHRESHOLD: u->SetValue( tr.light_threshold ); break; default: ALERT( at_error, "%s: unhandled uniform %s\n", RI->currentshader->name, u->name ); break; } } DrawMeshFromBuffer( pMesh ); } void CStudioModelRenderer :: RenderSolidStudioList( void ) { if( !RI->frame.solid_meshes.Count() ) return; pglAlphaFunc( GL_GEQUAL, 0.5f ); GL_Blend( GL_FALSE ); GL_AlphaTest( GL_FALSE ); GL_DepthMask( GL_TRUE ); if( GL_Support( R_SEAMLESS_CUBEMAP )) pglEnable( GL_TEXTURE_CUBE_MAP_SEAMLESS ); // sorting list to reduce shader switches if( !CVAR_TO_BOOL( cv_nosort )) RI->frame.solid_meshes.Sort( SortSolidMeshes ); RI->currententity = NULL; RI->currentmodel = NULL; m_pCurrentMaterial = NULL; int i; for( i = 0; i < RI->frame.solid_meshes.Count(); i++ ) { CSolidEntry *entry = &RI->frame.solid_meshes[i]; if( entry->m_bDrawType != DRAWTYPE_MESH ) continue; if( m_iDrawModelType == DRAWSTUDIO_NORMAL ) { if( entry->m_pParentEntity->curstate.renderfx == SKYBOX_ENTITY ) { GL_DepthRange( 0.8f, 0.9f ); GL_ClipPlane( false ); } else { GL_DepthRange( gldepthmin, gldepthmax ); GL_ClipPlane( true ); } } DrawSingleMesh( entry, ( i == 0 )); } if( GL_Support( R_SEAMLESS_CUBEMAP )) pglDisable( GL_TEXTURE_CUBE_MAP_SEAMLESS ); if( m_iDrawModelType == DRAWSTUDIO_NORMAL ) GL_DepthRange( gldepthmin, gldepthmax ); GL_AlphaTest( GL_FALSE ); GL_ClipPlane( true ); GL_Cull( GL_FRONT ); RenderDynLightList( true ); GL_CleanupDrawState(); // now draw studio decals (unsorted) for( i = 0; i < RI->frame.solid_meshes.Count(); i++ ) { DrawDecal( &RI->frame.solid_meshes[i] ); } } void CStudioModelRenderer :: RenderTransMesh( CTransEntry *entry ) { if( entry->m_bDrawType != DRAWTYPE_MESH ) return; pglAlphaFunc( GL_GEQUAL, 0.5f ); GL_DepthMask( GL_TRUE ); if( m_iDrawModelType == DRAWSTUDIO_NORMAL ) { if( entry->m_pParentEntity->curstate.renderfx == SKYBOX_ENTITY ) { GL_DepthRange( 0.8f, 0.9f ); GL_ClipPlane( false ); } else { GL_DepthRange( gldepthmin, gldepthmax ); GL_ClipPlane( true ); } } if( entry->m_pParentEntity->curstate.rendermode == kRenderGlow ) { pglBlendFunc( GL_ONE, GL_ONE ); if( FBitSet( entry->m_pParentEntity->curstate.effects, EF_NODEPTHTEST )) pglDisable( GL_DEPTH_TEST ); GL_Blend( GL_TRUE ); } else { pglEnable( GL_DEPTH_TEST ); GL_Blend( GL_FALSE ); } // draw decals behind the glass DrawDecal( entry, GL_BACK ); DrawSingleMesh( entry, true ); // draw decals that lies on glass DrawDecal( entry, GL_FRONT ); pglEnable( GL_DEPTH_TEST ); GL_Blend( GL_FALSE ); GL_ClipPlane( true ); } void CStudioModelRenderer :: RenderShadowStudioList( void ) { if( !RI->frame.solid_meshes.Count() ) return; RI->currententity = NULL; RI->currentmodel = NULL; m_pCurrentMaterial = NULL; for( int i = 0; i < RI->frame.solid_meshes.Count(); i++ ) { CSolidEntry *entry = &RI->frame.solid_meshes[i]; DrawSingleMesh( entry, ( i == 0 )); } GL_AlphaTest( GL_FALSE ); GL_CleanupDrawState(); } void CStudioModelRenderer :: RenderDebugStudioList( bool bViewModel ) { int i; if( r_drawentities->value <= 1.0f ) return; if( FBitSet( RI->params, RP_SHADOWVIEW|RP_ENVVIEW|RP_SKYVIEW )) return; GL_CleanupDrawState(); if( bViewModel ) { StudioDrawDebug( GET_VIEWMODEL( )); StudioDrawDebug( gHUD.m_pHeadShieldEnt ); } else { // render debug lines for( i = 0; i < tr.num_draw_entities; i++ ) StudioDrawDebug( tr.draw_entities[i] ); } } void CStudioModelRenderer :: ClearLightCache( void ) { for( int i = 0; i < MAX_LIGHTCACHE; i++ ) { mstudiocache_t *cache = tr.surface_light_cache[i]; if( !cache || cache->numsurfaces <= 0 ) continue; for( int j = 0; j < cache->numsurfaces; j++ ) { mstudiosurface_t *surf = &cache->surfaces[j]; SetBits( surf->flags, SURF_LM_UPDATE|SURF_GRASS_UPDATE ); } cache->update_light = true; } }