// Ridah, cg_trails.c - draws a trail using multiple junction points #include "cg_local.h" typedef struct trailJunc_s { struct trailJunc_s *nextGlobal, *prevGlobal; // next junction in the global list it is in (free or used) struct trailJunc_s *nextJunc; // next junction in the trail struct trailJunc_s *nextHead, *prevHead; // next head junc in the world qboolean inuse, freed; int ownerIndex; qhandle_t shader; int sType; int flags; float sTex; vec3_t pos; int spawnTime, endTime; float alphaStart, alphaEnd; vec3_t colorStart, colorEnd; float widthStart, widthEnd; // current settings float alpha; float width; vec3_t color; } trailJunc_t; #define MAX_TRAILJUNCS 4096 trailJunc_t trailJuncs[MAX_TRAILJUNCS]; trailJunc_t *freeTrails, *activeTrails; trailJunc_t *headTrails; qboolean initTrails = qfalse; int numTrailsInuse; /* =============== CG_ClearTrails =============== */ void CG_ClearTrails (void) { int i; memset( trailJuncs, 0, sizeof(trailJunc_t) * MAX_TRAILJUNCS ); freeTrails = trailJuncs; activeTrails = NULL; headTrails = NULL; for (i=0 ;i0) trailJuncs[i].prevGlobal = &trailJuncs[i-1]; else trailJuncs[i].prevGlobal = NULL; trailJuncs[i].inuse = qfalse; } trailJuncs[MAX_TRAILJUNCS-1].nextGlobal = NULL; initTrails = qtrue; numTrailsInuse = 0; } /* =============== CG_SpawnTrailJunc =============== */ trailJunc_t *CG_SpawnTrailJunc( trailJunc_t *headJunc ) { trailJunc_t *j; if (!freeTrails) return NULL; if ( cg_paused.integer ) return NULL; // select the first free trail, and remove it from the list j = freeTrails; freeTrails = j->nextGlobal; if (freeTrails) freeTrails->prevGlobal = NULL; j->nextGlobal = activeTrails; if (activeTrails) activeTrails->prevGlobal = j; activeTrails = j; j->prevGlobal = NULL; j->inuse = qtrue; j->freed = qfalse; // if this owner has a headJunc, add us to the start if (headJunc) { // remove the headJunc from the list of heads if (headJunc == headTrails) { headTrails = headJunc->nextHead; if (headTrails) headTrails->prevHead = NULL; } else { if (headJunc->nextHead) headJunc->nextHead->prevHead = headJunc->prevHead; if (headJunc->prevHead) headJunc->prevHead->nextHead = headJunc->nextHead; } headJunc->prevHead = NULL; headJunc->nextHead = NULL; } // make us the headTrail if (headTrails) headTrails->prevHead = j; j->nextHead = headTrails; j->prevHead = NULL; headTrails = j; j->nextJunc = headJunc; // if headJunc is NULL, then we'll just be the end of the list numTrailsInuse++; // debugging // CG_Printf( "NumTrails: %i\n", numTrailsInuse ); return j; } /* =============== CG_AddTrailJunc returns the index of the trail junction created Used for generic trails =============== */ int CG_AddTrailJunc(int headJuncIndex, qhandle_t shader, int spawnTime, int sType, vec3_t pos, int trailLife, float alphaStart, float alphaEnd, float startWidth, float endWidth, int flags, vec3_t colorStart, vec3_t colorEnd, float sRatio, float animSpeed) { trailJunc_t *j, *headJunc; if (headJuncIndex > 0) { headJunc = &trailJuncs[headJuncIndex-1]; if (!headJunc->inuse) headJunc = NULL; } else headJunc = NULL; j = CG_SpawnTrailJunc(headJunc); if (!j) { // CG_Printf("couldnt spawn trail junc\n"); return 0; } if (alphaStart > 1.0) alphaStart = 1.0; if (alphaStart < 0.0) alphaStart = 0.0; if (alphaEnd > 1.0) alphaEnd = 1.0; if (alphaEnd < 0.0) alphaEnd = 0.0; // setup the trail junction j->shader = shader; j->sType = sType; VectorCopy( pos, j->pos ); j->flags = flags; j->spawnTime = spawnTime; j->endTime = spawnTime + trailLife; VectorCopy( colorStart, j->colorStart ); VectorCopy( colorEnd, j->colorEnd ); j->alphaStart = alphaStart; j->alphaEnd = alphaEnd; j->widthStart = startWidth; j->widthEnd = endWidth; if (sType == STYPE_REPEAT) { if (headJunc) { j->sTex = headJunc->sTex + ((Distance( headJunc->pos, pos ) / sRatio) / j->widthEnd); } else { // FIXME: need a way to specify offset timing j->sTex = (animSpeed * (1.0 - ((float)(cg.time%1000) / 1000.0))) / (sRatio); // j->sTex = 0; } } return ((int)(j - trailJuncs) + 1); } /* =============== CG_AddSparkJunc returns the index of the trail junction created =============== */ int CG_AddSparkJunc(int headJuncIndex, qhandle_t shader, vec3_t pos, int trailLife, float alphaStart, float alphaEnd, float startWidth, float endWidth) { trailJunc_t *j, *headJunc; if (headJuncIndex > 0) { headJunc = &trailJuncs[headJuncIndex-1]; if (!headJunc->inuse) headJunc = NULL; } else headJunc = NULL; j = CG_SpawnTrailJunc(headJunc); if (!j) return 0; // setup the trail junction j->shader = shader; j->sType = STYPE_STRETCH; VectorCopy( pos, j->pos ); j->flags = TJFL_NOCULL; // don't worry about fading up close j->spawnTime = cg.time; j->endTime = cg.time + trailLife; VectorSet(j->colorStart, 1.0, 0.8 + 0.2 * alphaStart, 0.4 + 0.4 * alphaStart); VectorSet(j->colorEnd, 1.0, 0.8 + 0.2 * alphaEnd, 0.4 + 0.4 * alphaEnd); // VectorScale( j->colorStart, alphaStart, j->colorStart ); // VectorScale( j->colorEnd, alphaEnd, j->colorEnd ); j->alphaStart = alphaStart*2; j->alphaEnd = alphaEnd*2; // j->alphaStart = 1.0; // j->alphaEnd = 1.0; j->widthStart = startWidth; j->widthEnd = endWidth; return ((int)(j - trailJuncs) + 1); } /* =============== CG_AddSmokeJunc returns the index of the trail junction created =============== */ int CG_AddSmokeJunc(int headJuncIndex, qhandle_t shader, vec3_t pos, int trailLife, float alpha, float startWidth, float endWidth) { #define ST_RATIO 4.0 // sprite image: width / height trailJunc_t *j, *headJunc; if (headJuncIndex > 0) { headJunc = &trailJuncs[headJuncIndex-1]; if (!headJunc->inuse) headJunc = NULL; } else headJunc = NULL; j = CG_SpawnTrailJunc(headJunc); if (!j) return 0; // setup the trail junction j->shader = shader; j->sType = STYPE_REPEAT; VectorCopy( pos, j->pos ); j->flags = TJFL_FADEIN; j->spawnTime = cg.time; j->endTime = cg.time + trailLife; // VectorSet(j->colorStart, 0.2, 0.2, 0.2); VectorSet(j->colorStart, 0.0, 0.0, 0.0); // VectorSet(j->colorEnd, 0.1, 0.1, 0.1); VectorSet(j->colorEnd, 0.0, 0.0, 0.0); j->alphaStart = alpha; j->alphaEnd = 0.0; j->widthStart = startWidth; j->widthEnd = endWidth; if (headJunc) { j->sTex = headJunc->sTex + ((Distance( headJunc->pos, pos ) / ST_RATIO) / j->widthEnd); } else { // first junction, so this will become the "tail" very soon, make it fade out j->sTex = 0; j->alphaStart = 0.0; j->alphaEnd = 0.0; } return ((int)(j - trailJuncs) + 1); } void CG_KillTrail( trailJunc_t *t ); /* =========== CG_FreeTrailJunc =========== */ void CG_FreeTrailJunc( trailJunc_t *junc ) { // kill any juncs after us, so they aren't left hanging if (junc->nextJunc) CG_KillTrail( junc ); // make it non-active junc->inuse = qfalse; junc->freed = qtrue; if (junc->nextGlobal) junc->nextGlobal->prevGlobal = junc->prevGlobal; if (junc->prevGlobal) junc->prevGlobal->nextGlobal = junc->nextGlobal; if (junc == activeTrails) activeTrails = junc->nextGlobal; // if it's a head, remove it if (junc == headTrails) headTrails = junc->nextHead; if (junc->nextHead) junc->nextHead->prevHead = junc->prevHead; if (junc->prevHead) junc->prevHead->nextHead = junc->nextHead; junc->nextHead = NULL; junc->prevHead = NULL; // stick it in the free list junc->prevGlobal = NULL; junc->nextGlobal = freeTrails; if (freeTrails) freeTrails->prevGlobal = junc; freeTrails = junc; numTrailsInuse--; } /* =========== CG_KillTrail =========== */ void CG_KillTrail( trailJunc_t *t ) { trailJunc_t *next; next = t->nextJunc; // kill the trail here t->nextJunc = NULL; if (next) CG_FreeTrailJunc( next ); } /* ============== CG_AddTrailToScene TODO: this can do with some major optimization ============== */ static vec3_t vforward, vright, vup; //TA: staticised to please QVM #define MAX_TRAIL_VERTS 2048 static polyVert_t verts[MAX_TRAIL_VERTS]; static polyVert_t outVerts[MAX_TRAIL_VERTS*3]; void CG_AddTrailToScene( trailJunc_t *trail, int iteration, int numJuncs ) { int k, i, n, l, numOutVerts; polyVert_t mid; float mod[4]; float sInc = 0.0f, s = 0.0f; // TTimo: init trailJunc_t *j, *jNext; vec3_t fwd, up, p, v; // clipping vars #define TRAIL_FADE_CLOSE_DIST 64.0 #define TRAIL_FADE_FAR_SCALE 4.0 vec3_t viewProj; float viewDist, fadeAlpha; // add spark shader at head position if (trail->flags & TJFL_SPARKHEADFLARE) { j = trail; VectorCopy( j->pos, p ); VectorMA (p, -j->width*2, vup, p); VectorMA (p, -j->width*2, vright, p); VectorCopy (p, verts[0].xyz); verts[0].st[0] = 0; verts[0].st[1] = 0; verts[0].modulate[0] = 255; verts[0].modulate[1] = 255; verts[0].modulate[2] = 255; verts[0].modulate[3] = (unsigned char)(j->alpha*255.0); VectorCopy( j->pos, p ); VectorMA (p, -j->width*2, vup, p); VectorMA (p, j->width*2, vright, p); VectorCopy (p, verts[1].xyz); verts[1].st[0] = 0; verts[1].st[1] = 1; verts[1].modulate[0] = 255; verts[1].modulate[1] = 255; verts[1].modulate[2] = 255; verts[1].modulate[3] = (unsigned char)(j->alpha*255.0); VectorCopy( j->pos, p ); VectorMA (p, j->width*2, vup, p); VectorMA (p, j->width*2, vright, p); VectorCopy (p, verts[2].xyz); verts[2].st[0] = 1; verts[2].st[1] = 1; verts[2].modulate[0] = 255; verts[2].modulate[1] = 255; verts[2].modulate[2] = 255; verts[2].modulate[3] = (unsigned char)(j->alpha*255.0); VectorCopy( j->pos, p ); VectorMA (p, j->width*2, vup, p); VectorMA (p, -j->width*2, vright, p); VectorCopy (p, verts[3].xyz); verts[3].st[0] = 1; verts[3].st[1] = 0; verts[3].modulate[0] = 255; verts[3].modulate[1] = 255; verts[3].modulate[2] = 255; verts[3].modulate[3] = (unsigned char)(j->alpha*255.0); trap_R_AddPolyToScene( cgs.media.sparkFlareShader, 4, verts ); } // if (trail->flags & TJFL_CROSSOVER && iteration < 1) { // iteration = 1; // } if (!numJuncs) { // first count the number of juncs in the trail j = trail; numJuncs = 0; sInc = 0; while (j) { numJuncs++; // check for a dead next junc if (!j->inuse && j->nextJunc && !j->nextJunc->inuse) { CG_KillTrail( j ); } else if (j->nextJunc && j->nextJunc->freed) { // not sure how this can happen, but it does, and causes infinite loops j->nextJunc = NULL; } if (j->nextJunc) sInc += VectorDistance( j->nextJunc->pos, j->pos ); j = j->nextJunc; } } if (numJuncs < 2) { return; } if (trail->sType == STYPE_STRETCH) { //sInc = ((1.0 - 0.1) / (float)(numJuncs)); // hack, the end of funnel shows a bit of the start (looping) s = 0.05; //s = 0.05; } else if (trail->sType == STYPE_REPEAT) { s = trail->sTex; } // now traverse the list j = trail; jNext = j->nextJunc; i = 0; while (jNext) { // first get the directional vectors to the next junc VectorSubtract( jNext->pos, j->pos, fwd ); GetPerpendicularViewVector( cg.refdef.vieworg, j->pos, jNext->pos, up ); // if it's a crossover, draw it twice if (j->flags & TJFL_CROSSOVER) { if (iteration > 0) { ProjectPointOntoVector( cg.refdef.vieworg, j->pos, jNext->pos, viewProj ); VectorSubtract( cg.refdef.vieworg, viewProj, v ); VectorNormalize( v ); if (iteration == 1) { VectorMA( up, 0.3, v, up ); } else { VectorMA( up, -0.3, v, up ); } VectorNormalize( up ); } } // do fading when moving towards the projection point onto the trail segment vector else if (!(j->flags & TJFL_NOCULL) && (j->widthEnd > 4 || jNext->widthEnd > 4)) { ProjectPointOntoVector( cg.refdef.vieworg, j->pos, jNext->pos, viewProj ); viewDist = Distance( viewProj, cg.refdef.vieworg ); if (viewDist < (TRAIL_FADE_CLOSE_DIST * TRAIL_FADE_FAR_SCALE)) { if (viewDist < TRAIL_FADE_CLOSE_DIST) { fadeAlpha = 0.0; } else { fadeAlpha = (viewDist - TRAIL_FADE_CLOSE_DIST) / (TRAIL_FADE_CLOSE_DIST * TRAIL_FADE_FAR_SCALE); } if (fadeAlpha < j->alpha) { j->alpha = fadeAlpha; } if (fadeAlpha < jNext->alpha) { jNext->alpha = fadeAlpha; } } } // now output the QUAD for this segment // 1 ---- VectorMA( j->pos, 0.5*j->width, up, p ); VectorCopy( p, verts[i].xyz ); verts[i].st[0] = s; verts[i].st[1] = 1.0; for (k=0; k<3; k++) verts[i].modulate[k] = (unsigned char)(j->color[k]*255.0); verts[i].modulate[3] = (unsigned char)(j->alpha*255.0); // blend this with the previous junc if (j != trail) { VectorAdd( verts[i].xyz, verts[i-1].xyz, verts[i].xyz ); VectorScale( verts[i].xyz, 0.5, verts[i].xyz ); VectorCopy( verts[i].xyz, verts[i-1].xyz ); } else if (j->flags & TJFL_FADEIN) { verts[i].modulate[3] = 0; // fade in } i++; // 2 ---- VectorMA( p, -1*j->width, up, p ); VectorCopy( p, verts[i].xyz ); verts[i].st[0] = s; verts[i].st[1] = 0.0; for (k=0; k<3; k++) verts[i].modulate[k] = (unsigned char)(j->color[k]*255.0); verts[i].modulate[3] = (unsigned char)(j->alpha*255.0); // blend this with the previous junc if (j != trail) { VectorAdd( verts[i].xyz, verts[i-3].xyz, verts[i].xyz ); VectorScale( verts[i].xyz, 0.5, verts[i].xyz ); VectorCopy( verts[i].xyz, verts[i-3].xyz ); } else if (j->flags & TJFL_FADEIN) { verts[i].modulate[3] = 0; // fade in } i++; if (trail->sType == STYPE_REPEAT) s = jNext->sTex; else { //s += sInc; s += VectorDistance( j->pos, jNext->pos ) / sInc; if (s > 1.0) s = 1.0; } // 3 ---- VectorMA( jNext->pos, -0.5*jNext->width, up, p ); VectorCopy( p, verts[i].xyz ); verts[i].st[0] = s; verts[i].st[1] = 0.0; for (k=0; k<3; k++) verts[i].modulate[k] = (unsigned char)(jNext->color[k]*255.0); verts[i].modulate[3] = (unsigned char)(jNext->alpha*255.0); i++; // 4 ---- VectorMA( p, jNext->width, up, p ); VectorCopy( p, verts[i].xyz ); verts[i].st[0] = s; verts[i].st[1] = 1.0; for (k=0; k<3; k++) verts[i].modulate[k] = (unsigned char)(jNext->color[k]*255.0); verts[i].modulate[3] = (unsigned char)(jNext->alpha*255.0); i++; if (i+4 > MAX_TRAIL_VERTS) break; j = jNext; jNext = j->nextJunc; } if (trail->flags & TJFL_FIXDISTORT) { // build the list of outVerts, by dividing up the QUAD's into 4 Tri's each, so as to allow // any shaped (convex) Quad without bilinear distortion for (k=0, numOutVerts=0; kflags & TJFL_NOPOLYMERGE)) { trap_R_AddPolysToScene( trail->shader, 3, &outVerts[0], numOutVerts/3 ); } else { int k; for (k=0; kshader, 3, &outVerts[k*3] ); } } } else { // send the polygons // FIXME: is it possible to send a GL_STRIP here? We are actually sending 2x the verts we really need to if (!(trail->flags & TJFL_NOPOLYMERGE)) { trap_R_AddPolysToScene( trail->shader, 4, &verts[0], i/4 ); } else { int k; for (k=0; kshader, 4, &verts[k*4] ); } } } // do we need to make another pass? if (trail->flags & TJFL_CROSSOVER) { if (iteration < 2) { CG_AddTrailToScene( trail, iteration + 1, numJuncs ); } } } /* =============== CG_AddTrails =============== */ void CG_AddTrails(void) { float lifeFrac; trailJunc_t *j, *jNext; if (!initTrails) { CG_ClearTrails(); } //AngleVectors( cg.snap->ps.viewangles, vforward, vright, vup ); VectorCopy( cg.refdef.viewaxis[0], vforward ); VectorCopy( cg.refdef.viewaxis[1], vright ); VectorCopy( cg.refdef.viewaxis[2], vup ); // update the settings for each junc j = activeTrails; while (j) { lifeFrac = (float)(cg.time - j->spawnTime) / (float)(j->endTime - j->spawnTime); if (lifeFrac >= 1.0) { j->inuse = qfalse; // flag it as dead j->width = j->widthEnd; j->alpha = j->alphaEnd; if (j->alpha > 1.0) j->alpha = 1.0; else if (j->alpha < 0.0) j->alpha = 0.0; VectorCopy( j->colorEnd, j->color ); } else { j->width = j->widthStart + (j->widthEnd - j->widthStart) * lifeFrac; j->alpha = j->alphaStart + (j->alphaEnd - j->alphaStart) * lifeFrac; if (j->alpha > 1.0) j->alpha = 1.0; else if (j->alpha < 0.0) j->alpha = 0.0; VectorSubtract( j->colorEnd, j->colorStart, j->color ); VectorMA( j->colorStart, lifeFrac, j->color, j->color ); } j = j->nextGlobal; } // draw the trailHeads j = headTrails; while (j) { jNext = j->nextHead; // in case it gets removed if (!j->inuse) { CG_FreeTrailJunc(j); } else { CG_AddTrailToScene( j, 0, 0 ); } j = jNext; } }