diff options
-rw-r--r-- | Makefile | 1 | ||||
-rw-r--r-- | depend | 12 | ||||
-rw-r--r-- | src/cgame/cg_ents.c | 2 | ||||
-rw-r--r-- | src/cgame/cg_local.h | 239 | ||||
-rw-r--r-- | src/cgame/cg_main.c | 7 | ||||
-rw-r--r-- | src/cgame/cg_particles.c | 1641 | ||||
-rw-r--r-- | src/cgame/cg_players.c | 8 | ||||
-rw-r--r-- | src/cgame/cg_view.c | 1 | ||||
-rw-r--r-- | src/game/bg_public.h | 4 | ||||
-rw-r--r-- | src/game/g_main.c | 29 | ||||
-rw-r--r-- | src/game/q_shared.h | 7 |
11 files changed, 1943 insertions, 8 deletions
@@ -66,6 +66,7 @@ CGOBJ = \ $(CGDIRNAME)/cg_mp3decoder.o \ $(CGDIRNAME)/cg_scanner.o \ $(CGDIRNAME)/cg_trails.o \ + $(CGDIRNAME)/cg_particles.o \ $(UIDIRNAME)/ui_shared.o UIOBJ = \ @@ -267,6 +267,10 @@ releasei386-glibc/cgame/cg_mp3decoder.o: src/cgame/cg_mp3decoder.c src/cgame/cg_ src/cgame/cg_local.h src/game/q_shared.h src/game/surfaceflags.h \ src/cgame/tr_types.h src/game/bg_public.h src/game/tremulous.h \ src/cgame/cg_public.h src/ui/ui_shared.h src/ui/keycodes.h ui/menudef.h +releasei386-glibc/cgame/cg_particles.o: src/cgame/cg_particles.c src/cgame/cg_local.h \ + src/game/q_shared.h src/game/surfaceflags.h src/cgame/tr_types.h \ + src/game/bg_public.h src/game/tremulous.h src/cgame/cg_public.h \ + src/ui/ui_shared.h src/ui/keycodes.h ui/menudef.h releasei386-glibc/cgame/cg_players.o: src/cgame/cg_players.c src/cgame/cg_local.h \ src/game/q_shared.h src/game/surfaceflags.h src/cgame/tr_types.h \ src/game/bg_public.h src/game/tremulous.h src/cgame/cg_public.h \ @@ -363,6 +367,10 @@ debugi386-glibc/cgame/cg_mp3decoder.o: src/cgame/cg_mp3decoder.c src/cgame/cg_mp src/cgame/cg_local.h src/game/q_shared.h src/game/surfaceflags.h \ src/cgame/tr_types.h src/game/bg_public.h src/game/tremulous.h \ src/cgame/cg_public.h src/ui/ui_shared.h src/ui/keycodes.h ui/menudef.h +debugi386-glibc/cgame/cg_particles.o: src/cgame/cg_particles.c src/cgame/cg_local.h \ + src/game/q_shared.h src/game/surfaceflags.h src/cgame/tr_types.h \ + src/game/bg_public.h src/game/tremulous.h src/cgame/cg_public.h \ + src/ui/ui_shared.h src/ui/keycodes.h ui/menudef.h debugi386-glibc/cgame/cg_players.o: src/cgame/cg_players.c src/cgame/cg_local.h \ src/game/q_shared.h src/game/surfaceflags.h src/cgame/tr_types.h \ src/game/bg_public.h src/game/tremulous.h src/cgame/cg_public.h \ @@ -459,6 +467,10 @@ qvm/cgame/cg_mp3decoder.asm: src/cgame/cg_mp3decoder.c src/cgame/cg_mp3decoder.h src/cgame/cg_local.h src/game/q_shared.h src/game/surfaceflags.h \ src/cgame/tr_types.h src/game/bg_public.h src/game/tremulous.h \ src/cgame/cg_public.h src/ui/ui_shared.h src/ui/keycodes.h ui/menudef.h +qvm/cgame/cg_particles.asm: src/cgame/cg_particles.c src/cgame/cg_local.h \ + src/game/q_shared.h src/game/surfaceflags.h src/cgame/tr_types.h \ + src/game/bg_public.h src/game/tremulous.h src/cgame/cg_public.h \ + src/ui/ui_shared.h src/ui/keycodes.h ui/menudef.h qvm/cgame/cg_players.asm: src/cgame/cg_players.c src/cgame/cg_local.h \ src/game/q_shared.h src/game/surfaceflags.h src/cgame/tr_types.h \ src/game/bg_public.h src/game/tremulous.h src/cgame/cg_public.h \ diff --git a/src/cgame/cg_ents.c b/src/cgame/cg_ents.c index c7590ca0..e7a8bd22 100644 --- a/src/cgame/cg_ents.c +++ b/src/cgame/cg_ents.c @@ -386,7 +386,7 @@ static void CG_Missile( centity_t *cent ) ent.renderfx = weapon->missileRenderfx | RF_NOSHADOW; // convert direction of travel into axis - if ( VectorNormalize2( s1->pos.trDelta, ent.axis[ 0 ] ) == 0 ) + if( VectorNormalize2( s1->pos.trDelta, ent.axis[ 0 ] ) == 0 ) ent.axis[ 0 ][ 2 ] = 1; RotateAroundDirection( ent.axis, cg.time / 4 ); diff --git a/src/cgame/cg_local.h b/src/cgame/cg_local.h index 941c3d31..281d732b 100644 --- a/src/cgame/cg_local.h +++ b/src/cgame/cg_local.h @@ -948,6 +948,8 @@ typedef struct sfxHandle_t buildableRepairSound; sfxHandle_t buildableRepairedSound; + + qhandle_t testParticleSystem; } cgMedia_t; @@ -1008,7 +1010,7 @@ typedef struct qhandle_t gameModels[ MAX_MODELS ]; qhandle_t gameShaders[ MAX_SHADERS ]; sfxHandle_t gameSounds[ MAX_SOUNDS ]; - + int numInlineModels; qhandle_t inlineDrawModel[ MAX_MODELS ]; vec3_t inlineModelMidpoints[ MAX_MODELS ]; @@ -1462,6 +1464,241 @@ void CG_DefragmentMemory( void ); // qboolean S_decodeMP3( char *mp3File, char *wavFile ); +// +// cg_particles.c +// + +#define MAX_SHADER_FRAMES 64 +#define MAX_EJECTORS_PER_SYSTEM 4 +#define MAX_PARTICLES_PER_EJECTOR 4 + +#define MAX_BASEPARTICLE_SYSTEMS 512 +#define MAX_BASEPARTICLE_EJECTORS MAX_BASEPARTICLE_SYSTEMS*MAX_EJECTORS_PER_SYSTEM +#define MAX_BASEPARTICLES MAX_BASEPARTICLE_EJECTORS*MAX_PARTICLES_PER_EJECTOR + +#define MAX_PARTICLE_SYSTEMS 16 +#define MAX_PARTICLE_EJECTORS MAX_PARTICLE_SYSTEMS*MAX_EJECTORS_PER_SYSTEM +#define MAX_PARTICLES MAX_PARTICLE_EJECTORS*8 + +#define PARTICLES_INFINITE -1 +#define PARTICLES_SAME_AS_INITIAL -2 + +/* +=============== + +COMPILE TIME STRUCTURES + +=============== +*/ + +typedef enum +{ + PMT_STATIC, + PMT_TAG, + PMT_CENT_ANGLES +} pMoveType_t; + +typedef enum +{ + PMD_LINEAR, + PMD_POINT +} pDirType_t; + +typedef struct pMoveValues_u +{ + pDirType_t dirType; + + //PMD_LINEAR + vec3_t dir; + float dirRandAngle; + + //PMD_POINT + vec3_t point; + float pointRandAngle; + + float mag; + float magRandFrac; + + float parentVelFrac; + float parentVelFracRandFrac; +} pMoveValues_t; + +typedef struct pLerpValues_s +{ + int delay; + float delayRandFrac; + + float initial; + float initialRandFrac; + + float final; + float finalRandFrac; + + float randFrac; +} pLerpValues_t; + +//particle template +typedef struct baseParticle_s +{ + float randDisplacement; + + pMoveType_t velMoveType; + pMoveValues_t velMoveValues; + + pMoveType_t accMoveType; + pMoveValues_t accMoveValues; + + int lifeTime; + float lifeTimeRandFrac; + + float bounceFrac; + float bounceFracRandFrac; + + pLerpValues_t radius; + pLerpValues_t alpha; + pLerpValues_t rotation; + + //particle invariant stuff + char shaderNames[ MAX_QPATH ][ MAX_SHADER_FRAMES ]; + qhandle_t shaders[ MAX_SHADER_FRAMES ]; + int numFrames; + float framerate; + + qboolean overdrawProtection; + qboolean realLight; +} baseParticle_t; + + +//ejector template +typedef struct baseParticleEjector_s +{ + baseParticle_t *particles[ MAX_PARTICLES_PER_EJECTOR ]; + int numParticles; + + pLerpValues_t eject; //zero period indicates creation of all particles at once + + int totalParticles; //can be infinite + float totalParticlesRandFrac; +} baseParticleEjector_t; + + +//particle system template +typedef struct baseParticleSystem_s +{ + char name[ MAX_QPATH ]; + baseParticleEjector_t *ejectors[ MAX_EJECTORS_PER_SYSTEM ]; + int numEjectors; + + qboolean registered; //whether or not the assets for this particle have been loaded +} baseParticleSystem_t; + + +/* +=============== + +RUN TIME STRUCTURES + +=============== +*/ + +typedef enum +{ + PSA_STATIC, + PSA_TAG, + PSA_CENT_ORIGIN +} psAttachmentType_t; + + +typedef struct psAttachment_s +{ + qboolean staticValid; + qboolean tagValid; + qboolean centValid; + + //PMT_STATIC + vec3_t origin; + + //PMT_TAG + refEntity_t re; + refEntity_t parent; + qhandle_t model; + char tagName[ MAX_STRING_CHARS ]; + + //PMT_CENT_ANGLES + centity_t *cent; +} psAttachment_t; + + +typedef struct particleSystem_s +{ + baseParticleSystem_t *class; + + psAttachmentType_t attachType; + psAttachment_t attachment; + qboolean attached; //is the particle system attached to anything + + qboolean enabled; //necessary? + + qboolean valid; +} particleSystem_t; + + +typedef struct particleEjector_s +{ + baseParticleEjector_t *class; + particleSystem_t *parent; + + pLerpValues_t ejectPeriod; + + int count; + int totalParticles; + + int nextEjectionTime; + + qboolean valid; +} particleEjector_t; + + +//used for actual particle evaluation +typedef struct particle_s +{ + baseParticle_t *class; + particleEjector_t *parent; + + int birthTime; + int lifeTime; + + vec3_t origin; + vec3_t velocity; + + pMoveType_t accMoveType; + pMoveValues_t accMoveValues; + + int lastEvalTime; + + pLerpValues_t radius; + pLerpValues_t alpha; + pLerpValues_t rotation; + + qboolean valid; +} particle_t; + +void CG_LoadParticleSystems( void ); +qhandle_t CG_RegisterParticleSystem( char *name ); + +particleSystem_t *CG_SpawnNewParticleSystem( qhandle_t psHandle ); +void CG_DestroyParticleSystem( particleSystem_t *ps ); + +void CG_SetParticleSystemCent( particleSystem_t *ps, centity_t *cent ); +void CG_AttachParticleSystemToCent( particleSystem_t *ps ); +void CG_SetParticleSystemTag( particleSystem_t *ps, refEntity_t parent, qhandle_t model, char *tagName ); +void CG_AttachParticleSystemToTag( particleSystem_t *ps ); +void CG_SetParticleSystemOrigin( particleSystem_t *ps, vec3_t origin ); +void CG_AttachParticleSystemOrigin( particleSystem_t *ps ); + +void CG_AddParticles( void ); + + //=============================================== // diff --git a/src/cgame/cg_main.c b/src/cgame/cg_main.c index 22db4dba..01e8bedd 100644 --- a/src/cgame/cg_main.c +++ b/src/cgame/cg_main.c @@ -788,6 +788,8 @@ static void CG_RegisterGraphics( void ) cgs.media.wakeMarkShader = trap_R_RegisterShader( "wake" ); cgs.media.bloodMarkShader = trap_R_RegisterShader( "bloodMark" ); + cgs.media.testParticleSystem = CG_RegisterParticleSystem( "blah" ); + // register the inline models cgs.numInlineModels = trap_CM_NumInlineModels( ); @@ -1677,8 +1679,11 @@ void CG_Init( int serverMessageNum, int serverCommandSequence, int clientNum ) cg.loading = qtrue; // force players to load instead of defer + CG_LoadParticleSystems( ); + CG_UpdateMediaFraction( 0.05f ); + CG_RegisterSounds( ); - CG_UpdateMediaFraction( 0.33f ); + CG_UpdateMediaFraction( 0.60f ); CG_RegisterGraphics( ); CG_UpdateMediaFraction( 0.90f ); diff --git a/src/cgame/cg_particles.c b/src/cgame/cg_particles.c new file mode 100644 index 00000000..99a9e352 --- /dev/null +++ b/src/cgame/cg_particles.c @@ -0,0 +1,1641 @@ +#include "cg_local.h" + +static baseParticleSystem_t baseParticleSystems[ MAX_BASEPARTICLE_SYSTEMS ]; +static baseParticleEjector_t baseParticleEjectors[ MAX_BASEPARTICLE_EJECTORS ]; +static baseParticle_t baseParticles[ MAX_BASEPARTICLES ]; +static int numBaseParticleSystems = 0; +static int numBaseParticleEjectors = 0; +static int numBaseParticles = 0; + +static particleSystem_t particleSystems[ MAX_PARTICLE_SYSTEMS ]; +static particleEjector_t particleEjectors[ MAX_PARTICLE_EJECTORS ]; +static particle_t particles[ MAX_PARTICLES ]; + +/* +=============== +CG_LerpValues + +Lerp between two values +=============== +*/ +static float CG_LerpValues( float a, float b, float f ) +{ + if( b == PARTICLES_SAME_AS_INITIAL ) + return a; + else + return ( (a) + (f) * ( (b) - (a) ) ); +} + +/* +=============== +CG_RandomiseValue + +Randomise some value by some variance +=============== +*/ +static float CG_RandomiseValue( float value, float variance ) +{ + if( value != 0.0f ) + return value * ( 1.0f + ( random( ) * variance ) ); + else + return random( ) * variance; +} + +/* +=============== +CG_SpreadVector + +Randomly spread a vector by some amount +=============== +*/ +static void CG_SpreadVector( vec3_t v, float spread ) +{ + vec3_t p, r1, r2; + float randomSpread = crandom( ) * spread; + float randomRotation = random( ) * 360.0f; + + PerpendicularVector( p, v ); + + RotatePointAroundVector( r1, p, v, randomSpread ); + RotatePointAroundVector( r2, v, r1, randomRotation ); + + VectorCopy( r2, v ); +} + +/* +=============== +CG_SpawnNewParticle + +Introduce a new particle into the world +=============== +*/ +static particle_t *CG_SpawnNewParticle( baseParticle_t *bp, particleEjector_t *parent ) +{ + int i, j, start; + particle_t *p = NULL; + particleEjector_t *pe = parent; + particleSystem_t *ps = parent->parent; + vec3_t forward; + + for( i = 0; i < MAX_PARTICLES; i++ ) + { + p = &particles[ i ]; + + if( !p->valid ) + { + //found a free slot + p->class = bp; + p->parent = pe; + + p->birthTime = cg.time; + p->lifeTime = (int)CG_RandomiseValue( (float)bp->lifeTime, bp->lifeTimeRandFrac ); + + p->radius.delay = (int)CG_RandomiseValue( (float)bp->radius.delay, bp->radius.delayRandFrac ); + p->radius.initial = CG_RandomiseValue( bp->radius.initial, bp->radius.initialRandFrac ); + p->radius.final = CG_RandomiseValue( bp->radius.final, bp->radius.finalRandFrac ); + + p->alpha.delay = (int)CG_RandomiseValue( (float)bp->alpha.delay, bp->alpha.delayRandFrac ); + p->alpha.initial = CG_RandomiseValue( bp->alpha.initial, bp->alpha.initialRandFrac ); + p->alpha.final = CG_RandomiseValue( bp->alpha.final, bp->alpha.finalRandFrac ); + + p->rotation.delay = (int)CG_RandomiseValue( (float)bp->rotation.delay, bp->rotation.delayRandFrac ); + p->rotation.initial = CG_RandomiseValue( bp->rotation.initial, bp->rotation.initialRandFrac ); + p->rotation.final = CG_RandomiseValue( bp->rotation.final, bp->rotation.finalRandFrac ); + + switch( ps->attachType ) + { + case PSA_STATIC: + if( !ps->attachment.staticValid ) + return NULL; + + VectorCopy( ps->attachment.origin, p->origin ); + break; + + case PSA_TAG: + if( !ps->attachment.tagValid ) + return NULL; + + CG_PositionRotatedEntityOnTag( &ps->attachment.re, &ps->attachment.parent, + ps->attachment.model, ps->attachment.tagName ); + VectorCopy( ps->attachment.re.origin, p->origin ); + break; + + case PSA_CENT_ORIGIN: + if( !ps->attachment.centValid ) + return NULL; + + VectorCopy( ps->attachment.cent->lerpOrigin, p->origin ); + break; + } + + + for( j = 0; j <= 2; j++ ) + p->origin[ j ] += ( crandom( ) * bp->randDisplacement ); + + switch( bp->velMoveType ) + { + case PMT_STATIC: + + if( bp->velMoveValues.dirType == PMD_POINT ) + VectorSubtract( bp->velMoveValues.point, p->origin, p->velocity ); + else if( bp->velMoveValues.dirType == PMD_LINEAR ) + VectorCopy( bp->velMoveValues.dir, p->velocity ); + + break; + + case PMT_TAG: + + if( !ps->attachment.tagValid ) + return NULL; + + if( bp->velMoveValues.dirType == PMD_POINT ) + VectorSubtract( ps->attachment.re.origin, p->origin, p->velocity ); + else if( bp->velMoveValues.dirType == PMD_LINEAR ) + VectorCopy( ps->attachment.re.axis[ 0 ], p->velocity ); + + break; + + case PMT_CENT_ANGLES: + + if( !ps->attachment.centValid ) + return NULL; + + if( bp->velMoveValues.dirType == PMD_POINT ) + VectorSubtract( ps->attachment.cent->lerpOrigin, p->origin, p->velocity ); + else if( bp->velMoveValues.dirType == PMD_LINEAR ) + { + AngleVectors( ps->attachment.cent->lerpAngles, forward, NULL, NULL ); + VectorCopy( forward, p->velocity ); + } + + break; + } + + VectorNormalize( p->velocity ); + CG_SpreadVector( p->velocity, bp->velMoveValues.dirRandAngle ); + VectorScale( p->velocity, + CG_RandomiseValue( bp->velMoveValues.mag, bp->velMoveValues.magRandFrac ), + p->velocity ); + + if( ps->attachment.centValid ) + { + VectorMA( p->velocity, + CG_RandomiseValue( bp->velMoveValues.parentVelFrac, bp->velMoveValues.parentVelFracRandFrac ), + ps->attachment.cent->currentState.pos.trDelta, p->velocity ); + } + + p->lastEvalTime = cg.time; + + p->valid = qtrue; + break; + } + } + + return p; +} + + +/* +=============== +CG_SpawnNewParticles + +Check if there are any ejectors that should be +introducing new particles +=============== +*/ +static void CG_SpawnNewParticles( void ) +{ + int i, j; + particleSystem_t *ps; + particleEjector_t *pe; + baseParticleEjector_t *bpe; + float lerpFrac; + + for( i = 0; i < MAX_PARTICLE_EJECTORS; i++ ) + { + pe = &particleEjectors[ i ]; + ps = pe->parent; + + if( pe->valid ) + { + //a non attached particle system can't make particles + if( !ps->attached ) + continue; + + bpe = particleEjectors[ i ].class; + + while( pe->nextEjectionTime <= cg.time && + ( pe->count > 0 || pe->totalParticles == PARTICLES_INFINITE ) ) + { + for( j = 0; j < bpe->numParticles; j++ ) + CG_SpawnNewParticle( bpe->particles[ j ], pe ); + + if( pe->count > 0 ) + pe->count--; + + //calculate next ejection time + lerpFrac = 1.0 - ( (float)pe->count / (float)pe->totalParticles ); + pe->nextEjectionTime = cg.time + CG_RandomiseValue( + CG_LerpValues( pe->ejectPeriod.initial, + pe->ejectPeriod.final, + lerpFrac ), + pe->ejectPeriod.randFrac ); + } + + if( !pe->parent->valid ) + pe->valid = qfalse; + + if( pe->count == 0 ) + pe->valid = qfalse; + } + } +} + + +/* +=============== +CG_SpawnNewParticleEjector + +Allocate a new particle ejector +=============== +*/ +static particleEjector_t *CG_SpawnNewParticleEjector( baseParticleEjector_t *bpe, + particleSystem_t *parent ) +{ + int i, start; + particleEjector_t *pe = NULL; + particleSystem_t *ps = parent; + + for( i = 0; i < MAX_PARTICLE_EJECTORS; i++ ) + { + pe = &particleEjectors[ i ]; + + if( !pe->valid ) + { + //found a free slot + pe->class = bpe; + pe->parent = ps; + + pe->ejectPeriod.initial = bpe->eject.initial; + pe->ejectPeriod.final = bpe->eject.final; + pe->ejectPeriod.randFrac = bpe->eject.randFrac; + + pe->nextEjectionTime = cg.time + + (int)CG_RandomiseValue( (float)bpe->eject.delay, bpe->eject.delayRandFrac ); + pe->count = pe->totalParticles = + (int)CG_RandomiseValue( (float)bpe->totalParticles, bpe->totalParticlesRandFrac ); + + pe->valid = qtrue; + break; + } + } + + return pe; +} + + +/* +=============== +CG_SpawnNewParticleSystem + +Allocate a new particle system +=============== +*/ +particleSystem_t *CG_SpawnNewParticleSystem( qhandle_t psHandle ) +{ + int i, j, start; + particleSystem_t *ps = NULL; + baseParticleSystem_t *bps = &baseParticleSystems[ psHandle ]; + + if( !bps->registered ) + { + CG_Printf( S_COLOR_RED "ERROR: particle system %s has not been registered yet\n", + bps->name ); + return NULL; + } + + for( i = 0; i < MAX_PARTICLE_SYSTEMS; i++ ) + { + ps = &particleSystems[ i ]; + + if( !ps->valid ) + { + //found a free slot + ps->class = bps; + + for( j = 0; j < bps->numEjectors; j++ ) + CG_SpawnNewParticleEjector( bps->ejectors[ j ], ps ); + + ps->valid = qtrue; + break; + } + } + + return ps; +} + +/* +=============== +CG_RegisterParticleSystem + +Load the shaders required for a particle system +=============== +*/ +qhandle_t CG_RegisterParticleSystem( char *name ) +{ + int i, j, k, l; + baseParticleSystem_t *bps; + baseParticleEjector_t *bpe; + baseParticle_t *bp; + + for( i = 0; i < MAX_PARTICLE_SYSTEMS; i++ ) + { + bps = &baseParticleSystems[ i ]; + + if( !strcmp( bps->name, name ) ) + { + for( j = 0; j < bps->numEjectors; j++ ) + { + bpe = bps->ejectors[ j ]; + + for( l = 0; l < bpe->numParticles; l++ ) + { + bp = bpe->particles[ l ]; + + for( k = 0; k < bp->numFrames; k++ ) + bp->shaders[ k ] = trap_R_RegisterShader( bp->shaderNames[ k ] ); + } + } + + bps->registered = qtrue; + + return i; + } + } + + return 0; +} + + +/* +=============== +atof_neg + +atof with an allowance for negative values +=============== +*/ +static float atof_neg( char *token, qboolean allowNegative ) +{ + float value; + + value = atof( token ); + + if( !allowNegative && value < 0.0f ) + { + CG_Printf( S_COLOR_YELLOW "WARNING: negative value %f is now allowed here, " + "replaced with 1.0f\n", value ); + value = 1.0f; + } + + return value; +} + +/* +=============== +atoi_neg + +atoi with an allowance for negative values +=============== +*/ +static int atoi_neg( char *token, qboolean allowNegative ) +{ + int value; + + value = atoi( token ); + + if( !allowNegative && value < 0 ) + { + CG_Printf( S_COLOR_YELLOW "WARNING: negative value %d is now allowed here, " + "replaced with 1\n", value ); + value = 1; + } + + return value; +} + + +/* +=============== +CG_ParseValueAndVariance + +Parse a value and its random variance +=============== +*/ +static void CG_ParseValueAndVariance( char *token, float *value, float *variance ) +{ + char valueBuffer[ 16 ]; + char varianceBuffer[ 16 ]; + char *variancePtr = NULL, *varEndPointer = NULL; + float localValue = 0.0f; + float localVariance = 0.0f; + + Q_strncpyz( valueBuffer, token, sizeof( valueBuffer ) ); + Q_strncpyz( varianceBuffer, token, sizeof( varianceBuffer ) ); + + variancePtr = strchr( valueBuffer, '~' ); + + //variance included + if( variancePtr ) + { + variancePtr[ 0 ] = '\0'; + variancePtr++; + + localValue = atof_neg( valueBuffer, qfalse ); + + varEndPointer = strchr( variancePtr, '%' ); + + if( varEndPointer ) + { + varEndPointer[ 0 ] = '\0'; + localVariance = atof_neg( variancePtr, qfalse ) / 100.0f; + } + else + { + if( localValue != 0.0f ) + localVariance = atof_neg( variancePtr, qfalse ) / localValue; + else + localVariance = atof_neg( variancePtr, qfalse ); + } + } + else + localValue = atof_neg( valueBuffer, qfalse ); + + if( value != NULL ) + *value = localValue; + + if( variance != NULL ) + *variance = localVariance; +} + + +/* +=============== +CG_ParseParticle + +Parse a particle section +=============== +*/ +static qboolean CG_ParseParticle( baseParticle_t *bp, char **text_p ) +{ + char *token; + float number, randFrac; + + + // read optional parameters + while( 1 ) + { + token = COM_Parse( text_p ); + + if( !token ) + break; + + if( !Q_stricmp( token, "" ) ) + return qfalse; + + if( !Q_stricmp( token, "bounce" ) ) + { + token = COM_Parse( text_p ); + if( !token ) + break; + + CG_ParseValueAndVariance( token, &number, &randFrac ); + + bp->bounceFrac = number; + bp->bounceFracRandFrac = randFrac; + + continue; + } + else if( !Q_stricmp( token, "shader" ) ) + { + token = COM_Parse( text_p ); + if( !token ) + break; + + if( !Q_stricmp( token, "sync" ) ) + bp->framerate = 0.0f; + else + bp->framerate = atof_neg( token, qfalse ); + + token = COM_ParseExt( text_p, qfalse ); + + while( token && token[ 0 ] != 0 ) + { + Q_strncpyz( bp->shaderNames[ bp->numFrames++ ], token, MAX_QPATH ); + token = COM_ParseExt( text_p, qfalse ); + } + + if( !token ) + break; + + continue; + } + /// + else if( !Q_stricmp( token, "velocityType" ) ) + { + token = COM_Parse( text_p ); + if( !token ) + break; + + if( !Q_stricmp( token, "static" ) ) + bp->velMoveType = PMT_STATIC; + else if( !Q_stricmp( token, "tag" ) ) + bp->velMoveType = PMT_TAG; + else if( !Q_stricmp( token, "cent" ) ) + bp->velMoveType = PMT_CENT_ANGLES; + + continue; + } + else if( !Q_stricmp( token, "velocityDir" ) ) + { + token = COM_Parse( text_p ); + if( !token ) + break; + + if( !Q_stricmp( token, "linear" ) ) + bp->velMoveValues.dirType = PMD_LINEAR; + else if( !Q_stricmp( token, "point" ) ) + bp->velMoveValues.dirType = PMD_POINT; + + continue; + } + else if( !Q_stricmp( token, "velocityMagnitude" ) ) + { + token = COM_Parse( text_p ); + if( !token ) + break; + + CG_ParseValueAndVariance( token, &number, &randFrac ); + + bp->velMoveValues.mag = number; + bp->velMoveValues.magRandFrac = randFrac; + + continue; + } + else if( !Q_stricmp( token, "parentVelocityFraction" ) ) + { + token = COM_Parse( text_p ); + if( !token ) + break; + + CG_ParseValueAndVariance( token, &number, &randFrac ); + + bp->velMoveValues.parentVelFrac = number; + bp->velMoveValues.parentVelFracRandFrac = randFrac; + + continue; + } + else if( !Q_stricmp( token, "velocity" ) ) + { + token = COM_Parse( text_p ); + if( !token ) + break; + + bp->velMoveValues.dir[ 0 ] = atof_neg( token, qtrue ); + + token = COM_Parse( text_p ); + if( !token ) + break; + + bp->velMoveValues.dir[ 1 ] = atof_neg( token, qtrue ); + + token = COM_Parse( text_p ); + if( !token ) + break; + + bp->velMoveValues.dir[ 2 ] = atof_neg( token, qtrue ); + + token = COM_Parse( text_p ); + if( !token ) + break; + + CG_ParseValueAndVariance( token, NULL, &randFrac ); + + bp->velMoveValues.dirRandAngle = randFrac; + + continue; + } + else if( !Q_stricmp( token, "velocityPoint" ) ) + { + token = COM_Parse( text_p ); + if( !token ) + break; + + bp->velMoveValues.point[ 0 ] = atof_neg( token, qtrue ); + + token = COM_Parse( text_p ); + if( !token ) + break; + + bp->velMoveValues.point[ 1 ] = atof_neg( token, qtrue ); + + token = COM_Parse( text_p ); + if( !token ) + break; + + bp->velMoveValues.point[ 2 ] = atof_neg( token, qtrue ); + + token = COM_Parse( text_p ); + if( !token ) + break; + + CG_ParseValueAndVariance( token, NULL, &randFrac ); + + bp->velMoveValues.pointRandAngle = randFrac; + + continue; + } + /// + else if( !Q_stricmp( token, "accelerationType" ) ) + { + token = COM_Parse( text_p ); + if( !token ) + break; + + if( !Q_stricmp( token, "static" ) ) + bp->accMoveType = PMT_STATIC; + else if( !Q_stricmp( token, "tag" ) ) + bp->accMoveType = PMT_TAG; + else if( !Q_stricmp( token, "cent" ) ) + bp->accMoveType = PMT_CENT_ANGLES; + + continue; + } + else if( !Q_stricmp( token, "accelerationDir" ) ) + { + token = COM_Parse( text_p ); + if( !token ) + break; + + if( !Q_stricmp( token, "linear" ) ) + bp->accMoveValues.dirType = PMD_LINEAR; + else if( !Q_stricmp( token, "point" ) ) + bp->accMoveValues.dirType = PMD_POINT; + + continue; + } + else if( !Q_stricmp( token, "accelerationMagnitude" ) ) + { + token = COM_Parse( text_p ); + if( !token ) + break; + + CG_ParseValueAndVariance( token, &number, &randFrac ); + + bp->accMoveValues.mag = number; + bp->accMoveValues.magRandFrac = randFrac; + + continue; + } + else if( !Q_stricmp( token, "acceleration" ) ) + { + token = COM_Parse( text_p ); + if( !token ) + break; + + bp->accMoveValues.dir[ 0 ] = atof_neg( token, qtrue ); + + token = COM_Parse( text_p ); + if( !token ) + break; + + bp->accMoveValues.dir[ 1 ] = atof_neg( token, qtrue ); + + token = COM_Parse( text_p ); + if( !token ) + break; + + bp->accMoveValues.dir[ 2 ] = atof_neg( token, qtrue ); + + token = COM_Parse( text_p ); + if( !token ) + break; + + CG_ParseValueAndVariance( token, NULL, &randFrac ); + + bp->accMoveValues.dirRandAngle = randFrac; + + continue; + } + else if( !Q_stricmp( token, "accelerationPoint" ) ) + { + token = COM_Parse( text_p ); + if( !token ) + break; + + bp->accMoveValues.point[ 0 ] = atof_neg( token, qtrue ); + + token = COM_Parse( text_p ); + if( !token ) + break; + + bp->accMoveValues.point[ 1 ] = atof_neg( token, qtrue ); + + token = COM_Parse( text_p ); + if( !token ) + break; + + bp->accMoveValues.point[ 2 ] = atof_neg( token, qtrue ); + + token = COM_Parse( text_p ); + if( !token ) + break; + + CG_ParseValueAndVariance( token, NULL, &randFrac ); + + bp->accMoveValues.pointRandAngle = randFrac; + + continue; + } + /// + else if( !Q_stricmp( token, "randomDisplacement" ) ) + { + token = COM_Parse( text_p ); + if( !token ) + break; + + bp->randDisplacement = atof_neg( token, qtrue ); + + continue; + } + else if( !Q_stricmp( token, "overdrawProtection" ) ) + { + bp->overdrawProtection = qtrue; + + continue; + } + else if( !Q_stricmp( token, "realLight" ) ) + { + bp->realLight = qtrue; + + continue; + } + else if( !Q_stricmp( token, "radius" ) ) + { + token = COM_Parse( text_p ); + if( !token ) + break; + + CG_ParseValueAndVariance( token, &number, &randFrac ); + + bp->radius.delay = (int)number; + bp->radius.delayRandFrac = randFrac; + + token = COM_Parse( text_p ); + if( !token ) + break; + + CG_ParseValueAndVariance( token, &number, &randFrac ); + + bp->radius.initial = number; + bp->radius.initialRandFrac = randFrac; + + token = COM_Parse( text_p ); + if( !token ) + break; + + if( !Q_stricmp( token, "-" ) ) + { + bp->radius.final = PARTICLES_SAME_AS_INITIAL; + bp->radius.finalRandFrac = 0.0f; + } + else + { + CG_ParseValueAndVariance( token, &number, &randFrac ); + + bp->radius.final = number; + bp->radius.finalRandFrac = randFrac; + } + + continue; + } + else if( !Q_stricmp( token, "alpha" ) ) + { + token = COM_Parse( text_p ); + if( !token ) + break; + + CG_ParseValueAndVariance( token, &number, &randFrac ); + + bp->alpha.delay = (int)number; + bp->alpha.delayRandFrac = randFrac; + + token = COM_Parse( text_p ); + if( !token ) + break; + + CG_ParseValueAndVariance( token, &number, &randFrac ); + + bp->alpha.initial = number; + bp->alpha.initialRandFrac = randFrac; + + token = COM_Parse( text_p ); + if( !token ) + break; + + if( !Q_stricmp( token, "-" ) ) + { + bp->alpha.final = PARTICLES_SAME_AS_INITIAL; + bp->alpha.finalRandFrac = 0.0f; + } + else + { + CG_ParseValueAndVariance( token, &number, &randFrac ); + + bp->alpha.final = number; + bp->alpha.finalRandFrac = randFrac; + } + + continue; + } + else if( !Q_stricmp( token, "rotation" ) ) + { + token = COM_Parse( text_p ); + if( !token ) + break; + + CG_ParseValueAndVariance( token, &number, &randFrac ); + + bp->rotation.delay = (int)number; + bp->rotation.delayRandFrac = randFrac; + + token = COM_Parse( text_p ); + if( !token ) + break; + + CG_ParseValueAndVariance( token, &number, &randFrac ); + + bp->rotation.initial = number; + bp->rotation.initialRandFrac = randFrac; + + token = COM_Parse( text_p ); + if( !token ) + break; + + if( !Q_stricmp( token, "-" ) ) + { + bp->rotation.final = PARTICLES_SAME_AS_INITIAL; + bp->rotation.finalRandFrac = 0.0f; + } + else + { + CG_ParseValueAndVariance( token, &number, &randFrac ); + + bp->rotation.final = number; + bp->rotation.finalRandFrac = randFrac; + } + + continue; + } + else if( !Q_stricmp( token, "lifeTime" ) ) + { + token = COM_Parse( text_p ); + if( !token ) + break; + + CG_ParseValueAndVariance( token, &number, &randFrac ); + + bp->lifeTime = (int)number; + bp->lifeTimeRandFrac = randFrac; + + continue; + } + else if( !Q_stricmp( token, "}" ) ) + return qtrue; //reached the end of this particle + else + { + CG_Printf( S_COLOR_RED "ERROR: unknown token '%s' in particle\n", token ); + return qfalse; + } + } + + return qfalse; +} + + +/* +=============== +CG_ParseParticleEjector + +Parse a particle ejector section +=============== +*/ +static qboolean CG_ParseParticleEjector( baseParticleEjector_t *bpe, char **text_p ) +{ + char *token; + float number, randFrac; + + // read optional parameters + while( 1 ) + { + token = COM_Parse( text_p ); + + if( !token ) + break; + + if( !Q_stricmp( token, "" ) ) + return qfalse; + + if( !Q_stricmp( token, "{" ) ) + { + if( !CG_ParseParticle( &baseParticles[ numBaseParticles ], text_p ) ) + { + CG_Printf( S_COLOR_RED "ERROR: failed to parse particle\n" ); + return qfalse; + } + + if( bpe->numParticles == MAX_PARTICLES_PER_EJECTOR ) + { + CG_Printf( S_COLOR_RED "ERROR: ejector has > %d particles\n", MAX_PARTICLES_PER_EJECTOR ); + return qfalse; + } + else if( numBaseParticles == MAX_BASEPARTICLES ) + { + CG_Printf( S_COLOR_RED "ERROR: maximum number of particles (%d) reached\n", MAX_BASEPARTICLES ); + return qfalse; + } + else + { + //start parsing particles again + bpe->particles[ bpe->numParticles ] = &baseParticles[ numBaseParticles ]; + bpe->numParticles++; + numBaseParticles++; + } + continue; + } + else if( !Q_stricmp( token, "delay" ) ) + { + token = COM_Parse( text_p ); + if( !token ) + break; + + CG_ParseValueAndVariance( token, &number, &randFrac ); + + bpe->eject.delay = (int)number; + bpe->eject.delayRandFrac = randFrac; + + continue; + } + else if( !Q_stricmp( token, "period" ) ) + { + token = COM_Parse( text_p ); + if( !token ) + break; + + bpe->eject.initial = atoi_neg( token, qfalse ); + + token = COM_Parse( text_p ); + if( !token ) + break; + + if( !Q_stricmp( token, "-" ) ) + bpe->eject.final = PARTICLES_SAME_AS_INITIAL; + else + bpe->eject.final = atoi_neg( token, qfalse ); + + token = COM_Parse( text_p ); + if( !token ) + break; + + CG_ParseValueAndVariance( token, NULL, &bpe->eject.randFrac ); + + continue; + } + else if( !Q_stricmp( token, "count" ) ) + { + token = COM_Parse( text_p ); + if( !token ) + break; + + if( !Q_stricmp( token, "infinite" ) ) + { + bpe->totalParticles = PARTICLES_INFINITE; + bpe->totalParticlesRandFrac = 0.0f; + } + else + { + CG_ParseValueAndVariance( token, &number, &randFrac ); + + bpe->totalParticles = (int)number; + bpe->totalParticlesRandFrac = randFrac; + } + + continue; + } + else if( !Q_stricmp( token, "particle" ) ) //acceptable text + continue; + else if( !Q_stricmp( token, "}" ) ) + return qtrue; //reached the end of this particle ejector + else + { + CG_Printf( S_COLOR_RED "ERROR: unknown token '%s' in particle ejector\n", token ); + return qfalse; + } + } + + return qfalse; +} + + +/* +=============== +CG_ParseParticleSystem + +Parse a particle system section +=============== +*/ +static qboolean CG_ParseParticleSystem( baseParticleSystem_t *bps, char **text_p ) +{ + char *token; + int i; + baseParticleEjector_t *bpe; + + // read optional parameters + while( 1 ) + { + token = COM_Parse( text_p ); + + if( !token ) + break; + + if( !Q_stricmp( token, "" ) ) + return qfalse; + + if( !Q_stricmp( token, "{" ) ) + { + if( !CG_ParseParticleEjector( &baseParticleEjectors[ numBaseParticleEjectors ], text_p ) ) + { + CG_Printf( S_COLOR_RED "ERROR: failed to parse particle ejector\n" ); + return qfalse; + } + + bpe = &baseParticleEjectors[ numBaseParticleEjectors ]; + + //check for infinite count + zero period + if( bpe->totalParticles == PARTICLES_INFINITE && + ( bpe->eject.initial == 0.0f || bpe->eject.final == 0.0f ) ) + { + CG_Printf( S_COLOR_RED "ERROR: ejector with 'count infinite' potentially has zero period\n" ); + return qfalse; + } + + if( bps->numEjectors == MAX_EJECTORS_PER_SYSTEM ) + { + CG_Printf( S_COLOR_RED "ERROR: particle system has > %d ejectors\n", MAX_EJECTORS_PER_SYSTEM ); + return qfalse; + } + else if( numBaseParticleEjectors == MAX_BASEPARTICLE_EJECTORS ) + { + CG_Printf( S_COLOR_RED "ERROR: maximum number of particle ejectors (%d) reached\n", MAX_BASEPARTICLE_EJECTORS ); + return qfalse; + } + else + { + //start parsing ejectors again + bps->ejectors[ bps->numEjectors ] = &baseParticleEjectors[ numBaseParticleEjectors ]; + bps->numEjectors++; + numBaseParticleEjectors++; + } + continue; + } + else if( !Q_stricmp( token, "ejector" ) ) //acceptable text + continue; + else if( !Q_stricmp( token, "}" ) ) + return qtrue; //reached the end of this particle system + else + { + CG_Printf( S_COLOR_RED "ERROR: unknown token '%s' in particle system %s\n", token, bps->name ); + return qfalse; + } + } + + return qfalse; +} + +/* +=============== +CG_ParseParticleFile + +Load the particle systems from a particle file +=============== +*/ +static qboolean CG_ParseParticleFile( const char *fileName ) +{ + char *text_p; + int i; + int len; + char *token; + char text[ 20000 ]; + char psName[ MAX_QPATH ]; + qboolean psNameSet = qfalse; + fileHandle_t f; + + // load the file + len = trap_FS_FOpenFile( fileName, &f, FS_READ ); + if( len <= 0 ) + return qfalse; + + if( len >= sizeof( text ) - 1 ) + { + CG_Printf( S_COLOR_RED "ERROR: particle file %s too long\n", fileName ); + return qfalse; + } + + trap_FS_Read( text, len, f ); + text[ len ] = 0; + trap_FS_FCloseFile( f ); + + // parse the text + text_p = text; + + // read optional parameters + while( 1 ) + { + token = COM_Parse( &text_p ); + + if( !token ) + break; + + if( !Q_stricmp( token, "" ) ) + return qfalse; + + if( !Q_stricmp( token, "{" ) ) + { + if( psNameSet ) + { + //check for name space clashes + for( i = 0; i < numBaseParticleSystems; i++ ) + { + if( !Q_stricmp( baseParticleSystems[ i ].name, psName ) ) + { + CG_Printf( S_COLOR_RED "ERROR: a particle system is already named %s\n", psName ); + return qfalse; + } + } + + Q_strncpyz( baseParticleSystems[ numBaseParticleSystems ].name, psName, MAX_QPATH ); + + if( !CG_ParseParticleSystem( &baseParticleSystems[ numBaseParticleSystems ], &text_p ) ) + { + CG_Printf( S_COLOR_RED "ERROR: %s: failed to parse particle system %s\n", fileName, psName ); + return qfalse; + } + + //start parsing particle systems again + psNameSet = qfalse; + + if( numBaseParticleSystems == MAX_BASEPARTICLE_SYSTEMS ) + { + CG_Printf( S_COLOR_RED "ERROR: maximum number of particle systems (%d) reached\n", MAX_BASEPARTICLE_EJECTORS ); + return qfalse; + } + else + numBaseParticleSystems++; + + continue; + } + else + { + CG_Printf( S_COLOR_RED "ERROR: unamed particle system\n" ); + return qfalse; + } + } + + if( !psNameSet ) + { + Q_strncpyz( psName, token, sizeof( psName ) ); + psNameSet = qtrue; + } + else + { + CG_Printf( S_COLOR_RED "ERROR: particle system already named\n" ); + return qfalse; + } + } + + return qtrue; +} + + +/* +=============== +CG_LoadParticleSystems + +Load particle systems from .particle files +=============== +*/ +void CG_LoadParticleSystems( void ) +{ + int i; + const char *s[ MAX_PARTICLE_FILES ]; + + numBaseParticleSystems = 0; + numBaseParticleEjectors = 0; + numBaseParticles = 0; + + for( i = 0; i < MAX_PARTICLE_FILES; i++ ) + { + s[ i ] = CG_ConfigString( CS_PARTICLE_FILES + i ); + + if( strlen( s[ i ] ) > 0 ) + { + CG_Printf( "...loading '%s'\n", s[ i ] ); + CG_ParseParticleFile( s[ i ] ); + } + else + break; + } +} + + +/* +=============== +CG_AttachParticleSystemToCent + +Attach a particle system to a centity_t +=============== +*/ +void CG_AttachParticleSystemToCent( particleSystem_t *ps ) +{ + ps->attachType = PSA_CENT_ORIGIN; + ps->attached = qtrue; +} + +/* +=============== +CG_SetParticleSystemCent + +Set a particle system attachment means +=============== +*/ +void CG_SetParticleSystemCent( particleSystem_t *ps, centity_t *cent ) +{ + ps->attachment.centValid = qtrue; + ps->attachment.cent = cent; +} + +/* +=============== +CG_AttachParticleSystemToTag + +Attach a particle system to a model tag +=============== +*/ +void CG_AttachParticleSystemToTag( particleSystem_t *ps ) +{ + ps->attachType = PSA_TAG; + ps->attached = qtrue; +} + +/* +=============== +CG_SetParticleSystemToTag + +Set a particle system attachment means +=============== +*/ +void CG_SetParticleSystemTag( particleSystem_t *ps, refEntity_t parent, + qhandle_t model, char *tagName ) +{ + ps->attachment.tagValid = qtrue; + ps->attachment.parent = parent; + ps->attachment.model = model; + strncpy( ps->attachment.tagName, tagName, MAX_STRING_CHARS ); +} + +/* +=============== +CG_AttachParticleSystemToOrigin + +Attach a particle system to a point in space +=============== +*/ +void CG_AttachParticleSystemToOrigin( particleSystem_t *ps ) +{ + ps->attachType = PSA_STATIC; + ps->attached = qtrue; +} + +/* +=============== +CG_SetParticleSystemOrigin + +Set a particle system attachment means +=============== +*/ +void CG_SetParticleSystemOrigin( particleSystem_t *ps, vec3_t origin ) +{ + ps->attachment.staticValid = qtrue; + VectorCopy( origin, ps->attachment.origin ); +} + + +/* +=============== +CG_DestroyParticleSystem + +Destroy a particle system +=============== +*/ +void CG_DestroyParticleSystem( particleSystem_t *ps ) +{ + ps->valid = qfalse; +} + +/* +=============== +CG_GarbageCollectParticleSystems + +Destroy inactive particle systems +=============== +*/ +static void CG_GarbageCollectParticleSystems( void ) +{ + //FIXME: test this and make sure it's efficient + int i, j, count; + particleSystem_t *ps; + particleEjector_t *pe; + + for( i = 0; i < MAX_PARTICLE_SYSTEMS; i++ ) + { + ps = &particleSystems[ i ]; + count = 0; + + for( j = 0; j < MAX_PARTICLE_EJECTORS; j++ ) + { + pe = &particleEjectors[ j ]; + + if( pe->valid && pe->parent == ps ) + count++; + } + + if( !count ) + ps->valid = qfalse; + } +} + + +/* +=============== +CG_CalculateTimeFrac + +Calculate the fraction of time passed +=============== +*/ +static float CG_CalculateTimeFrac( int birth, int life, int delay ) +{ + float frac; + + frac = ( (float)cg.time - (float)( birth + delay ) ) / (float)( life - delay ); + + if( frac < 0.0f ) + frac = 0.0f; + else if( frac > 1.0f ) + frac = 1.0f; + + return frac; +} + +/* +=============== +CG_EvaluateParticlePhysics + +Compute the physics on a specific particle +=============== +*/ +static void CG_EvaluateParticlePhysics( particle_t *p ) +{ + particleSystem_t *ps = p->parent->parent; + baseParticle_t *bp = p->class; + vec3_t acceleration, forward, newOrigin; + vec3_t mins, maxs; + float deltaTime, bounce, radius, dot; + trace_t trace; + + switch( bp->accMoveType ) + { + case PMT_STATIC: + + if( bp->accMoveValues.dirType == PMD_POINT ) + VectorSubtract( bp->accMoveValues.point, p->origin, acceleration ); + else if( bp->accMoveValues.dirType == PMD_LINEAR ) + VectorCopy( bp->accMoveValues.dir, acceleration ); + + break; + + case PMT_TAG: + + if( !ps->attachment.tagValid ) + return; + + if( bp->accMoveValues.dirType == PMD_POINT ) + VectorSubtract( ps->attachment.re.origin, p->origin, acceleration ); + else if( bp->accMoveValues.dirType == PMD_LINEAR ) + VectorCopy( ps->attachment.re.axis[ 0 ], acceleration ); + + break; + + case PMT_CENT_ANGLES: + + if( !ps->attachment.centValid ) + return; + + if( bp->accMoveValues.dirType == PMD_POINT ) + VectorSubtract( ps->attachment.cent->lerpOrigin, p->origin, acceleration ); + else if( bp->accMoveValues.dirType == PMD_LINEAR ) + { + AngleVectors( ps->attachment.cent->lerpAngles, forward, NULL, NULL ); + VectorCopy( forward, acceleration ); + } + + break; + } + +#define MAX_ACC_RADIUS 1000.0f + + if( bp->accMoveValues.dirType == PMD_POINT ) + { + //FIXME: so this fall off is a bit... odd -- it works.. + float r2 = DotProduct( acceleration, acceleration ); // = radius^2 + float scale = ( MAX_ACC_RADIUS - r2 ) / MAX_ACC_RADIUS; + + if( scale > 1.0f ) + scale = 1.0f; + else if( scale < 0.1f ) + scale = 0.1f; + + scale *= CG_RandomiseValue( bp->accMoveValues.mag, bp->accMoveValues.magRandFrac ); + + VectorNormalize( acceleration ); + CG_SpreadVector( acceleration, bp->accMoveValues.dirRandAngle ); + VectorScale( acceleration, scale, acceleration ); + } + else if( bp->accMoveValues.dirType == PMD_LINEAR ) + { + VectorNormalize( acceleration ); + CG_SpreadVector( acceleration, bp->accMoveValues.dirRandAngle ); + VectorScale( acceleration, + CG_RandomiseValue( bp->accMoveValues.mag, bp->accMoveValues.magRandFrac ), + acceleration ); + } + + radius = CG_LerpValues( p->radius.initial, + p->radius.final, + CG_CalculateTimeFrac( p->birthTime, + p->lifeTime, + p->radius.delay ) ); + + VectorSet( mins, -radius, -radius, -radius ); + VectorSet( maxs, radius, radius, radius ); + + bounce = CG_RandomiseValue( bp->bounceFrac, bp->bounceFracRandFrac ); + + deltaTime = (float)( cg.time - p->lastEvalTime ) * 0.001; + VectorMA( p->velocity, deltaTime, acceleration, p->velocity ); + VectorMA( p->origin, deltaTime, p->velocity, newOrigin ); + p->lastEvalTime = cg.time; + + CG_Trace( &trace, p->origin, mins, maxs, newOrigin, -1, CONTENTS_SOLID ); + + //not hit anything or not a collider + if( trace.fraction == 1.0f || bounce == 0.0f ) + { + VectorCopy( newOrigin, p->origin ); + return; + } + + //remove particles that get into a CONTENTS_NODROP brush + if( ( trap_CM_PointContents( trace.endpos, 0 ) & CONTENTS_NODROP ) || trace.startsolid ) + { + p->valid = qfalse; + return; + } + + //reflect the velocity on the trace plane + dot = DotProduct( p->velocity, trace.plane.normal ); + VectorMA( p->velocity, -2.0f * dot, trace.plane.normal, p->velocity ); + + VectorScale( p->velocity, bounce, p->velocity ); + + VectorCopy( trace.endpos, p->origin ); +} + + +/* +=============== +CG_RenderParticle + +Actually render a particle +=============== +*/ +static void CG_RenderParticle( particle_t *p ) +{ + refEntity_t re; + float timeFrac, frac; + int index; + baseParticle_t *bp = p->class; + vec3_t alight, dlight, lightdir; + int i; + + memset( &re, 0, sizeof( refEntity_t ) ); + + for( i = 0; i <= 3; re.shaderRGBA[ i++ ] = 0xFF ); + + timeFrac = CG_CalculateTimeFrac( p->birthTime, p->lifeTime, 0 ); + + re.reType = RT_SPRITE; + re.shaderTime = p->birthTime / 1000.0f; //FIXME: allow user to change? + + re.shaderRGBA[ 3 ] = (byte)( (float)0xFF * + CG_LerpValues( p->alpha.initial, + p->alpha.final, + CG_CalculateTimeFrac( p->birthTime, + p->lifeTime, + p->alpha.delay ) ) ); + + re.radius = CG_LerpValues( p->radius.initial, + p->radius.final, + CG_CalculateTimeFrac( p->birthTime, + p->lifeTime, + p->radius.delay ) ); + + re.rotation = CG_LerpValues( p->rotation.initial, + p->rotation.final, + CG_CalculateTimeFrac( p->birthTime, + p->lifeTime, + p->rotation.delay ) ); + + // if the view would be "inside" the sprite, kill the sprite + // so it doesn't add too much overdraw + if( Distance( p->origin, cg.refdef.vieworg ) < re.radius && bp->overdrawProtection ) + return; + + //apply environmental lighting to the particle + if( bp->realLight ) + { + trap_R_LightForPoint( p->origin, alight, dlight, lightdir ); + for( i = 0; i <= 2; i++ ) + re.shaderRGBA[ i ] = (int)alight[ i ]; + } + + if( bp->framerate == 0.0f ) + { + //sync animation time to lifeTime of particle + index = (int)( timeFrac * bp->numFrames ); + re.customShader = bp->shaders[ index ]; + } + else + { + //looping animation + index = (int)( bp->framerate * timeFrac * p->lifeTime * 0.001 ) % bp->numFrames; + re.customShader = bp->shaders[ index ]; + } + + VectorCopy( p->origin, re.origin ); + + trap_R_AddRefEntityToScene( &re ); +} + +/* +=============== +CG_AddParticles + +Add particles to the scene +=============== +*/ +void CG_AddParticles( void ) +{ + int i; + particle_t *p; + + /*CG_GarbageCollectParticleSystems( );*/ + + //check each ejector and introduce any new particles + CG_SpawnNewParticles( ); + + for( i = 0; i < MAX_PARTICLES; i++ ) + { + p = &particles[ i ]; + + if( p->valid ) + { + if( p->birthTime + p->lifeTime > cg.time ) + { + //particle is active + CG_EvaluateParticlePhysics( p ); + CG_RenderParticle( p ); + } + else + p->valid = qfalse; + } + } +} diff --git a/src/cgame/cg_players.c b/src/cgame/cg_players.c index cfb015ac..bbb8569d 100644 --- a/src/cgame/cg_players.c +++ b/src/cgame/cg_players.c @@ -1850,7 +1850,15 @@ void CG_Player( centity_t *cent ) vec3_t angles; int held = es->modelindex; pTeam_t team = es->powerups & 0xFF; + static particleSystem_t *partSystem = NULL; + if( partSystem == NULL ) + { + CG_Printf( S_COLOR_GREEN "particle system created\n" ); + partSystem = CG_SpawnNewParticleSystem( cgs.media.testParticleSystem ); + CG_SetParticleSystemCent( partSystem, cent ); + CG_AttachParticleSystemToCent( partSystem ); + } // the client number is stored in clientNum. It can't be derived // from the entity number, because a single client may have // multiple corpses on the level using the same clientinfo diff --git a/src/cgame/cg_view.c b/src/cgame/cg_view.c index b01c273f..81ce0e58 100644 --- a/src/cgame/cg_view.c +++ b/src/cgame/cg_view.c @@ -1233,6 +1233,7 @@ void CG_DrawActiveFrame( int serverTime, stereoFrame_t stereoView, qboolean demo CG_AddPacketEntities( ); // adter calcViewValues, so predicted player state is correct CG_AddMarks( ); CG_AddLocalEntities( ); + CG_AddParticles( ); //TA: wolf trails stuff CG_AddTrails( ); // this must come last, so the trails dropped this frame get drawn diff --git a/src/game/bg_public.h b/src/game/bg_public.h index 494c3cf7..920df33b 100644 --- a/src/game/bg_public.h +++ b/src/game/bg_public.h @@ -76,9 +76,9 @@ #define CS_PLAYERS (CS_SHADERS+MAX_SHADERS) #define CS_PRECACHES (CS_PLAYERS+MAX_CLIENTS) #define CS_LOCATIONS (CS_PRECACHES+MAX_CLIENTS) -/*#define CS_PARTICLES (CS_LOCATIONS+MAX_LOCATIONS) TA: never used?!*/ +#define CS_PARTICLE_FILES (CS_LOCATIONS+MAX_LOCATIONS) -#define CS_MAX (CS_LOCATIONS+MAX_LOCATIONS) +#define CS_MAX (CS_PARTICLE_FILES+MAX_PARTICLE_FILES) #if (CS_MAX) > MAX_CONFIGSTRINGS #error overflow: (CS_MAX) > MAX_CONFIGSTRINGS diff --git a/src/game/g_main.c b/src/game/g_main.c index 4283b8eb..f74f7e7d 100644 --- a/src/game/g_main.c +++ b/src/game/g_main.c @@ -402,6 +402,34 @@ void G_UpdateCvars( void ) /* +=============== +G_GenerateParticleFileList + +Make a list of particle files for each client to parse since fsr +the client does not have trap_FS_GetFileList +=============== +*/ +static void G_GenerateParticleFileList( void ) +{ + int i, numFiles, fileLen; + char fileList[ MAX_PARTICLE_FILES ]; + char fileName[ MAX_QPATH ]; + char *filePtr; + + numFiles = trap_FS_GetFileList( "scripts", ".particle", fileList, 1024 ); + filePtr = fileList; + + for( i = 0; i < numFiles; i++, filePtr += fileLen + 1 ) + { + fileLen = strlen( filePtr ); + strcpy( fileName, "scripts/" ); + strcat( fileName, filePtr ); + trap_SetConfigstring( CS_PARTICLE_FILES + i, fileName ); + } +} + + +/* ============ G_InitGame @@ -482,6 +510,7 @@ void G_InitGame( int levelTime, int randomSeed, int restart ) //TA: G_InitDamageLocations( ); + G_GenerateParticleFileList( ); //reset stages trap_Cvar_Set( "g_alienStage", va( "%d", S1 ) ); diff --git a/src/game/q_shared.h b/src/game/q_shared.h index fefeed3b..34d2ffb7 100644 --- a/src/game/q_shared.h +++ b/src/game/q_shared.h @@ -1095,9 +1095,10 @@ typedef enum { #define ENTITYNUM_MAX_NORMAL (MAX_GENTITIES-2) -#define MAX_MODELS 256 // these are sent over the net as 8 bits -#define MAX_SOUNDS 256 // so they cannot be blindly increased -#define MAX_SHADERS 256 //TA: should be in bg_public.h +#define MAX_MODELS 256 // these are sent over the net as 8 bits +#define MAX_SOUNDS 256 // so they cannot be blindly increased +#define MAX_SHADERS 128 //TA: should be in bg_public.h +#define MAX_PARTICLE_FILES 128 #define MAX_CONFIGSTRINGS 1024 |