forked from a1batross/Paranoia2_original
595 lines
15 KiB
C++
595 lines
15 KiB
C++
/*
|
|
gl_sprite.cpp - sprite model rendering
|
|
Copyright (C) 2011 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 <mathlib.h>
|
|
#include "gl_sprite.h"
|
|
#include "gl_studio.h"
|
|
#include "event_api.h"
|
|
#include "pm_defs.h"
|
|
#include "studio.h"
|
|
#include "triangleapi.h"
|
|
|
|
CSpriteModelRenderer g_SpriteRenderer;
|
|
|
|
/*
|
|
====================
|
|
Init
|
|
|
|
====================
|
|
*/
|
|
void CSpriteModelRenderer :: Init( void )
|
|
{
|
|
// Set up some variables shared with engine
|
|
m_pCvarLerping = IEngineStudio.GetCvar( "r_sprite_lerping" );
|
|
m_pCvarLighting = IEngineStudio.GetCvar( "r_sprite_lighting" );
|
|
}
|
|
|
|
/*
|
|
====================
|
|
CSpriteModelRenderer
|
|
|
|
====================
|
|
*/
|
|
CSpriteModelRenderer :: CSpriteModelRenderer( void )
|
|
{
|
|
m_pCurrentEntity = NULL;
|
|
m_pCvarLerping = NULL;
|
|
m_pCvarLighting = NULL;
|
|
m_pSpriteHeader = NULL;
|
|
m_pRenderModel = NULL;
|
|
}
|
|
|
|
CSpriteModelRenderer :: ~CSpriteModelRenderer( void )
|
|
{
|
|
}
|
|
|
|
/*
|
|
================
|
|
GetSpriteFrame
|
|
|
|
================
|
|
*/
|
|
mspriteframe_t *CSpriteModelRenderer :: GetSpriteFrame( int frame, float yaw )
|
|
{
|
|
mspritegroup_t *pspritegroup;
|
|
mspriteframe_t *pspriteframe = NULL;
|
|
|
|
if( frame < 0 )
|
|
{
|
|
frame = 0;
|
|
}
|
|
else if( frame >= m_pSpriteHeader->numframes )
|
|
{
|
|
ALERT( at_warning, "R_GetSpriteFrame: no such frame %d (%s)\n", frame, m_pRenderModel->name );
|
|
frame = m_pSpriteHeader->numframes - 1;
|
|
}
|
|
|
|
if( m_pSpriteHeader->frames[frame].type == SPR_SINGLE )
|
|
{
|
|
pspriteframe = (mspriteframe_t *)m_pSpriteHeader->frames[frame].frameptr;
|
|
}
|
|
else if( m_pSpriteHeader->frames[frame].type == SPR_GROUP )
|
|
{
|
|
pspritegroup = (mspritegroup_t *)m_pSpriteHeader->frames[frame].frameptr;
|
|
|
|
float *pintervals = pspritegroup->intervals;
|
|
int numframes = pspritegroup->numframes;
|
|
float fullinterval = pintervals[numframes-1];
|
|
|
|
// when loading in Mod_LoadSpriteGroup, we guaranteed all interval values
|
|
// are positive, so we don't have to worry about division by zero
|
|
float targettime = m_clTime - ((int)( m_clTime / fullinterval )) * fullinterval;
|
|
|
|
for( int i = 0; i < (numframes - 1); i++ )
|
|
{
|
|
if( pintervals[i] > targettime )
|
|
break;
|
|
}
|
|
pspriteframe = pspritegroup->frames[i];
|
|
}
|
|
else if( m_pSpriteHeader->frames[frame].type == FRAME_ANGLED )
|
|
{
|
|
int angleframe = (int)(Q_rint(( RI->view.angles[YAW] - yaw + 45.0f ) / 360 * 8 ) - 4) & 7;
|
|
|
|
// doom-style sprite monsters
|
|
pspritegroup = (mspritegroup_t *)m_pSpriteHeader->frames[frame].frameptr;
|
|
pspriteframe = pspritegroup->frames[angleframe];
|
|
}
|
|
|
|
return pspriteframe;
|
|
}
|
|
|
|
/*
|
|
================
|
|
CSpriteModelRenderer
|
|
|
|
Compute renderer origin (include parent movement, sky movement, etc)
|
|
================
|
|
*/
|
|
void CSpriteModelRenderer :: SpriteComputeOrigin( cl_entity_t *e )
|
|
{
|
|
sprite_origin = e->origin; // set render origin
|
|
|
|
// link sprite with parent (if present)
|
|
if( e->curstate.aiment > 0 && e->curstate.movetype == MOVETYPE_FOLLOW )
|
|
{
|
|
cl_entity_t *parent = GET_ENTITY( e->curstate.aiment );
|
|
|
|
if( parent && parent->model )
|
|
{
|
|
if( parent->model->type == mod_studio && e->curstate.body > 0 )
|
|
{
|
|
int num = bound( 1, e->curstate.body, MAXSTUDIOATTACHMENTS );
|
|
sprite_origin = R_StudioAttachmentPos( parent, num - 1 );
|
|
}
|
|
else
|
|
{
|
|
sprite_origin = parent->origin;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( e->curstate.renderfx == SKYBOX_ENTITY )
|
|
{
|
|
Vector trans = GetVieworg() - tr.sky_origin;
|
|
|
|
if( tr.sky_speed )
|
|
{
|
|
trans = trans - (GetVieworg() - tr.sky_world_origin) / tr.sky_speed;
|
|
Vector skypos = tr.sky_origin + (GetVieworg() - tr.sky_world_origin) / tr.sky_speed;
|
|
tr.modelorg = skypos - sprite_origin;
|
|
}
|
|
else
|
|
{
|
|
tr.modelorg = tr.sky_origin - sprite_origin;
|
|
}
|
|
|
|
sprite_origin += trans; // move to the sky position
|
|
}
|
|
else
|
|
{
|
|
tr.modelorg = sprite_origin;
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
SspriteComputeBBox
|
|
|
|
Compute a full bounding box for reference
|
|
================
|
|
*/
|
|
void CSpriteModelRenderer :: SpriteComputeBBox( cl_entity_t *e, Vector bbox[8] )
|
|
{
|
|
// always compute origin first
|
|
if( bbox != NULL ) SpriteComputeOrigin( e );
|
|
|
|
// copy original bbox (no rotation for sprites)
|
|
sprite_absmin = e->model->mins;
|
|
sprite_absmax = e->model->maxs;
|
|
|
|
float scale = 1.0f;
|
|
|
|
if( e->curstate.scale > 0.0f )
|
|
scale = e->curstate.scale;
|
|
|
|
sprite_absmin *= scale;
|
|
sprite_absmax *= scale;
|
|
|
|
sprite_absmin += sprite_origin;
|
|
sprite_absmax += sprite_origin;
|
|
|
|
// compute a full bounding box
|
|
for( int i = 0; bbox != NULL && i < 8; i++ )
|
|
{
|
|
bbox[i][0] = ( i & 1 ) ? sprite_absmin[0] : sprite_absmax[0];
|
|
bbox[i][1] = ( i & 2 ) ? sprite_absmin[1] : sprite_absmax[1];
|
|
bbox[i][2] = ( i & 4 ) ? sprite_absmin[2] : sprite_absmax[2];
|
|
}
|
|
}
|
|
|
|
/*
|
|
================
|
|
CullSpriteModel
|
|
|
|
Cull sprite model by bbox
|
|
================
|
|
*/
|
|
bool CSpriteModelRenderer :: CullSpriteModel( void )
|
|
{
|
|
if( !m_pSpriteHeader )
|
|
return true;
|
|
|
|
SpriteComputeBBox( m_pCurrentEntity, NULL );
|
|
|
|
return R_CullModel( m_pCurrentEntity, sprite_absmin, sprite_absmax );
|
|
}
|
|
|
|
/*
|
|
================
|
|
GlowSightDistance
|
|
|
|
Calc sight distance for glow-sprites
|
|
================
|
|
*/
|
|
float CSpriteModelRenderer :: GlowSightDistance( void )
|
|
{
|
|
pmtrace_t tr;
|
|
|
|
float dist = (sprite_origin - GetVieworg( )).Length();
|
|
|
|
if( !FBitSet( RI->params, RP_MIRRORVIEW ))
|
|
{
|
|
gEngfuncs.pEventAPI->EV_SetTraceHull( 2 );
|
|
gEngfuncs.pEventAPI->EV_PlayerTrace( GetVieworg(), sprite_origin, PM_GLASS_IGNORE, -1, &tr );
|
|
|
|
if((( 1.0f - tr.fraction ) * dist ) > 8.0f )
|
|
return -1;
|
|
}
|
|
|
|
return dist;
|
|
}
|
|
|
|
/*
|
|
================
|
|
R_GlowSightDistance
|
|
|
|
Set sprite brightness factor
|
|
================
|
|
*/
|
|
float CSpriteModelRenderer :: SpriteGlowBlend( int rendermode, int renderfx, int alpha, float &scale )
|
|
{
|
|
float dist = GlowSightDistance();
|
|
float brightness;
|
|
|
|
if( dist <= 0 ) return 0.0f; // occluded
|
|
|
|
if( renderfx == kRenderFxNoDissipation )
|
|
return (float)alpha * (1.0f / 255.0f);
|
|
|
|
scale = 0.0f; // variable sized glow
|
|
|
|
brightness = 19000.0 / ( dist * dist );
|
|
brightness = bound( 0.05f, brightness, 1.0f );
|
|
|
|
// make the glow fixed size in screen space, taking into consideration the scale setting.
|
|
if( scale == 0.0f ) scale = 1.0f;
|
|
scale *= dist * ( 1.0f / 200.0f );
|
|
|
|
return brightness;
|
|
}
|
|
|
|
/*
|
|
================
|
|
SpriteOccluded
|
|
|
|
Do occlusion test for glow-sprites
|
|
================
|
|
*/
|
|
int CSpriteModelRenderer :: SpriteOccluded( int &alpha, float &pscale )
|
|
{
|
|
// always compute origin first
|
|
SpriteComputeOrigin( m_pCurrentEntity );
|
|
|
|
if( m_pCurrentEntity->curstate.rendermode == kRenderGlow )
|
|
{
|
|
// don't reflect this entity in mirrors
|
|
if( FBitSet( m_pCurrentEntity->curstate.effects, EF_NOREFLECT ) && FBitSet( RI->params, RP_MIRRORVIEW ))
|
|
return true;
|
|
|
|
// draw only in mirrors
|
|
if( FBitSet( m_pCurrentEntity->curstate.effects, EF_REFLECTONLY ) && !FBitSet( RI->params, RP_MIRRORVIEW ))
|
|
return true;
|
|
|
|
// original glow occlusion code by BUzer from Paranoia
|
|
if( m_pCurrentEntity->curstate.renderfx == kRenderFxNoDissipation && CVAR_TO_BOOL( v_glows ))
|
|
{
|
|
mspriteframe_t *frame = GetSpriteFrame( m_pCurrentEntity->curstate.frame, m_pCurrentEntity->angles[YAW] );
|
|
Vector left = Vector( 0.0f, ( frame->left * pscale ) / 5.0f, 0.0f );
|
|
Vector right = Vector( 0.0f, ( frame->right * pscale ) / 5.0f, 0.0f );
|
|
matrix4x4 sprite_transform = RI->view.matrix;
|
|
|
|
sprite_transform.SetOrigin( sprite_origin );
|
|
Vector aleft = sprite_transform.VectorTransform( left );
|
|
Vector aright = sprite_transform.VectorTransform( right );
|
|
Vector dist = aright - aleft;
|
|
|
|
float dst = DotProduct( GetVForward(), sprite_origin - GetVieworg() );
|
|
dst = bound( 0.0f, dst, 64.0f );
|
|
dst = dst / 64.0f;
|
|
|
|
int numtraces = GLOW_NUM_TRACES;
|
|
if( numtraces < 1 ) numtraces = 1;
|
|
Vector step = dist * (1.0f / ( numtraces + 1 ));
|
|
float frac = 1.0f / numtraces;
|
|
float totalfrac = 0.0f;
|
|
|
|
gEngfuncs.pEventAPI->EV_SetTraceHull( 2 );
|
|
|
|
for( int j = 0; j < numtraces; j++ )
|
|
{
|
|
Vector start = aleft + step * (j+1);
|
|
|
|
pmtrace_t pmtrace;
|
|
gEngfuncs.pEventAPI->EV_PlayerTrace( GetVieworg(), start, PM_GLASS_IGNORE|PM_STUDIO_IGNORE, -1, &pmtrace );
|
|
|
|
if( pmtrace.fraction == 1.0f )
|
|
totalfrac += frac;
|
|
}
|
|
|
|
float targetalpha = totalfrac;
|
|
float blend;
|
|
|
|
if( m_pCurrentEntity->latched.sequencetime > targetalpha )
|
|
{
|
|
m_pCurrentEntity->latched.sequencetime -= tr.frametime * GLOW_INTERP_SPEED;
|
|
if( m_pCurrentEntity->latched.sequencetime <= targetalpha )
|
|
m_pCurrentEntity->latched.sequencetime = targetalpha;
|
|
}
|
|
else if( m_pCurrentEntity->latched.sequencetime < targetalpha )
|
|
{
|
|
m_pCurrentEntity->latched.sequencetime += tr.frametime * GLOW_INTERP_SPEED;
|
|
if( m_pCurrentEntity->latched.sequencetime >= targetalpha )
|
|
m_pCurrentEntity->latched.sequencetime = targetalpha;
|
|
}
|
|
|
|
blend = m_pCurrentEntity->latched.sequencetime * dst;
|
|
alpha *= blend;
|
|
|
|
if( blend <= 0.01f )
|
|
return true; // faded
|
|
}
|
|
else
|
|
{
|
|
Vector screenVec;
|
|
float blend;
|
|
|
|
WorldToScreen( sprite_origin, screenVec );
|
|
|
|
if( screenVec[0] < RI->view.port[0] || screenVec[0] > RI->view.port[0] + RI->view.port[2] )
|
|
return true; // out of screen
|
|
if( screenVec[1] < RI->view.port[1] || screenVec[1] > RI->view.port[1] + RI->view.port[3] )
|
|
return true; // out of screen
|
|
|
|
blend = SpriteGlowBlend( m_pCurrentEntity->curstate.rendermode, m_pCurrentEntity->curstate.renderfx, alpha, pscale );
|
|
alpha *= blend;
|
|
|
|
if( blend <= 0.01f )
|
|
return true; // faded
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if( CullSpriteModel( ))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
DrawSpriteQuad
|
|
|
|
=================
|
|
*/
|
|
void CSpriteModelRenderer :: DrawSpriteQuad( mspriteframe_t *frame, const Vector &org, const Vector &right, const Vector &up, float scale )
|
|
{
|
|
Vector point;
|
|
|
|
pglBegin( GL_QUADS );
|
|
pglTexCoord2f( 0.0f, 1.0f );
|
|
point = org + up * (frame->down * scale);
|
|
point = point + right * (frame->left * scale);
|
|
pglVertex3fv( point );
|
|
pglTexCoord2f( 0.0f, 0.0f );
|
|
point = org + up * (frame->up * scale);
|
|
point = point + right * (frame->left * scale);
|
|
pglVertex3fv( point );
|
|
pglTexCoord2f( 1.0f, 0.0f );
|
|
point = org + up * (frame->up * scale);
|
|
point = point + right * (frame->right * scale);
|
|
pglVertex3fv( point );
|
|
pglTexCoord2f( 1.0f, 1.0f );
|
|
point = org + up * (frame->down * scale);
|
|
point = point + right * (frame->right * scale);
|
|
pglVertex3fv( point );
|
|
pglEnd();
|
|
}
|
|
|
|
int CSpriteModelRenderer :: SpriteHasLightmap( int texFormat )
|
|
{
|
|
if( m_pCvarLighting->value == 0 )
|
|
return false;
|
|
|
|
if( texFormat != SPR_ALPHTEST )
|
|
return false;
|
|
|
|
if( FBitSet( m_pCurrentEntity->curstate.effects, EF_FULLBRIGHT ))
|
|
return false;
|
|
|
|
if( m_pCurrentEntity->curstate.renderamt <= 127 )
|
|
return false;
|
|
|
|
switch( m_pCurrentEntity->curstate.rendermode )
|
|
{
|
|
case kRenderNormal:
|
|
case kRenderTransAlpha:
|
|
case kRenderTransTexture:
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
=================
|
|
R_DrawSpriteModel
|
|
=================
|
|
*/
|
|
void CSpriteModelRenderer :: AddSpriteModelToDrawList( cl_entity_t *e, bool update )
|
|
{
|
|
// obviously we can't update sprites...
|
|
if( update ) return;
|
|
|
|
m_pCurrentEntity = RI->currententity;
|
|
|
|
IEngineStudio.GetTimes( &m_nFrameCount, &m_clTime, &m_clOldTime );
|
|
|
|
m_pRenderModel = m_pCurrentEntity->model;
|
|
m_pSpriteHeader = (msprite_t *)Mod_Extradata( m_pRenderModel );
|
|
|
|
if( !m_pSpriteHeader ) return;
|
|
|
|
int alpha = m_pCurrentEntity->curstate.renderamt;
|
|
float scale = m_pCurrentEntity->curstate.scale;
|
|
int rendermode = m_pCurrentEntity->curstate.rendermode;
|
|
|
|
if( SpriteOccluded( alpha, scale ))
|
|
return; // sprite culled
|
|
|
|
if( m_pSpriteHeader->texFormat == SPR_ALPHTEST )
|
|
{
|
|
if( rendermode != kRenderGlow && rendermode != kRenderTransAdd )
|
|
rendermode = kRenderTransAlpha;
|
|
}
|
|
|
|
Vector color, color2;
|
|
|
|
// add basecolor (any rendermode can have colored sprite)
|
|
color[0] = (float)m_pCurrentEntity->curstate.rendercolor.r * ( 1.0f / 255.0f );
|
|
color[1] = (float)m_pCurrentEntity->curstate.rendercolor.g * ( 1.0f / 255.0f );
|
|
color[2] = (float)m_pCurrentEntity->curstate.rendercolor.b * ( 1.0f / 255.0f );
|
|
|
|
if( SpriteHasLightmap( m_pSpriteHeader->texFormat ))
|
|
{
|
|
gEngfuncs.pTriAPI->LightAtPoint( sprite_origin, (float *)&color2 );
|
|
color2 *= (1.0f / 255.0f);
|
|
color *= color2;
|
|
}
|
|
|
|
mspriteframe_t *frame = GetSpriteFrame( m_pCurrentEntity->curstate.frame, m_pCurrentEntity->angles[YAW] );
|
|
|
|
int type = m_pSpriteHeader->type;
|
|
|
|
// automatically roll parallel sprites if requested
|
|
if( m_pCurrentEntity->angles[ROLL] != 0.0f && type == SPR_FWD_PARALLEL )
|
|
type = SPR_FWD_PARALLEL_ORIENTED;
|
|
|
|
Vector v_forward, v_right, v_up;
|
|
|
|
switch( type )
|
|
{
|
|
case SPR_ORIENTED:
|
|
{
|
|
AngleVectors( m_pCurrentEntity->angles, v_forward, v_right, v_up );
|
|
sprite_origin = sprite_origin - v_forward * 0.01f; // to avoid z-fighting
|
|
}
|
|
break;
|
|
case SPR_FACING_UPRIGHT:
|
|
{
|
|
v_right.x = sprite_origin.y - GetVieworg().y;
|
|
v_right.y = -(sprite_origin.x - GetVieworg().x);
|
|
v_right.z = 0.0f;
|
|
v_up.x = v_up.y = 0.0f;
|
|
v_up.z = 1.0f;
|
|
v_right = v_right.Normalize();
|
|
}
|
|
break;
|
|
case SPR_FWD_PARALLEL_UPRIGHT:
|
|
{
|
|
float dot = GetVForward().z;
|
|
if(( dot > 0.999848f ) || ( dot < -0.999848f )) // cos(1 degree) = 0.999848
|
|
return; // invisible
|
|
|
|
v_right.x = GetVForward().y;
|
|
v_right.y = -GetVForward().x;
|
|
v_right.z = 0.0f;
|
|
v_up.x = v_up.y = 0.0f;
|
|
v_up.z = 1.0f;
|
|
v_right = v_right.Normalize();
|
|
}
|
|
break;
|
|
case SPR_FWD_PARALLEL_ORIENTED:
|
|
{
|
|
float angle = m_pCurrentEntity->angles[ROLL] * (M_PI * 2.0f / 360.0f);
|
|
float sr, cr;
|
|
|
|
SinCos( angle, &sr, &cr );
|
|
|
|
for( int i = 0; i < 3; i++ )
|
|
{
|
|
v_right[i] = (GetVLeft()[i] * cr + GetVUp()[i] * sr);
|
|
v_up[i] = GetVLeft()[i] * -sr + GetVUp()[i] * cr;
|
|
}
|
|
}
|
|
break;
|
|
case SPR_FWD_PARALLEL: // normal sprite
|
|
default:
|
|
{
|
|
v_right = GetVLeft();
|
|
v_up = GetVUp();
|
|
}
|
|
break;
|
|
}
|
|
|
|
float flAlpha = (float)alpha * ( 1.0f / 255.0f );
|
|
|
|
if( m_pCurrentEntity->curstate.rendermode == kRenderGlow && m_pCurrentEntity->curstate.renderfx == kRenderFxNoDissipation )
|
|
{
|
|
if( CVAR_TO_BOOL( v_glows ))
|
|
{
|
|
flAlpha = m_pCurrentEntity->curstate.renderamt * ( 1.0f/ 255.0f );
|
|
color *= (float)alpha * ( 1.0f/ 255.0f );
|
|
}
|
|
}
|
|
|
|
Vector point[4];
|
|
Vector4D spriteColor;
|
|
Vector absmin, absmax;
|
|
|
|
spriteColor = Vector4D( color[0], color[1], color[2], flAlpha );
|
|
point[0] = sprite_origin + v_up * (frame->down * scale);
|
|
point[0] = point[0] + v_right * (frame->left * scale);
|
|
point[1] = sprite_origin + v_up * (frame->up * scale);
|
|
point[1] = point[1] + v_right * (frame->left * scale);
|
|
point[2] = sprite_origin + v_up * (frame->up * scale);
|
|
point[2] = point[2] + v_right * (frame->right * scale);
|
|
point[3] = sprite_origin + v_up * (frame->down * scale);
|
|
point[3] = point[3] + v_right * (frame->right * scale);
|
|
|
|
// more precision bounds than sprite_absmin\absmax
|
|
ClearBounds( absmin, absmax );
|
|
for( int i = 0; i < 4; i++ )
|
|
AddPointToBounds( point[i], absmin, absmax );
|
|
|
|
CTransEntry entry;
|
|
entry.SetRenderPrimitive( point, spriteColor, frame->gl_texturenum, rendermode );
|
|
entry.ComputeViewDistance( absmin, absmax );
|
|
RI->frame.trans_list.AddToTail( entry );
|
|
}
|
|
|
|
mspriteframe_t *CSpriteModelRenderer :: GetSpriteFrame( const model_t *m_pSpriteModel, int frame )
|
|
{
|
|
if(( m_pSpriteHeader = (msprite_t *)Mod_Extradata( (model_t *)m_pSpriteModel )) == NULL )
|
|
return NULL;
|
|
|
|
m_pCurrentEntity = NULL;
|
|
return GetSpriteFrame( frame, 0.0f );
|
|
} |