//======================================================================= // Copyright XashXT Group 2007 © // r_sprite.c - render sprite models //======================================================================= #include "r_local.h" #include "mathlib.h" #include "byteorder.h" #include "const.h" /* ============================================================= SPRITE MODELS ============================================================= */ string frame_prefix; uint frame_type; uint group_num; string sp_name; ref_shader_t **frames = NULL; cvar_t *r_sprite_lerping; uint tex_flags = 0; vec3_t sprite_mins, sprite_maxs; float sprite_radius; /* ==================== R_SpriteInit ==================== */ void R_SpriteInit( void ) { r_sprite_lerping = Cvar_Get( "r_sprite_lerping", "1", CVAR_ARCHIVE, "enables sprite model animation lerping" ); } /* ==================== Sprite model loader ==================== */ dframetype_t *R_SpriteLoadFrame( ref_model_t *mod, void *pin, mspriteframe_t **ppframe, int framenum ) { texture_t *tex; dspriteframe_t *pinframe; mspriteframe_t *pspriteframe; string name, shadername; // build uinque frame name if( !sp_name[0] ) FS_FileBase( mod->name, sp_name ); com.snprintf( name, MAX_STRING, "Sprite( %s_%s_%i%i )", sp_name, frame_prefix, framenum/10, framenum%10 ); pinframe = (dspriteframe_t *)pin; SwapBlock((int *)pinframe, sizeof( dspriteframe_t )); // setup frame description pspriteframe = Mem_Alloc( mod->mempool, sizeof( mspriteframe_t )); pspriteframe->width = pinframe->width; pspriteframe->height = pinframe->height; pspriteframe->up = pinframe->origin[1]; pspriteframe->left = pinframe->origin[0]; pspriteframe->down = pinframe->origin[1] - pinframe->height; pspriteframe->right = pinframe->width + pinframe->origin[0]; pspriteframe->radius = com.sqrt((pinframe->width * pinframe->width) + (pinframe->height * pinframe->height)); tex = R_FindTexture( name, (byte *)pin, pinframe->width * pinframe->height, 0 ); tex_flags |= tex->flags; *ppframe = pspriteframe; R_ShaderAddStageTexture( tex ); if( frame_type == FRAME_SINGLE ) { com.snprintf( shadername, MAX_STRING, "sprites/%s.spr/%s_%i%i )", sp_name, frame_prefix, framenum/10, framenum%10 ); pspriteframe->shader = R_LoadShader( shadername, SHADER_SPRITE, true, tex_flags, SHADER_INVALID )->shadernum; frames = Mem_Realloc( mod->mempool, frames, sizeof( ref_shader_t* ) * (mod->numshaders + 1)); frames[mod->numshaders++] = &r_shaders[pspriteframe->shader]; } return (dframetype_t *)((byte *)(pinframe + 1) + pinframe->width * pinframe->height ); } dframetype_t *R_SpriteLoadGroup( ref_model_t *mod, void * pin, mspriteframe_t **ppframe, int framenum ) { dspritegroup_t *pingroup; mspritegroup_t *pspritegroup; dspriteinterval_t *pin_intervals; float *poutintervals; shader_t group_shader; int i, groupsize, numframes; string shadername; void *ptemp; pingroup = (dspritegroup_t *)pin; numframes = LittleLong( pingroup->numframes ); groupsize = sizeof(mspritegroup_t) + (numframes - 1) * sizeof( pspritegroup->frames[0] ); pspritegroup = Mem_Alloc( mod->mempool, groupsize ); pspritegroup->numframes = numframes; *ppframe = (mspriteframe_t *)pspritegroup; pin_intervals = (dspriteinterval_t *)(pingroup + 1); poutintervals = Mem_Alloc( mod->mempool, numframes * sizeof( float )); pspritegroup->intervals = poutintervals; for( i = 0; i < numframes; i++ ) { *poutintervals = LittleFloat( pin_intervals->interval ); if( *poutintervals <= 0.0 ) *poutintervals = 1.0f; // set error value if( frame_type == FRAME_GROUP ) R_ShaderAddStageIntervals( *poutintervals ); poutintervals++; pin_intervals++; } ptemp = (void *)pin_intervals; for( i = 0; i < numframes; i++ ) { ptemp = R_SpriteLoadFrame( mod, ptemp, &pspritegroup->frames[i], framenum * 10 + i ); } com.snprintf( shadername, MAX_STRING, "sprites/%s.spr/%s_%i%i", sp_name, frame_prefix, group_num/10, group_num%10 ); group_shader = R_LoadShader( shadername, SHADER_SPRITE, true, tex_flags, SHADER_INVALID )->shadernum; frames = Mem_Realloc( mod->mempool, frames, sizeof( ref_shader_t* ) * (mod->numshaders + 1)); frames[mod->numshaders++] = &r_shaders[group_shader]; // apply this shader for all frames in group for( i = 0; i < numframes; i++ ) pspritegroup->frames[i]->shader = group_shader; group_num++; return (dframetype_t *)ptemp; } void Mod_SpriteLoadModel( ref_model_t *mod, const void *buffer ) { dsprite_t *pin; short *numi; msprite_t *psprite; dframetype_t *pframetype; int i, size, numframes; bool twoSided; pin = (dsprite_t *)buffer; i = LittleLong( pin->version ); if( i != SPRITE_VERSION ) { MsgDev( D_ERROR, "%s has wrong version number (%i should be %i)\n", mod->name, i, SPRITE_VERSION ); return; } numframes = LittleLong( pin->numframes ); size = sizeof (msprite_t) + (numframes - 1) * sizeof( psprite->frames ); psprite = Mem_Alloc( mod->mempool, size ); mod->extradata = psprite; // make link to extradata mod->numshaders = 0; // reset frames psprite->type = LittleLong( pin->type ); psprite->rendermode = LittleLong( pin->texFormat ); psprite->numframes = numframes; twoSided = (LittleLong( pin->facetype == SPR_CULL_NONE )) ? true : false; mod->mins[0] = mod->mins[1] = -LittleLong( pin->bounds[0] ) / 2; mod->maxs[0] = mod->maxs[1] = LittleLong( pin->bounds[0] ) / 2; mod->mins[2] = -LittleLong( pin->bounds[1] ) / 2; mod->maxs[2] = LittleLong( pin->bounds[1] ) / 2; numi = (short *)(pin + 1); if( LittleShort( *numi ) == 256 ) { byte *src = (byte *)(numi+1); rgbdata_t *pal; // install palette switch( psprite->rendermode ) { case SPR_ADDGLOW: pal = FS_LoadImage( "#normal.pal", src, 768 ); R_ShaderSetRenderMode( kRenderGlow, twoSided ); break; case SPR_ADDITIVE: pal = FS_LoadImage( "#normal.pal", src, 768 ); R_ShaderSetRenderMode( kRenderTransAdd, twoSided ); break; case SPR_INDEXALPHA: pal = FS_LoadImage( "#decal.pal", src, 768 ); R_ShaderSetRenderMode( kRenderTransTexture, twoSided ); break; case SPR_ALPHTEST: pal = FS_LoadImage( "#transparent.pal", src, 768 ); R_ShaderSetRenderMode( kRenderTransAlpha, twoSided ); break; case SPR_NORMAL: default: pal = FS_LoadImage( "#normal.pal", src, 768 ); R_ShaderSetRenderMode( kRenderNormal, twoSided ); break; } pframetype = (dframetype_t *)(src + 768); FS_FreeImage( pal ); // palette installed, no reason to keep this data } else { MsgDev( D_ERROR, "%s has wrong number of palette colors %i (should be 256)\n", mod->name, numi ); return; } if( numframes < 1 ) { MsgDev( D_ERROR, "%s has invalid # of frames: %d\n", mod->name, numframes ); return; } MsgDev( D_LOAD, "%s, rendermode %d\n", mod->name, psprite->rendermode ); mod->touchFrame = tr.registration_sequence; frames = NULL; // invalidate pointer sp_name[0] = 0; group_num = 0; for( i = 0; i < numframes; i++ ) { frametype_t frametype = LittleLong( pframetype->type ); psprite->frames[i].type = frametype; frame_type = frametype; switch( frametype ) { case FRAME_SINGLE: tex_flags = 0; com.strncpy( frame_prefix, "one", MAX_STRING ); pframetype = R_SpriteLoadFrame(mod, pframetype + 1, &psprite->frames[i].frameptr, i ); break; case FRAME_GROUP: tex_flags = 0; com.strncpy( frame_prefix, "grp", MAX_STRING ); pframetype = R_SpriteLoadGroup(mod, pframetype + 1, &psprite->frames[i].frameptr, i ); break; case FRAME_ANGLED: tex_flags = 0; com.strncpy( frame_prefix, "ang", MAX_STRING ); pframetype = R_SpriteLoadGroup(mod, pframetype + 1, &psprite->frames[i].frameptr, i ); break; } if( pframetype == NULL ) break; // technically an error } mod->shaders = frames; // setup texture links mod->type = mod_sprite; } /* ================ R_GetSpriteFrame assume pModel is valid ================ */ mspriteframe_t *R_GetSpriteFrame( ref_model_t *pModel, int frame, float yawAngle ) { msprite_t *psprite; int i, numframes; mspritegroup_t *pspritegroup; mspriteframe_t *pspriteframe; float *pintervals, fullinterval, targettime, time; Com_Assert( pModel == NULL ); psprite = pModel->extradata; if( frame < 0 ) { frame = 0; } else if( frame >= psprite->numframes ) { MsgDev( D_WARN, "R_GetSpriteFrame: no such frame %d (%s)\n", frame, pModel->name ); frame = psprite->numframes - 1; } if( psprite->frames[frame].type == FRAME_SINGLE ) { pspriteframe = psprite->frames[frame].frameptr; } else if( psprite->frames[frame].type == FRAME_GROUP ) { pspritegroup = (mspritegroup_t *)psprite->frames[frame].frameptr; pintervals = pspritegroup->intervals; numframes = pspritegroup->numframes; fullinterval = pintervals[numframes-1]; time = RI.refdef.time; // when loading in Mod_LoadSpriteGroup, we guaranteed all interval values // are positive, so we don't have to worry about division by zero targettime = time - ((int)(time / fullinterval)) * fullinterval; for( i = 0; i < (numframes - 1); i++ ) { if( pintervals[i] > targettime ) break; } pspriteframe = pspritegroup->frames[i]; } else if( psprite->frames[frame].type == FRAME_ANGLED ) { // e.g. doom-style sprite monsters float yaw = yawAngle - 45; // angled bias int angleframe = (int)(( RI.refdef.viewangles[1] - yaw ) / 360 * 8 + 0.5 - 4) & 7; pspritegroup = (mspritegroup_t *)psprite->frames[frame].frameptr; pspriteframe = pspritegroup->frames[angleframe]; } return pspriteframe; } /* ================ R_GetSpriteFrameInterpolant ================ */ float R_GetSpriteFrameInterpolant( ref_entity_t *ent, mspriteframe_t **oldframe, mspriteframe_t **curframe ) { msprite_t *psprite; mspritegroup_t *pspritegroup; int i, j, numframes, frame; float lerpFrac, time, jtime, jinterval; float *pintervals, fullinterval, targettime; int m_fDoInterp; psprite = ent->model->extradata; frame = (int)ent->lerp->curstate.frame; lerpFrac = 1.0f; // misc info if( r_sprite_lerping->integer ) m_fDoInterp = (ent->flags & EF_NOINTERP) ? false : true; else m_fDoInterp = false; if((frame >= psprite->numframes) || (frame < 0)) { MsgDev( D_WARN, "R_GetSpriteFrame: no such frame %d (%s)\n", frame, ent->model->name ); frame = 0; } if( psprite->frames[frame].type == FRAME_SINGLE ) { if( m_fDoInterp ) { if( ent->lerp->latched.sequence >= psprite->numframes || psprite->frames[ent->lerp->latched.sequence].type != FRAME_SINGLE ) { // this can be happens when rendering switched between single and angled frames // or change model on replace delta-entity ent->lerp->latched.sequence = ent->lerp->curstate.sequence = frame; ent->lerp->curstate.animtime = RI.refdef.time; lerpFrac = 1.0f; } if( ent->lerp->curstate.animtime < RI.refdef.time ) { if( frame != ent->lerp->curstate.sequence ) { ent->lerp->latched.sequence = ent->lerp->curstate.sequence; ent->lerp->curstate.sequence = frame; ent->lerp->curstate.animtime = RI.refdef.time; lerpFrac = 0.0f; } else lerpFrac = (RI.refdef.time - ent->lerp->curstate.animtime) * ent->framerate; } else { ent->lerp->latched.sequence = ent->lerp->curstate.sequence = frame; ent->lerp->curstate.animtime = RI.refdef.time; lerpFrac = 0.0f; } } else { ent->lerp->latched.sequence = ent->lerp->curstate.sequence = frame; lerpFrac = 1.0f; } if( ent->lerp->latched.sequence >= psprite->numframes ) { // reset interpolation on change model ent->lerp->latched.sequence = ent->lerp->curstate.sequence = frame; ent->lerp->curstate.animtime = RI.refdef.time; lerpFrac = 0.0f; } // get the interpolated frames if( oldframe ) *oldframe = psprite->frames[ent->lerp->latched.sequence].frameptr; if( curframe ) *curframe = psprite->frames[frame].frameptr; } else if( psprite->frames[frame].type == FRAME_GROUP ) { pspritegroup = (mspritegroup_t *)psprite->frames[frame].frameptr; pintervals = pspritegroup->intervals; numframes = pspritegroup->numframes; fullinterval = pintervals[numframes-1]; jinterval = pintervals[1] - pintervals[0]; time = RI.refdef.time; jtime = 0.0f; // when loading in Mod_LoadSpriteGroup, we guaranteed all interval values // are positive, so we don't have to worry about division by zero targettime = time - ((int)(time / fullinterval)) * fullinterval; // LordHavoc: since I can't measure the time properly when it loops from numframes - 1 to 0, // i instead measure the time of the first frame, hoping it is consistent for( i = 0, j = numframes - 1; i < (numframes - 1); i++ ) { if( pintervals[i] > targettime ) break; j = i; jinterval = pintervals[i] - jtime; jtime = pintervals[i]; } if( m_fDoInterp ) lerpFrac = (targettime - jtime) / jinterval; else j = i; // no lerping // get the interpolated frames if( oldframe ) *oldframe = pspritegroup->frames[j]; if( curframe ) *curframe = pspritegroup->frames[i]; } else if( psprite->frames[frame].type == FRAME_ANGLED ) { // e.g. doom-style sprite monsters float yaw = ent->angles[1] - 45; // angled bias int angleframe = (int)((RI.refdef.viewangles[1] - yaw) / 360 * 8 + 0.5 - 4) & 7; if( m_fDoInterp ) { if( ent->lerp->latched.sequence >= psprite->numframes || psprite->frames[ent->lerp->latched.sequence].type != FRAME_ANGLED ) { // this can be happens when rendering switched between single and angled frames // or change model on replace delta-entity ent->lerp->latched.sequence = ent->lerp->curstate.sequence = frame; ent->lerp->curstate.animtime = RI.refdef.time; lerpFrac = 1.0f; } if( ent->lerp->curstate.animtime < RI.refdef.time ) { if( frame != ent->lerp->curstate.sequence ) { ent->lerp->latched.sequence = ent->lerp->curstate.sequence; ent->lerp->curstate.sequence = frame; ent->lerp->curstate.animtime = RI.refdef.time; lerpFrac = 0.0f; } else lerpFrac = (RI.refdef.time - ent->lerp->curstate.animtime) * ent->framerate; } else { ent->lerp->latched.sequence = ent->lerp->curstate.sequence = frame; ent->lerp->curstate.animtime = RI.refdef.time; lerpFrac = 0.0f; } } else { ent->lerp->latched.sequence = ent->lerp->curstate.sequence = frame; lerpFrac = 1.0f; } pspritegroup = (mspritegroup_t *)psprite->frames[ent->lerp->latched.sequence].frameptr; if( oldframe ) *oldframe = pspritegroup->frames[angleframe]; pspritegroup = (mspritegroup_t *)psprite->frames[frame].frameptr; if( curframe ) *curframe = pspritegroup->frames[angleframe]; } return lerpFrac; } static float R_GlowSightDistance( vec3_t glowOrigin ) { float dist; vec3_t glowDist; trace_t tr; VectorSubtract( glowOrigin, RI.viewOrigin, glowDist ); dist = VectorLength( glowDist ); R_TraceLine( &tr, RI.viewOrigin, glowOrigin ); if(( 1.0 - tr.flFraction ) * dist > 8 ) return -1; return dist; } static float R_SpriteGlowBlend( ref_entity_t *e ) { float dist = R_GlowSightDistance( e->origin ); float brightness; if( e->renderfx == kRenderFxNoDissipation ) { return (float)e->renderamt * (1.0f/255.0f); } if( dist <= 0 ) return 0.0f; // occluded // UNDONE: Tweak these magic numbers (19000 - falloff & 200 - sprite size) 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( e->scale == 0 ) e->scale = 1.0f; e->scale *= dist * (1.0f / 200.0f ); return brightness; } bool R_SpriteOccluded( ref_entity_t *e ) { if( e->rendermode == kRenderGlow ) { float blend = 1.0f; vec3_t v; R_TransformToScreen_Vec3( e->origin, v ); if( v[0] < RI.refdef.viewport[0] || v[0] > RI.refdef.viewport[0] + RI.refdef.viewport[2] ) return true; // do scissor if( v[1] < RI.refdef.viewport[1] || v[1] > RI.refdef.viewport[1] + RI.refdef.viewport[3] ) return true; // do scissor blend *= R_SpriteGlowBlend( e ); e->renderamt *= blend; if( blend <= 0.05f ) return true; // faded } return false; } /* ================= R_DrawSpriteModel ================= */ void R_DrawSpriteModel( const meshbuffer_t *mb ) { ref_entity_t *e; mspriteframe_t *frame, *oldframe; msprite_t *psprite; ref_model_t *model; float lerp = 1.0f, ilerp; meshbuffer_t *rb = (meshbuffer_t *)mb; byte renderamt; MB_NUM2ENTITY( mb->sortkey, e ); model = e->model; if( OCCLUSION_QUERIES_ENABLED( RI ) && OCCLUSION_TEST_ENTITY( e )) { ref_shader_t *shader; MB_NUM2SHADER( mb->shaderkey, shader ); if( !R_GetOcclusionQueryResultBool( shader->type == 1 ? OQ_PLANARSHADOW : OQ_ENTITY, e - r_entities, true )) return; } psprite = (msprite_t * )model->extradata; switch( e->rendermode ) { case kRenderNormal: case kRenderTransColor: case kRenderTransAlpha: frame = oldframe = R_GetSpriteFrame( e->model, e->lerp->curstate.frame, e->angles[YAW] ); break; case kRenderTransTexture: case kRenderTransAdd: case kRenderGlow: lerp = R_GetSpriteFrameInterpolant( e, &oldframe, &frame ); break; default: Host_Error( "R_DrawSpriteModel: %s bad rendermode %i\n", model->name, e->rendermode ); break; } // do movewith if( e->parent && e->movetype == MOVETYPE_FOLLOW ) { if(( e->colormap & 0xFF ) > 0 && e->parent->model && e->parent->model->type == mod_studio ) { // pev->colormap is hardcoded to attachment number if( !ri.GetAttachment( e->parent->index, (e->colormap & 0xFF), e->origin2, NULL )) VectorCopy( e->parent->origin, e->origin2 ); } else VectorCopy( e->parent->origin, e->origin2 ); } // no rotation for sprites R_TranslateForEntity( e ); if( oldframe == frame ) { // draw the single non-lerped frame if( R_PushSprite( rb, psprite->type, frame->left, frame->right, frame->down, frame->up )) R_RenderMeshBuffer( rb ); } else { // draw two combined lerped frames renderamt = e->renderamt; lerp = bound( 0, lerp, 1 ); ilerp = 1.0f - lerp; if( ilerp != 0 ) { e->renderamt = renderamt * ilerp; // merge prevframe alpha if( e->customShader ) rb->shaderkey = e->customShader->sortkey; else rb->shaderkey = r_shaders[oldframe->shader].sortkey; if( R_PushSprite( rb, psprite->type, oldframe->left, oldframe->right, oldframe->down, oldframe->up )) R_RenderMeshBuffer( rb ); } if( lerp != 0 ) { e->renderamt = renderamt * lerp; // merge frame alpha if( e->customShader ) rb->shaderkey = e->customShader->sortkey; else rb->shaderkey = r_shaders[frame->shader].sortkey; if( R_PushSprite( rb, psprite->type, frame->left, frame->right, frame->down, frame->up )) R_RenderMeshBuffer( rb ); } // restore current values (e.g. for right mirror passes) e->renderamt = renderamt; } } bool R_CullSpriteModel( ref_entity_t *e ) { bool frustum, query, clipped; if( !e->model->extradata ) return true; if( e->ent_type == ED_VIEWMODEL && r_lefthand->integer >= 2 ) return true; VectorCopy( e->model->mins, sprite_mins ); VectorCopy( e->model->maxs, sprite_maxs ); sprite_radius = RadiusFromBounds( sprite_mins, sprite_maxs ); clipped = R_CullModel( e, sprite_mins, sprite_maxs, sprite_radius ); frustum = clipped & 1; if( clipped & 2 ) return true; query = OCCLUSION_QUERIES_ENABLED( RI ) && OCCLUSION_TEST_ENTITY( e ) ? true : false; if( !frustum && query ) R_IssueOcclusionQuery( R_GetOcclusionQueryNum( OQ_ENTITY, e - r_entities ), e, sprite_mins, sprite_maxs ); return frustum; }