/* gl_studio_init.cpp - loading 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" // Global engine <-> studio model rendering code interface engine_studio_api_t IEngineStudio; // the renderer object, created on the stack. CStudioModelRenderer g_StudioRenderer; //================================================================================================ // HUD_GetStudioModelInterface // Export this function for the engine to use the studio renderer class to render objects. //================================================================================================ extern "C" int DLLEXPORT HUD_GetStudioModelInterface( int version, struct r_studio_interface_s **ppinterface, struct engine_studio_api_s *pstudio ) { if( version != STUDIO_INTERFACE_VERSION ) return 0; // Copy in engine helper functions memcpy( &IEngineStudio, pstudio, sizeof( IEngineStudio )); if( g_fRenderInitialized ) { // Initialize local variables, etc. g_StudioRenderer.Init(); g_SpriteRenderer.Init(); } // Success return 1; } //================================================================================================ // // Implementation of bone setup class // //================================================================================================ void CBaseBoneSetup :: debugMsg( char *szFmt, ... ) { char buffer[2048]; // must support > 1k messages va_list args; if( developer_level <= DEV_NONE ) return; va_start( args, szFmt ); Q_vsnprintf( buffer, 2048, szFmt, args ); va_end( args ); gEngfuncs.Con_Printf( buffer ); } mstudioanim_t *CBaseBoneSetup :: GetAnimSourceData( mstudioseqdesc_t *pseqdesc ) { return g_StudioRenderer.StudioGetAnim( RI->currentmodel, pseqdesc ); } void CBaseBoneSetup :: debugLine( const Vector& origin, const Vector& dest, int r, int g, int b, bool noDepthTest, float duration ) { if( noDepthTest ) pglDisable( GL_DEPTH_TEST ); pglColor3ub( r, g, b ); pglBegin( GL_LINES ); pglVertex3fv( origin ); pglVertex3fv( dest ); pglEnd(); } /* ==================== Init ==================== */ void CStudioModelRenderer :: Init( void ) { // Set up some variables shared with engine m_pCvarHiModels = IEngineStudio.GetCvar( "cl_himodels" ); m_pCvarDrawViewModel = IEngineStudio.GetCvar( "r_drawviewmodel" ); m_pCvarHand = CVAR_REGISTER( "cl_righthand", "0", FCVAR_ARCHIVE ); m_pCvarViewmodelFov = CVAR_REGISTER( "cl_viewmodel_fov", "60", FCVAR_ARCHIVE ); m_pCvarHeadShieldFov = CVAR_REGISTER( "cl_headshield_fov", "63", FCVAR_ARCHIVE ); m_pCvarLegsOffset = CVAR_REGISTER( "legs_offset", "20", FCVAR_ARCHIVE ); m_pCvarDrawLegs = CVAR_REGISTER( "r_drawlegs", "1", FCVAR_ARCHIVE ); m_pCvarCompatible = CVAR_REGISTER( "r_studio_compatible", "1", FCVAR_ARCHIVE ); m_pCvarLodScale = CVAR_REGISTER( "cl_lod_scale", "5.0", FCVAR_ARCHIVE ); m_pCvarLodBias = CVAR_REGISTER( "cl_lod_bias", "0", FCVAR_ARCHIVE ); } /* ==================== Init ==================== */ void CStudioModelRenderer :: VidInit( void ) { // tell the engine what models is used m_pPlayerLegsModel = IEngineStudio.Mod_ForName( "models/player_legs.mdl", false ); } /* ==================== CStudioModelRenderer ==================== */ CStudioModelRenderer :: CStudioModelRenderer( void ) { m_iDrawModelType = DRAWSTUDIO_NORMAL; m_pCurrentMaterial = NULL; m_pCvarHiModels = NULL; m_pCvarDrawViewModel= NULL; m_pCvarHand = NULL; m_pStudioHeader = NULL; m_pVboModel = NULL; m_pSubModel = NULL; m_pModelInstance = NULL; } /* ==================== ~CStudioModelRenderer ==================== */ CStudioModelRenderer :: ~CStudioModelRenderer( void ) { } /* ==================== Prepare all the pointers for working with current entity ==================== */ bool CStudioModelRenderer :: StudioSetEntity( cl_entity_t *pEnt ) { if( !pEnt || !pEnt->model || pEnt->model->type != mod_studio ) return false; RI->currententity = pEnt; SET_CURRENT_ENTITY( RI->currententity ); if( m_iDrawModelType == DRAWSTUDIO_NORMAL && ( RI->currententity->player || RI->currententity->curstate.renderfx == kRenderFxDeadPlayer )) { int iPlayerIndex; if( RP_NORMALPASS() && RP_LOCALCLIENT( RI->currententity ) && !FBitSet( RI->params, RP_THIRDPERSON )) { if( !CVAR_TO_BOOL( m_pCvarDrawLegs ) || !m_pPlayerLegsModel ) return false; // do simple cull for player legs if( FBitSet( gHUD.m_iKeyBits, IN_DUCK ) && RI->view.angles[PITCH] <= 30.0f ) return false; else if( !FBitSet( gHUD.m_iKeyBits, IN_DUCK ) && RI->view.angles[PITCH] <= 50.0f ) return false; RI->currentmodel = m_pPlayerLegsModel; } else { if( RI->currententity->curstate.renderfx == kRenderFxDeadPlayer ) iPlayerIndex = RI->currententity->curstate.renderamt - 1; else iPlayerIndex = RI->currententity->curstate.number - 1; if( iPlayerIndex < 0 || iPlayerIndex >= GET_MAX_CLIENTS( )) return false; RI->currentmodel = IEngineStudio.SetupPlayerModel( iPlayerIndex ); // show highest resolution multiplayer model if( CVAR_TO_BOOL( m_pCvarHiModels ) && RI->currentmodel != RI->currententity->model ) RI->currententity->curstate.body = 255; if( !( !developer_level && GET_MAX_CLIENTS() == 1 ) && ( RI->currentmodel == RI->currententity->model )) RI->currententity->curstate.body = 1; // force helmet } } else { RI->currentmodel = RI->currententity->model; } if( RI->currentmodel == NULL ) return false; m_pStudioHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata( RI->currentmodel ); // downloading in-progress ? if( m_pStudioHeader == NULL ) return false; // tell the engine about model IEngineStudio.StudioSetHeader( m_pStudioHeader ); IEngineStudio.SetRenderModel( RI->currentmodel ); if( !StudioSetupInstance( )) { ALERT( at_error, "Couldn't create instance for entity %d\n", pEnt->index ); return false; // out of memory ? } // all done return true; } //----------------------------------------------------------------------------- // Fast version without data reconstruction or changing //----------------------------------------------------------------------------- bool CStudioModelRenderer :: StudioSetEntity( CSolidEntry *entry ) { studiohdr_t *phdr; if( !entry || entry->m_bDrawType != DRAWTYPE_MESH ) return false; if( !entry->m_pParentEntity || !entry->m_pRenderModel ) return false; // bad entry? if( entry->m_pParentEntity->modelhandle == INVALID_HANDLE ) return false; // not initialized? if(( phdr = (studiohdr_t *)IEngineStudio.Mod_Extradata( entry->m_pRenderModel )) == NULL ) return false; // no model? RI->currentmodel = entry->m_pRenderModel; RI->currententity = entry->m_pParentEntity; m_pModelInstance = &m_ModelInstances[entry->m_pParentEntity->modelhandle]; m_pStudioHeader = phdr; return true; } bool CStudioModelRenderer :: StudioSetupInstance( void ) { // first call ? if( RI->currententity->modelhandle == INVALID_HANDLE ) { RI->currententity->modelhandle = m_ModelInstances.AddToTail(); if( RI->currententity->modelhandle == INVALID_HANDLE ) return false; // out of memory ? m_pModelInstance = &m_ModelInstances[RI->currententity->modelhandle]; ClearInstanceData( true ); } else { m_pModelInstance = &m_ModelInstances[RI->currententity->modelhandle]; // model has been changed or something like if( !IsModelInstanceValid( m_pModelInstance )) ClearInstanceData( false ); } m_boneSetup.SetStudioPointers( m_pStudioHeader, m_pModelInstance->m_poseparameter ); return true; } //----------------------------------------------------------------------------- // It's not valid if the model index changed + we have non-zero instance data //----------------------------------------------------------------------------- bool CStudioModelRenderer :: IsModelInstanceValid( ModelInstance_t *inst ) { const model_t *pModel; if( m_iDrawModelType == DRAWSTUDIO_NORMAL && ( inst->m_pEntity->player || RI->currententity->curstate.renderfx == kRenderFxDeadPlayer )) { if( RI->currententity->curstate.renderfx == kRenderFxDeadPlayer ) pModel = IEngineStudio.SetupPlayerModel( inst->m_pEntity->curstate.renderamt - 1 ); else pModel = IEngineStudio.SetupPlayerModel( inst->m_pEntity->curstate.number - 1 ); } else { pModel = inst->m_pEntity->model; } return inst->m_pModel == pModel; } void CStudioModelRenderer :: DeleteVBOMesh( vbomesh_t *pMesh ) { // purge all GPU data if( pMesh->vao ) pglDeleteVertexArrays( 1, &pMesh->vao ); if( pMesh->vbo ) pglDeleteBuffersARB( 1, &pMesh->vbo ); if( pMesh->ibo ) pglDeleteBuffersARB( 1, &pMesh->ibo ); tr.total_vbo_memory -= pMesh->cacheSize; pMesh->cacheSize = 0; } void CStudioModelRenderer :: DeleteStudioCache( mstudiocache_t **ppstudiocache ) { ASSERT( ppstudiocache != NULL ); mstudiocache_t *pstudiocache = *ppstudiocache; if( !pstudiocache ) return; for( int i = 0; i < pstudiocache->numbodyparts; i++ ) { mbodypart_t *pBodyPart = &pstudiocache->bodyparts[i]; for( int j = 0; j < pBodyPart->nummodels; j++ ) { msubmodel_t *pSubModel = pBodyPart->models[j]; if( !pSubModel || pSubModel->nummesh <= 0 ) continue; // blank submodel for( int k = 0; k < pSubModel->nummesh; k++ ) { vbomesh_t *pMesh = &pSubModel->meshes[k]; DeleteVBOMesh( pMesh ); } } } if( pstudiocache != NULL ) Mem_Free( pstudiocache ); *ppstudiocache = NULL; } void CStudioModelRenderer :: DestroyMeshCache( void ) { FreeStudioMaterials (); DeleteStudioCache( &RI->currentmodel->studiocache ); if( RI->currentmodel->poseToBone != NULL ) Mem_Free( RI->currentmodel->poseToBone ); RI->currentmodel->poseToBone = NULL; } void CStudioModelRenderer :: DestroyInstance( word handle ) { if( !m_ModelInstances.IsValidIndex( handle )) return; ModelInstance_t *inst = &m_ModelInstances[handle]; PurgeDecals( inst ); if( inst->materials != NULL ) Mem_Free( inst->materials ); inst->materials = NULL; if( inst->m_pJiggleBones != NULL ) delete inst->m_pJiggleBones; inst->m_pJiggleBones = NULL; m_ModelInstances.Remove( handle ); } void CStudioModelRenderer :: DestroyAllModelInstances( void ) { // if caused by Host_Error during draw the viewmodel or gasmask m_iDrawModelType = DRAWSTUDIO_NORMAL; m_fShootDecal = false; // NOTE: should destroy in reverse-order because it's linked list not array! for( int i = m_ModelInstances.Count(); --i >= 0; ) DestroyInstance( i ); } void CStudioModelRenderer :: UpdateInstanceMaterials( void ) { ASSERT( m_pStudioHeader != NULL ); ASSERT( m_pModelInstance != NULL ); // model was changed, so we need to realloc materials if( m_pModelInstance->materials != NULL ) Mem_Free( m_pModelInstance->materials ); // create a local copy of all the model material for cache uber-shaders m_pModelInstance->materials = (mstudiomaterial_t *)Mem_Alloc( sizeof( mstudiomaterial_t ) * m_pStudioHeader->numtextures ); memcpy( m_pModelInstance->materials, RI->currentmodel->materials, sizeof( mstudiomaterial_t ) * m_pStudioHeader->numtextures ); // invalidate sequences when a new instance was created for( int i = 0; i < m_pStudioHeader->numtextures; i++ ) { m_pModelInstance->materials[i].forwardScene.Invalidate(); m_pModelInstance->materials[i].forwardLightSpot.Invalidate(); m_pModelInstance->materials[i].forwardLightOmni.Invalidate(); m_pModelInstance->materials[i].forwardLightProj.Invalidate(); } } void CStudioModelRenderer :: ClearInstanceData( bool create ) { if( create ) { m_pModelInstance->m_DecalList.Purge(); m_pModelInstance->m_pJiggleBones = NULL; m_pModelInstance->materials = NULL; } else { if( m_pModelInstance->m_pJiggleBones != NULL ) delete m_pModelInstance->m_pJiggleBones; m_pModelInstance->m_pJiggleBones = NULL; PurgeDecals( m_pModelInstance ); } m_pModelInstance->m_pEntity = RI->currententity; m_pModelInstance->m_pModel = RI->currentmodel; m_pModelInstance->m_VlCache = NULL; m_pModelInstance->m_FlCache = NULL; m_pModelInstance->m_DecalCount = 0; m_pModelInstance->cached_frame = -1; m_pModelInstance->visframe = -1; m_pModelInstance->radius = 0.0f; m_pModelInstance->info_flags = 0; m_pModelInstance->lerpFactor = 0.0f; m_pModelInstance->cubemap[0] = &world->defaultCubemap; m_pModelInstance->cubemap[1] = &world->defaultCubemap; ClearBounds( m_pModelInstance->absmin, m_pModelInstance->absmax ); memset( &m_pModelInstance->bonecache, 0, sizeof( BoneCache_t )); memset( m_pModelInstance->m_protationmatrix, 0, sizeof( matrix3x4 )); memset( m_pModelInstance->m_pbones, 0, sizeof( matrix3x4 ) * MAXSTUDIOBONES ); memset( m_pModelInstance->m_pwpnbones, 0, sizeof( matrix3x4 ) * MAXSTUDIOBONES ); memset( m_pModelInstance->attachment, 0, sizeof( StudioAttachment_t ) * MAXSTUDIOATTACHMENTS ); memset( m_pModelInstance->m_glstudiobones, 0, sizeof( Vector4D ) * MAXSTUDIOBONES * 3 ); memset( m_pModelInstance->m_glweaponbones, 0, sizeof( Vector4D ) * MAXSTUDIOBONES * 3 ); memset( m_pModelInstance->m_studioquat, 0, sizeof( Vector4D ) * MAXSTUDIOBONES ); memset( m_pModelInstance->m_studiopos, 0, sizeof( Vector ) * MAXSTUDIOBONES ); memset( m_pModelInstance->m_weaponquat, 0, sizeof( Vector4D ) * MAXSTUDIOBONES ); memset( m_pModelInstance->m_weaponpos, 0, sizeof( Vector ) * MAXSTUDIOBONES ); memset( &m_pModelInstance->lerp, 0, sizeof( mstudiolerp_t )); memset( &m_pModelInstance->light, 0, sizeof( mstudiolight_t )); memset( &m_pModelInstance->oldlight, 0, sizeof( mstudiolight_t )); memset( &m_pModelInstance->newlight, 0, sizeof( mstudiolight_t )); memset( &m_pModelInstance->lights, 255, sizeof( byte[MAXDYNLIGHTS] )); memset( &m_pModelInstance->m_controller, 0, sizeof( m_pModelInstance->m_controller )); memset( &m_pModelInstance->m_seqblend, 0, sizeof( m_pModelInstance->m_seqblend )); m_pModelInstance->lerp.stairoldz = RI->currententity->origin[2]; m_pModelInstance->lerp.stairtime = tr.time; m_pModelInstance->m_current_seqblend = 0; m_pModelInstance->light_update = false; m_pModelInstance->lighttimecheck = 0.0f; m_boneSetup.SetStudioPointers( m_pStudioHeader, m_pModelInstance->m_poseparameter ); // set poseparam sliders to their default values m_boneSetup.CalcDefaultPoseParameters( m_pModelInstance->m_poseparameter ); // refresh the materials list UpdateInstanceMaterials(); // copy attachments names mstudioattachment_t *pattachment = (mstudioattachment_t *)((byte *)m_pStudioHeader + m_pStudioHeader->attachmentindex); StudioAttachment_t *att = m_pModelInstance->attachment; // setup attachment names for( int i = 0; i < Q_min( MAXSTUDIOATTACHMENTS, m_pStudioHeader->numattachments ); i++ ) { Q_strncpy( att[i].name, pattachment[i].name, sizeof( att[0].name )); att[i].local.Identity(); att[i].local.SetOrigin( pattachment[i].org ); if( !Q_strnicmp( att[i].name, "LightProbe.", 11 )) SetBits( m_pModelInstance->info_flags, MF_CUSTOM_LIGHTGRID ); } m_pModelInstance->numattachments = m_pStudioHeader->numattachments; for( int map = 0; map < MAXLIGHTMAPS; map++ ) m_pModelInstance->styles[map] = 255; } void CStudioModelRenderer :: PrecacheStudioShaders( void ) { bool bone_weights = FBitSet( m_pStudioHeader->flags, STUDIO_HAS_BONEWEIGHTS ) ? true : false; mstudiomaterial_t *pmaterial = (mstudiomaterial_t *)RI->currentmodel->materials; // this function is called when loading vertexlight cache, so we guessed what vertex light is active for( int i = 0; i < m_pStudioHeader->numtextures; i++, pmaterial++ ) { ShaderSceneForward( pmaterial, true, bone_weights, m_pStudioHeader->numbones ); } } #include "material.h" void CStudioModelRenderer :: LoadStudioMaterials( void ) { // first we need alloc copy of all the materials to prevent modify mstudiotexture_t RI->currentmodel->materials = (mstudiomaterial_t *)Mem_Alloc( sizeof( mstudiomaterial_t ) * m_pStudioHeader->numtextures ); bool bone_weights = FBitSet( m_pStudioHeader->flags, STUDIO_HAS_BONEWEIGHTS ) ? true : false; mstudiotexture_t *ptexture = (mstudiotexture_t *)((byte *)m_pStudioHeader + m_pStudioHeader->textureindex); char diffuse[128], bumpmap[128], glossmap[128], glowmap[128], heightmap[128]; mstudiomaterial_t *pmaterial = (mstudiomaterial_t *)RI->currentmodel->materials; char texname[128], matname[64], mdlname[64]; COM_FileBase( RI->currentmodel->name, mdlname ); // loading studio materials from studio textures for( int i = 0; i < m_pStudioHeader->numtextures; i++, ptexture++, pmaterial++ ) { COM_FileBase( ptexture->name, texname ); // build material names Q_snprintf( matname, sizeof( matname ), "%s/%s", mdlname, texname ); // material description // setup material constants matdesc_t *desc = CL_FindMaterial( matname ); #if 0 if( Q_strlen( desc->diffusemap ) && !IMAGE_EXISTS( desc->diffusemap )) Msg( "need: %s\n", desc->diffusemap ); #endif if( Q_strlen( desc->diffusemap ) && IMAGE_EXISTS( desc->diffusemap )) Q_strncpy( diffuse, desc->diffusemap, sizeof( diffuse )); else Q_snprintf( diffuse, sizeof( diffuse ), "textures/%s/%s", mdlname, texname ); if( Q_strlen( desc->normalmap ) && IMAGE_EXISTS( desc->normalmap )) Q_strncpy( bumpmap, desc->normalmap, sizeof( bumpmap )); else Q_snprintf( bumpmap, sizeof( bumpmap ), "textures/%s/%s_norm", mdlname, texname ); if( Q_strlen( desc->glossmap ) && IMAGE_EXISTS( desc->glossmap )) Q_strncpy( glossmap, desc->glossmap, sizeof( glossmap )); else Q_snprintf( glossmap, sizeof( glossmap ), "textures/%s/%s_gloss", mdlname, texname ); Q_snprintf( glowmap, sizeof( glowmap ), "textures/%s/%s_luma", mdlname, texname ); Q_snprintf( heightmap, sizeof( heightmap ), "textures/%s/%s_hmap", mdlname, texname ); #if 0 Msg( "\"%s\"\n", matname ); Msg( "{\n" ); Msg( "\t\"diffuseMap\"\t\"%s\"\n", diffuse ); Msg( "\t\"normalMap\"\t\"%s\"\n", bumpmap ); Msg( "\t\"glossMap\"\t\"%s\"\n", glossmap ); Msg( "\t\"material\"\t\"%s\"\n", desc->effects->name ); Msg( "}\n\n" ); #endif pmaterial->pSource = ptexture; pmaterial->flags = ptexture->flags; if( IMAGE_EXISTS( diffuse )) { pmaterial->gl_diffuse_id = LOAD_TEXTURE( diffuse, NULL, 0, 0 ); // semi-transparent textures must have additive flag to invoke renderer insert supposed mesh into translist if( FBitSet( pmaterial->flags, STUDIO_NF_ADDITIVE )) { if( RENDER_GET_PARM( PARM_TEX_FLAGS, pmaterial->gl_diffuse_id ) & TF_HAS_ALPHA ) SetBits( pmaterial->flags, STUDIO_NF_HAS_ALPHA ); } if( FBitSet( pmaterial->flags, STUDIO_NF_MASKED )) SetBits( pmaterial->flags, STUDIO_NF_HAS_ALPHA ); } if( pmaterial->gl_diffuse_id != 0 ) { // so engine can be draw HQ image for gl_renderer 0 if( ptexture->index != tr.defaultTexture ) FREE_TEXTURE( ptexture->index ); ptexture->index = pmaterial->gl_diffuse_id; } else { // reuse original texture pmaterial->gl_diffuse_id = ptexture->index; } if( IMAGE_EXISTS( bumpmap )) { pmaterial->gl_normalmap_id = LOAD_TEXTURE( bumpmap, NULL, 0, TF_NORMALMAP ); if( pmaterial->gl_normalmap_id > 0 ) SetBits( pmaterial->flags, STUDIO_NF_NORMALMAP ); } else { // try alternate suffix Q_snprintf( bumpmap, sizeof( bumpmap ), "textures/%s/%s_local", mdlname, texname ); if( IMAGE_EXISTS( bumpmap )) pmaterial->gl_normalmap_id = LOAD_TEXTURE( bumpmap, NULL, 0, TF_NORMALMAP ); else pmaterial->gl_normalmap_id = tr.normalmapTexture; // blank bumpy } if( IMAGE_EXISTS( glossmap )) { pmaterial->gl_specular_id = LOAD_TEXTURE( glossmap, NULL, 0, 0 ); } else { // try alternate suffix Q_snprintf( glossmap, sizeof( glossmap ), "textures/%s/%s_gloss", mdlname, texname ); if( IMAGE_EXISTS( glossmap )) pmaterial->gl_specular_id = LOAD_TEXTURE( glossmap, NULL, 0, 0 ); else pmaterial->gl_specular_id = tr.blackTexture; } if( IMAGE_EXISTS( glowmap )) pmaterial->gl_glowmap_id = LOAD_TEXTURE( glowmap, NULL, 0, 0 ); else pmaterial->gl_glowmap_id = tr.blackTexture; if( IMAGE_EXISTS( heightmap )) { pmaterial->gl_heightmap_id = LOAD_TEXTURE( heightmap, NULL, 0, 0 ); } else { // try alternate suffix Q_snprintf( heightmap, sizeof( heightmap ), "textures/%s/%s_bump", mdlname, texname ); if( IMAGE_EXISTS( heightmap )) pmaterial->gl_heightmap_id = LOAD_TEXTURE( heightmap, NULL, 0, 0 ); else pmaterial->gl_heightmap_id = tr.blackTexture; } // current model has bumpmapping effect if( pmaterial->gl_normalmap_id > 0 && pmaterial->gl_normalmap_id != tr.normalmapTexture ) SetBits( m_pStudioHeader->flags, STUDIO_HAS_BUMP ); if( pmaterial->gl_specular_id != tr.blackTexture ) SetBits( pmaterial->flags, STUDIO_NF_GLOSSMAP ); if( pmaterial->gl_glowmap_id != tr.blackTexture ) SetBits( pmaterial->flags, STUDIO_NF_LUMA ); if( pmaterial->gl_heightmap_id != tr.blackTexture ) SetBits( pmaterial->flags, STUDIO_NF_HEIGHTMAP ); pmaterial->gl_detailmap_id = desc->dt_texturenum; pmaterial->smoothness = desc->smoothness; pmaterial->detailScale[0] = desc->detailScale[0]; pmaterial->detailScale[1] = desc->detailScale[1]; pmaterial->reflectScale = desc->reflectScale; pmaterial->refractScale = desc->refractScale; pmaterial->aberrationScale = desc->aberrationScale; pmaterial->reliefScale = desc->reliefScale; pmaterial->effects = desc->effects; if( pmaterial->smoothness <= 0.0f ) // don't waste time ClearBits( pmaterial->flags, STUDIO_NF_GLOSSMAP ); if( pmaterial->gl_detailmap_id > 0 && pmaterial->gl_detailmap_id != tr.grayTexture ) SetBits( pmaterial->flags, STUDIO_NF_HAS_DETAIL ); // time to precache shaders ShaderSceneForward( pmaterial, false, bone_weights, m_pStudioHeader->numbones ); ShaderLightForward( &tr.defaultlightSpot, pmaterial, bone_weights, m_pStudioHeader->numbones ); ShaderLightForward( &tr.defaultlightOmni, pmaterial, bone_weights, m_pStudioHeader->numbones ); ShaderLightForward( &tr.defaultlightProj, pmaterial, bone_weights, m_pStudioHeader->numbones ); pmaterial->forwardScene.Invalidate(); // don't keep shadernum } } void CStudioModelRenderer :: FreeStudioMaterials( void ) { if( !RI->currentmodel->materials ) return; mstudiomaterial_t *pmaterial = (mstudiomaterial_t *)RI->currentmodel->materials; // release textures for current model for( int i = 0; i < m_pStudioHeader->numtextures; i++, pmaterial++ ) { if( pmaterial->pSource->index != pmaterial->gl_diffuse_id ) FREE_TEXTURE( pmaterial->gl_diffuse_id ); if( pmaterial->gl_normalmap_id != tr.normalmapTexture ) FREE_TEXTURE( pmaterial->gl_normalmap_id ); if( pmaterial->gl_specular_id != tr.blackTexture ) FREE_TEXTURE( pmaterial->gl_specular_id ); if( pmaterial->gl_glowmap_id != tr.blackTexture ) FREE_TEXTURE( pmaterial->gl_glowmap_id ); } Mem_Free( RI->currentmodel->materials ); RI->currentmodel->materials = NULL; } void CStudioModelRenderer :: LoadLocalMatrix( int bone, mstudioboneinfo_t *boneinfo ) { mposetobone_t *m = RI->currentmodel->poseToBone; // transform Valve matrix to Xash matrix m->posetobone[bone][0][0] = boneinfo->poseToBone[0][0]; m->posetobone[bone][0][1] = boneinfo->poseToBone[1][0]; m->posetobone[bone][0][2] = boneinfo->poseToBone[2][0]; m->posetobone[bone][1][0] = boneinfo->poseToBone[0][1]; m->posetobone[bone][1][1] = boneinfo->poseToBone[1][1]; m->posetobone[bone][1][2] = boneinfo->poseToBone[2][1]; m->posetobone[bone][2][0] = boneinfo->poseToBone[0][2]; m->posetobone[bone][2][1] = boneinfo->poseToBone[1][2]; m->posetobone[bone][2][2] = boneinfo->poseToBone[2][2]; m->posetobone[bone][3][0] = boneinfo->poseToBone[0][3]; m->posetobone[bone][3][1] = boneinfo->poseToBone[1][3]; m->posetobone[bone][3][2] = boneinfo->poseToBone[2][3]; } void CStudioModelRenderer :: ComputeSkinMatrix( mstudioboneweight_t *boneweights, const matrix3x4 worldtransform[], matrix3x4 &result ) { float flWeight0, flWeight1, flWeight2, flWeight3; int numbones = 0; float flTotal; for( int i = 0; i < MAXSTUDIOBONEWEIGHTS; i++ ) { if( boneweights->bone[i] != -1 ) numbones++; } if( numbones == 4 ) { const matrix3x4 &boneMat0 = worldtransform[boneweights->bone[0]]; const matrix3x4 &boneMat1 = worldtransform[boneweights->bone[1]]; const matrix3x4 &boneMat2 = worldtransform[boneweights->bone[2]]; const matrix3x4 &boneMat3 = worldtransform[boneweights->bone[3]]; flWeight0 = boneweights->weight[0] / 255.0f; flWeight1 = boneweights->weight[1] / 255.0f; flWeight2 = boneweights->weight[2] / 255.0f; flWeight3 = boneweights->weight[3] / 255.0f; flTotal = flWeight0 + flWeight1 + flWeight2 + flWeight3; if( flTotal < 1.0f ) flWeight0 += 1.0f - flTotal; // compensate rounding error result[0][0] = boneMat0[0][0] * flWeight0 + boneMat1[0][0] * flWeight1 + boneMat2[0][0] * flWeight2 + boneMat3[0][0] * flWeight3; result[0][1] = boneMat0[0][1] * flWeight0 + boneMat1[0][1] * flWeight1 + boneMat2[0][1] * flWeight2 + boneMat3[0][1] * flWeight3; result[0][2] = boneMat0[0][2] * flWeight0 + boneMat1[0][2] * flWeight1 + boneMat2[0][2] * flWeight2 + boneMat3[0][2] * flWeight3; result[1][0] = boneMat0[1][0] * flWeight0 + boneMat1[1][0] * flWeight1 + boneMat2[1][0] * flWeight2 + boneMat3[1][0] * flWeight3; result[1][1] = boneMat0[1][1] * flWeight0 + boneMat1[1][1] * flWeight1 + boneMat2[1][1] * flWeight2 + boneMat3[1][1] * flWeight3; result[1][2] = boneMat0[1][2] * flWeight0 + boneMat1[1][2] * flWeight1 + boneMat2[1][2] * flWeight2 + boneMat3[1][2] * flWeight3; result[2][0] = boneMat0[2][0] * flWeight0 + boneMat1[2][0] * flWeight1 + boneMat2[2][0] * flWeight2 + boneMat3[2][0] * flWeight3; result[2][1] = boneMat0[2][1] * flWeight0 + boneMat1[2][1] * flWeight1 + boneMat2[2][1] * flWeight2 + boneMat3[2][1] * flWeight3; result[2][2] = boneMat0[2][2] * flWeight0 + boneMat1[2][2] * flWeight1 + boneMat2[2][2] * flWeight2 + boneMat3[2][2] * flWeight3; result[3][0] = boneMat0[3][0] * flWeight0 + boneMat1[3][0] * flWeight1 + boneMat2[3][0] * flWeight2 + boneMat3[3][0] * flWeight3; result[3][1] = boneMat0[3][1] * flWeight0 + boneMat1[3][1] * flWeight1 + boneMat2[3][1] * flWeight2 + boneMat3[3][1] * flWeight3; result[3][2] = boneMat0[3][2] * flWeight0 + boneMat1[3][2] * flWeight1 + boneMat2[3][2] * flWeight2 + boneMat3[3][2] * flWeight3; } else if( numbones == 3 ) { const matrix3x4 &boneMat0 = worldtransform[boneweights->bone[0]]; const matrix3x4 &boneMat1 = worldtransform[boneweights->bone[1]]; const matrix3x4 &boneMat2 = worldtransform[boneweights->bone[2]]; flWeight0 = boneweights->weight[0] / 255.0f; flWeight1 = boneweights->weight[1] / 255.0f; flWeight2 = boneweights->weight[2] / 255.0f; flTotal = flWeight0 + flWeight1 + flWeight2; if( flTotal < 1.0f ) flWeight0 += 1.0f - flTotal; // compensate rounding error result[0][0] = boneMat0[0][0] * flWeight0 + boneMat1[0][0] * flWeight1 + boneMat2[0][0] * flWeight2; result[0][1] = boneMat0[0][1] * flWeight0 + boneMat1[0][1] * flWeight1 + boneMat2[0][1] * flWeight2; result[0][2] = boneMat0[0][2] * flWeight0 + boneMat1[0][2] * flWeight1 + boneMat2[0][2] * flWeight2; result[1][0] = boneMat0[1][0] * flWeight0 + boneMat1[1][0] * flWeight1 + boneMat2[1][0] * flWeight2; result[1][1] = boneMat0[1][1] * flWeight0 + boneMat1[1][1] * flWeight1 + boneMat2[1][1] * flWeight2; result[1][2] = boneMat0[1][2] * flWeight0 + boneMat1[1][2] * flWeight1 + boneMat2[1][2] * flWeight2; result[2][0] = boneMat0[2][0] * flWeight0 + boneMat1[2][0] * flWeight1 + boneMat2[2][0] * flWeight2; result[2][1] = boneMat0[2][1] * flWeight0 + boneMat1[2][1] * flWeight1 + boneMat2[2][1] * flWeight2; result[2][2] = boneMat0[2][2] * flWeight0 + boneMat1[2][2] * flWeight1 + boneMat2[2][2] * flWeight2; result[3][0] = boneMat0[3][0] * flWeight0 + boneMat1[3][0] * flWeight1 + boneMat2[3][0] * flWeight2; result[3][1] = boneMat0[3][1] * flWeight0 + boneMat1[3][1] * flWeight1 + boneMat2[3][1] * flWeight2; result[3][2] = boneMat0[3][2] * flWeight0 + boneMat1[3][2] * flWeight1 + boneMat2[3][2] * flWeight2; } else if( numbones == 2 ) { const matrix3x4 &boneMat0 = worldtransform[boneweights->bone[0]]; const matrix3x4 &boneMat1 = worldtransform[boneweights->bone[1]]; flWeight0 = boneweights->weight[0] / 255.0f; flWeight1 = boneweights->weight[1] / 255.0f; flTotal = flWeight0 + flWeight1; if( flTotal < 1.0f ) flWeight0 += 1.0f - flTotal; // compensate rounding error // NOTE: Inlining here seems to make a fair amount of difference result[0][0] = boneMat0[0][0] * flWeight0 + boneMat1[0][0] * flWeight1; result[0][1] = boneMat0[0][1] * flWeight0 + boneMat1[0][1] * flWeight1; result[0][2] = boneMat0[0][2] * flWeight0 + boneMat1[0][2] * flWeight1; result[1][0] = boneMat0[1][0] * flWeight0 + boneMat1[1][0] * flWeight1; result[1][1] = boneMat0[1][1] * flWeight0 + boneMat1[1][1] * flWeight1; result[1][2] = boneMat0[1][2] * flWeight0 + boneMat1[1][2] * flWeight1; result[2][0] = boneMat0[2][0] * flWeight0 + boneMat1[2][0] * flWeight1; result[2][1] = boneMat0[2][1] * flWeight0 + boneMat1[2][1] * flWeight1; result[2][2] = boneMat0[2][2] * flWeight0 + boneMat1[2][2] * flWeight1; result[3][0] = boneMat0[3][0] * flWeight0 + boneMat1[3][0] * flWeight1; result[3][1] = boneMat0[3][1] * flWeight0 + boneMat1[3][1] * flWeight1; result[3][2] = boneMat0[3][2] * flWeight0 + boneMat1[3][2] * flWeight1; } else { result = worldtransform[boneweights->bone[0]]; } } void CStudioModelRenderer :: ComputeSkinMatrix( svert_t *vertex, const matrix3x4 worldtransform[], matrix3x4 &result ) { float flWeight0, flWeight1, flWeight2, flWeight3; int numbones = 0; float flTotal; for( int i = 0; i < MAXSTUDIOBONEWEIGHTS; i++ ) { if( vertex->boneid[i] != -1 ) numbones++; } if( numbones == 4 ) { const matrix3x4 &boneMat0 = worldtransform[vertex->boneid[0]]; const matrix3x4 &boneMat1 = worldtransform[vertex->boneid[1]]; const matrix3x4 &boneMat2 = worldtransform[vertex->boneid[2]]; const matrix3x4 &boneMat3 = worldtransform[vertex->boneid[3]]; flWeight0 = vertex->weight[0] / 255.0f; flWeight1 = vertex->weight[1] / 255.0f; flWeight2 = vertex->weight[2] / 255.0f; flWeight3 = vertex->weight[3] / 255.0f; flTotal = flWeight0 + flWeight1 + flWeight2 + flWeight3; if( flTotal < 1.0f ) flWeight0 += 1.0f - flTotal; // compensate rounding error result[0][0] = boneMat0[0][0] * flWeight0 + boneMat1[0][0] * flWeight1 + boneMat2[0][0] * flWeight2 + boneMat3[0][0] * flWeight3; result[0][1] = boneMat0[0][1] * flWeight0 + boneMat1[0][1] * flWeight1 + boneMat2[0][1] * flWeight2 + boneMat3[0][1] * flWeight3; result[0][2] = boneMat0[0][2] * flWeight0 + boneMat1[0][2] * flWeight1 + boneMat2[0][2] * flWeight2 + boneMat3[0][2] * flWeight3; result[1][0] = boneMat0[1][0] * flWeight0 + boneMat1[1][0] * flWeight1 + boneMat2[1][0] * flWeight2 + boneMat3[1][0] * flWeight3; result[1][1] = boneMat0[1][1] * flWeight0 + boneMat1[1][1] * flWeight1 + boneMat2[1][1] * flWeight2 + boneMat3[1][1] * flWeight3; result[1][2] = boneMat0[1][2] * flWeight0 + boneMat1[1][2] * flWeight1 + boneMat2[1][2] * flWeight2 + boneMat3[1][2] * flWeight3; result[2][0] = boneMat0[2][0] * flWeight0 + boneMat1[2][0] * flWeight1 + boneMat2[2][0] * flWeight2 + boneMat3[2][0] * flWeight3; result[2][1] = boneMat0[2][1] * flWeight0 + boneMat1[2][1] * flWeight1 + boneMat2[2][1] * flWeight2 + boneMat3[2][1] * flWeight3; result[2][2] = boneMat0[2][2] * flWeight0 + boneMat1[2][2] * flWeight1 + boneMat2[2][2] * flWeight2 + boneMat3[2][2] * flWeight3; result[3][0] = boneMat0[3][0] * flWeight0 + boneMat1[3][0] * flWeight1 + boneMat2[3][0] * flWeight2 + boneMat3[3][0] * flWeight3; result[3][1] = boneMat0[3][1] * flWeight0 + boneMat1[3][1] * flWeight1 + boneMat2[3][1] * flWeight2 + boneMat3[3][1] * flWeight3; result[3][2] = boneMat0[3][2] * flWeight0 + boneMat1[3][2] * flWeight1 + boneMat2[3][2] * flWeight2 + boneMat3[3][2] * flWeight3; } else if( numbones == 3 ) { const matrix3x4 &boneMat0 = worldtransform[vertex->boneid[0]]; const matrix3x4 &boneMat1 = worldtransform[vertex->boneid[1]]; const matrix3x4 &boneMat2 = worldtransform[vertex->boneid[2]]; flWeight0 = vertex->weight[0] / 255.0f; flWeight1 = vertex->weight[1] / 255.0f; flWeight2 = vertex->weight[2] / 255.0f; flTotal = flWeight0 + flWeight1 + flWeight2; if( flTotal < 1.0f ) flWeight0 += 1.0f - flTotal; // compensate rounding error result[0][0] = boneMat0[0][0] * flWeight0 + boneMat1[0][0] * flWeight1 + boneMat2[0][0] * flWeight2; result[0][1] = boneMat0[0][1] * flWeight0 + boneMat1[0][1] * flWeight1 + boneMat2[0][1] * flWeight2; result[0][2] = boneMat0[0][2] * flWeight0 + boneMat1[0][2] * flWeight1 + boneMat2[0][2] * flWeight2; result[1][0] = boneMat0[1][0] * flWeight0 + boneMat1[1][0] * flWeight1 + boneMat2[1][0] * flWeight2; result[1][1] = boneMat0[1][1] * flWeight0 + boneMat1[1][1] * flWeight1 + boneMat2[1][1] * flWeight2; result[1][2] = boneMat0[1][2] * flWeight0 + boneMat1[1][2] * flWeight1 + boneMat2[1][2] * flWeight2; result[2][0] = boneMat0[2][0] * flWeight0 + boneMat1[2][0] * flWeight1 + boneMat2[2][0] * flWeight2; result[2][1] = boneMat0[2][1] * flWeight0 + boneMat1[2][1] * flWeight1 + boneMat2[2][1] * flWeight2; result[2][2] = boneMat0[2][2] * flWeight0 + boneMat1[2][2] * flWeight1 + boneMat2[2][2] * flWeight2; result[3][0] = boneMat0[3][0] * flWeight0 + boneMat1[3][0] * flWeight1 + boneMat2[3][0] * flWeight2; result[3][1] = boneMat0[3][1] * flWeight0 + boneMat1[3][1] * flWeight1 + boneMat2[3][1] * flWeight2; result[3][2] = boneMat0[3][2] * flWeight0 + boneMat1[3][2] * flWeight1 + boneMat2[3][2] * flWeight2; } else if( numbones == 2 ) { const matrix3x4 &boneMat0 = worldtransform[vertex->boneid[0]]; const matrix3x4 &boneMat1 = worldtransform[vertex->boneid[1]]; flWeight0 = vertex->weight[0] / 255.0f; flWeight1 = vertex->weight[1] / 255.0f; flTotal = flWeight0 + flWeight1; if( flTotal < 1.0f ) flWeight0 += 1.0f - flTotal; // compensate rounding error // NOTE: Inlining here seems to make a fair amount of difference result[0][0] = boneMat0[0][0] * flWeight0 + boneMat1[0][0] * flWeight1; result[0][1] = boneMat0[0][1] * flWeight0 + boneMat1[0][1] * flWeight1; result[0][2] = boneMat0[0][2] * flWeight0 + boneMat1[0][2] * flWeight1; result[1][0] = boneMat0[1][0] * flWeight0 + boneMat1[1][0] * flWeight1; result[1][1] = boneMat0[1][1] * flWeight0 + boneMat1[1][1] * flWeight1; result[1][2] = boneMat0[1][2] * flWeight0 + boneMat1[1][2] * flWeight1; result[2][0] = boneMat0[2][0] * flWeight0 + boneMat1[2][0] * flWeight1; result[2][1] = boneMat0[2][1] * flWeight0 + boneMat1[2][1] * flWeight1; result[2][2] = boneMat0[2][2] * flWeight0 + boneMat1[2][2] * flWeight1; result[3][0] = boneMat0[3][0] * flWeight0 + boneMat1[3][0] * flWeight1; result[3][1] = boneMat0[3][1] * flWeight0 + boneMat1[3][1] * flWeight1; result[3][2] = boneMat0[3][2] * flWeight0 + boneMat1[3][2] * flWeight1; } else { result = worldtransform[vertex->boneid[0]]; } } bool CStudioModelRenderer :: StudioSaveTBN( void ) { char szFilename[MAX_PATH]; char szModelname[MAX_PATH]; Q_strncpy( szModelname, RI->currentmodel->name + Q_strlen( "models/" ), sizeof( szModelname )); COM_StripExtension( szModelname ); Q_snprintf( szFilename, sizeof( szFilename ), "cache/%s.tbn", szModelname ); size_t tbn_size = sizeof( dmodeltbn_t ) + (m_tbnverts->numverts - 1) * sizeof( dvertmatrix_t ); if( SAVE_FILE( szFilename, m_tbnverts, tbn_size )) return true; ALERT( at_error, "StudioSaveCache: couldn't store %s\n", szFilename ); return false; } bool CStudioModelRenderer :: StudioLoadTBN( void ) { char szFilename[MAX_PATH]; char szModelname[MAX_PATH]; int length, iCompare; Q_strncpy( szModelname, RI->currentmodel->name + Q_strlen( "models/" ), sizeof( szModelname )); COM_StripExtension( szModelname ); Q_snprintf( szFilename, sizeof( szFilename ), "cache/%s.tbn", szModelname ); if( COMPARE_FILE_TIME( RI->currentmodel->name, szFilename, &iCompare )) { // MDL file is newer. if( iCompare > 0 ) return false; } else { return false; } byte *aMemFile = LOAD_FILE( szFilename, &length ); if( !aMemFile ) return false; // TBN is a footprint m_tbnverts = (dmodeltbn_t *)aMemFile; if( m_tbnverts->ident != IDTBNHEADER ) { ALERT( at_warning, "%s has wrong id (%x should be %x)\n", szFilename, m_tbnverts->ident, IDTBNHEADER ); FREE_FILE( aMemFile ); m_tbnverts = NULL; return false; } if( m_tbnverts->version != TBN_VERSION ) { ALERT( at_warning, "%s has wrong version (%i should be %i)\n", szFilename, m_tbnverts->version, TBN_VERSION ); FREE_FILE( aMemFile ); m_tbnverts = NULL; return false; } if( m_tbnverts->modelCRC != RI->currentmodel->modelCRC ) { ALERT( at_console, "%s was changed, TBN cache will be updated\n", szFilename ); FREE_FILE( aMemFile ); m_tbnverts = NULL; return false; } // all done, we can load TBN from disk m_iTBNState = TBNSTATE_LOADING; return true; } bool CStudioModelRenderer :: CalcLightmapAxis( mstudiosurface_t *surf, const dfacelight_t *fl, const dmodelfacelight_t *dfl ) { int ssize = dfl->texture_step; Vector mins, maxs, size, t1, t2; Vector planeNormal; Vector lmvecs[2]; int i, axis; ClearBounds( mins, maxs ); for( i = 0; i < 3; i++ ) AddPointToBounds( m_arrayverts[m_nNumTempVerts+i], mins, maxs ); // compute triangle normal t1 = m_arrayverts[m_nNumTempVerts+0] - m_arrayverts[m_nNumTempVerts+1]; t2 = m_arrayverts[m_nNumTempVerts+2] - m_arrayverts[m_nNumTempVerts+1]; planeNormal = CrossProduct( t1, t2 ); planeNormal = planeNormal.Normalize(); // round to the lightmap resolution for( i = 0; i < 3; i++ ) { mins[i] = ssize * floor( mins[i] / ssize ); maxs[i] = ssize * ceil( maxs[i] / ssize ); size[i] = (maxs[i] - mins[i]) / ssize; } // the two largest axis will be the lightmap size planeNormal.x = fabs( planeNormal.x ); planeNormal.y = fabs( planeNormal.y ); planeNormal.z = fabs( planeNormal.z ); lmvecs[0] = lmvecs[1] = g_vecZero; if( planeNormal.x >= planeNormal.y && planeNormal.x >= planeNormal.z ) { surf->lightextents[0] = size[1]; surf->lightextents[1] = size[2]; lmvecs[0][1] = 1.0 / ssize; lmvecs[1][2] = 1.0 / ssize; axis = 0; } else if( planeNormal.y >= planeNormal.x && planeNormal.y >= planeNormal.z ) { surf->lightextents[0] = size[0]; surf->lightextents[1] = size[2]; lmvecs[0][0] = 1.0 / ssize; lmvecs[1][2] = 1.0 / ssize; axis = 1; } else { surf->lightextents[0] = size[0]; surf->lightextents[1] = size[1]; lmvecs[0][0] = 1.0 / ssize; lmvecs[1][1] = 1.0 / ssize; axis = 2; } // bad normal? if( !planeNormal[axis] ) { // set lightmap number to avoid assertation surf->lightmaptexturenum = tr.current_lightmap_texture; return true; } if( surf->lightextents[0] > ( MAX_STUDIO_LIGHTMAP_SIZE - 1 )) { lmvecs[0] *= (float)(MAX_STUDIO_LIGHTMAP_SIZE - 1.0f) / surf->lightextents[0]; surf->lightextents[0] = MAX_STUDIO_LIGHTMAP_SIZE - 1; } if( surf->lightextents[1] > ( MAX_STUDIO_LIGHTMAP_SIZE - 1 )) { lmvecs[1] *= (float)(MAX_STUDIO_LIGHTMAP_SIZE - 1.0f) / surf->lightextents[1]; surf->lightextents[1] = MAX_STUDIO_LIGHTMAP_SIZE - 1; } memcpy( surf->styles, fl->styles, sizeof( surf->styles )); surf->texture_step = ssize; if( fl->lightofs != -1 ) { if( worldmodel->lightdata != NULL ) surf->samples = worldmodel->lightdata + (fl->lightofs/3); if( world->deluxedata != NULL ) surf->normals = world->deluxedata + (fl->lightofs/3); if( world->shadowdata != NULL ) surf->shadows = world->shadowdata + (fl->lightofs/3); } // calculate the world coordinates of the lightmap samples if( !GL_AllocLightmapForFace( surf )) return false; // page is full for( i = 0; i < 3; i++ ) { svert_t *v = &m_arrayxvert[m_nNumTempVerts+i]; Vector point = m_arrayverts[m_nNumTempVerts+i] - mins; R_LightmapCoords( surf, point, lmvecs, v->lmcoord0, 0 ); // styles 0-1 R_LightmapCoords( surf, point, lmvecs, v->lmcoord1, 2 ); // styles 2-3 } return true; } void CStudioModelRenderer :: AllocLightmapsForMesh( StudioMesh_t *pCurMesh, const dmodelfacelight_t *dfl ) { int start_light_faces = m_nNumLightFaces; int start_temp_verts = m_nNumTempVerts; bool lightmap_restarted = false; // should have the surface cache to store lightmap results if( !m_pStudioCache || !m_pStudioCache->surfaces ) return; if( !dfl || dfl->numfaces <= 0 ) return; lm_restart: pCurMesh->lightmapnum = tr.current_lightmap_texture; m_nNumLightFaces = start_light_faces; m_nNumTempVerts = start_temp_verts; // now vertexes are stored into intermediate array and we can using it to compute lightmatix for( int i = 0; i < (pCurMesh->numindices / 3); i++ ) { const dfacelight_t *fl = &dfl->faces[m_nNumLightFaces]; mstudiosurface_t *surf = &m_pStudioCache->surfaces[m_nNumLightFaces]; if( !CalcLightmapAxis( surf, fl, dfl )) { if( !lightmap_restarted ) { lightmap_restarted = true; goto lm_restart; } Msg( "%s: mesh %d failed to allocate lightmap\n", RI->currentmodel->name, pCurMesh - m_pTempMesh ); } ASSERT( surf->lightmaptexturenum == pCurMesh->lightmapnum ); // Msg( "face %d, lightofs %d, extents %d %d\n", m_nNumLightFaces, fl->lightofs, surf->lightextents[0], surf->lightextents[1] ); ASSERT( m_nNumTempVerts < MAXARRAYVERTS ); m_nNumTempVerts += 3; m_nNumLightFaces++; } } void CStudioModelRenderer :: SetupSubmodelVerts( const mstudiomodel_t *pSubModel, const matrix3x4 bones[], void *srclight, int lightmode ) { short *pskinref = (short *)((byte *)m_pStudioHeader + m_pStudioHeader->skinindex); // setup skinref for skin == 0 mstudiotexture_t *ptexture = (mstudiotexture_t *)((byte *)m_pStudioHeader + m_pStudioHeader->textureindex); mstudioboneweight_t *pvertweight = (mstudioboneweight_t *)((byte *)m_pStudioHeader + pSubModel->blendvertinfoindex); mstudioboneweight_t *pnormweight = (mstudioboneweight_t *)((byte *)m_pStudioHeader + pSubModel->blendnorminfoindex); mstudiobone_t *pbones = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); Vector *pstudioverts = (Vector *)((byte *)m_pStudioHeader + pSubModel->vertindex); Vector *pstudionorms = (Vector *)((byte *)m_pStudioHeader + pSubModel->normindex); bool has_vertlight = ( lightmode == LIGHTSTATIC_VERTEX ) ? true : false; bool has_surflight = ( lightmode == LIGHTSTATIC_SURFACE ) ? true : false; byte *pvertbone = ((byte *)m_pStudioHeader + pSubModel->vertinfoindex); byte *pnormbone = ((byte *)m_pStudioHeader + pSubModel->norminfoindex); mstudiomaterial_t *pmaterial = (mstudiomaterial_t *)RI->currentmodel->materials; static Vector localverts[MAXARRAYVERTS]; bool use_fan_sequence = false; bool smooth_tbn = false; dmodelvertlight_t *dvl = NULL; dmodelfacelight_t *dfl = NULL; int i, count; matrix3x4 skinMat; switch( lightmode ) { case LIGHTSTATIC_VERTEX: dvl = (dmodelvertlight_t *)srclight; break; case LIGHTSTATIC_SURFACE: dfl = (dmodelfacelight_t *)srclight; use_fan_sequence = true; break; } if( m_iTBNState == TBNSTATE_GENERATE || use_fan_sequence ) { // we need to build TBN in refrence pose to avoid seams if( RI->currentmodel->poseToBone ) { // compute weighted vertexes for( int i = 0; i < pSubModel->numverts; i++ ) { ComputeSkinMatrix( &pvertweight[i], bones, skinMat ); localverts[i] = skinMat.VectorTransform( pstudioverts[i] ); } } else { // compute unweighted vertexes for( int i = 0; i < pSubModel->numverts; i++ ) localverts[i] = bones[pvertbone[i]].VectorTransform( pstudioverts[i] ); } } memset( m_pTempMesh, 0 , sizeof( m_pTempMesh )); m_nNumArrayVerts = m_nNumArrayElems = 0; m_nNumTempVerts = 0; // build all the data for current submodel for( i = 0; i < pSubModel->nummesh; i++ ) { mstudiomesh_t *pmesh = (mstudiomesh_t *)((byte *)m_pStudioHeader + pSubModel->meshindex) + i; mstudiomaterial_t *pmaterial = &RI->currentmodel->materials[pskinref[pmesh->skinref]]; short *ptricmds = (short *)((byte *)m_pStudioHeader + pmesh->triindex); StudioMesh_t *pCurMesh = &m_pTempMesh[i]; // fill temp mesh info pCurMesh->firstvertex = m_nNumArrayVerts; pCurMesh->firstindex = m_nNumArrayElems; pCurMesh->lightmapnum = -1; pCurMesh->numvertices = 0; pCurMesh->numindices = 0; mstudiotexture_t *ptexture = pmaterial->pSource; float s = 1.0f / (float)ptexture->width; float t = 1.0f / (float)ptexture->height; // at least one of submodel mesh reqiuried smoothing if( FBitSet( ptexture->flags, STUDIO_NF_SMOOTH )) smooth_tbn = true; // first create trifan array from studiomodel mesh while( count = *( ptricmds++ )) { bool strip = ( count < 0 ) ? false : true; int vertexState = 0; if( count < 0 ) count = -count; for( ; count > 0; count--, ptricmds += 4 ) { ASSERT( m_nNumArrayVerts < MAXARRAYVERTS ); ASSERT( m_nNumArrayElems < MAXARRAYVERTS * 3 ); if( vertexState++ < 3 ) { m_arrayelems[m_nNumArrayElems++] = m_nNumArrayVerts; } else if( strip ) { // flip triangles between clockwise and counter clockwise if( vertexState & 1 ) { // draw triangle [n-2 n-1 n] m_arrayelems[m_nNumArrayElems++] = m_nNumArrayVerts - 2; m_arrayelems[m_nNumArrayElems++] = m_nNumArrayVerts - 1; m_arrayelems[m_nNumArrayElems++] = m_nNumArrayVerts; } else { // draw triangle [n-1 n-2 n] m_arrayelems[m_nNumArrayElems++] = m_nNumArrayVerts - 1; m_arrayelems[m_nNumArrayElems++] = m_nNumArrayVerts - 2; m_arrayelems[m_nNumArrayElems++] = m_nNumArrayVerts; } } else { // draw triangle fan [0 n-1 n] m_arrayelems[m_nNumArrayElems++] = m_nNumArrayVerts - ( vertexState - 1 ); m_arrayelems[m_nNumArrayElems++] = m_nNumArrayVerts - 1; m_arrayelems[m_nNumArrayElems++] = m_nNumArrayVerts; } // don't concat by matrix here - it's should be done on GPU m_arrayxvert[m_nNumArrayVerts].vertex = pstudioverts[ptricmds[0]]; m_arrayxvert[m_nNumArrayVerts].normal = pstudionorms[ptricmds[1]]; if( m_iTBNState == TBNSTATE_GENERATE || use_fan_sequence ) { // transformed vertices to build TBN m_arrayverts[m_nNumArrayVerts] = localverts[ptricmds[0]]; } if( m_iTBNState == TBNSTATE_LOADING && m_tbnverts != NULL ) { // loading TBN from cache m_arrayxvert[m_nNumArrayVerts].tangent = m_tbnverts->verts[m_nNumTBNVerts].tangent; m_arrayxvert[m_nNumArrayVerts].binormal = m_tbnverts->verts[m_nNumTBNVerts].binormal; m_arrayxvert[m_nNumArrayVerts].normal = m_tbnverts->verts[m_nNumTBNVerts].normal; m_nNumTBNVerts++; } if( dvl != NULL && dvl->numverts > 0 ) { dvertlight_t *vl = &dvl->verts[m_nNumLightVerts++]; // now setup light and deluxe vector for( int map = 0; map < MAXLIGHTMAPS; map++ ) { m_arrayxvert[m_nNumArrayVerts].light[map] = PackColor( vl->light[map] ); m_arrayxvert[m_nNumArrayVerts].deluxe[map] = PackColor( vl->deluxe[map] ); } } if( FBitSet( ptexture->flags, STUDIO_NF_CHROME )) { // probably always equal 64 (see studiomdl.c for details) m_arrayxvert[m_nNumArrayVerts].stcoord[0] = s; m_arrayxvert[m_nNumArrayVerts].stcoord[1] = t; } else if( FBitSet( ptexture->flags, STUDIO_NF_UV_COORDS )) { m_arrayxvert[m_nNumArrayVerts].stcoord[0] = HalfToFloat( ptricmds[2] ); m_arrayxvert[m_nNumArrayVerts].stcoord[1] = HalfToFloat( ptricmds[3] ); } else { m_arrayxvert[m_nNumArrayVerts].stcoord[0] = ptricmds[2] * s; m_arrayxvert[m_nNumArrayVerts].stcoord[1] = ptricmds[3] * t; } m_arrayxvert[m_nNumArrayVerts].lmcoord0[0] = 0.0f; m_arrayxvert[m_nNumArrayVerts].lmcoord0[1] = 0.0f; m_arrayxvert[m_nNumArrayVerts].lmcoord0[2] = 0.0f; m_arrayxvert[m_nNumArrayVerts].lmcoord0[3] = 0.0f; m_arrayxvert[m_nNumArrayVerts].lmcoord1[0] = 0.0f; m_arrayxvert[m_nNumArrayVerts].lmcoord1[1] = 0.0f; m_arrayxvert[m_nNumArrayVerts].lmcoord1[2] = 0.0f; m_arrayxvert[m_nNumArrayVerts].lmcoord1[3] = 0.0f; if( RI->currentmodel->poseToBone != NULL ) { mstudioboneweight_t *pCurWeight = &pvertweight[ptricmds[0]]; m_arrayxvert[m_nNumArrayVerts].boneid[0] = pCurWeight->bone[0]; m_arrayxvert[m_nNumArrayVerts].boneid[1] = pCurWeight->bone[1]; m_arrayxvert[m_nNumArrayVerts].boneid[2] = pCurWeight->bone[2]; m_arrayxvert[m_nNumArrayVerts].boneid[3] = pCurWeight->bone[3]; m_arrayxvert[m_nNumArrayVerts].weight[0] = pCurWeight->weight[0]; m_arrayxvert[m_nNumArrayVerts].weight[1] = pCurWeight->weight[1]; m_arrayxvert[m_nNumArrayVerts].weight[2] = pCurWeight->weight[2]; m_arrayxvert[m_nNumArrayVerts].weight[3] = pCurWeight->weight[3]; } else { m_arrayxvert[m_nNumArrayVerts].boneid[0] = pvertbone[ptricmds[0]]; m_arrayxvert[m_nNumArrayVerts].boneid[1] = -1; m_arrayxvert[m_nNumArrayVerts].boneid[2] = -1; m_arrayxvert[m_nNumArrayVerts].boneid[3] = -1; m_arrayxvert[m_nNumArrayVerts].weight[0] = 255; m_arrayxvert[m_nNumArrayVerts].weight[1] = 0; m_arrayxvert[m_nNumArrayVerts].weight[2] = 0; m_arrayxvert[m_nNumArrayVerts].weight[3] = 0; } m_nNumArrayVerts++; } } // store counts pCurMesh->numvertices = m_nNumArrayVerts - pCurMesh->firstvertex; pCurMesh->numindices = m_nNumArrayElems - pCurMesh->firstindex; } #if 0 if( lightmode == LIGHTSTATIC_SURFACE ) { Vector *normals = (Vector *)Mem_Alloc( m_nNumArrayVerts * sizeof( Vector )); for( int hashSize = 1; hashSize < m_nNumArrayVerts; hashSize <<= 1 ); hashSize = hashSize >> 2; // build a map from vertex to a list of triangles that share the vert. CUtlArray vertHashMap; vertHashMap.AddMultipleToTail( hashSize ); for( int vertID = 0; vertID < m_nNumArrayVerts; vertID++ ) { svert_t *v = &m_arrayxvert[vertID]; uint hash = VertexHashKey( v->vertex, hashSize ); vertHashMap[hash].AddToTail( vertID ); } for( int hashID = 0; hashID < hashSize; hashID++ ) { for( int i = 0; i < vertHashMap[hashID].Size(); i++ ) { int vertID = vertHashMap[hashID][i]; svert_t *v0 = &m_arrayxvert[vertID]; for( int j = 0; j < vertHashMap[hashID].Size(); j++ ) { svert_t *v1 = &m_arrayxvert[vertHashMap[hashID][j]]; if( !VectorCompareEpsilon( v0->vertex, v1->vertex, ON_EPSILON )) continue; if( DotProduct( v0->normal, v1->normal ) >= tr.smoothing_threshold ) VectorAdd( normals[vertID], v1->normal, normals[vertID] ); } } } // copy smoothed normals back for( i = 0; i < m_nNumArrayVerts; i++ ) m_arrayxvert[i].normal = normals[i].Normalize(); Mem_Free( normals ); } #endif if( use_fan_sequence ) { CUtlArray tempxvert; CUtlArray tempvert; // convert strips to fan sequences (that never exceeds MAXARRAYVERTS) for( i = 0; i < m_nNumArrayElems; i++ ) { tempxvert.AddToTail( m_arrayxvert[m_arrayelems[i]] ); tempvert.AddToTail( m_arrayverts[m_arrayelems[i]] ); } // also update mesh data for( i = 0; i < pSubModel->nummesh; i++ ) { StudioMesh_t *pCurMesh = &m_pTempMesh[i]; pCurMesh->firstvertex = pCurMesh->firstindex; pCurMesh->numvertices = pCurMesh->numindices; } // convert indexes for( i = 0; i < m_nNumArrayElems; i++ ) m_arrayelems[i] = i; ASSERT( tempxvert.Count() < MAXARRAYVERTS ); ASSERT( tempvert.Count() < MAXARRAYVERTS ); // copy unstripified vertexes back memcpy( m_arrayxvert, tempxvert.Base(), tempxvert.Count() * sizeof( svert_t )); memcpy( m_arrayverts, tempvert.Base(), tempvert.Count() * sizeof( Vector )); m_nNumArrayVerts = tempxvert.Count(); } if( dfl != NULL ) { // build all the data for current submodel for( i = 0; i < pSubModel->nummesh; i++ ) { StudioMesh_t *pCurMesh = &m_pTempMesh[i]; // allocate lightmaps for static meshes AllocLightmapsForMesh( pCurMesh, dfl ); } } // compute tangent space for all submodel meshes to avoid seams if( m_iTBNState == TBNSTATE_GENERATE ) { // build a map from vertex to a list of triangles that share the vert. CUtlArray vertToTriMap; vertToTriMap.AddMultipleToTail( m_nNumArrayVerts ); int triID; for( triID = 0; triID < (m_nNumArrayElems / 3); triID++ ) { vertToTriMap[m_arrayelems[triID*3+0]].AddToTail( triID ); vertToTriMap[m_arrayelems[triID*3+1]].AddToTail( triID ); vertToTriMap[m_arrayelems[triID*3+2]].AddToTail( triID ); } // calculate the tangent space for each triangle. CUtlArray triSVect, triTVect; triSVect.AddMultipleToTail( (m_nNumArrayElems / 3) ); triTVect.AddMultipleToTail( (m_nNumArrayElems / 3) ); float *v[3], *tc[3]; for( triID = 0; triID < (m_nNumArrayElems / 3); triID++ ) { for( int i = 0; i < 3; i++ ) { v[i] = (float *)&m_arrayverts[m_arrayelems[triID*3+i]]; // transformed to global pose to avoid seams tc[i] = (float *)&m_arrayxvert[m_arrayelems[triID*3+i]].stcoord; } CalcTBN( v[0], v[1], v[2], tc[0], tc[1], tc[2], triSVect[triID], triTVect[triID] ); } // calculate an average tangent space for each vertex. for( int vertID = 0; vertID < m_nNumArrayVerts; vertID++ ) { svert_t *v = &m_arrayxvert[vertID]; const Vector &normal = v->normal; Vector &finalSVect = v->tangent; Vector &finalTVect = v->binormal; Vector sVect, tVect; sVect = tVect = g_vecZero; for( triID = 0; triID < vertToTriMap[vertID].Size(); triID++ ) { sVect += triSVect[vertToTriMap[vertID][triID]]; tVect += triTVect[vertToTriMap[vertID][triID]]; } if( smooth_tbn ) { // smooth TBN Vector vertPos1 = m_arrayverts[vertID]; // transformed to global pose to avoid seams for( int vertID2 = 0; vertID2 < m_nNumArrayVerts; vertID2++ ) { if( vertID2 == vertID ) continue; Vector vertPos2 = m_arrayverts[vertID2]; // transformed to global pose to avoid seams if( vertPos1 == vertPos2 ) { for( int triID2 = 0; triID2 < vertToTriMap[vertID2].Size(); triID2++ ) { sVect += triSVect[vertToTriMap[vertID2][triID2]]; tVect += triTVect[vertToTriMap[vertID2][triID2]]; } } } } // rotate tangent and binormal back to bone space ComputeSkinMatrix( &m_arrayxvert[vertID], bones, skinMat ); sVect = skinMat.VectorIRotate( sVect ); tVect = skinMat.VectorIRotate( tVect ); if( !smooth_tbn ) { Vector tmpVect = CrossProduct( sVect, tVect ); bool leftHanded = DotProduct( tmpVect, normal ) < 0.0f; if( !leftHanded ) { tVect = CrossProduct( normal, sVect ); sVect = CrossProduct( tVect, normal ); } else { tVect = CrossProduct( sVect, normal ); sVect = CrossProduct( normal, tVect ); } } finalSVect = sVect.Normalize(); finalTVect = tVect.Normalize(); } // search for submodel offset int offset = (byte *)pSubModel - (byte *)m_pStudioHeader; int j; for( j = 0; j < MAXSTUDIOMODELS; j++ ) { if( m_tbnverts->submodels[j].submodel_offset == offset ) break; } // store vertex offset for bounds checking m_tbnverts->submodels[j].vertex_offset = m_nNumTBNVerts; // store precomputed TBN into our cache for( int i = 0; i < m_nNumArrayVerts; i++ ) { m_tbnverts->verts[m_nNumTBNVerts].tangent = m_arrayxvert[i].tangent; m_tbnverts->verts[m_nNumTBNVerts].binormal = m_arrayxvert[i].binormal; m_tbnverts->verts[m_nNumTBNVerts].normal = m_arrayxvert[i].normal; m_nNumTBNVerts++; } } } void CStudioModelRenderer :: MeshCreateBuffer( vbomesh_t *pOut, const mstudiomesh_t *pMesh, const StudioMesh_t *pMeshInfo, int lightmode ) { // FIXME: if various skinfamilies has different sizes then our texcoords probably will be invalid for pev->skin != 0 short *pskinref = (short *)((byte *)m_pStudioHeader + m_pStudioHeader->skinindex); // setup skinref for skin == 0 mstudiotexture_t *ptexture = (mstudiotexture_t *)((byte *)m_pStudioHeader + m_pStudioHeader->textureindex); mstudiomaterial_t *pmaterial = (mstudiomaterial_t *)RI->currentmodel->materials + pskinref[pMesh->skinref]; mstudiobone_t *pbones = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); bool has_bumpmap = FBitSet( pmaterial->flags, STUDIO_NF_NORMALMAP ) ? true : false; bool has_boneweights = ( RI->currentmodel->poseToBone != NULL ) ? true : false; bool has_vertexlight = ( lightmode == LIGHTSTATIC_VERTEX ) ? true : false; bool has_lightmap = ( lightmode == LIGHTSTATIC_SURFACE ) ? true : false; static uint arrayelems[MAXARRAYVERTS*3]; Vector mins, maxs; int i; pOut->skinref = pMesh->skinref; pOut->parentbone = 0xFF; ClearBounds( mins, maxs ); // we need to compute some things individually per mesh for( i = 0; i < pMeshInfo->numvertices; i++ ) { svert_t *vert = &m_arrayxvert[pMeshInfo->firstvertex + i]; AddPointToBounds( vert->vertex, mins, maxs ); int boneid = vert->boneid[0]; if( pOut->parentbone == 0xFF ) pOut->parentbone = boneid; // update bone if it was parent of current bone if( pOut->parentbone != boneid ) { for( int k = pOut->parentbone; k != -1; k = pbones[k].parent ) { if( k == boneid ) { pOut->parentbone = boneid; break; } } } } // remap indices to local range for( i = 0; i < pMeshInfo->numindices; i++ ) arrayelems[i] = m_arrayelems[pMeshInfo->firstindex + i] - pMeshInfo->firstvertex; pOut->mins = mins; pOut->maxs = maxs; pOut->lightmapnum = pMeshInfo->lightmapnum; pOut->numVerts = pMeshInfo->numvertices; pOut->numElems = pMeshInfo->numindices; GL_CheckVertexArrayBinding(); // determine optimal mesh loader uint attribs = ComputeAttribFlags( m_pStudioHeader->numbones, has_bumpmap, has_boneweights, has_vertexlight, has_lightmap ); uint type = SelectMeshLoader( m_pStudioHeader->numbones, has_bumpmap, has_boneweights, has_vertexlight, has_lightmap ); // move data to video memory if( glConfig.version < ACTUAL_GL_VERSION ) m_pfnMeshLoaderGL21[type].CreateBuffer( pOut, &m_arrayxvert[pMeshInfo->firstvertex] ); else m_pfnMeshLoaderGL30[type].CreateBuffer( pOut, &m_arrayxvert[pMeshInfo->firstvertex] ); CreateIndexBuffer( pOut, arrayelems ); // Msg( "%s -> %s\n", m_pfnMeshLoaderGL21[type].BufferName, RI->currentmodel->name ); // link it with vertex array object pglGenVertexArrays( 1, &pOut->vao ); pglBindVertexArray( pOut->vao ); if( glConfig.version < ACTUAL_GL_VERSION ) m_pfnMeshLoaderGL21[type].BindBuffer( pOut, attribs ); else m_pfnMeshLoaderGL30[type].BindBuffer( pOut, attribs ); BindIndexBuffer( pOut ); pglBindVertexArray( GL_FALSE ); // update stats tr.total_vbo_memory += pOut->cacheSize; } mstudiocache_t *CStudioModelRenderer :: CreateStudioCache( void *srclight, int lightmode ) { float start_time = Sys_DoubleTime(); bool unique_model = (srclight == NULL); // just for more readable code TmpModel_t submodel[MAXSTUDIOMODELS]; // list of unique models float poseparams[MAXSTUDIOPOSEPARAM]; static matrix3x4 bones[MAXSTUDIOBONES]; static Vector pos[MAXSTUDIOBONES]; static Vector4D q[MAXSTUDIOBONES]; int i, j, k, bufSize = 0; int num_submodels = 0; byte *buffer, *bufend; // simple bounds check dmodelvertlight_t *dvl = NULL; dmodelfacelight_t *dfl = NULL; mstudiocache_t *studiocache; mstudiobodyparts_t *pbodypart; mstudiomodel_t *psubmodel; msubmodel_t *pModel; mstudiobone_t *pbones; matrix3x4 root; switch( lightmode ) { case LIGHTSTATIC_VERTEX: dvl = (dmodelvertlight_t *)srclight; break; case LIGHTSTATIC_SURFACE: dfl = (dmodelfacelight_t *)srclight; break; } // materials goes first to determine bump if( unique_model ) LoadStudioMaterials (); else PrecacheStudioShaders (); // build default pose to build seamless TBN-space pbones = (mstudiobone_t *)((byte *)m_pStudioHeader + m_pStudioHeader->boneindex); m_boneSetup.SetStudioPointers( m_pStudioHeader, poseparams ); m_boneSetup.CalcDefaultPoseParameters( poseparams ); root.Identity(); // setup local bone matrices if( unique_model && FBitSet( m_pStudioHeader->flags, STUDIO_HAS_BONEINFO )) { // NOTE: extended boneinfo goes immediately after bones mstudioboneinfo_t *boneinfo = (mstudioboneinfo_t *)((byte *)pbones + m_pStudioHeader->numbones * sizeof( mstudiobone_t )); // alloc storage for bone array RI->currentmodel->poseToBone = (mposetobone_t *)Mem_Alloc( sizeof( mposetobone_t )); for( j = 0; j < m_pStudioHeader->numbones; j++ ) LoadLocalMatrix( j, &boneinfo[j] ); } if( lightmode == LIGHTSTATIC_SURFACE ) { root = matrix3x4( Vector( dfl->origin ), Vector( dfl->angles ), Vector( dfl->scale )); m_boneSetup.InitPose( pos, q ); m_boneSetup.AccumulatePose( NULL, pos, q, 0, 0.0f, 1.0f ); } else { // compute default pose with no anim m_boneSetup.InitPose( pos, q ); } for( i = 0; i < m_pStudioHeader->numbones; i++ ) { // initialize bonematrix matrix3x4 bonematrix = matrix3x4( pos[i], q[i] ); if( pbones[i].parent == -1 ) bones[i] = root.ConcatTransforms( bonematrix ); else bones[i] = bones[pbones[i].parent].ConcatTransforms( matrix3x4( pos[i], q[i] )); } if( RI->currentmodel->poseToBone != NULL ) { // convert bones into worldtransform for( i = 0; i < m_pStudioHeader->numbones; i++ ) bones[i] = bones[i].ConcatTransforms( RI->currentmodel->poseToBone->posetobone[i] ); } memset( submodel, 0, sizeof( submodel )); word meshUniqueID = 0; m_nNumTBNVerts = 0; // counting through all the submodels num_submodels = 0; // build list of unique submodels (by name) for( i = 0; i < m_pStudioHeader->numbodyparts; i++ ) { pbodypart = (mstudiobodyparts_t *)((byte *)m_pStudioHeader + m_pStudioHeader->bodypartindex) + i; for( j = 0; j < pbodypart->nummodels; j++ ) { psubmodel = (mstudiomodel_t *)((byte *)m_pStudioHeader + pbodypart->modelindex) + j; if( !psubmodel->nummesh ) continue; // blank submodel, ignore it for( k = 0; k < num_submodels; k++ ) { if( !Q_stricmp( submodel[k].name, psubmodel->name )) break; } // add new one if( k == num_submodels ) { Q_strncpy( submodel[k].name, psubmodel->name, sizeof( submodel[k].name )); submodel[k].pmodel = psubmodel; num_submodels++; } } } m_iTBNState = TBNSTATE_INACTIVE; // only models with bump-mapping is required to build TBN matrices if( FBitSet( m_pStudioHeader->flags, STUDIO_HAS_BUMP )) { if( !StudioLoadTBN( )) { int max_model_verts = 0; // multiplier 8 is a good enough to predict max vertices count for( i = 0; i < num_submodels; i++ ) max_model_verts += submodel[i].pmodel->numverts * 8; // reserve space for all the model verts m_tbnverts = (dmodeltbn_t *)calloc( sizeof( dmodeltbn_t ), max_model_verts ); m_tbnverts->ident = IDTBNHEADER; m_tbnverts->version = TBN_VERSION; m_tbnverts->modelCRC = RI->currentmodel->modelCRC; // store submodel offsets here for( i = 0; i < num_submodels; i++ ) { psubmodel = submodel[i].pmodel; m_tbnverts->submodels[i].submodel_offset = (byte *)psubmodel - (byte *)m_pStudioHeader; } m_iTBNState = TBNSTATE_GENERATE; } if( !unique_model && m_iTBNState == TBNSTATE_GENERATE ) ALERT( at_warning, "%s: generate TBN due loading light cache\n", RI->currentmodel->name ); } // compute cache size (include individual meshes) bufSize = sizeof( mstudiocache_t ) + sizeof( mbodypart_t ) * m_pStudioHeader->numbodyparts; for( i = 0; i < num_submodels; i++ ) bufSize += sizeof( msubmodel_t ) + sizeof( vbomesh_t ) * submodel[i].pmodel->nummesh; if( dfl != NULL && dfl->numfaces > 0 ) bufSize += sizeof( mstudiosurface_t ) * dfl->numfaces; buffer = (byte *)Mem_Alloc( bufSize ); bufend = buffer + bufSize; // setup pointers studiocache = (mstudiocache_t *)buffer; buffer += sizeof( mstudiocache_t ); m_pStudioCache = studiocache; if( dfl != NULL && dfl->numfaces > 0 ) { studiocache->surfaces = (mstudiosurface_t *)buffer; studiocache->numsurfaces = dfl->numfaces; buffer += sizeof( mstudiosurface_t ) * dfl->numfaces; } studiocache->bodyparts = (mbodypart_t *)buffer; buffer += sizeof( mbodypart_t ) * m_pStudioHeader->numbodyparts; studiocache->numbodyparts = m_pStudioHeader->numbodyparts; // begin to building submodels for( i = 0; i < num_submodels; i++ ) { psubmodel = submodel[i].pmodel; pModel = (msubmodel_t *)buffer; buffer += sizeof( msubmodel_t ); pModel->nummesh = psubmodel->nummesh; // setup meshes pModel->meshes = (vbomesh_t *)buffer; buffer += sizeof( vbomesh_t ) * psubmodel->nummesh; // sanity check for vertexlighting if( dvl != NULL && dvl->numverts > 0 ) { // search for submodel offset int offset = (byte *)psubmodel - (byte *)m_pStudioHeader; for( j = 0; j < MAXSTUDIOMODELS; j++ ) { if( dvl->submodels[j].submodel_offset == offset ) break; } ASSERT( j != MAXSTUDIOMODELS ); ASSERT( m_nNumLightVerts == dvl->submodels[j].vertex_offset ); } // sanity check for surfacelighting if( dfl != NULL && dfl->numfaces > 0 ) { // search for submodel offset int offset = (byte *)psubmodel - (byte *)m_pStudioHeader; for( j = 0; j < MAXSTUDIOMODELS; j++ ) { if( dfl->submodels[j].submodel_offset == offset ) break; } ASSERT( j != MAXSTUDIOMODELS ); ASSERT( m_nNumLightFaces == dfl->submodels[j].surface_offset ); } // sanity check for TBN matrix if( m_iTBNState == TBNSTATE_LOADING && m_tbnverts != NULL ) { // search for submodel offset int offset = (byte *)psubmodel - (byte *)m_pStudioHeader; for( j = 0; j < MAXSTUDIOMODELS; j++ ) { if( m_tbnverts->submodels[j].submodel_offset == offset ) break; } ASSERT( j != MAXSTUDIOMODELS ); ASSERT( m_nNumTBNVerts == m_tbnverts->submodels[j].vertex_offset ); } // setup all the vertices for a given submodel SetupSubmodelVerts( psubmodel, bones, srclight, lightmode ); for( j = 0; j < psubmodel->nummesh; j++ ) { mstudiomesh_t *pSrc = (mstudiomesh_t *)((byte *)m_pStudioHeader + psubmodel->meshindex) + j; vbomesh_t *pDst = &pModel->meshes[j]; MeshCreateBuffer( pDst, pSrc, &m_pTempMesh[j], lightmode ); pDst->uniqueID = meshUniqueID++; } submodel[i].pout = pModel; // store unique submodel } // and finally setup bodyparts for( i = 0; i < m_pStudioHeader->numbodyparts; i++ ) { pbodypart = (mstudiobodyparts_t *)((byte *)m_pStudioHeader + m_pStudioHeader->bodypartindex) + i; mbodypart_t *pBodyPart = &studiocache->bodyparts[i]; pBodyPart->base = pbodypart->base; pBodyPart->nummodels = pbodypart->nummodels; // setup pointers to unique models for( j = 0; j < pBodyPart->nummodels; j++ ) { psubmodel = (mstudiomodel_t *)((byte *)m_pStudioHeader + pbodypart->modelindex) + j; if( !psubmodel->nummesh ) continue; // blank submodel, leave null pointer // find supposed model for( k = 0; k < num_submodels; k++ ) { if( !Q_stricmp( submodel[k].name, psubmodel->name )) { pBodyPart->models[j] = submodel[k].pout; break; } } if( k == num_submodels ) ALERT( at_error, "Couldn't find submodel %s for bodypart %i\n", psubmodel->name, i ); } } // TBN are created, time to store it if( m_iTBNState == TBNSTATE_GENERATE && m_tbnverts != NULL ) { // store total vertices count m_tbnverts->numverts = m_nNumTBNVerts; if( StudioSaveTBN( )) ALERT( at_console, "%s: TBN build time %g secs\n", RI->currentmodel->name, Sys_DoubleTime() - start_time ); m_iTBNState = TBNSTATE_INACTIVE; free( m_tbnverts ); m_tbnverts = NULL; } else if( m_iTBNState == TBNSTATE_LOADING && m_tbnverts != NULL ) { m_iTBNState = TBNSTATE_INACTIVE; FREE_FILE( m_tbnverts ); m_tbnverts = NULL; } // load lightmaps m_pStudioCache->update_light = true; // invalidate m_pStudioCache = NULL; // bounds checking if( buffer != bufend ) { if( buffer > bufend ) ALERT( at_error, "CreateStudioCache: memory buffer overrun\n" ); else ALERT( at_error, "CreateStudioCache: memory buffer underrun\n" ); } return studiocache; } //----------------------------------------------------------------------------- // all the caches should be build before starting the new map //----------------------------------------------------------------------------- void CStudioModelRenderer :: CreateStudioCacheVL( const char *modelname, int cacheID ) { dvlightlump_t *vl = world->vertex_lighting; // first we need throw previous mesh DeleteStudioCache( &tr.vertex_light_cache[cacheID] ); if( world->vertex_lighting == NULL ) return; // for some reasons we missed this lump RI->currentmodel = IEngineStudio.Mod_ForName( modelname, false ); m_pStudioHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata( RI->currentmodel ); if( !RI->currentmodel || !m_pStudioHeader ) return; // download in progress? // first initialization if( cacheID < vl->nummodels && vl->dataofs[cacheID] != -1 ) { dmodelvertlight_t *dml = (dmodelvertlight_t *)((byte *)vl + vl->dataofs[cacheID]); m_nNumLightVerts = 0; if( RI->currentmodel->modelCRC == dml->modelCRC ) { // now create mesh per entity with instanced vertex lighting tr.vertex_light_cache[cacheID] = CreateStudioCache( dml, LIGHTSTATIC_VERTEX ); } if( dml->numverts == m_nNumLightVerts ) { ALERT( at_aiconsole, "%s vertexlit instance created\n", modelname ); } else if( RI->currentmodel->modelCRC != dml->modelCRC ) { ALERT( at_error, "%s failed to create vertex lighting: model CRC %p != %p\n", modelname, RI->currentmodel->modelCRC, dml->modelCRC ); } else { ALERT( at_error, "%s failed to create vertex lighting: model verts %i != total verts %i\n", modelname, dml->numverts, m_nNumLightVerts ); } m_nNumLightVerts = 0; } } void CStudioModelRenderer :: CreateStudioCacheFL( const char *modelname, int cacheID ) { dvlightlump_t *vl = world->surface_lighting; // first we need throw previous mesh DeleteStudioCache( &tr.surface_light_cache[cacheID] ); if( world->surface_lighting == NULL ) return; // for some reasons we missed this lump RI->currentmodel = IEngineStudio.Mod_ForName( modelname, false ); m_pStudioHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata( RI->currentmodel ); if( !RI->currentmodel || !m_pStudioHeader ) return; // download in progress? // first initialization if( cacheID < vl->nummodels && vl->dataofs[cacheID] != -1 ) { dmodelfacelight_t *dml = (dmodelfacelight_t *)((byte *)vl + vl->dataofs[cacheID]); m_nNumLightFaces = 0; if( RI->currentmodel->modelCRC == dml->modelCRC ) { // now create mesh per entity with instanced surface lighting tr.surface_light_cache[cacheID] = CreateStudioCache( dml, LIGHTSTATIC_SURFACE ); } if( dml->numfaces == m_nNumLightFaces ) { ALERT( at_aiconsole, "%s surfacelit instance created\n", modelname ); } else if( RI->currentmodel->modelCRC != dml->modelCRC ) { ALERT( at_error, "%s failed to create surface lighting: model CRC %p != %p\n", modelname, RI->currentmodel->modelCRC, dml->modelCRC ); } else { ALERT( at_error, "%s failed to create surface lighting: model faces %i != total faces %i\n", modelname, dml->numfaces, m_nNumLightFaces ); } m_nNumLightFaces = 0; } } void CStudioModelRenderer :: CreateStudioCacheVL( dmodelvertlight_t *dml, int cacheID ) { if( RI->currentmodel->modelCRC == dml->modelCRC ) { // get lighting cache m_pModelInstance->m_VlCache = tr.vertex_light_cache[cacheID]; } if( m_pModelInstance->m_VlCache != NULL ) { SetBits( m_pModelInstance->info_flags, MF_VERTEX_LIGHTING ); for( int map = 0; map < MAXLIGHTMAPS; map++ ) m_pModelInstance->styles[map] = dml->styles[map]; for( int i = 0; i < m_pStudioHeader->numtextures; i++ ) { mstudiomaterial_t *mat = &m_pModelInstance->materials[i]; mat->forwardScene.Invalidate(); // refresh shaders } } else { ALERT( at_warning, "failed to create vertex lighting for %s\n", RI->currentmodel->name ); SetBits( m_pModelInstance->info_flags, MF_VL_BAD_CACHE ); } } void CStudioModelRenderer :: CreateStudioCacheFL( dmodelfacelight_t *dml, int cacheID ) { if( RI->currentmodel->modelCRC == dml->modelCRC ) { // get lighting cache m_pModelInstance->m_FlCache = tr.surface_light_cache[cacheID]; } if( m_pModelInstance->m_FlCache != NULL ) { SetBits( m_pModelInstance->info_flags, MF_SURFACE_LIGHTING ); for( int map = 0; map < MAXLIGHTMAPS; map++ ) m_pModelInstance->styles[map] = dml->styles[map]; for( int i = 0; i < m_pStudioHeader->numtextures; i++ ) { mstudiomaterial_t *mat = &m_pModelInstance->materials[i]; mat->forwardScene.Invalidate(); // refresh shaders } } else { ALERT( at_warning, "failed to create surface lighting for %s\n", RI->currentmodel->name ); SetBits( m_pModelInstance->info_flags, MF_VL_BAD_CACHE ); } } //----------------------------------------------------------------------------- // all the caches should be build before starting the new map //----------------------------------------------------------------------------- void CStudioModelRenderer :: FreeStudioCacheVL( void ) { for( int i = 0; i < MAX_LIGHTCACHE; i++ ) DeleteStudioCache( &tr.vertex_light_cache[i] ); } void CStudioModelRenderer :: FreeStudioCacheFL( void ) { for( int i = 0; i < MAX_LIGHTCACHE; i++ ) DeleteStudioCache( &tr.surface_light_cache[i] ); } void CStudioModelRenderer :: ProcessUserData( model_t *mod, qboolean create, const byte *buffer ) { // to get something valid here RI->currententity = GET_ENTITY( 0 ); RI->currentmodel = mod; if( !( m_pStudioHeader = (studiohdr_t *)IEngineStudio.Mod_Extradata( RI->currentmodel ))) return; IEngineStudio.StudioSetHeader( m_pStudioHeader ); IEngineStudio.SetRenderModel( RI->currentmodel ); if( create ) { // compute model CRC to verify vertexlighting data // NOTE: source buffer is not equal to Mod_Extradata! studiohdr_t *src = (studiohdr_t *)buffer; RI->currentmodel->modelCRC = FILE_CRC32( buffer, src->length ); double start = Sys_DoubleTime(); RI->currentmodel->studiocache = CreateStudioCache(); double end = Sys_DoubleTime(); r_buildstats.create_buffer_object += (end - start); r_buildstats.total_buildtime += (end - start); } else { // release collision mesh if( mod->bodymesh != NULL ) { mod->bodymesh->CMeshDesc::~CMeshDesc(); Mem_Free( mod->bodymesh ); mod->bodymesh = NULL; } DestroyMeshCache(); } }