/* gl_alias.c - alias model renderer Copyright (C) 2017 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 "gl_local.h" #include "xash3d_mathlib.h" #include "const.h" #include "r_studioint.h" #include "triangleapi.h" #include "alias.h" #include "pm_local.h" #include "pmtrace.h" extern cvar_t r_shadows; typedef struct { double time; double frametime; int framecount; // alias framecount qboolean interpolate; float ambientlight; float shadelight; vec3_t lightvec; // averaging light direction vec3_t lightvec_local; // light direction in local space vec3_t lightspot; // shadow spot vec3_t lightcolor; // averaging lightcolor int oldpose; // shadow used int newpose; // shadow used float lerpfrac; // lerp frames } alias_draw_state_t; static alias_draw_state_t g_alias; // global alias state /* ================================================================= ALIAS MODEL DISPLAY LIST GENERATION ================================================================= */ static qboolean m_fDoRemap; static aliashdr_t *m_pAliasHeader; static trivertex_t *g_poseverts[MAXALIASFRAMES]; static dtriangle_t g_triangles[MAXALIASTRIS]; static stvert_t g_stverts[MAXALIASVERTS]; static int g_used[8192]; // a pose is a single set of vertexes. a frame may be // an animating sequence of poses int g_posenum; // the command list holds counts and s/t values that are valid for // every frame static int g_commands[8192]; static int g_numcommands; // all frames will have their vertexes rearranged and expanded // so they are in the order expected by the command list static int g_vertexorder[8192]; static int g_numorder; static int g_stripverts[128]; static int g_striptris[128]; static int g_stripcount; /* ==================== R_StudioInit ==================== */ void R_AliasInit( void ) { g_alias.interpolate = true; m_fDoRemap = false; } /* ================ StripLength ================ */ static int StripLength( int starttri, int startv ) { int m1, m2, j, k; dtriangle_t *last, *check; g_used[starttri] = 2; last = &g_triangles[starttri]; g_stripverts[0] = last->vertindex[(startv+0) % 3]; g_stripverts[1] = last->vertindex[(startv+1) % 3]; g_stripverts[2] = last->vertindex[(startv+2) % 3]; g_striptris[0] = starttri; g_stripcount = 1; m1 = last->vertindex[(startv+2)%3]; m2 = last->vertindex[(startv+1)%3]; nexttri: // look for a matching triangle for( j = starttri + 1, check = &g_triangles[starttri + 1]; j < m_pAliasHeader->numtris; j++, check++ ) { if( check->facesfront != last->facesfront ) continue; for( k = 0; k < 3; k++ ) { if( check->vertindex[k] != m1 ) continue; if( check->vertindex[(k+1) % 3] != m2 ) continue; // this is the next part of the fan // if we can't use this triangle, this tristrip is done if( g_used[j] ) goto done; // the new edge if( g_stripcount & 1 ) m2 = check->vertindex[(k+2) % 3]; else m1 = check->vertindex[(k+2) % 3]; g_stripverts[g_stripcount+2] = check->vertindex[(k+2) % 3]; g_striptris[g_stripcount] = j; g_stripcount++; g_used[j] = 2; goto nexttri; } } done: // clear the temp used flags for( j = starttri + 1; j < m_pAliasHeader->numtris; j++ ) { if( g_used[j] == 2 ) g_used[j] = 0; } return g_stripcount; } /* =========== FanLength =========== */ static int FanLength( int starttri, int startv ) { int m1, m2, j, k; dtriangle_t *last, *check; g_used[starttri] = 2; last = &g_triangles[starttri]; g_stripverts[0] = last->vertindex[(startv+0) % 3]; g_stripverts[1] = last->vertindex[(startv+1) % 3]; g_stripverts[2] = last->vertindex[(startv+2) % 3]; g_striptris[0] = starttri; g_stripcount = 1; m1 = last->vertindex[(startv+0) % 3]; m2 = last->vertindex[(startv+2) % 3]; nexttri: // look for a matching triangle for( j = starttri + 1, check = &g_triangles[starttri + 1]; j < m_pAliasHeader->numtris; j++, check++ ) { if( check->facesfront != last->facesfront ) continue; for( k = 0; k < 3; k++ ) { if( check->vertindex[k] != m1 ) continue; if( check->vertindex[(k+1) % 3] != m2 ) continue; // this is the next part of the fan // if we can't use this triangle, this tristrip is done if( g_used[j] ) goto done; // the new edge m2 = check->vertindex[(k+2) % 3]; g_stripverts[g_stripcount + 2] = m2; g_striptris[g_stripcount] = j; g_stripcount++; g_used[j] = 2; goto nexttri; } } done: // clear the temp used flags for( j = starttri + 1; j < m_pAliasHeader->numtris; j++ ) { if( g_used[j] == 2 ) g_used[j] = 0; } return g_stripcount; } /* ================ BuildTris Generate a list of trifans or strips for the model, which holds for all frames ================ */ void BuildTris( void ) { int len, bestlen, besttype = 0; int bestverts[1024]; int besttris[1024]; int type, startv; int i, j, k; float s, t; // // build tristrips // memset( g_used, 0, sizeof( g_used )); g_numcommands = 0; g_numorder = 0; for( i = 0; i < m_pAliasHeader->numtris; i++ ) { // pick an unused triangle and start the trifan if( g_used[i] ) continue; bestlen = 0; for( type = 0; type < 2; type++ ) { for( startv = 0; startv < 3; startv++ ) { if( type == 1 ) len = StripLength( i, startv ); else len = FanLength( i, startv ); if( len > bestlen ) { besttype = type; bestlen = len; for( j = 0; j < bestlen + 2; j++ ) bestverts[j] = g_stripverts[j]; for( j = 0; j < bestlen; j++ ) besttris[j] = g_striptris[j]; } } } // mark the tris on the best strip as used for( j = 0; j < bestlen; j++ ) g_used[besttris[j]] = 1; if( besttype == 1 ) g_commands[g_numcommands++] = (bestlen + 2); else g_commands[g_numcommands++] = -(bestlen + 2); for( j = 0; j < bestlen + 2; j++ ) { // emit a vertex into the reorder buffer k = bestverts[j]; g_vertexorder[g_numorder++] = k; // emit s/t coords into the commands stream s = g_stverts[k].s; t = g_stverts[k].t; if( !g_triangles[besttris[0]].facesfront && g_stverts[k].onseam ) s += m_pAliasHeader->skinwidth / 2; // on back side s = (s + 0.5f) / m_pAliasHeader->skinwidth; t = (t + 0.5f) / m_pAliasHeader->skinheight; // Carmack use floats and Valve use shorts here... *(float *)&g_commands[g_numcommands++] = s; *(float *)&g_commands[g_numcommands++] = t; } } g_commands[g_numcommands++] = 0; // end of list marker } /* ================ GL_MakeAliasModelDisplayLists ================ */ void GL_MakeAliasModelDisplayLists( model_t *m ) { trivertex_t *verts; int i, j; BuildTris( ); // save the data out m_pAliasHeader->poseverts = g_numorder; m_pAliasHeader->commands = Mem_Malloc( m->mempool, g_numcommands * 4 ); memcpy( m_pAliasHeader->commands, g_commands, g_numcommands * 4 ); m_pAliasHeader->posedata = Mem_Malloc( m->mempool, m_pAliasHeader->numposes * m_pAliasHeader->poseverts * sizeof( trivertex_t )); verts = m_pAliasHeader->posedata; for( i = 0; i < m_pAliasHeader->numposes; i++ ) { for( j = 0; j < g_numorder; j++ ) *verts++ = g_poseverts[i][g_vertexorder[j]]; } } /* ============================================================================== ALIAS MODELS ============================================================================== */ /* ================= Mod_LoadAliasFrame ================= */ void *Mod_LoadAliasFrame( void *pin, maliasframedesc_t *frame ) { daliasframe_t *pdaliasframe; trivertex_t *pinframe; int i; pdaliasframe = (daliasframe_t *)pin; Q_strncpy( frame->name, pdaliasframe->name, sizeof( frame->name )); frame->firstpose = g_posenum; frame->numposes = 1; for( i = 0; i < 3; i++ ) { frame->bboxmin.v[i] = pdaliasframe->bboxmin.v[i]; frame->bboxmax.v[i] = pdaliasframe->bboxmax.v[i]; } pinframe = (trivertex_t *)(pdaliasframe + 1); g_poseverts[g_posenum] = (trivertex_t *)pinframe; pinframe += m_pAliasHeader->numverts; g_posenum++; return (void *)pinframe; } /* ================= Mod_LoadAliasGroup ================= */ void *Mod_LoadAliasGroup( void *pin, maliasframedesc_t *frame ) { daliasgroup_t *pingroup; int i, numframes; daliasinterval_t *pin_intervals; void *ptemp; pingroup = (daliasgroup_t *)pin; numframes = pingroup->numframes; frame->firstpose = g_posenum; frame->numposes = numframes; for( i = 0; i < 3; i++ ) { frame->bboxmin.v[i] = pingroup->bboxmin.v[i]; frame->bboxmax.v[i] = pingroup->bboxmax.v[i]; } pin_intervals = (daliasinterval_t *)(pingroup + 1); // all the intervals are always equal 0.1 so we don't care about them frame->interval = pin_intervals->interval; pin_intervals += numframes; ptemp = (void *)pin_intervals; for( i = 0; i < numframes; i++ ) { g_poseverts[g_posenum] = (trivertex_t *)((daliasframe_t *)ptemp + 1); ptemp = (trivertex_t *)((daliasframe_t *)ptemp + 1) + m_pAliasHeader->numverts; g_posenum++; } return ptemp; } /* =============== Mod_CreateSkinData =============== */ rgbdata_t *Mod_CreateSkinData( model_t *mod, byte *data, int width, int height ) { static rgbdata_t skin; char name[MAX_QPATH]; int i; model_t *loadmodel = gEngfuncs.Mod_GetCurrentLoadingModel(); skin.width = width; skin.height = height; skin.depth = 1; skin.type = PF_INDEXED_24; skin.flags = IMAGE_HAS_COLOR|IMAGE_QUAKEPAL; skin.encode = DXT_ENCODE_DEFAULT; skin.numMips = 1; skin.buffer = data; skin.palette = (byte *)gEngfuncs.CL_GetPaletteColor( 0 ); skin.size = width * height; if( !gEngfuncs.Image_CustomPalette() ) { for( i = 0; i < skin.width * skin.height; i++ ) { if( data[i] > 224 && data[i] != 255 ) { SetBits( skin.flags, IMAGE_HAS_LUMA ); break; } } } COM_FileBase( loadmodel->name, name ); // for alias models only player can have remap textures if( mod != NULL && !Q_stricmp( name, "player" )) { texture_t *tx = NULL; int i, size; i = mod->numtextures; mod->textures = (texture_t **)Mem_Realloc( mod->mempool, mod->textures, ( i + 1 ) * sizeof( texture_t* )); size = width * height + 768; tx = Mem_Calloc( mod->mempool, sizeof( *tx ) + size ); mod->textures[i] = tx; Q_strncpy( tx->name, "DM_Skin", sizeof( tx->name )); tx->anim_min = SHIRT_HUE_START; // topcolor start tx->anim_max = SHIRT_HUE_END; // topcolor end // bottomcolor start always equal is (topcolor end + 1) tx->anim_total = PANTS_HUE_END;// bottomcolor end tx->width = width; tx->height = height; // the pixels immediately follow the structures memcpy( (tx+1), data, width * height ); memcpy( ((byte *)(tx+1)+(width * height)), skin.palette, 768 ); mod->numtextures++; // done } // make an copy return gEngfuncs.FS_CopyImage( &skin ); } void *Mod_LoadSingleSkin( daliasskintype_t *pskintype, int skinnum, int size ) { string name, lumaname; string checkname; rgbdata_t *pic; model_t *loadmodel = gEngfuncs.Mod_GetCurrentLoadingModel(); Q_snprintf( name, sizeof( name ), "%s:frame%i", loadmodel->name, skinnum ); Q_snprintf( lumaname, sizeof( lumaname ), "%s:luma%i", loadmodel->name, skinnum ); Q_snprintf( checkname, sizeof( checkname ), "%s_%i.tga", loadmodel->name, skinnum ); if( !gEngfuncs.FS_FileExists( checkname, false ) || ( pic = gEngfuncs.FS_LoadImage( checkname, NULL, 0 )) == NULL ) pic = Mod_CreateSkinData( loadmodel, (byte *)(pskintype + 1), m_pAliasHeader->skinwidth, m_pAliasHeader->skinheight ); m_pAliasHeader->gl_texturenum[skinnum][0] = m_pAliasHeader->gl_texturenum[skinnum][1] = m_pAliasHeader->gl_texturenum[skinnum][2] = m_pAliasHeader->gl_texturenum[skinnum][3] = GL_LoadTextureInternal( name, pic, 0 ); gEngfuncs.FS_FreeImage( pic ); if( R_GetTexture( m_pAliasHeader->gl_texturenum[skinnum][0] )->flags & TF_HAS_LUMA ) { pic = Mod_CreateSkinData( NULL, (byte *)(pskintype + 1), m_pAliasHeader->skinwidth, m_pAliasHeader->skinheight ); m_pAliasHeader->fb_texturenum[skinnum][0] = m_pAliasHeader->fb_texturenum[skinnum][1] = m_pAliasHeader->fb_texturenum[skinnum][2] = m_pAliasHeader->fb_texturenum[skinnum][3] = GL_LoadTextureInternal( lumaname, pic, TF_MAKELUMA ); gEngfuncs.FS_FreeImage( pic ); } return ((byte *)(pskintype + 1) + size); } void *Mod_LoadGroupSkin( daliasskintype_t *pskintype, int skinnum, int size ) { daliasskininterval_t *pinskinintervals; daliasskingroup_t *pinskingroup; string name, lumaname; rgbdata_t *pic; int i, j; model_t *loadmodel = gEngfuncs.Mod_GetCurrentLoadingModel(); // animating skin group. yuck. pskintype++; pinskingroup = (daliasskingroup_t *)pskintype; pinskinintervals = (daliasskininterval_t *)(pinskingroup + 1); pskintype = (void *)(pinskinintervals + pinskingroup->numskins); for( i = 0; i < pinskingroup->numskins; i++ ) { Q_snprintf( name, sizeof( name ), "%s_%i_%i", loadmodel->name, skinnum, i ); pic = Mod_CreateSkinData( loadmodel, (byte *)(pskintype), m_pAliasHeader->skinwidth, m_pAliasHeader->skinheight ); m_pAliasHeader->gl_texturenum[skinnum][i & 3] = GL_LoadTextureInternal( name, pic, 0 ); gEngfuncs.FS_FreeImage( pic ); if( R_GetTexture( m_pAliasHeader->gl_texturenum[skinnum][i & 3] )->flags & TF_HAS_LUMA ) { Q_snprintf( lumaname, sizeof( lumaname ), "%s_%i_%i_luma", loadmodel->name, skinnum, i ); pic = Mod_CreateSkinData( NULL, (byte *)(pskintype), m_pAliasHeader->skinwidth, m_pAliasHeader->skinheight ); m_pAliasHeader->fb_texturenum[skinnum][i & 3] = GL_LoadTextureInternal( lumaname, pic, TF_MAKELUMA ); gEngfuncs.FS_FreeImage( pic ); } pskintype = (daliasskintype_t *)((byte *)(pskintype) + size); } for( j = i; i < 4; i++ ) { m_pAliasHeader->gl_texturenum[skinnum][i & 3] = m_pAliasHeader->gl_texturenum[skinnum][i - j]; m_pAliasHeader->fb_texturenum[skinnum][i & 3] = m_pAliasHeader->fb_texturenum[skinnum][i - j]; } return pskintype; } /* =============== Mod_LoadAllSkins =============== */ void *Mod_LoadAllSkins( int numskins, daliasskintype_t *pskintype ) { int i, size; if( numskins < 1 || numskins > MAX_SKINS ) gEngfuncs.Host_Error( "Mod_LoadAliasModel: Invalid # of skins: %d\n", numskins ); size = m_pAliasHeader->skinwidth * m_pAliasHeader->skinheight; for( i = 0; i < numskins; i++ ) { if( pskintype->type == ALIAS_SKIN_SINGLE ) { pskintype = (daliasskintype_t *)Mod_LoadSingleSkin( pskintype, i, size ); } else { pskintype = (daliasskintype_t *)Mod_LoadGroupSkin( pskintype, i, size ); } } return (void *)pskintype; } //========================================================================= /* ================= Mod_CalcAliasBounds ================= */ void Mod_CalcAliasBounds( model_t *mod ) { int i, j, k; float radius; float dist; vec3_t v; ClearBounds( mod->mins, mod->maxs ); radius = 0.0f; // process verts for( i = 0; i < m_pAliasHeader->numposes; i++ ) { for( j = 0; j < m_pAliasHeader->numverts; j++ ) { for( k = 0; k < 3; k++ ) v[k] = g_poseverts[i][j].v[k] * m_pAliasHeader->scale[k] + m_pAliasHeader->scale_origin[k]; AddPointToBounds( v, mod->mins, mod->maxs ); dist = DotProduct( v, v ); if( radius < dist ) radius = dist; } } mod->radius = sqrt( radius ); } /* ================= Mod_LoadAliasModel ================= */ void Mod_LoadAliasModel( model_t *mod, const void *buffer, qboolean *loaded ) { daliashdr_t *pinmodel; stvert_t *pinstverts; dtriangle_t *pintriangles; daliasframetype_t *pframetype; daliasskintype_t *pskintype; int i, j, size; if( loaded ) *loaded = false; pinmodel = (daliashdr_t *)buffer; i = pinmodel->version; if( i != ALIAS_VERSION ) { gEngfuncs.Con_DPrintf( S_ERROR "%s has wrong version number (%i should be %i)\n", mod->name, i, ALIAS_VERSION ); return; } if( pinmodel->numverts <= 0 || pinmodel->numtris <= 0 || pinmodel->numframes <= 0 ) return; // how to possible is make that? mod->mempool = Mem_AllocPool( va( "^2%s^7", mod->name )); // allocate space for a working header, plus all the data except the frames, // skin and group info size = sizeof( aliashdr_t ) + (pinmodel->numframes - 1) * sizeof( maliasframedesc_t ); m_pAliasHeader = Mem_Calloc( mod->mempool, size ); mod->flags = pinmodel->flags; // share effects flags // endian-adjust and copy the data, starting with the alias model header m_pAliasHeader->boundingradius = pinmodel->boundingradius; m_pAliasHeader->numskins = pinmodel->numskins; m_pAliasHeader->skinwidth = pinmodel->skinwidth; m_pAliasHeader->skinheight = pinmodel->skinheight; m_pAliasHeader->numverts = pinmodel->numverts; m_pAliasHeader->numtris = pinmodel->numtris; m_pAliasHeader->numframes = pinmodel->numframes; if( m_pAliasHeader->numverts > MAXALIASVERTS ) { gEngfuncs.Con_DPrintf( S_ERROR "model %s has too many vertices\n", mod->name ); return; } m_pAliasHeader->size = pinmodel->size; // mod->synctype = pinmodel->synctype; mod->numframes = m_pAliasHeader->numframes; for( i = 0; i < 3; i++ ) { m_pAliasHeader->scale[i] = pinmodel->scale[i]; m_pAliasHeader->scale_origin[i] = pinmodel->scale_origin[i]; m_pAliasHeader->eyeposition[i] = pinmodel->eyeposition[i]; } // load the skins pskintype = (daliasskintype_t *)&pinmodel[1]; pskintype = Mod_LoadAllSkins( m_pAliasHeader->numskins, pskintype ); // load base s and t vertices pinstverts = (stvert_t *)pskintype; for( i = 0; i < m_pAliasHeader->numverts; i++ ) { g_stverts[i].onseam = pinstverts[i].onseam; g_stverts[i].s = pinstverts[i].s; g_stverts[i].t = pinstverts[i].t; } // load triangle lists pintriangles = (dtriangle_t *)&pinstverts[m_pAliasHeader->numverts]; for( i = 0; i < m_pAliasHeader->numtris; i++ ) { g_triangles[i].facesfront = pintriangles[i].facesfront; for( j = 0; j < 3; j++ ) g_triangles[i].vertindex[j] = pintriangles[i].vertindex[j]; } // load the frames pframetype = (daliasframetype_t *)&pintriangles[m_pAliasHeader->numtris]; g_posenum = 0; for( i = 0; i < m_pAliasHeader->numframes; i++ ) { aliasframetype_t frametype = pframetype->type; if( frametype == ALIAS_SINGLE ) pframetype = (daliasframetype_t *)Mod_LoadAliasFrame( pframetype + 1, &m_pAliasHeader->frames[i] ); else pframetype = (daliasframetype_t *)Mod_LoadAliasGroup( pframetype + 1, &m_pAliasHeader->frames[i] ); } m_pAliasHeader->numposes = g_posenum; Mod_CalcAliasBounds( mod ); mod->type = mod_alias; // build the draw lists GL_MakeAliasModelDisplayLists( mod ); // move the complete, relocatable alias model to the cache gEngfuncs.Mod_GetCurrentLoadingModel()->cache.data = m_pAliasHeader; if( loaded ) *loaded = true; // done } void Mod_AliasUnloadTextures( void *data ) { aliashdr_t *palias; int i, j; palias = data; if( !palias ) return; // already freed for( i = 0; i < MAX_SKINS; i++ ) { if( !palias->gl_texturenum[i][0] ) break; for( j = 0; j < 4; j++ ) { GL_FreeTexture( palias->gl_texturenum[i][j] ); GL_FreeTexture( palias->fb_texturenum[i][j] ); } } } /* ============================================================= ALIAS MODELS ============================================================= */ /* =============== R_AliasDynamicLight similar to R_StudioDynamicLight =============== */ void R_AliasDynamicLight( cl_entity_t *ent, alight_t *plight ) { movevars_t *mv = gEngfuncs.pfnGetMoveVars(); vec3_t lightDir, vecSrc, vecEnd; vec3_t origin, dist, finalLight; float add, radius, total; colorVec light; uint lnum; dlight_t *dl; if( !plight || !ent ) return; if( !RI.drawWorld || r_fullbright->value || FBitSet( ent->curstate.effects, EF_FULLBRIGHT )) { plight->shadelight = 0; plight->ambientlight = 192; VectorSet( plight->plightvec, 0.0f, 0.0f, -1.0f ); VectorSet( plight->color, 1.0f, 1.0f, 1.0f ); return; } // determine plane to get lightvalues from: ceil or floor if( FBitSet( ent->curstate.effects, EF_INVLIGHT )) VectorSet( lightDir, 0.0f, 0.0f, 1.0f ); else VectorSet( lightDir, 0.0f, 0.0f, -1.0f ); VectorCopy( ent->origin, origin ); VectorSet( vecSrc, origin[0], origin[1], origin[2] - lightDir[2] * 8.0f ); light.r = light.g = light.b = light.a = 0; if(( mv->skycolor_r + mv->skycolor_g + mv->skycolor_b ) != 0 ) { msurface_t *psurf = NULL; pmtrace_t trace; if( FBitSet( ENGINE_GET_PARM( PARM_FEATURES ), ENGINE_WRITE_LARGE_COORD )) { vecEnd[0] = origin[0] - mv->skyvec_x * 65536.0f; vecEnd[1] = origin[1] - mv->skyvec_y * 65536.0f; vecEnd[2] = origin[2] - mv->skyvec_z * 65536.0f; } else { vecEnd[0] = origin[0] - mv->skyvec_x * 8192.0f; vecEnd[1] = origin[1] - mv->skyvec_y * 8192.0f; vecEnd[2] = origin[2] - mv->skyvec_z * 8192.0f; } trace = gEngfuncs.CL_TraceLine( vecSrc, vecEnd, PM_STUDIO_IGNORE ); if( trace.ent > 0 ) psurf = gEngfuncs.EV_TraceSurface( trace.ent, vecSrc, vecEnd ); else psurf = gEngfuncs.EV_TraceSurface( 0, vecSrc, vecEnd ); if( psurf && FBitSet( psurf->flags, SURF_DRAWSKY )) { VectorSet( lightDir, mv->skyvec_x, mv->skyvec_y, mv->skyvec_z ); light.r = gEngfuncs.LightToTexGamma( bound( 0, mv->skycolor_r, 255 )); light.g = gEngfuncs.LightToTexGamma( bound( 0, mv->skycolor_g, 255 )); light.b = gEngfuncs.LightToTexGamma( bound( 0, mv->skycolor_b, 255 )); } } if(( light.r + light.g + light.b ) == 0 ) { colorVec gcolor; float grad[4]; VectorScale( lightDir, 2048.0f, vecEnd ); VectorAdd( vecEnd, vecSrc, vecEnd ); light = R_LightVec( vecSrc, vecEnd, g_alias.lightspot, g_alias.lightvec ); if( VectorIsNull( g_alias.lightvec )) { vecSrc[0] -= 16.0f; vecSrc[1] -= 16.0f; vecEnd[0] -= 16.0f; vecEnd[1] -= 16.0f; gcolor = R_LightVec( vecSrc, vecEnd, NULL, NULL ); grad[0] = ( gcolor.r + gcolor.g + gcolor.b ) / 768.0f; vecSrc[0] += 32.0f; vecEnd[0] += 32.0f; gcolor = R_LightVec( vecSrc, vecEnd, NULL, NULL ); grad[1] = ( gcolor.r + gcolor.g + gcolor.b ) / 768.0f; vecSrc[1] += 32.0f; vecEnd[1] += 32.0f; gcolor = R_LightVec( vecSrc, vecEnd, NULL, NULL ); grad[2] = ( gcolor.r + gcolor.g + gcolor.b ) / 768.0f; vecSrc[0] -= 32.0f; vecEnd[0] -= 32.0f; gcolor = R_LightVec( vecSrc, vecEnd, NULL, NULL ); grad[3] = ( gcolor.r + gcolor.g + gcolor.b ) / 768.0f; lightDir[0] = grad[0] - grad[1] - grad[2] + grad[3]; lightDir[1] = grad[1] + grad[0] - grad[2] - grad[3]; VectorNormalize( lightDir ); } else { VectorCopy( g_alias.lightvec, lightDir ); } } VectorSet( finalLight, light.r, light.g, light.b ); ent->cvFloorColor = light; total = Q_max( Q_max( light.r, light.g ), light.b ); if( total == 0.0f ) total = 1.0f; // scale lightdir by light intentsity VectorScale( lightDir, total, lightDir ); for( lnum = 0; lnum < MAX_DLIGHTS; lnum++ ) { dl = gEngfuncs.GetDynamicLight( lnum ); if( dl->die < g_alias.time || !r_dynamic->value ) continue; VectorSubtract( origin, dl->origin, dist ); radius = VectorLength( dist ); add = dl->radius - radius; if( add > 0.0f ) { total += add; if( radius > 1.0f ) VectorScale( dist, ( add / radius ), dist ); else VectorScale( dist, add, dist ); VectorAdd( lightDir, dist, lightDir ); finalLight[0] += gEngfuncs.LightToTexGamma( dl->color.r ) * ( add / 256.0f ) * 2.0f; finalLight[1] += gEngfuncs.LightToTexGamma( dl->color.g ) * ( add / 256.0f ) * 2.0f; finalLight[2] += gEngfuncs.LightToTexGamma( dl->color.b ) * ( add / 256.0f ) * 2.0f; } } VectorScale( lightDir, 0.9f, lightDir ); plight->shadelight = VectorLength( lightDir ); plight->ambientlight = total - plight->shadelight; total = Q_max( Q_max( finalLight[0], finalLight[1] ), finalLight[2] ); if( total > 0.0f ) { plight->color[0] = finalLight[0] * ( 1.0f / total ); plight->color[1] = finalLight[1] * ( 1.0f / total ); plight->color[2] = finalLight[2] * ( 1.0f / total ); } else VectorSet( plight->color, 1.0f, 1.0f, 1.0f ); if( plight->ambientlight > 128 ) plight->ambientlight = 128; if( plight->ambientlight + plight->shadelight > 255 ) plight->shadelight = 255 - plight->ambientlight; VectorNormalize2( lightDir, plight->plightvec ); } /* =============== R_AliasSetupLighting =============== */ void R_AliasSetupLighting( alight_t *plight ) { if( !m_pAliasHeader || !plight ) return; g_alias.ambientlight = plight->ambientlight; g_alias.shadelight = plight->shadelight; VectorCopy( plight->plightvec, g_alias.lightvec ); VectorCopy( plight->color, g_alias.lightcolor ); // transform back to local space Matrix4x4_VectorIRotate( RI.objectMatrix, g_alias.lightvec, g_alias.lightvec_local ); VectorNormalize( g_alias.lightvec_local ); } /* =============== R_AliasLighting =============== */ void R_AliasLighting( float *lv, const vec3_t normal ) { float illum = g_alias.ambientlight; float r, lightcos; lightcos = DotProduct( normal, g_alias.lightvec_local ); // -1 colinear, 1 opposite if( lightcos > 1.0f ) lightcos = 1.0f; illum += g_alias.shadelight; r = SHADE_LAMBERT; // do modified hemispherical lighting if( r <= 1.0f ) { r += 1.0f; lightcos = (( r - 1.0f ) - lightcos) / r; if( lightcos > 0.0f ) illum += g_alias.shadelight * lightcos; } else { lightcos = (lightcos + ( r - 1.0f )) / r; if( lightcos > 0.0f ) illum -= g_alias.shadelight * lightcos; } illum = Q_max( illum, 0.0f ); illum = Q_min( illum, 255.0f ); *lv = illum * (1.0f / 255.0f); } /* =============== R_AliasSetRemapColors =============== */ void R_AliasSetRemapColors( int newTop, int newBottom ) { gEngfuncs.CL_AllocRemapInfo( RI.currententity, RI.currentmodel, newTop, newBottom ); if( gEngfuncs.CL_GetRemapInfoForEntity( RI.currententity )) { gEngfuncs.CL_UpdateRemapInfo( RI.currententity, newTop, newBottom ); m_fDoRemap = true; } } /* ============= GL_DrawAliasFrame ============= */ void GL_DrawAliasFrame( aliashdr_t *paliashdr ) { float lv_tmp; trivertex_t *verts0; trivertex_t *verts1; vec3_t vert, norm; int *order; int count; verts0 = verts1 = paliashdr->posedata; verts0 += g_alias.oldpose * paliashdr->poseverts; verts1 += g_alias.newpose * paliashdr->poseverts; order = paliashdr->commands; while( 1 ) { // get the vertex count and primitive type count = *order++; if( !count ) break; // done if( count < 0 ) { pglBegin( GL_TRIANGLE_FAN ); count = -count; } else { pglBegin( GL_TRIANGLE_STRIP ); } do { // texture coordinates come from the draw list if( GL_Support( GL_ARB_MULTITEXTURE ) && glState.activeTMU > 0 ) { GL_MultiTexCoord2f( XASH_TEXTURE0, ((float *)order)[0], ((float *)order)[1] ); GL_MultiTexCoord2f( XASH_TEXTURE1, ((float *)order)[0], ((float *)order)[1] ); } else { pglTexCoord2f( ((float *)order)[0], ((float *)order)[1] ); } order += 2; VectorLerp( m_bytenormals[verts0->lightnormalindex], g_alias.lerpfrac, m_bytenormals[verts1->lightnormalindex], norm ); VectorNormalize( norm ); R_AliasLighting( &lv_tmp, norm ); pglColor4f( g_alias.lightcolor[0] * lv_tmp, g_alias.lightcolor[1] * lv_tmp, g_alias.lightcolor[2] * lv_tmp, tr.blend ); VectorLerp( verts0->v, g_alias.lerpfrac, verts1->v, vert ); pglVertex3fv( vert ); verts0++, verts1++; } while( --count ); pglEnd(); } } /* ============= GL_DrawAliasShadow ============= */ void GL_DrawAliasShadow( aliashdr_t *paliashdr ) { trivertex_t *verts0; trivertex_t *verts1; float vec_x, vec_y; vec3_t av, point; int *order; float height; int count; if( FBitSet( RI.currententity->curstate.effects, EF_NOSHADOW )) return; if( glState.stencilEnabled ) pglEnable( GL_STENCIL_TEST ); height = g_alias.lightspot[2] + 1.0f; vec_x = -g_alias.lightvec[0] * 8.0f; vec_y = -g_alias.lightvec[1] * 8.0f; r_stats.c_alias_polys += paliashdr->numtris; verts0 = verts1 = paliashdr->posedata; verts0 += g_alias.oldpose * paliashdr->poseverts; verts1 += g_alias.newpose * paliashdr->poseverts; order = paliashdr->commands; while( 1 ) { // get the vertex count and primitive type count = *order++; if( !count ) break; // done if( count < 0 ) { pglBegin( GL_TRIANGLE_FAN ); count = -count; } else { pglBegin( GL_TRIANGLE_STRIP ); } do { // texture coordinates come from the draw list // (skipped for shadows) pglTexCoord2fv ((float *)order); order += 2; // normals and vertexes come from the frame list VectorLerp( verts0->v, g_alias.lerpfrac, verts1->v, av ); point[0] = av[0] * paliashdr->scale[0] + paliashdr->scale_origin[0]; point[1] = av[1] * paliashdr->scale[1] + paliashdr->scale_origin[1]; point[2] = av[2] * paliashdr->scale[2] + paliashdr->scale_origin[2]; Matrix3x4_VectorTransform( RI.objectMatrix, point, av ); point[0] = av[0] - (vec_x * ( av[2] - g_alias.lightspot[2] )); point[1] = av[1] - (vec_y * ( av[2] - g_alias.lightspot[2] )); point[2] = g_alias.lightspot[2] + 1.0f; pglVertex3fv( point ); verts0++, verts1++; } while( --count ); pglEnd(); } if( glState.stencilEnabled ) pglDisable( GL_STENCIL_TEST ); } /* ==================== R_AliasLerpMovement ==================== */ void R_AliasLerpMovement( cl_entity_t *e ) { float f = 1.0f; // don't do it if the goalstarttime hasn't updated in a while. // NOTE: Because we need to interpolate multiplayer characters, the interpolation time limit // was increased to 1.0 s., which is 2x the max lag we are accounting for. if( g_alias.interpolate && ( g_alias.time < e->curstate.animtime + 1.0f ) && ( e->curstate.animtime != e->latched.prevanimtime )) f = ( g_alias.time - e->curstate.animtime ) / ( e->curstate.animtime - e->latched.prevanimtime ); if( ENGINE_GET_PARM( PARM_PLAYING_DEMO ) == DEMO_QUAKE1 ) f = f + 1.0f; g_alias.lerpfrac = bound( 0.0f, f, 1.0f ); if( e->player || e->curstate.movetype != MOVETYPE_STEP ) return; // monsters only // Con_Printf( "%4.2f %.2f %.2f\n", f, e->curstate.animtime, g_alias.time ); VectorLerp( e->latched.prevorigin, f, e->curstate.origin, e->origin ); if( !VectorCompareEpsilon( e->curstate.angles, e->latched.prevangles, ON_EPSILON )) { vec4_t q, q1, q2; AngleQuaternion( e->curstate.angles, q1, false ); AngleQuaternion( e->latched.prevangles, q2, false ); QuaternionSlerp( q2, q1, f, q ); QuaternionAngle( q, e->angles ); } else VectorCopy( e->curstate.angles, e->angles ); // NOTE: this completely over control about angles and don't broke interpolation if( FBitSet( e->model->flags, ALIAS_ROTATE )) e->angles[1] = anglemod( 100.0f * g_alias.time ); } /* ================= R_SetupAliasFrame ================= */ void R_SetupAliasFrame( cl_entity_t *e, aliashdr_t *paliashdr ) { int newpose, oldpose; int newframe, oldframe; int numposes, cycle; float interval; oldframe = e->latched.prevframe; newframe = e->curstate.frame; if( newframe < 0 ) { newframe = 0; } else if( newframe >= paliashdr->numframes ) { if( newframe > paliashdr->numframes ) gEngfuncs.Con_Reportf( S_WARN "R_GetAliasFrame: no such frame %d (%s)\n", newframe, e->model->name ); newframe = paliashdr->numframes - 1; } if(( oldframe >= paliashdr->numframes ) || ( oldframe < 0 )) oldframe = newframe; numposes = paliashdr->frames[newframe].numposes; if( numposes > 1 ) { oldpose = newpose = paliashdr->frames[newframe].firstpose; interval = 1.0f / paliashdr->frames[newframe].interval; cycle = (int)(g_alias.time * interval); oldpose += (cycle + 0) % numposes; // lerpframe from newpose += (cycle + 1) % numposes; // lerpframe to g_alias.lerpfrac = ( g_alias.time * interval ); g_alias.lerpfrac -= (int)g_alias.lerpfrac; } else { oldpose = paliashdr->frames[oldframe].firstpose; newpose = paliashdr->frames[newframe].firstpose; } g_alias.oldpose = oldpose; g_alias.newpose = newpose; GL_DrawAliasFrame( paliashdr ); } /* =============== R_StudioDrawAbsBBox =============== */ static void R_AliasDrawAbsBBox( cl_entity_t *e, const vec3_t absmin, const vec3_t absmax ) { vec3_t p[8]; int i; // looks ugly, skip if( r_drawentities->value != 5 || e == gEngfuncs.GetViewModel() ) return; // compute a full bounding box for( i = 0; i < 8; i++ ) { p[i][0] = ( i & 1 ) ? absmin[0] : absmax[0]; p[i][1] = ( i & 2 ) ? absmin[1] : absmax[1]; p[i][2] = ( i & 4 ) ? absmin[2] : absmax[2]; } GL_Bind( XASH_TEXTURE0, tr.whiteTexture ); TriColor4f( 0.5f, 0.5f, 1.0f, 0.5f ); TriRenderMode( kRenderTransAdd ); pglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); TriBegin( TRI_QUADS ); for( i = 0; i < 6; i++ ) { TriBrightness( g_alias.shadelight / 255.0f ); TriVertex3fv( p[boxpnt[i][0]] ); TriVertex3fv( p[boxpnt[i][1]] ); TriVertex3fv( p[boxpnt[i][2]] ); TriVertex3fv( p[boxpnt[i][3]] ); } TriEnd(); TriRenderMode( kRenderNormal ); } static void R_AliasDrawLightTrace( cl_entity_t *e ) { if( r_drawentities->value == 7 ) { vec3_t origin; pglDisable( GL_TEXTURE_2D ); pglDisable( GL_DEPTH_TEST ); pglBegin( GL_LINES ); pglColor3f( 1, 0.5, 0 ); pglVertex3fv( e->origin ); pglVertex3fv( g_alias.lightspot ); pglEnd(); pglBegin( GL_LINES ); pglColor3f( 0, 0.5, 1 ); VectorMA( g_alias.lightspot, -64.0f, g_alias.lightvec, origin ); pglVertex3fv( g_alias.lightspot ); pglVertex3fv( origin ); pglEnd(); pglPointSize( 5.0f ); pglColor3f( 1, 0, 0 ); pglBegin( GL_POINTS ); pglVertex3fv( g_alias.lightspot ); pglEnd(); pglPointSize( 1.0f ); pglEnable( GL_DEPTH_TEST ); pglEnable( GL_TEXTURE_2D ); } } /* ================ R_AliasSetupTimings init current time for a given model ================ */ static void R_AliasSetupTimings( void ) { if( RI.drawWorld ) { // synchronize with server time g_alias.time = gpGlobals->time; } else { // menu stuff g_alias.time = gpGlobals->realtime; } m_fDoRemap = false; } /* ================= R_DrawAliasModel ================= */ void R_DrawAliasModel( cl_entity_t *e ) { model_t *clmodel; vec3_t absmin, absmax; remap_info_t *pinfo = NULL; int anim, skin; alight_t lighting; player_info_t *playerinfo; vec3_t dir, angles; clmodel = RI.currententity->model; VectorAdd( e->origin, clmodel->mins, absmin ); VectorAdd( e->origin, clmodel->maxs, absmax ); if( R_CullModel( e, absmin, absmax )) return; // // locate the proper data // m_pAliasHeader = (aliashdr_t *)gEngfuncs.Mod_Extradata( mod_alias, RI.currententity->model ); if( !m_pAliasHeader ) return; // init time R_AliasSetupTimings(); // angles will be modify below keep original VectorCopy( e->angles, angles ); R_AliasLerpMovement( e ); if( !FBitSet( ENGINE_GET_PARM( PARM_FEATURES ), ENGINE_COMPENSATE_QUAKE_BUG )) e->angles[PITCH] = -e->angles[PITCH]; // stupid quake bug // don't rotate clients, only aim if( e->player ) e->angles[PITCH] = 0.0f; // // get lighting information // lighting.plightvec = dir; R_AliasDynamicLight( e, &lighting ); r_stats.c_alias_polys += m_pAliasHeader->numtris; r_stats.c_alias_models_drawn++; // // draw all the triangles // R_RotateForEntity( e ); // model and frame independant R_AliasSetupLighting( &lighting ); GL_SetRenderMode( e->curstate.rendermode ); // setup remapping only for players if( e->player && ( playerinfo = pfnPlayerInfo( e->curstate.number - 1 )) != NULL ) { // get remap colors int topcolor = bound( 0, playerinfo->topcolor, 13 ); int bottomcolor = bound( 0, playerinfo->bottomcolor, 13 ); R_AliasSetRemapColors( topcolor, bottomcolor ); } if( tr.fFlipViewModel ) { pglTranslatef( m_pAliasHeader->scale_origin[0], -m_pAliasHeader->scale_origin[1], m_pAliasHeader->scale_origin[2] ); pglScalef( m_pAliasHeader->scale[0], -m_pAliasHeader->scale[1], m_pAliasHeader->scale[2] ); } else { pglTranslatef( m_pAliasHeader->scale_origin[0], m_pAliasHeader->scale_origin[1], m_pAliasHeader->scale_origin[2] ); pglScalef( m_pAliasHeader->scale[0], m_pAliasHeader->scale[1], m_pAliasHeader->scale[2] ); } anim = (int)(g_alias.time * 10) & 3; skin = bound( 0, RI.currententity->curstate.skin, m_pAliasHeader->numskins - 1 ); if( m_fDoRemap ) pinfo = gEngfuncs.CL_GetRemapInfoForEntity( e ); if( r_lightmap->value && !r_fullbright->value ) GL_Bind( XASH_TEXTURE0, tr.whiteTexture ); else if( pinfo != NULL && pinfo->textures[skin] != 0 ) GL_Bind( XASH_TEXTURE0, pinfo->textures[skin] ); // FIXME: allow remapping for skingroups someday else { GL_Bind( XASH_TEXTURE0, m_pAliasHeader->gl_texturenum[skin][anim] ); if( FBitSet( R_GetTexture( m_pAliasHeader->gl_texturenum[skin][anim] )->flags, TF_HAS_ALPHA )) { pglEnable( GL_ALPHA_TEST ); pglAlphaFunc( GL_GREATER, 0.0f ); tr.blend = 1.0f; } } pglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); if( GL_Support( GL_ARB_MULTITEXTURE ) && m_pAliasHeader->fb_texturenum[skin][anim] ) { GL_Bind( XASH_TEXTURE1, m_pAliasHeader->fb_texturenum[skin][anim] ); pglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_ADD ); } pglShadeModel( GL_SMOOTH ); R_SetupAliasFrame( e, m_pAliasHeader ); if( GL_Support( GL_ARB_MULTITEXTURE ) && m_pAliasHeader->fb_texturenum[skin][anim] ) GL_CleanUpTextureUnits( 1 ); pglShadeModel( GL_FLAT ); R_LoadIdentity(); // get lerped origin VectorAdd( e->origin, clmodel->mins, absmin ); VectorAdd( e->origin, clmodel->maxs, absmax ); R_AliasDrawAbsBBox( e, absmin, absmax ); R_AliasDrawLightTrace( e ); pglTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE ); pglAlphaFunc( GL_GREATER, DEFAULT_ALPHATEST ); pglDisable( GL_ALPHA_TEST ); if( r_shadows.value ) { // need to compute transformation matrix Matrix4x4_CreateFromEntity( RI.objectMatrix, e->angles, e->origin, 1.0f ); pglDisable( GL_TEXTURE_2D ); pglBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); pglEnable( GL_BLEND ); pglColor4f( 0.0f, 0.0f, 0.0f, 0.5f ); pglDepthFunc( GL_LESS ); GL_DrawAliasShadow( m_pAliasHeader ); pglDepthFunc( GL_LEQUAL ); pglEnable( GL_TEXTURE_2D ); pglDisable( GL_BLEND ); pglColor4f( 1.0f, 1.0f, 1.0f, 1.0f ); R_LoadIdentity(); } // restore original angles VectorCopy( angles, e->angles ); } //==================================================================================