/* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. Copyright (C) 2007 HermitWorks Entertainment Corporation This file is part of the Space Trader source code. The Space Trader source code 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 2 of the License, or (at your option) any later version. The Space Trader source code 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. You should have received a copy of the GNU General Public License along with the Space Trader source code; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA =========================================================================== */ #include "ui_local.h" #include "ui_anim.h" /* These are the curve types. CT_FREE indicates an empty curve slot and always evaluates to <0, done>. A curve of a given type is created by calling curve_create_. */ typedef enum curveType_t { CT_NOT_FREE = -1, CT_FREE = 0, CT_FUNC, CT_CONSTANT, CT_LERP, } curveType_t; /* A func_t is a function that takes a [0, 1] value that represents the function's full "natural" period and returns it's "natural" value at that point. For example, a func_t implementing sin would return sin( t * M_PI * 2 ). */ typedef float (*func_t)( float t ); /* The props for the sprite and the effect. Note that the first N props must both sprites and effects have in common must be in the same order. */ typedef enum commonProp_t { CP_POSITION_X, CP_POSITION_Y, CP_SCALE_X, CP_SCALE_Y, CP_ROTATION, } commonProp_t; typedef enum spriteProp_t { SP_POSITION_X, SP_POSITION_Y, SP_SCALE_X, SP_SCALE_Y, SP_ROTATION, SP_COLOR_R, SP_COLOR_G, SP_COLOR_B, SP_COLOR_A, SP_NUM_PROPS, } spriteProp_t; typedef enum { EP_POSITION_X, EP_POSITION_Y, EP_SCALE_X, EP_SCALE_Y, EP_ROTATION, EP_NUM_PROPS, } effectProp_t; #define CURVE_DONE 0x00000001 #define CURVE_DELETEME 0x00000002 typedef struct { curveType_t type; /* This union holds all of the parameters to run the curve. Data members shall be named the same as the curveType_t element they correspond to, all lowercase and without the CT_ prefix. Data should be strucutred such that zero-initialization makes sense. */ union { struct { func_t fcb; float base_val; float amplitude; float phase; float frequency; } func; struct { float val; float duration; } constant; struct { float start; float end; float duration; qboolean hold; } lerp; } data; /* Evaluation time = (toSecs(current_time - timeBase) * timeScale) % timeMod. The toSecs conversion is premultiplied into the timeScale factor upon curve creation. Note that timeMod is only applied if timeMod > 0. The timeBase subtraction is done in unsigned integer milliseconds in order to avoid cancellation errors on large time values. Everything beyond that is done in floats as we should (ideally) be in the 0-1 neighborhood. */ uint timeBase; float timeScale; float timeMod; float value; int flags; /* This is a counter to give us a way to track lifetimes. It gets incremented upon acurve expiring. Zero is reserved as invalid - if seqNum is zero, it is incremented upon the curve's creation as well. */ short seqNum; } animCurve_t; #define SPRITE_DELETEME 0x000000001 typedef struct sprite_t { float baseVals[SP_NUM_PROPS]; //a set of implicit non-expiring constant curves that are always in effect qhandle_t bindings[SP_NUM_PROPS][8]; float propVals[SP_NUM_PROPS]; //the final computed values float w,h; vec4_t uv_coord; qhandle_t shader; int flags; short seqNum; } sprite_t; typedef struct { const char * pc; int i; } stackFrame_t; typedef struct effect_t { effectDef_t * def; rectDef_t rect; char text[ EFFECT_TEXT_LENGTH ]; qhandle_t shader; qhandle_t props[EP_NUM_PROPS][8]; float baseVals[EP_NUM_PROPS]; float propVals[EP_NUM_PROPS]; int thread; qhandle_t curves[EFFECT_TEXT_LENGTH * 4]; int curveCount; qhandle_t sprites[EFFECT_TEXT_LENGTH * 4]; int spriteCount; int flags; // status flags from outside int touch; // counter that decrements (stopping at zero) on each draw int waitfor; // bitfield of stuff to wait for int sleeping; itemDef_t * item; // a small computer stackFrame_t stack[ 8 ]; int sp; short seqNum; } effect_t; static struct { sprite_t sprites [ 384 ]; sprite_t * freelist[ 384 ]; int count; } sprites; static struct { animCurve_t curves [ 512 ]; animCurve_t * freelist[ 512 ]; int count; } curves; static struct { effect_t effects [ 64 ]; effect_t * freelist[ 64 ]; int count; } effects; /* Animation curves **************************************************************************/ static ID_INLINE animCurve_t * curve_get( qhandle_t h ) { if ( h ) { int index = (h&0xFFFF)-1; int seqNum = (h>>16)&0xFFFF; animCurve_t * c = curves.curves + index; if ( c->seqNum == seqNum ) return c; } return 0; } static ID_INLINE qhandle_t curve_getId( animCurve_t * c ) { return ((c->seqNum&0xFFFF)<<16) | ((c-curves.curves+1)&0xFFFF); } static ID_INLINE animCurve_t * curve_alloc( void ) { short seqNum; animCurve_t *cv; if( curves.count >= lengthof( curves.freelist ) ) return 0; cv = curves.freelist[curves.count++]; seqNum = cv->seqNum; Com_Memset( cv, 0, sizeof( *cv ) ); cv->seqNum = seqNum; return cv; } /* The following functions initialize new curves. */ static ID_INLINE animCurve_t * curve_create_base( uint timeBase, float timeScale, float timeMod, curveType_t type ) { animCurve_t * c = curve_alloc(); if( !c ) return 0; c->type = type; c->timeBase = timeBase; c->timeScale = timeScale; c->timeMod = timeMod; c->flags = 0; return c; } /* This is pretty much the cannonical curve create function. It calls curve_create_base to set up the common curve parameters, checks for an allocation failure, and then sets up the curve-type specific parameters. Finally it calls curve_eval to set up the initial curve value. One of these should exist for each curve type. */ qhandle_t curve_create_func( int timeBase, float timeScale, float timeMod, func_t func, float freq, float phase, float amp, float base ) { animCurve_t *c; if( !func ) return 0; c = curve_create_base( timeBase, timeScale, timeMod, CT_FUNC ); if( !c ) return 0; c->data.func.fcb = func; c->data.func.frequency = freq; c->data.func.phase = phase; c->data.func.amplitude = amp; c->data.func.base_val = base; return curve_getId( c ); } qhandle_t curve_create_constant( int timeBase, float val, float duration ) { animCurve_t *c; c = curve_create_base( timeBase, 1, 0, CT_CONSTANT ); if( !c ) return 0; c->data.constant.val = val; c->data.constant.duration = duration; return curve_getId( c ); } static qhandle_t curve_create_lerp( int timeBase, float start, float end, float duration, qboolean hold ) { animCurve_t *c; c = curve_create_base( timeBase, 1.0F, 0.0F, CT_LERP ); if( !c ) return 0; c->data.lerp.start = start; c->data.lerp.end = end; c->data.lerp.duration = duration; c->data.lerp.hold = hold; return curve_getId( c ); } /* Curve destruction. */ void curve_destroy( qhandle_t h ) { animCurve_t * c = curve_get( h ); if ( c ) c->flags |= CURVE_DELETEME; } /* Curve evaluation. Fun stuff. */ float fracf( float f ) { return f - floorf( f ); } static void curve_eval( animCurve_t *cv, uint eval_time ) { float t; if( !cv ) return; if( eval_time >= cv->timeBase ) eval_time -= cv->timeBase; else eval_time = 0; t = (float)eval_time * cv->timeScale; if( cv->timeMod > 0 ) t = fmodf( t, cv->timeMod ); switch( cv->type ) { case CT_FUNC: if( t > 1 ) { t = 1; cv->flags |= CURVE_DONE; } else cv->flags &= ~CURVE_DONE; t = fracf( t * cv->data.func.frequency + cv->data.func.phase ); cv->value = cv->data.func.fcb( t ) * cv->data.func.amplitude + cv->data.func.base_val; break; case CT_CONSTANT: cv->value = cv->data.constant.val; break; case CT_LERP: t = t / cv->data.lerp.duration; if( t > 1.0f ) { t = 1.0f; cv->flags |= CURVE_DONE; } else cv->flags &= ~CURVE_DONE; cv->value = cv->data.lerp.start * (1.0F - t) + cv->data.lerp.end * t; break; default: cv->flags |= CURVE_DONE; cv->value = 0; break; } } void curves_update() { int i; for ( i=0; iflags & CURVE_DELETEME ) { curves.freelist[ i-- ] = curves.freelist[ --curves.count ]; curves.freelist[ curves.count ] = c; c->seqNum++; } } } float curve_get_value( qhandle_t h ) { animCurve_t * c = curve_get( h ); if ( c ) return c->value; return 0.0f; } qboolean curve_is_done( qhandle_t h ) { animCurve_t * c = curve_get( h ); return (c)?c->flags&CURVE_DONE:1; } /* Sprites **************************************************************************/ static ID_INLINE sprite_t * sprite_get( qhandle_t h ) { if ( h ) { int index = (h&0xFFFF)-1; int seqNum = (h>>16)&0xFFFF; sprite_t * s = sprites.sprites + index; if ( s->seqNum == seqNum ) return s; } return 0; } static ID_INLINE qhandle_t sprite_getId( sprite_t * s ) { return ((s->seqNum&0xFFFF)<<16) | ((s-sprites.sprites+1)&0xFFFF); } static ID_INLINE sprite_t * sprite_alloc( void ) { if ( sprites.count >= lengthof( sprites.freelist ) ) return 0; return sprites.freelist[ sprites.count++ ]; } static void sprite_eval( sprite_t *spr ) { int i; for ( i=0; ipropVals ); i++ ) { int j; spr->propVals[i] = spr->baseVals[i]; for ( j=0; jbindings[i] ); j++ ) { animCurve_t *cv = curve_get( spr->bindings[i][j] ); if( !cv ) continue; switch ( i ) { case SP_POSITION_X: case SP_POSITION_Y: case SP_ROTATION: spr->propVals[i] += cv->value; break; case SP_SCALE_X: case SP_SCALE_Y: case SP_COLOR_R: case SP_COLOR_G: case SP_COLOR_B: case SP_COLOR_A: spr->propVals[i] *= cv->value; break; } } } } void sprite_bake( qhandle_t h, spriteProp_t prop ) { sprite_t * s = sprite_get( h ); if( !s ) return; s->baseVals[ prop ] = s->propVals[ prop ]; } void sprites_update( void ) { int i; for ( i=0; iflags & SPRITE_DELETEME ) { sprites.freelist[ i-- ] = sprites.freelist[ --sprites.count ]; sprites.freelist[ sprites.count ] = s; s->seqNum++; } } } void sprite_set_base_prop( qhandle_t h, spriteProp_t prop, float val ) { sprite_t *spr = sprite_get( h ); if( !spr ) return; spr->baseVals[prop] = val; } void sprite_set_base_props( qhandle_t h, float vals[SP_NUM_PROPS] ) { sprite_t *spr = sprite_get( h ); if( !spr ) return; Com_Memcpy( spr->baseVals, vals, sizeof( spr->baseVals ) ); } void sprite_attach_curve( qhandle_t h, spriteProp_t prop, qhandle_t curve ) { uint i; sprite_t *spr = sprite_get( h ); if( !spr ) return; for( i=0; ibindings[prop] ); i++ ) { if( !curve_get( spr->bindings[ prop ][ i ] ) ) { spr->bindings[prop][i] = curve; break; } } } const float* sprite_get_props( qhandle_t h ) { sprite_t *spr = sprite_get( h ); if( !spr ) return NULL; return spr->propVals; } qhandle_t sprite_create_rect( rectDef_t * r, qhandle_t shader, vec4_t color, vec4_t uv_coord ) { sprite_t *ret; ret = sprite_alloc(); if( !ret ) return 0; ret->flags = 0; ret->baseVals[ SP_POSITION_X ] = r->x + r->w*0.5f; ret->baseVals[ SP_POSITION_Y ] = r->y + r->h*0.5f; ret->baseVals[ SP_SCALE_X ] = 1.0f; ret->baseVals[ SP_SCALE_Y ] = 1.0f; ret->baseVals[ SP_COLOR_R ] = color[ 0 ]; ret->baseVals[ SP_COLOR_G ] = color[ 1 ]; ret->baseVals[ SP_COLOR_B ] = color[ 2 ]; ret->baseVals[ SP_COLOR_A ] = color[ 3 ]; ret->w = r->w; ret->h = r->h; ret->uv_coord[ 0 ] = uv_coord[ 0 ]; ret->uv_coord[ 1 ] = uv_coord[ 1 ]; ret->uv_coord[ 2 ] = uv_coord[ 2 ]; ret->uv_coord[ 3 ] = uv_coord[ 3 ]; ret->shader = shader; return sprite_getId( ret ); } void sprite_destroy( qhandle_t h ) { //do any sprite-specific data cleanup sprite_t * s = sprite_get( h ); if ( s ) s->flags |= SPRITE_DELETEME; } void sprite_draw( sprite_t * s, vec3_t m[2] ) { float w = s->w * s->propVals[SP_SCALE_X]; float h = s->h * s->propVals[SP_SCALE_Y]; float Tx = s->propVals[SP_POSITION_X]; float Ty = s->propVals[SP_POSITION_Y]; float R = DEG2RAD( s->propVals[SP_ROTATION] ); float Rc = cosf( R ); float Rs = sinf( R ); float Sx = DC->glconfig.xscale; float Sy = DC->glconfig.yscale; float Bx = DC->glconfig.xbias; float By = 0; //y bias, if we ever get one float mat[2][3]; /* Set the matrix equal to S * M * T * R where: S is a matrix that adjusts for screen resolution. M is the incoming effect matrix. T is the sprite's translation matrix. R is the sprite's rotation matrix. Note that the sprite comes in pre-scaled. */ mat[0][0] = Sx * (Rc * m[0][0] + Rs * m[0][1]); mat[0][1] = Sx * (Rc * m[0][1] - Rs * m[0][0]); mat[0][2] = Sx * (Tx * m[0][0] + Ty * m[0][1] + m[0][2]) + Bx; mat[1][0] = Sy * (Rc * m[1][0] + Rs * m[1][1]); mat[1][1] = Sy * (Rc * m[1][1] - Rs * m[1][0]); mat[1][2] = Sy * (Tx * m[1][0] + Ty * m[1][1] + m[1][2]) + By; DC->setColor( &s->propVals[ SP_COLOR_R ] ); trap_R_DrawSprite( -w * 0.5F, -h * 0.5F, w, h, mat, s->uv_coord, s->shader ); } static qhandle_t effect_getId( effect_t * e ) { return ((e->seqNum&0xFFFF)<<16) | ((e-effects.effects+1)&0xFFFF); } static effect_t * effect_get( qhandle_t h ) { if ( h!=0 && h!=-1 ) { int index = (h&0xFFFF)-1; int seqNum = (h>>16)&0xFFFF; effect_t * e = effects.effects + index; if ( e->seqNum == seqNum ) return e; } return 0; } void UI_Effect_Init() { int i; for ( i=0; i= lengthof( effects.freelist ) ) return 0; return effects.freelist[ effects.count++ ]; } void UI_Effect_SetFlag( qhandle_t h, int flags ) { effect_t *e = effect_get( h ); if ( e ) e->flags |= flags; } void UI_Effect_ClearFlag( qhandle_t h, int flags ) { effect_t * e = effect_get( h ); if( e ) e->flags &= ~flags; } int UI_Effect_GetFlags( qhandle_t h ) { effect_t *e = effect_get( h ); return e ? e->flags : 0; } int UI_Effect_GetTouchCount( qhandle_t h ) { effect_t *e = effect_get( h ); if( !e ) return 0; return e->touch; } int UI_Effect_SetTouchCount( qhandle_t h, int newTouch ) { int ret; effect_t *e = effect_get( h ); if( !e ) return 0; ret = e->touch; e->touch = newTouch; return ret; } effectDef_t * UI_Effect_Find( const char * name ) { int lower = 0; int upper = DC->Assets.effectCount; int i; if ( DC->Assets.effectCount == 0 ) return 0; for ( i=(lower+upper)>>1; ( lower<=upper ); ) { int c = Q_stricmp( DC->Assets.effects[ i ]->name, name ); if ( c == 0 ) break; else if ( c > 0 ) upper = i-1; else lower = i+1; i = (lower+upper)>>1; } if ( lower <= upper ) return DC->Assets.effects[ i ]; return 0; } static void UI_Effect_PrintLine( effect_t * e, const lineInfo_t * line, fontInfo_t * font, float italic, float x, float y, float fontScale ) { int i; float rgba[4]; x += line->ox; italic = (italic * fontScale) / (float)font->glyphs[ 'A' ].height; Vec4_Cpy( rgba, line->startColor ); for( i = 0; i < line->count; i++ ) { if( Q_IsColorString( line->text + i ) ) { i++; if( line->text[i] == '-' ) Vec4_Cpy( rgba, line->defaultColor ); else Vec4_Cpy( rgba, g_color_table[ColorIndex( line->text[i] )] ); continue; } else { float step = 0.0f; if( line->text[i] == ' ' ) step = (font->glyphs[' '].xSkip * fontScale) + line->sa; else { glyphInfo_t * glyph = font->glyphs + line->text[i]; rectDef_t r; vec4_t uv; if( e->spriteCount >= lengthof( e->sprites ) ) break; r.x = x; r.y = y - (float)glyph->top * fontScale; r.w = glyph->imageWidth * fontScale; r.h = glyph->imageHeight * fontScale; uv[ 0 ] = glyph->s; uv[ 1 ] = glyph->t; uv[ 2 ] = glyph->s2; uv[ 3 ] = glyph->t2; e->sprites[ e->spriteCount++ ] = sprite_create_rect( &r, glyph->glyph, rgba, uv ); step = glyph->xSkip * fontScale; } x += step; } } } static void UI_Effect_Print2( effect_t * e, fontInfo_t * font, const char * text, float x, float y ) { int i; float fontScale = e->def->textscale * font->glyphScale; float oy = 0.0f; float h; float lineHeight = font->glyphs[ 'A' ].height * fontScale; float lineSpace; float italic; lineInfo_t lines[ 16 ]; int lineCount; int lim = e->def->maxChars ? e->def->maxChars : -1; lineCount = trap_R_FlowText( font, fontScale, e->def->forecolor, e->def->forecolor, text, lim, e->def->textalignx, e->rect.w - x, lines, lengthof(lines) ); lineSpace = lineHeight * 0.4f; h = lineCount * lineHeight + ((lineCount-1)*lineSpace); // vertically justify text switch ( e->def->textaligny ) { case TEXT_ALIGN_CENTER: oy = (e->rect.h - h)*0.5f; break; // middle case TEXT_ALIGN_RIGHT: oy = (e->rect.h - h); break; // bottom case TEXT_ALIGN_JUSTIFY: lineHeight += (e->rect.h - h) / (float)lineCount; break; } italic = (e->def->textStyle&ITEM_TEXTSTYLE_ITALIC)?4.0f:0.0f; for( i = 0; i < lineCount; i++ ) { float ly = y + oy + (lineHeight * (i+1)) + (lineSpace * i); UI_Effect_PrintLine( e, lines + i, font, italic, x, ly, fontScale ); } } static void UI_Effect_Print( effect_t * e, const char * text, float x, float y ) { fontInfo_t f; trap_R_GetFont( e->def->font, e->def->textscale, &f ); UI_Effect_Print2( e, &f, text, x, y ); } static void AttachCurveToSprite( effect_t * e, int sprite, spriteProp_t prop, qhandle_t curve ) { if ( sprite == -1 ) { int i; for ( i=0; ispriteCount; i++ ) sprite_attach_curve( e->sprites[ i ], prop, curve ); } else sprite_attach_curve( e->sprites[ sprite ], prop, curve ); } static void SetSpriteProp( effect_t * e, int sprite, spriteProp_t prop, float value ) { if ( sprite == -1 ) { int i; for ( i=0; ispriteCount; i++ ) sprite_set_base_prop( e->sprites[ i ], prop, value ); } else sprite_set_base_prop( e->sprites[ sprite ], prop, value ); } static void BakeSprite( effect_t * e, int sprite, spriteProp_t prop ) { if ( sprite == -1 ) { int i; for ( i=0; ispriteCount; i++ ) sprite_bake( e->sprites[ i ], prop ); } else sprite_bake( e->sprites[ sprite ], prop ); } static void effect_eval( effect_t *e ) { int i; for( i = 0; i < EP_NUM_PROPS; i++ ) { int j; e->propVals[i] = e->baseVals[i]; for( j = 0; j < lengthof( e->props[i] ); j++ ) { animCurve_t *cv = curve_get( e->props[i][j] ); if( !cv ) continue; switch( i ) { case EP_SCALE_X: case EP_SCALE_Y: e->propVals[i] *= curve_get_value( e->props[i][j] ); break; default: e->propVals[i] += curve_get_value( e->props[i][j] ); break; } } } } static void effect_attach_curve( effect_t *e, effectProp_t prop, qhandle_t curve ) { int i; for( i = 0; i < lengthof( e->props[prop] ); i++ ) { if( !curve_get( e->props[prop][i] ) ) { e->props[prop][i] = curve; break; } } } typedef struct duration_t { float time; qboolean hold; } duration_t; static duration_t ParseDuration( const char **s ) { duration_t ret; const char *tmp; ret.time = fatof( COM_ParseExt( s, qfalse ) ); tmp = *s; if( Q_stricmp( COM_ParseExt( &tmp, qfalse ), "hold" ) == 0 ) { ret.hold = qtrue; *s = tmp; } else ret.hold = qfalse; return ret; } static float ParseAngle( const char **s ) { char *tok = COM_ParseExt( s, qfalse ); if( Q_stricmp( tok, "rand" ) == 0 ) { return Q_random() * M_PI * 2; } return DEG2RAD( fatof( tok ) ); } /* Isct_RayLineComp Find the intersection between a ray originating at o, with a vector v, and a perpendicular line that crosses l. */ qboolean Isct_RayLineComp( float o, float d, float l, float *isctT ) { float t; if( fabsf( d ) < 0.001F ) { if( o == l ) { if( isctT ) *isctT = 0; return qtrue; } return qfalse; } t = (l - o) / d; if( isctT ) *isctT = t; return t >= 0 ? qtrue : qfalse; } qboolean Isct_RayLeavingBox( const vec2_t ro, const vec2_t rd, const rectDef_t *rc, float *isctT ) { float tmin = 0; //ASSUME: that the ray originates inside the rectangle. //ASSUME: that rd != < 0, 0 > if( fabsf( rd[0] ) > 0.001F ) { if( rd[0] < 0 ) { //only check the left side if( !Isct_RayLineComp( ro[0], rd[0], rc->x, &tmin ) ) return qfalse; } else { //only the right if( !Isct_RayLineComp( ro[0], rd[0], rc->x + rc->w, &tmin ) ) return qfalse; } } if( fabsf( rd[1] ) > 0.001F ) { float t; if( rd[1] < 0 ) { //only check the top side if( !Isct_RayLineComp( ro[1], rd[1], rc->y, &t ) ) return qfalse; } else { //only the right if( !Isct_RayLineComp( ro[1], rd[1], rc->y + rc->h, &t ) ) return qfalse; } if( t < tmin ) tmin = t; } if( isctT ) *isctT = tmin; return qtrue; } qboolean Rect_PlaceOutsideOfRect( rectDef_t *rc, const rectDef_t *ro, const vec2_t v, float *isctT ) { float t; vec2_t o; //first find the spot on rc that's opposite the center along -v o[0] = rc->x + rc->w * 0.5F; o[1] = rc->y + rc->h * 0.5F; //use v instead of -v (just negate t after), works since ray origin = center of rect if( !Isct_RayLeavingBox( o, v, rc, &t ) ) return qfalse; o[0] += v[0] * -t; o[1] += v[1] * -t; //now find out how far along we have to go to get this point outside the outer rect if( !Isct_RayLeavingBox( o, v, ro, &t ) ) { //the rect is already outside if( isctT ) *isctT = 0; return qtrue; } if( isctT ) *isctT = t; //move the rectangle rc->x += v[0] * t; rc->y += v[1] * t; return qtrue; } static void AttachCurveToSpriteCB( effect_t *e, stackFrame_t *sf, int prop, qhandle_t curve ) { AttachCurveToSprite( e, sf->i, prop, curve ); } static void AttachCurveToEffectCB( effect_t *e, stackFrame_t *sf, int prop, qhandle_t curve ) { REF_PARAM( sf ); effect_attach_curve( e, prop, curve ); } #define EnsureCurveCount( newCount, stmt ) \ if( e->curveCount + (newCount) >= lengthof( e->curves ) ) \ stmt; \ else \ (void)0 #define EnsureCurveCount_return( newCount ) EnsureCurveCount( newCount, return ) #define EnsureCurveCount_break( newCount ) EnsureCurveCount( newCount, break ) typedef void (*AttachCurves_t)( effect_t *e, stackFrame_t *sf, int prop, qhandle_t curve ); static void AnimEffect_SlideDirected( AttachCurves_t attach, effect_t *e, stackFrame_t *sf, qboolean in ) { vec2_t v; rectDef_t ro, ri; qhandle_t c; float angle = ParseAngle( &sf->pc ); duration_t dur = ParseDuration( &sf->pc ); EnsureCurveCount_return( 2 ); ro.x = -(DC->glconfig.xbias / DC->glconfig.xscale); ro.w = 640 + (DC->glconfig.xbias / DC->glconfig.xscale) * 2.0F; ro.y = 0; ro.h = 480; v[0] = cosf( angle ); v[1] = -sinf( angle ); ri = e->rect; if( !Rect_PlaceOutsideOfRect( &ri, &ro, v, NULL ) ) //degenerate rectangle... return; if( in ) { c = curve_create_lerp( DC->realTime, ri.x - e->rect.x, 0.0F, dur.time, dur.hold ); if( c ) { e->curves[ e->curveCount++ ] = c; attach( e, sf, CP_POSITION_X, c ); } c = curve_create_lerp( DC->realTime, ri.y - e->rect.y, 0.0F, dur.time, dur.hold ); if( c ) { e->curves[ e->curveCount++ ] = c; attach( e, sf, CP_POSITION_Y, c ); } } else { c = curve_create_lerp( DC->realTime, 0.0F, ri.x - e->rect.x, dur.time, dur.hold ); if( c ) { e->curves[ e->curveCount++ ] = c; attach( e, sf, CP_POSITION_X, c ); } c = curve_create_lerp( DC->realTime, 0.0F, ri.y - e->rect.y, dur.time, dur.hold ); if( c ) { e->curves[ e->curveCount++ ] = c; attach( e, sf, CP_POSITION_Y, c ); } } } static void effect_exec( effect_t * e ) { stackFrame_t * sf; if( e->def->flags & ED_DIEWITHMENU ) { if ( e->item && !(((menuDef_t*)e->item->parent)->window.flags & WINDOW_VISIBLE) ) { e->flags |= EF_DELETEME; return; } } if ( e->flags & EF_NORECT ) return; if ( e->sleeping > 0 ) { e->sleeping -= DC->frameTime; if ( e->sleeping > 0 ) return; } if ( e->waitfor ) { e->waitfor &= ~e->flags; if ( e->waitfor ) return; } sf = &e->stack[ e->sp - 1 ]; for ( ;; ) { char * t = COM_ParseExt( &sf->pc, qtrue ); if ( t[ 0 ] == '\0' ) break; switch( SWITCHSTRING( t ) ) { // for case CS('f','o','r',0): { const char * pc; int start = 0; // eat '<' pc = COM_ParseExt( &sf->pc, qfalse ); if ( pc[0] != '<' ) { start = atoi( pc ); COM_ParseExt( &sf->pc, qfalse ); } pc = sf->pc; sf++; sf->i = start; sf->pc = pc; } break; // > - end of loop case CS('>',0,0,0): { sf->i++; if ( sf->i < e->spriteCount ) sf->pc = sf[ -1 ].pc; else { const char * pc = sf->pc; sf--; sf->pc = pc; } } break; // wait case CS('w','a','i','t'): { char * t = COM_ParseExt( &sf->pc, qfalse ); switch ( SWITCHSTRING( t ) ) { //curves case CS('c','u','r','v'): e->waitfor |= EF_NOANIM; break; //nofocus case CS('n','o','f','o'): e->waitfor |= EF_NOFOCUS; break; //forever case CS('f','o','r','e'): e->waitfor |= EF_FOREVER; break; default: e->sleeping = atoi( t ); } e->sp = sf - e->stack + 1; return; } break; // die_if_nofocus case CS('d','i','e','_'): { if ( e->flags & EF_NOFOCUS ) e->flags |= EF_DELETEME; } break; // offset_from_cursor case CS('o','f','f','s'): { float xb = DC->glconfig.xbias / DC->glconfig.xscale; e->rect.x = e->def->rect.x + (float)DC->cursorx; e->rect.y = e->def->rect.y + (float)DC->cursory; e->rect.w = e->def->rect.w; e->rect.h = e->def->rect.h; // don't let rect go off screen if ( e->rect.x < -xb ) e->rect.x = -xb; if ( e->rect.y < 0.0f ) e->rect.y = 0.0f; if ( e->rect.x + e->rect.w > 640.0f + xb ) e->rect.x = 640.0f + xb - e->rect.w; if ( e->rect.y + e->rect.h > 480.0f ) e->rect.y = 480.0f - e->rect.h; } break; // sprite case CS('s','p','r','i'): { vec4_t uv = { 0.0f, 0.0f, 1.0f, 1.0f }; rectDef_t r; if( e->spriteCount >= lengthof( e->sprites ) ) break; r.x = 0.0f; r.y = 0.0f; r.w = e->rect.w; r.h = e->rect.h; e->sprites[ e->spriteCount++ ] = sprite_create_rect( &r, (e->def->shader)?e->def->shader:e->shader, e->def->backcolor, uv ); } break; case CS('d','r','a','w'): { vec4_t uv = { 0.0f, 0.0f, 1.0f, 1.0f }; rectDef_t r; qhandle_t s = (e->def->shader)?e->def->shader:e->shader; r.x = fatof( COM_ParseExt( &sf->pc, qfalse ) ); r.y = fatof( COM_ParseExt( &sf->pc, qfalse ) ); r.w = fatof( COM_ParseExt( &sf->pc, qfalse ) ); r.h = fatof( COM_ParseExt( &sf->pc, qfalse ) ); if( e->spriteCount >= lengthof( e->sprites ) ) break; if ( s ) e->sprites[ e->spriteCount++ ] = sprite_create_rect( &r, s, e->def->backcolor, uv ); } break; // print case CS('p','r','i','n'): { float x = fatof( COM_ParseExt( &sf->pc, qfalse ) ); float y = fatof( COM_ParseExt( &sf->pc, qfalse ) ); UI_Effect_Print( e, e->text, x,y ); } break; case CS('s','e','t',0): { switch( SWITCHSTRING( COM_ParseExt( &sf->pc, qfalse ) ) ) { case CS('a',0,0,0): SetSpriteProp( e, sf->i, SP_COLOR_A, fatof( COM_ParseExt( &sf->pc, qfalse ) ) ); break; case CS( 'r', 'o', 't', 0 ): SetSpriteProp( e, sf->i, SP_ROTATION, fatof( COM_ParseExt( &sf->pc, qfalse ) ) ); break; } } break; case CS('b','a','k','e'): { switch( SWITCHSTRING( COM_ParseExt( &sf->pc, qfalse ) ) ) { case CS('s','c','a','l'): BakeSprite( e, sf->i, SP_SCALE_X ); BakeSprite( e, sf->i, SP_SCALE_Y ); break; } } break; /* Animation launchers. The commands that actuall attach curves and kick off animations go here: */ // move case CS('m','o','v','e'): { qhandle_t c; float x = fatof( COM_ParseExt( &sf->pc, qfalse ) ); float y = fatof( COM_ParseExt( &sf->pc, qfalse ) ); EnsureCurveCount_break( 2 ); c = curve_create_constant( DC->realTime, x, 0.0f ); if( c ) { e->curves[ e->curveCount++ ] = c; AttachCurveToSprite( e, sf->i, SP_POSITION_X, c ); } c = curve_create_constant( DC->realTime, y, 0.0f ); if( c ) { e->curves[ e->curveCount++ ] = c; AttachCurveToSprite( e, sf->i, SP_POSITION_Y, c ); } } break; // fade case CS('f','a','d','e'): { switch( SWITCHSTRING( COM_ParseExt( &sf->pc, qfalse ) ) ) { // out case CS('o','u','t',0): { duration_t dur = ParseDuration( &sf->pc ); qhandle_t c; EnsureCurveCount_break( 1 ); c = curve_create_lerp( DC->realTime, 1.0f, 0.0f, dur.time, dur.hold ); if( c ) { e->curves[ e->curveCount++ ] = c; AttachCurveToSprite( e, sf->i, SP_COLOR_A, c ); } } break; // in case CS('i','n',0,0): { duration_t dur = ParseDuration( &sf->pc ); qhandle_t c; EnsureCurveCount_break( 1 ); c = curve_create_lerp( DC->realTime, 0.0f, 1.0f, dur.time, dur.hold ); if( c ) { e->curves[ e->curveCount++ ] = c; AttachCurveToSprite( e, sf->i, SP_COLOR_A, c ); } } break; } } break; // slide case CS('s','l','i','d'): { switch( SWITCHSTRING( COM_ParseExt( &sf->pc, qfalse ) ) ) { // in_from_right case CS('i','n','_','f'): { float x = 640.0f + (DC->glconfig.xbias / DC->glconfig.xscale)*2.0f; duration_t dur = ParseDuration( &sf->pc ); qhandle_t c; EnsureCurveCount_break( 1 ); c = curve_create_lerp( DC->realTime, x - e->rect.x, 0.0f, dur.time, dur.hold ); if( c ) { e->curves[ e->curveCount++ ] = c; AttachCurveToSprite( e, sf->i, SP_POSITION_X, c ); } } break; // up case CS('u','p',0,0): { float distance = fatof( COM_ParseExt( &sf->pc, qfalse ) ); duration_t dur = ParseDuration( &sf->pc ); qhandle_t c; EnsureCurveCount_break( 1 ); c = curve_create_lerp( DC->realTime, 0.0f, -distance, dur.time, dur.hold ); if( c ) { e->curves[ e->curveCount++ ] = c; AttachCurveToSprite( e, sf->i, SP_POSITION_Y, c ); } } break; //efx_slide down case CS('d','o','w','n'): { float distance = fatof( COM_ParseExt( &sf->pc, qfalse ) ); duration_t dur = ParseDuration( &sf->pc ); qhandle_t c; EnsureCurveCount_break( 1 ); c = curve_create_lerp( DC->realTime, 0.0f, distance, dur.time, dur.hold ); if( c ) { e->curves[ e->curveCount++ ] = c; AttachCurveToSprite( e, sf->i, SP_POSITION_Y, c ); } } break; case CS( 'f', 'r', 'o', 'm' ): AnimEffect_SlideDirected( AttachCurveToSpriteCB, e, sf, qtrue ); break; case CS( 't', 'o', 0, 0 ): AnimEffect_SlideDirected( AttachCurveToSpriteCB, e, sf, qfalse ); break; } } break; // scale case CS('s','c','a','l'): { int cmd = SWITCHSTRING( COM_ParseExt( &sf->pc, qfalse ) ); float start = fatof( COM_ParseExt( &sf->pc, qfalse ) ); float end = fatof( COM_ParseExt( &sf->pc, qfalse ) ); duration_t dur = ParseDuration( &sf->pc ); qhandle_t c; EnsureCurveCount_break( 1 ); c = curve_create_lerp( DC->realTime, start, end, dur.time, dur.hold ); if( !c ) break; e->curves[ e->curveCount++ ] = c; switch( cmd ) { case CS('x',0,0,0): AttachCurveToSprite( e, sf->i, SP_SCALE_X, c ); break; case CS('y',0,0,0): AttachCurveToSprite( e, sf->i, SP_SCALE_Y, c ); break; case CS('x','y',0,0): AttachCurveToSprite( e, sf->i, SP_SCALE_X, c ); AttachCurveToSprite( e, sf->i, SP_SCALE_Y, c ); break; } } break; case CS( 'r', 'o', 't', 'a' ): { float start = fatof( COM_ParseExt( &sf->pc, qfalse ) ); float end = fatof( COM_ParseExt( &sf->pc, qfalse ) ); duration_t dur = ParseDuration( &sf->pc ); qhandle_t c; EnsureCurveCount_break( 1 ); c = curve_create_lerp( DC->realTime, start, end, dur.time, dur.hold ); if( c ) { e->curves[ e->curveCount++ ] = c; AttachCurveToSprite( e, sf->i, SP_ROTATION, c ); } } break; /* Effect animation launchers. Same as above only with the efx_ prefix. These apply to the entire effect. */ case CS( 'e', 'f', 'x', '_' ): switch( SWITCHSTRING( t + 4 ) ) { //efx_move case CS('m','o','v','e'): { qhandle_t c; float x = fatof( COM_ParseExt( &sf->pc, qfalse ) ); float y = fatof( COM_ParseExt( &sf->pc, qfalse ) ); EnsureCurveCount_break( 2 ); c = curve_create_constant( DC->realTime, x, 0.0f ); if( c ) { e->curves[e->curveCount++] = c; effect_attach_curve( e, EP_POSITION_X, c ); } c = curve_create_constant( DC->realTime, y, 0.0f ); if( c ) { e->curves[e->curveCount++] = c; effect_attach_curve( e, EP_POSITION_Y, c ); } } break; //efx_slide case CS('s','l','i','d'): { switch( SWITCHSTRING( COM_ParseExt( &sf->pc, qfalse ) ) ) { //efx_slide in_from_right case CS('i','n','_','f'): { float x = 640.0f + (DC->glconfig.xbias / DC->glconfig.xscale) * 2.0F; duration_t dur = ParseDuration( &sf->pc ); qhandle_t c; EnsureCurveCount_break( 1 ); c = curve_create_lerp( DC->realTime, x - e->rect.x, 0.0F, dur.time, dur.hold ); if( c ) { e->curves[ e->curveCount++ ] = c; effect_attach_curve( e, EP_POSITION_X, c ); } } break; //efx_slide up case CS('u','p',0,0): { float distance = fatof( COM_ParseExt( &sf->pc, qfalse ) ); duration_t dur = ParseDuration( &sf->pc ); qhandle_t c; EnsureCurveCount_break( 1 ); c = curve_create_lerp( DC->realTime, 0.0f, -distance, dur.time, dur.hold ); if( c ) { e->curves[ e->curveCount++ ] = c; effect_attach_curve( e, EP_POSITION_Y, c ); } } break; //efx_slide down case CS('d','o','w','n'): { float distance = fatof( COM_ParseExt( &sf->pc, qfalse ) ); duration_t dur = ParseDuration( &sf->pc ); qhandle_t c; EnsureCurveCount_break( 1 ); c = curve_create_lerp( DC->realTime, 0.0f, distance, dur.time, dur.hold ); if( c ) { e->curves[ e->curveCount++ ] = c; effect_attach_curve( e, EP_POSITION_Y, c ); } } break; case CS( 'f', 'r', 'o', 'm' ): AnimEffect_SlideDirected( AttachCurveToEffectCB, e, sf, qtrue ); break; case CS( 't', 'o', 0, 0 ): AnimEffect_SlideDirected( AttachCurveToEffectCB, e, sf, qfalse ); break; } } break; //efx_scale case CS('s','c','a','l'): { int cmd = SWITCHSTRING( COM_ParseExt( &sf->pc, qfalse ) ); float start = fatof( COM_ParseExt( &sf->pc, qfalse ) ); float end = fatof( COM_ParseExt( &sf->pc, qfalse ) ); duration_t dur = ParseDuration( &sf->pc ); qhandle_t c; EnsureCurveCount_break( 1 ); c = curve_create_lerp( DC->realTime, start, end, dur.time, dur.hold ); if( !c ) break; e->curves[ e->curveCount++ ] = c; switch( cmd ) { case CS('x',0,0,0): effect_attach_curve( e, EP_SCALE_X, c ); break; case CS('y',0,0,0): effect_attach_curve( e, EP_SCALE_Y, c ); break; case CS('x','y',0,0): effect_attach_curve( e, EP_SCALE_X, c ); effect_attach_curve( e, EP_SCALE_Y, c ); break; } } case CS( 'r', 'o', 't', 'a' ): { float start = fatof( COM_ParseExt( &sf->pc, qfalse ) ); float end = fatof( COM_ParseExt( &sf->pc, qfalse ) ); duration_t dur = ParseDuration( &sf->pc ); qhandle_t c; EnsureCurveCount_break( 1 ); c = curve_create_lerp( DC->realTime, start, end, dur.time, dur.hold ); if( c ) { e->curves[ e->curveCount++ ] = c; effect_attach_curve( e, EP_ROTATION, c ); } } break; } break; } } sf--; e->sp = sf - e->stack + 1; } static void effect_setrect( effect_t * e, rectDef_t *r ) { e->rect = *r; } static void effect_buildmatrix( effect_t * e, vec3_t m[2] ) { float Sx, Sy, R, Tx, Ty; float Rs, Rc; float Cx, Cy; /* Scale, rotate, translate. Build a matrix that: - scales by e->props[EP_SCALE_X/Y] from the center of e->rect - rotates by e->props[EP_ROTATION] degress around the center of e->rect - translates by e->props[EP_POSITION_X/Y] */ Sx = e->propVals[EP_SCALE_X]; Sy = e->propVals[EP_SCALE_Y]; R = DEG2RAD( e->propVals[EP_ROTATION] ); Rs = sinf( R ); Rc = cosf( R ); Tx = e->propVals[EP_POSITION_X] + e->rect.x; Ty = e->propVals[EP_POSITION_Y] + e->rect.y; Cx = -e->rect.w * 0.5F; Cy = -e->rect.h * 0.5F; /* This nasty beast is the matrix M = T * Tc-1 * R * S * Tc where: T is the required translation Tc is a translation that centers e->rect on the origin R is the required rotation S is the required scale */ m[0][0] = Rc * Sx; m[0][1] = -Rs * Sy; m[0][2] = Rc * Sx * Cx - Rs * Sy * Cy + Tx - Cx; m[1][0] = Rs * Sx; m[1][1] = Rc * Sy; m[1][2] = Rs * Sx * Cx + Rc * Cy * Sy + Ty - Cy; } void UI_Effect_SetJustRect( qhandle_t h, rectDef_t * r ) { effect_t * e = effect_get( h ); if ( e ) { effect_setrect( e, r ); e->flags &= ~EF_NORECT; } } void UI_Effect_SetRect( qhandle_t h, rectDef_t * r, itemDef_t * item ) { effect_t * e = effect_get( h ); if ( e ) { effect_setrect( e, r ); e->item = item; e->flags &= ~EF_NORECT; } } void UI_Effect_SetItem( qhandle_t h, itemDef_t * item ) { effect_t * e = effect_get( h ); if ( e ) e->item = item; } static effect_t* effect_create( effectDef_t *effect ) { effect_t * e = effect_alloc(); short seqNum; if( !e ) return NULL; seqNum = e->seqNum; Com_Memset( e, 0, sizeof(*e) ); e->seqNum = seqNum; e->def = effect; e->baseVals[EP_SCALE_X] = 1; e->baseVals[EP_SCALE_Y] = 1; return e; } qhandle_t UI_Effect_SpawnText( rectDef_t * r, effectDef_t * effect, const char * text, qhandle_t shader ) { if ( effect ) { effect_t * e = effect_create( effect ); if( !e ) return 0; if ( r ) effect_setrect( e, r ); else e->flags |= EF_NORECT; if ( text ) Q_strncpyz( e->text, text, sizeof(e->text) ); e->shader = shader; e->stack[ 0 ].pc = (char*)e->def->action; e->stack[ 0 ].i = -1; e->sp = 1; effect_exec( e ); return effect_getId( e ); } return 0; } qboolean UI_Effect_IsAlive( qhandle_t h ) { return effect_get(h) != 0; } effectDef_t* UI_Effect_GetEffect( qhandle_t h ) { effect_t *e = effect_get( h ); if( !e ) return NULL; return e->def; } void UI_Effects_Update() { curves_update(); sprites_update(); } extern int trap_Key_GetCatcher( void ); void UI_Effects_Draw( menuDef_t * menu ) { int i,j; int ui_active = trap_Key_GetCatcher() & KEYCATCH_UI; for ( i=0; idef->flags & ED_DIEWITHMENU ) { if( e->item && !(((menuDef_t*)e->item->parent)->window.flags & WINDOW_VISIBLE) ) e->flags |= EF_DELETEME; } // delete effect if( e->sp == 0 || e->flags & EF_DELETEME ) { for ( j=0; jcurveCount; j++ ) curve_destroy( e->curves[ j ] ); for ( j=0; jspriteCount; j++ ) sprite_destroy( e->sprites[ j ] ); effects.freelist[ i-- ] = effects.freelist[ --effects.count ]; effects.freelist[ effects.count ] = e; e->seqNum++; continue; } if ( (e->def->flags & ED_NODRAW_INUI) && ui_active ) continue; if( menu ) { //only want items attached to menu, excluding ALWAYSONTOP if( !e->item || e->item->parent != menu || e->def->flags & ED_ALWAYSONTOP ) continue; // do not draw if not attached to the current menu, or always on top } else { //want unattached items except where always on top if( e->item ) { if ( !(e->def->flags & ED_ALWAYSONTOP) ) continue; // do not draw if attached to a menu and not on top if ( !(((menuDef_t*)e->item->parent)->window.flags & WINDOW_VISIBLE) ) continue; // do not draw if associated menu is not drawing } } if ( e->sp > 0 ) effect_exec( e ); if( e->sp == 0 || e->flags & EF_DELETEME ) //effect just died, skip (will get eaten on next frame) continue; e->flags |= EF_NOANIM; // flag as nothing animating for ( j=0; jcurveCount; j++ ) { animCurve_t * c = curve_get( e->curves[ j ] ); if ( !c ) continue; if ( !c->flags&CURVE_DONE ) { if ( c->type != CT_CONSTANT ) e->flags &= ~EF_NOANIM; // something is animating curve_eval( c, DC->realTime ); continue; } // don't delete curves set to hold if ( c->type == CT_LERP && c->data.lerp.hold ) continue; c->flags |= CURVE_DELETEME; e->curves[ j-- ] = e->curves[ --e->curveCount ]; } effect_eval( e ); effect_buildmatrix( e, m ); for ( j=0; jspriteCount; j++ ) { if ( !(e->flags & EF_NORECT) ) { sprite_t * s = sprite_get( e->sprites[ j ] ); if ( s ) { sprite_eval( s ); sprite_draw( s, m ); } } } switch( e->flags & EF_ONEFRAME_BITS ) { case EF_ONEFRAME_RECT: e->flags |= EF_NORECT; break; case EF_ONEFRAME_NOFOCUS: e->flags |= EF_NOFOCUS; break; } if( e->touch ) e->touch--; } }