/*============================================================================= Name : Trails.c Purpose : Code to draw ship trails Created 8/11/1997 by lmoloney Copyright Relic Entertainment, Inc. All rights reserved. =============================================================================*/ #define MIN2(x,y) ((x) < (y) ? (x) : (y)) #define MAX2(x,y) ((x) > (y) ? (x) : (y)) #define VECCOPY(D,S) memcpy(D, S, sizeof(vector)) #define MATCOPY(D,S) memcpy(D, S, sizeof(matrix)) #define HMATCOPY(D,S) memcpy(D, S, sizeof(hmatrix)) #define TRAIL_LINE_CUTOFF_LOD 2 #include #include #include #include #include "glinc.h" #include "types.h" #include "debug.h" #include "memory.h" #include "prim3d.h" #include "render.h" #include "statscript.h" #include "spaceobj.h" #include "universe.h" #include "teams.h" #include "trails.h" #include "main.h" #include "fastmath.h" #include "particle.h" #include "randy.h" #include "camera.h" #include "clouds.h" #include "select.h" #include "tactics.h" #include "glcaps.h" #include "shader.h" #include "devstats.h" extern udword gDevcaps2; sdword bTrailRender = 1; sdword bTrailDraw = 1; sdword trailsUpdated; sdword trailsNotUpdated; sdword trailsRendered; extern Camera* mrCamera; extern real32 meshFadeAlpha; //defaults for reference in case Trail.script gets munged beyond repair static real32 RAN_MAX = 10.0f; static real32 RAN_JITTER = 0.75f; static real32 VELRATIO_THRESH = 0.1f; static real32 VELRATIO_ACCELTHRESH = 0.8f; static real32 VELRATIO_CUTOFF = 0.9f; static real32 ACCELTHRESH = 8.0f; static real32 SIZE_MAJOR = 5.0f; static real32 SIZE_INC = 1.0f; static real32 SIZEINC_INC = 1.0f; static real32 SIZE_MAX = 42.0f; static real32 XADJ = 8.0f; static real32 HALFWIDTH = 4.2f; static real32 HALFWIDTH_FALLOFF = 0.3f; static real32 HALFWIDTH_MIN = 4.0f; static real32 GLOW_RADIALADJUST = 0.82f; static real32 GLOW_BASERADIALSCALE = 0.2f; static real32 BURN_RATIOSCALE = 25.0f; static real32 BURN_RATIOMIN = 6.0f; static real32 BURN_SIZESCALE = 1.5f; static real32 RANDOMBURN_FREQ = 0.02f; static sdword BURN_COLORADJUST = -64; static sdword COLORADJUST_FREQ = 40; static sdword COLOR_ADJUST = 12; static real32 TRAIL_EXPONENT_BASE = 3.6f; static real32 TRAIL_EXPONENT_RANGE = 0.6f; static sdword TRAIL_GLOW_1_RED = 255; static sdword TRAIL_GLOW_1_GREEN = 180; static sdword TRAIL_GLOW_1_BLUE = 40; static sdword TRAIL_GLOW_2_RED = 40; static sdword TRAIL_GLOW_2_GREEN = 180; static sdword TRAIL_GLOW_2_BLUE = 255; static sdword TRAIL_EXPANSION_TICKS = 16; scriptEntry TrailTweaks[] = { makeEntry(RAN_MAX, scriptSetReal32CB), makeEntry(RAN_JITTER, scriptSetReal32CB), makeEntry(VELRATIO_THRESH, scriptSetReal32CB), makeEntry(VELRATIO_ACCELTHRESH, scriptSetReal32CB), makeEntry(VELRATIO_CUTOFF, scriptSetReal32CB), makeEntry(ACCELTHRESH, scriptSetReal32CB), makeEntry(SIZE_MAJOR, scriptSetReal32CB), makeEntry(SIZE_INC, scriptSetReal32CB), makeEntry(SIZEINC_INC, scriptSetReal32CB), makeEntry(SIZE_MAX, scriptSetReal32CB), makeEntry(XADJ, scriptSetReal32CB), makeEntry(HALFWIDTH, scriptSetReal32CB), makeEntry(HALFWIDTH_FALLOFF, scriptSetReal32CB), makeEntry(HALFWIDTH_MIN, scriptSetReal32CB), makeEntry(GLOW_RADIALADJUST, scriptSetReal32CB), makeEntry(GLOW_BASERADIALSCALE, scriptSetReal32CB), makeEntry(BURN_RATIOSCALE, scriptSetReal32CB), makeEntry(BURN_RATIOMIN, scriptSetReal32CB), makeEntry(BURN_SIZESCALE, scriptSetReal32CB), makeEntry(BURN_COLORADJUST, scriptSetSwordCB), makeEntry(RANDOMBURN_FREQ, scriptSetReal32CB), makeEntry(COLORADJUST_FREQ, scriptSetSwordCB), makeEntry(COLOR_ADJUST, scriptSetSwordCB), makeEntry(TRAIL_EXPONENT_BASE, scriptSetReal32CB), makeEntry(TRAIL_EXPONENT_RANGE, scriptSetReal32CB), makeEntry(TRAIL_GLOW_1_RED, scriptSetSwordCB), makeEntry(TRAIL_GLOW_1_GREEN, scriptSetSwordCB), makeEntry(TRAIL_GLOW_1_BLUE, scriptSetSwordCB), makeEntry(TRAIL_GLOW_2_RED, scriptSetSwordCB), makeEntry(TRAIL_GLOW_2_GREEN, scriptSetSwordCB), makeEntry(TRAIL_GLOW_2_BLUE, scriptSetSwordCB), makeEntry(TRAIL_EXPANSION_TICKS, scriptSetSwordCB), endEntry }; /*============================================================================= Data: =============================================================================*/ static real32 xaxis[] = {1.0f, 0.0f, 0.0f}; static real32 yaxis[] = {0.0f, 1.0f, 0.0f}; static real32 zaxis[] = {0.0f, 0.0f, -1.0f}; static real32 origin[] = {0.0f, 0.0f, 0.0f}; static real32 NLipsScaleFactor = 1.0f; #define trailCopySegment(D,S) memcpy(D, S, sizeof(trailsegment)) trhandle g_glowHandle = 0; #define TM_ACCEL 0 #define TM_GLOW_0 1 #define TM_GLOW_1 2 #define TM_GLOW_2 3 #define TM_GLOW_3 4 #define TM_FRIGATE_GLOW_0 5 #define TM_FRIGATE_GLOW_1 6 #define TM_FRIGATE_GLOW_2 7 #define TM_FRIGATE_GLOW_3 8 #define TM_MOSHIP_GLOW_R1_0 9 #define TM_MOSHIP_GLOW_R1_1 10 #define TM_MOSHIP_GLOW_R1_2 11 #define TM_MOSHIP_GLOW_R1_3 12 #define TM_MOSHIP_GLOW_R2_0 13 #define TM_MOSHIP_GLOW_R2_1 14 #define TM_MOSHIP_GLOW_R2_2 15 #define TM_MOSHIP_GLOW_R2_3 16 #define TM_MOSHIP_GLOW_P1_0 17 #define TM_MOSHIP_GLOW_P1_1 18 #define TM_MOSHIP_GLOW_P1_2 19 #define TM_MOSHIP_GLOW_P1_3 20 #define TM_NUMDEFS 21 char* trailMeshNames[TM_NUMDEFS] = { "energybolt", "engine_glow00", "engine_glow01", "engine_glow02", "engine_glow03", "engine_glow_frigate00", "engine_glow_frigate01", "engine_glow_frigate02", "engine_glow_frigate03", "r1moship0", "r1moship1", "r1moship2", "r1moship3", "r2moship0", "r2moship1", "r2moship2", "r2moship3", "p1moship0", "p1moship1", "p1moship2", "p1moship3" }; meshdata* trailMeshes[TM_NUMDEFS]; sdword trailInsertCount = 0; bool8 _fastBlends; bool8 _afterburning; static void trailSegmentsRead(char *directory,char *field,void *dataToFillIn); static void trailGranularityRead(char *directory,char *field,void *dataToFillIn); static void trailColorRead(char *directory,char *field,void *dataToFillIn); scriptEntry trailStaticScriptTable[] = { { "trailSegments", trailSegmentsRead, NULL }, { "trailGranularity", trailGranularityRead, NULL }, { "trailColor", trailColorRead, NULL }, { NULL,NULL, NULL} }; /*============================================================================= Functions: =============================================================================*/ /*----------------------------------------------------------------------------- Name : trailStartup Description : Startup the trail module Inputs : Outputs : nothing yet! Return : ----------------------------------------------------------------------------*/ void trailStartup(void) { sdword i; char filename[128]; #if TRAIL_VERBOSE_LEVEL >= 1 dbgMessage("\nStartup engine trail module"); #endif strcpy(filename, "etg\\textures\\glow32"); g_glowHandle = trTextureRegister(filename, NULL, NULL); for (i = 0; i < TM_NUMDEFS; i++) { strcpy(filename, "misc\\EngineGlows\\"); strcat(filename, trailMeshNames[i]); strcat(filename, ".geo"); trailMeshes[i] = meshLoad(filename); } } /*----------------------------------------------------------------------------- Name : trailShutdown Description : Shut down the engine trails. Inputs : Outputs : nothing yet! Return : ----------------------------------------------------------------------------*/ void trailShutdown(void) { sdword i; #if TRAIL_VERBOSE_LEVEL >= 1 dbgMessage("\nShutdown engine trail module"); #endif for (i = 0; i < TM_NUMDEFS; i++) { if (trailMeshes[i] != NULL) { meshFree(trailMeshes[i]); trailMeshes[i] = NULL; } } } /*----------------------------------------------------------------------------- Name : trailStaticDelete Description : Deletes a trail static structure and all info associated therewith Inputs : trailInfo - structure to delete Outputs : .. Return : ----------------------------------------------------------------------------*/ void trailStaticDelete(trailstatic *trailInfo) { memFree(trailInfo); } /*----------------------------------------------------------------------------- Name : mistrailNew Description : allocate and initialize a new missile trail Inputs : Outputs : Return : ----------------------------------------------------------------------------*/ missiletrail* mistrailNew(trailstatic* staticInfo, void* vmissile) { missiletrail* trail; MissileStaticInfo* missilestaticinfo; Missile* missile; missile = (Missile*)vmissile; dbgAssert(staticInfo != NULL); #if TRAIL_VERBOSE_LEVEL >= 2 dbgMessagef("\nmistrailNew: %d segments, 0x%x staticInfo", staticInfo->nSegments, staticInfo); #endif trail = memAlloc(mistrailSize(staticInfo->nSegments), "Missile Trail", NonVolatile); trail->vmissile = vmissile; trail->iHead = trail->iTail = trail->nLength = 0; trail->grainCounter = 0; trail->staticInfo = staticInfo; missilestaticinfo = missile->staticinfo; trail->width = missilestaticinfo->trailWidth; trail->height = missilestaticinfo->trailHeight; return trail; } /*----------------------------------------------------------------------------- Name : trailNew Description : Allocate and initialize a new ship trail Inputs : Outputs : Return : ----------------------------------------------------------------------------*/ shiptrail *trailNew(trailstatic *staticInfo, void* vship, bool8 second, ubyte trailNum) { shiptrail *trail; Ship* ship; ShipStaticInfo* shipstaticinfo; #if TRAIL_VERBOSE_LEVEL >= 2 if (staticInfo != NULL) { dbgMessagef("\ntrailNew: %d segments, 0x%x staticInfo", staticInfo->nSegments, staticInfo); } #endif if (staticInfo != NULL) { trail = memAlloc(trailSize(staticInfo->nSegments), "Ship Engine Trail", NonVolatile); } else { trail = memAlloc(trailSize(1), "Ship Capital Trail", NonVolatile); } trail->ran = 0.0f; trail->ranCounter = 0; trail->lastvelsquared = trail->prevvelsquared = 0.0f; trail->vship = vship; trail->iHead = trail->iTail = trail->nLength = 0; trail->grainCounter = 0; trail->staticInfo = staticInfo; trail->second = second; trail->trailNum = trailNum; trail->wobbly = FALSE; ship = (Ship*)vship; shipstaticinfo = (ShipStaticInfo*)ship->staticinfo; if (second == 2 || second == 3) { //if second == 3, race 1 trail->width = shipstaticinfo->trailWidth[trailNum]; trail->height = shipstaticinfo->trailHeight[trailNum]; trail->angle = shipstaticinfo->trailAngle[trailNum]; if (shipstaticinfo->trailRibbonAdjust[trailNum] == -1.0f) { trail->ribbonadjust = 1.28f; } else { trail->ribbonadjust = shipstaticinfo->trailRibbonAdjust[trailNum]; } if (shipstaticinfo->trailLength[trailNum] != -1.0f) { trail->ribbonadjust = shipstaticinfo->trailLength[trailNum] / trail->width; } trail->style = (bool8)(second+1); if (shipstaticinfo->trailStyle[trailNum] == 3) { trail->style = 5; } trail->exponent = 4.0f; trail->state = TRAIL_CONTRACTED; trail->counter = 0; trail->scalecap = shipstaticinfo->trailScaleCap[trailNum]; return trail; } trail->state = TRAIL_CONTRACTED; trail->counter = 0; trail->width = shipstaticinfo->trailWidth[trailNum]; trail->height = shipstaticinfo->trailHeight[trailNum]; if (shipstaticinfo->trailAngle[trailNum] == -1.0f) { trail->angle = -1.0f; } else { trail->angle = DEG_TO_RAD(shipstaticinfo->trailAngle[trailNum]); } trail->ribbonadjust = shipstaticinfo->trailRibbonAdjust[trailNum]; trail->style = (bool8)shipstaticinfo->trailStyle[trailNum]; return(trail); } /*----------------------------------------------------------------------------- Name : mistrailDelete Description : delete a missile trail structure Inputs : Outputs : frees its memory with memFree Return : ----------------------------------------------------------------------------*/ void mistrailDelete(missiletrail* trail) { #if TRAIL_VERBOSE_LEVEL >= 2 if (trail->staticInfo != NULL) { dbgMessagef("\nmistrailDelete: freeing trail0x%x with %d segments", trail, trail->staticInfo->nSegments); } #endif memFree(trail); } /*----------------------------------------------------------------------------- Name : trailDelete Description : Delete an engine trail structure Inputs : Outputs : Frees its memory with memFree Return : ----------------------------------------------------------------------------*/ void trailDelete(shiptrail *trail) { #if TRAIL_VERBOSE_LEVEL >= 2 if (trail->staticInfo != NULL) { dbgMessagef("\ntrailDelete: freeing trail 0x%x with %d segments", trail, trail->staticInfo->nSegments); } #endif memFree(trail); } static void homogenize(vector *v, hvector *h) { real32 oneOverW; vecGrabVecFromHVec(*v, *h); if (h->w == 0.0f || h->w == 1.0f) return; oneOverW = 1.0f / h->w; v->x *= oneOverW; v->y *= oneOverW; v->z *= oneOverW; } /*----------------------------------------------------------------------------- Name : trailInplacePossibleRotate Description : rotate a vector around the z axis if provided angle != -1.0 Inputs : vec - the vector to rotate degrees - angle Outputs : vec is modified (rotated), or not (if degrees = -1.0) Return : ----------------------------------------------------------------------------*/ void trailInplacePossibleRotate(vector* vec, real32 degrees) { hmatrix hmat; hvector hvec0, hvec1; if (degrees == -1.0f) { return; } vecMakeHVecFromVec(hvec0, *vec); hmatMakeRotAboutZ(&hmat, (real32)cos(degrees), (real32)sin(degrees)); hmatMultiplyHMatByHVec(&hvec1, &hmat, &hvec0); homogenize(vec, &hvec1); vecNormalize(vec); } /*----------------------------------------------------------------------------- Name : trailInplaceShipToWorld Description : object -> world transform on a point Inputs : vec - the vector to transform coordsys - 3x3 rotation matrix position - translation vector Outputs : vec is modified (moved to worldspace) Return : ----------------------------------------------------------------------------*/ void trailInplaceShipToWorld(real32* vec, real32* coordsys, real32* position) { vector temp; matMultiplyMatByVec(&temp, (matrix*)coordsys, (vector*)vec); vec[0] = position[0] + temp.x; vec[1] = position[1] + temp.y; vec[2] = position[2] + temp.z; } //convenience fn. copies shipstaticinfo.enginenozzleoffset into provided vector void trailGetNozzleOffset(real32* vec, shiptrail* trail) { Ship* ship = (Ship*)trail->vship; VECCOPY(vec, &((ShipStaticInfo*)ship->staticinfo)->engineNozzleOffset[trail->trailNum]); } //convenience fn. copies ship.coordsys into provided matrix void trailGetCoordsys(real32* mat, shiptrail* trail) { Ship* ship = (Ship*)trail->vship; MATCOPY(mat, &ship->rotinfo.coordsys); } //convenience fn. copies ship.position into provided vector void trailGetTranslation(real32* trans, shiptrail* trail) { Ship* ship = (Ship*)trail->vship; VECCOPY(trans, &ship->posinfo.position); } //random real 0->n real32 trailRealRand(real32 n) { if (n == 0.0f) { return 0.0f; } else { return((real32)(ranRandom(RAN_Trails0) % (sdword)(n * 10000.0f)) / 10000.0f); } } /*----------------------------------------------------------------------------- Name : mistrailUpdate Description : update a missile trail Inputs : Outputs : updates trail's circular queue Return : ----------------------------------------------------------------------------*/ void mistrailUpdate(missiletrail* trail, vector* position) { trailstatic* trailStatic = trail->staticInfo; if (!enableTrails) { return; } trail->grainCounter--; if (trail->grainCounter < 0) { trail->grainCounter = trailStatic->granularity; } else { return; } dbgAssert(trail->iHead < trailStatic->nSegments); trail->segments[trail->iHead].position = *position; if (trail->nLength < trailStatic->nSegments) { //if still building missile trail, insert new point trail->nLength++; trail->iHead++; if (trail->iHead >= trailStatic->nSegments) { trail->iHead = 0; } } else { //else the list is full size, wrap around trail->iHead = (trail->iHead + 1) < trailStatic->nSegments ? trail->iHead + 1 : 0; } } /*----------------------------------------------------------------------------- Name : trailUpdate Description : Update a trail Inputs : position - 3d location to put at head of trail Outputs : Updates trail's circular queue Return : void ----------------------------------------------------------------------------*/ void trailUpdate(shiptrail *trail, vector *position) { trailstatic *trailStatic = trail->staticInfo; trailsegment* seg; trailsegment* seg2; if (!enableTrails) { return; } if (trail->style > 2) { trail->exponent = trailRealRand(TRAIL_EXPONENT_RANGE) + TRAIL_EXPONENT_BASE; if (trail->counter > 0) { trail->counter--; } switch (trail->state) { case TRAIL_CONTRACTING: if (trail->counter == 0) { trail->state = TRAIL_CONTRACTED; } break; case TRAIL_CONTRACTED: break; case TRAIL_EXPANDING: if (trail->counter == 0) { trail->state = TRAIL_EXPANDED; } break; case TRAIL_EXPANDED: break; } return; } trail->grainCounter--; if (trail->grainCounter < 0) { trail->grainCounter = trailStatic->granularity; } else { return; } dbgAssert(trail->iHead < trailStatic->nSegments); #if TRAIL_GATHER_STATS trailsUpdated++; #endif seg = &trail->segments[trail->iHead]; trailGetNozzleOffset(seg->position, trail); trailGetCoordsys(seg->rotation, trail); trailGetTranslation(seg->translation, trail); trailInplaceShipToWorld(seg->position, seg->rotation, seg->translation); VECCOPY(seg->horizontal, yaxis); trailInplacePossibleRotate((vector*)seg->horizontal, trail->angle); trailInplaceShipToWorld(seg->horizontal, seg->rotation, origin); VECCOPY(seg->vertical, xaxis); trailInplacePossibleRotate((vector*)seg->vertical, trail->angle); trailInplaceShipToWorld(seg->vertical, seg->rotation, origin); trail->segments[trail->iHead].wide = FALSE; seg2 = &trail->segments[(trail->iHead - 1) <= 1 ? trailStatic->nSegments - 1 : trail->iHead - 1]; if (trail->nLength < trailStatic->nSegments) { //if still building ship trail, insert new point trail->nLength++; //update head and length trail->iHead++; if ((trail->iHead) >= trailStatic->nSegments) { trail->iHead = 0; } } else { //else the list is full size, wrap around trail->iHead = (trail->iHead + 1) < trailStatic->nSegments ? trail->iHead + 1 : 0; } } /*----------------------------------------------------------------------------- Name : trailColorDesaturate Description : desaturates a color. involves an hsv conversion, and back Inputs : c - the color to desaturate de - amount to desaturate [0..255] Outputs : Return : modified color ----------------------------------------------------------------------------*/ color trailColorDesaturate(color c, GLint de) { GLfloat h, s, v; GLfloat red = colUbyteToReal(colRed(c)); GLfloat green = colUbyteToReal(colGreen(c)); GLfloat blue = colUbyteToReal(colBlue(c)); colRGBToHSV(&h, &s, &v, red, green, blue); s -= colUbyteToReal(de); if (s < 0.0f) { s = 0.0f; } colHSVToRGB(&red, &green, &blue, h, s, v); return colRGB(colRealToUbyte(red), colRealToUbyte(green), colRealToUbyte(blue)); } /*----------------------------------------------------------------------------- Name : trailColorSaturate Description : saturates a color. involves an hsv conversion, and back Inputs : c - the color to saturate inc - amount to saturate [0..255] Outputs : Return : modified color ----------------------------------------------------------------------------*/ color trailColorSaturate(color c, GLint inc) { GLfloat h, s, v; GLfloat red = colUbyteToReal(colRed(c)); GLfloat green = colUbyteToReal(colGreen(c)); GLfloat blue = colUbyteToReal(colBlue(c)); colRGBToHSV(&h, &s, &v, red, green, blue); s += colUbyteToReal(inc); if (s > 1.0f) { s = 1.0f; } colHSVToRGB(&red, &green, &blue, h, s, v); return colRGB(colRealToUbyte(red), colRealToUbyte(green), colRealToUbyte(blue)); } //returns the ratio of trail height to width real32 trailHeightScalar(shiptrail* trail) { return (trail->height == -1.0f || trail->width == -1.0f) ? 0.25f : trail->height / trail->width; } //returns half the trail width real32 trailHalfWidth(shiptrail* trail) { return (trail->width == -1.0f) ? HALFWIDTH : 0.5f * trail->width; } //returns half the trail height real32 trailHalfHeight(shiptrail* trail) { return (trail->height == -1.0f) ? trailHeightScalar(trail) * HALFWIDTH : 0.5f * trail->height; } //returns a scalar to modify the size of a ribbon, normally the height of the trail real32 trailRibbonAdjust(shiptrail* trail) { return (trail->ribbonadjust == -1.0f) ? 1.0f : trail->ribbonadjust; } /*----------------------------------------------------------------------------- Name : trailDrawCapitalGlow Description : renders a capital ship's engine glow Inputs : trail - the ship trail structure LOD - the lod of the ship Outputs : Return : ----------------------------------------------------------------------------*/ void trailDrawCapitalGlow(shiptrail* trail, sdword LOD) { Ship* ship = (Ship*)trail->vship; ShipStaticInfo* shipstaticinfo = (ShipStaticInfo*)ship->staticinfo; real32 maxvel, mag, velratio; vector vel, pos; real32 w = 20.0f; real32 h = 60.0f; hmatrix coordMatrixForGL; real32 scaleFactor; real32 scale[3]; bool moshipGlow; extern bool8 g_NoMatSwitch; if (trail->width == -1.0f || trail->state == TRAIL_CONTRACTED) { return; } if (trail->width == 1.0f && trail->height == 1.0f) { moshipGlow = TRUE; } else { moshipGlow = FALSE; } if (shipstaticinfo->shiprace == P1 && shipstaticinfo->shipclass == CLASS_Mothership) { moshipGlow = 2; trail->width = 1.0f; trail->height = 1.0f; trail->ribbonadjust = 1.0f; } // maxvel = ship->staticinfo->staticheader.maxvelocity; maxvel = tacticsGetShipsMaxVelocity(ship); VECCOPY(&vel, &ship->posinfo.velocity); mag = fsqrt(vecMagnitudeSquared(vel)); velratio = 0.5f * mag / maxvel; if (velratio == 0.0f) { switch (trail->state) { case TRAIL_CONTRACTED: return; case TRAIL_CONTRACTING: break; case TRAIL_EXPANDED: case TRAIL_EXPANDING: trail->state = TRAIL_CONTRACTING; trail->counter = (ubyte)TRAIL_EXPANSION_TICKS; break; } } trailGetNozzleOffset((real32*)&pos, trail); rndLightingEnable(TRUE); rndTextureEnable(FALSE); glEnable(GL_CULL_FACE); glDepthMask(GL_FALSE); if (!usingShader && RGL) { rglFeature(RGL_SPECULAR3_RENDER); } glEnable(GL_BLEND); rndAdditiveBlends(TRUE); if (trail->style == 4 || trail->style == 5) { if (usingShader) { meshSetSpecular(2, (ubyte)TRAIL_GLOW_1_RED, (ubyte)TRAIL_GLOW_1_GREEN, (ubyte)TRAIL_GLOW_1_BLUE, 160); } else { glColor4ub((GLubyte)TRAIL_GLOW_1_RED, (GLubyte)TRAIL_GLOW_1_GREEN, (GLubyte)TRAIL_GLOW_1_BLUE, 160); } } else { if (usingShader) { meshSetSpecular(2, (ubyte)TRAIL_GLOW_2_RED, (ubyte)TRAIL_GLOW_2_GREEN, (ubyte)TRAIL_GLOW_2_BLUE, 160); } else { glColor4ub((GLubyte)TRAIL_GLOW_2_RED, (GLubyte)TRAIL_GLOW_2_GREEN, (GLubyte)TRAIL_GLOW_2_BLUE, 160); } } glPushMatrix(); hmatMakeHMatFromMat(&coordMatrixForGL, &ship->rotinfo.coordsys); hmatPutVectIntoHMatrixCol4(ship->posinfo.position, coordMatrixForGL); glMultMatrixf((GLfloat*)&coordMatrixForGL); if (moshipGlow && (moshipGlow != 2)) { scaleFactor = 1.0f; glTranslatef(0.0f, 0.0f, pos.z); } else { if (moshipGlow) { scaleFactor = 1.0f; glTranslatef(pos.x, pos.y, pos.z); } else { if (shipstaticinfo->scaleCap != 0.0f) { scaleFactor = 1.0f - selCameraSpace.z * shipstaticinfo->scaleCap; glTranslatef(pos.x * scaleFactor, pos.y * scaleFactor, pos.z * scaleFactor); } else { glTranslatef(pos.x, pos.y, pos.z); } if (trail->scalecap == -1.0f || trail->scalecap == 0.0f) { scaleFactor = 1.0f; } else { scaleFactor = 1.0f - selCameraSpace.z * trail->scalecap; } } } if (trail->counter) { real32 x, y, z; real32 scalar; x = trail->height; y = trail->width; z = trail->width * trail->ribbonadjust; if (trail->state == TRAIL_EXPANDING) { scalar = 1.0f - ((real32)trail->counter / (real32)TRAIL_EXPANSION_TICKS); } else { scalar = (real32)trail->counter / (real32)TRAIL_EXPANSION_TICKS; } z *= scalar; if (z == 0.0f) { z = 0.023f; } scale[0] = x * scaleFactor; scale[1] = y * scaleFactor; scale[2] = z * scaleFactor; } else { scale[0] = scaleFactor * trail->height; scale[1] = scaleFactor * trail->width; scale[2] = scaleFactor * trail->width * trail->ribbonadjust; } glScalef(scale[0], scale[1], scale[2]); g_NoMatSwitch = TRUE; glEnable(GL_NORMALIZE); if (usingShader) { shSetExponent(2, trail->exponent); } else if (RGL) { rglSpecExp(2, trail->exponent); } if (bitTest(gDevcaps2, DEVSTAT2_NO_IALPHA)) { glShadeModel(GL_FLAT); } else { glShadeModel(GL_SMOOTH); } if (shipstaticinfo->shiprace == P1 && shipstaticinfo->shipclass == CLASS_Mothership) { switch (LOD) { case 0: case 1: meshRender(trailMeshes[TM_MOSHIP_GLOW_P1_0], 0); break; case 2: meshRender(trailMeshes[TM_MOSHIP_GLOW_P1_1], 0); break; case 3: meshRender(trailMeshes[TM_MOSHIP_GLOW_P1_2], 0); break; default: meshRender(trailMeshes[TM_MOSHIP_GLOW_P1_3], 0); } } else if (trail->style == 5) { switch (LOD) { case 0: case 1: meshRender(trailMeshes[TM_FRIGATE_GLOW_0], 0); break; case 2: meshRender(trailMeshes[TM_FRIGATE_GLOW_1], 0); break; case 3: meshRender(trailMeshes[TM_FRIGATE_GLOW_2], 0); break; default: meshRender(trailMeshes[TM_FRIGATE_GLOW_3], 0); } } else { if (moshipGlow) { if (shipstaticinfo->shiprace == R1) { glScalef(1.0f, 1.5f, 1.0f); switch (LOD) { case 0: case 1: meshRender(trailMeshes[TM_MOSHIP_GLOW_R1_0], 0); break; case 2: meshRender(trailMeshes[TM_MOSHIP_GLOW_R1_1], 0); break; case 3: meshRender(trailMeshes[TM_MOSHIP_GLOW_R1_2], 0); break; default: meshRender(trailMeshes[TM_MOSHIP_GLOW_R1_3], 0); } } else { switch (LOD) { case 0: case 1: meshRender(trailMeshes[TM_MOSHIP_GLOW_R2_0], 0); break; case 2: meshRender(trailMeshes[TM_MOSHIP_GLOW_R2_1], 0); break; case 3: meshRender(trailMeshes[TM_MOSHIP_GLOW_R2_2], 0); break; default: meshRender(trailMeshes[TM_MOSHIP_GLOW_R2_3], 0); } } } else { switch (LOD) { case 0: case 1: meshRender(trailMeshes[TM_GLOW_0], 0); break; case 2: meshRender(trailMeshes[TM_GLOW_1], 0); break; case 3: meshRender(trailMeshes[TM_GLOW_2], 0); break; default: meshRender(trailMeshes[TM_GLOW_3], 0); } } } glDisable(GL_NORMALIZE); g_NoMatSwitch = FALSE; if (!usingShader && RGL) { rglFeature(RGL_NORMAL_RENDER); } glDepthMask(GL_TRUE); glDisable(GL_BLEND); rndAdditiveBlends(FALSE); glPopMatrix(); if (usingShader) meshSetSpecular(-1, 0, 0, 0, 0); } /*----------------------------------------------------------------------------- Name : trailDrawBillboardedSquareThingz Description : draws the engine glow(s) and possible afterburner flash, appropriately scaled. also sets a flag in provided trailsegment indicating whether afterburners were engaged Inputs : trail - the shiptrail structure seg - current segment being considered. not necessarily a part of trail rad - radius of object (ship) ship - the ship c - color of the segment Outputs : Return : ----------------------------------------------------------------------------*/ void trailDrawBillboardedSquareThingz(shiptrail* trail, trailsegment* seg, real32 rad, Ship* ship, color c) { vector pos, vel; real32 mag, maxvel, velratio; seg->wide = FALSE; if (--trail->ranCounter < 0) { trail->ran = 1.0f - ((real32)(ranRandom(RAN_Trails1) % 1000) * (RAN_JITTER * 0.0001f)); trail->ranCounter = (sdword)RAN_MAX; } maxvel = tacticsGetShipsMaxVelocity(ship); //scale radius by velocity fn VECCOPY(&vel, &ship->posinfo.velocity); mag = fsqrt(vecMagnitudeSquared(vel)); velratio = mag / maxvel; if (velratio < VELRATIO_THRESH) { return; } //grab ship's static glow radius if one exists if (ship->staticinfo->trailSpriteRadius[trail->trailNum] != -1.0f) { rad = ship->staticinfo->trailSpriteRadius[trail->trailNum]; } rad = (1.0f - GLOW_BASERADIALSCALE) * (GLOW_RADIALADJUST * velratio * rad * trail->ran) + GLOW_BASERADIALSCALE * rad; rndLightingEnable(FALSE); glDisable(GL_CULL_FACE); glDepthMask(GL_FALSE); rndTextureEnvironment(RTE_Modulate); glColor4ub(255,255,255,255); VECCOPY(&pos, seg->position); pos.z -= 0.6f*XADJ; //offset also by ship's static sprite offset amount pos.z -= ship->staticinfo->trailSpriteOffset[trail->trailNum]; trailInplaceShipToWorld((real32*)&pos, seg->rotation, seg->translation); if (g_glowHandle != TR_InvalidHandle) { trMakeCurrent(g_glowHandle); partFilter(TRUE); rndBillboardEnable(&pos); primSolidTexture3((vector*)&origin, rad, c, g_glowHandle); rndBillboardDisable(); } else { rndBillboardEnable(&pos); primCircleSolid3((vector*)&origin, rad, 16, c); rndBillboardDisable(); } glEnable(GL_CULL_FACE); rndLightingEnable(TRUE); glDepthMask(GL_TRUE); if (trailMeshes[TM_ACCEL] != NULL && (trail->lastvelsquared - trail->prevvelsquared > ACCELTHRESH) && (velratio > VELRATIO_ACCELTHRESH)) { _afterburning = TRUE; seg->wide = TRUE; } } /*----------------------------------------------------------------------------- Name : trailStraighten Description : apply tension to the initial portion of a trail Inputs : positions - segment positions coordsys - coordinate system at engine nozzle Outputs : positions is modified Return : ----------------------------------------------------------------------------*/ void trailStraighten(vector positions[], real32* coordsys) { vector directions[6]; real32 distances[6]; sdword i; //gather info for (i = 0; i < 6; i++) { vecSub(directions[i], positions[i+1], positions[i]); distances[i] = vecMagnitudeSquared(directions[i]); if (distances[i] > 0.0f) { distances[i] = fsqrt(distances[i]); } vecNormalize(&directions[i]); } //setup tension #if 1 VECCOPY(&directions[0], &zaxis); trailInplaceShipToWorld((real32*)&directions[0], coordsys, origin); vecNormalize(&directions[0]); vecAddTo(directions[1], directions[0]); vecMultiplyByScalar(directions[1], 0.5f); vecMultiplyByScalar(directions[2], 2.0f); vecAddTo(directions[2], directions[0]); vecMultiplyByScalar(directions[2], 0.3333f); // vecMultiplyByScalar(directions[3], 4.0f); // vecAddTo(directions[3], directions[0]); // vecMultiplyByScalar(directions[3], 0.25f); // vecMultiplyByScalar(directions[4], 6.0f); // vecAddTo(directions[4], directions[0]); // vecMultiplyByScalar(directions[4], 0.1666f); #endif //rebuild trail segments for (i = 2; i < 6; i++) { VECCOPY(&positions[i], &directions[i]); vecMultiplyByScalar(positions[i], distances[i]); vecAddTo(positions[i], positions[i-1]); } } /*----------------------------------------------------------------------------- Name : trailPositions Description : store the trail segment info in provided lists Inputs : Outputs : Return : ----------------------------------------------------------------------------*/ void trailPositions(sdword n, vector positions[], vector horizontals[], vector verticals[], shiptrail* trail) { sdword i, index; trailsegment* segment; index = trail->iHead <= 0 ? trail->staticInfo->nSegments - 1 : trail->iHead - 1; if (horizontals == NULL) { for (i = 0; i < n; i++) { segment = &trail->segments[index]; VECCOPY(&positions[i+1], segment->position); index = index <= 1 ? trail->staticInfo->nSegments - 1 : index - 1; } } else { for (i = 0; i < n; i++) { segment = &trail->segments[index]; VECCOPY(&horizontals[i+1], segment->horizontal); VECCOPY(&verticals[i+1], segment->vertical); VECCOPY(&positions[i+1], segment->position); index = index <= 1 ? trail->staticInfo->nSegments - 1 : index - 1; } } #if 0 if (n > 6) { Ship* ship = (Ship*)trail->vship; trailStraighten(positions, (real32*)&ship->rotinfo.coordsys); } #endif } static real32 _HALFWIDTH, _HALFHEIGHT; static shiptrail* activeTrail; static sdword activeSegments; /*----------------------------------------------------------------------------- Name : trailLineInit Description : initializes globals before a trail is rendered Inputs : trail - the trail n - maximum number of segments Outputs : activeTrail, _HALFWIDTH, _HALFHEIGHT are set Return : ----------------------------------------------------------------------------*/ void trailLineInit(shiptrail* trail, sdword n) { activeTrail = trail; activeSegments = n; _HALFWIDTH = trailHalfWidth(trail) - HALFWIDTH_FALLOFF; _HALFHEIGHT = trailHalfHeight(trail) - HALFWIDTH_FALLOFF; } #define VERT(V) glVertex3fv((GLfloat*)&V) #define COLx(C,A) glColor4ub(colRed(C), colGreen(C), colBlue(C), (A)) #define PYRAMID_ALPHA_LO 15 #define PYRAMID_ALPHA_MID 79 #define PYRAMID_ALPHA_HI 143 /*----------------------------------------------------------------------------- Name : trailLinePyramid Description : draws a translucent pyramid, the outer sheath of engine trails at particular LODs Inputs : LOD - the lod i - which position in provided arrays vectora, vectorb - from, to vectors c - the colour of the segment horiz - horizontal components of the coordinate space vert - vertical components wide - whether this trail is wide or not Outputs : Return : ----------------------------------------------------------------------------*/ void trailLinePyramid( sdword LOD, sdword i, vector* vectora, vector* vectorb, color c, vector horiz[], vector vert[], bool wide) { vector from, to; real32 VS, halfWidth, width, height; real32 scaleFactor, temp; static vector a0, a0hi, a0lo, a1, a2; static vector b0, b0hi, b0lo, b1, b2; static color cb; //setup VECCOPY(&from, vectora); VECCOPY(&to, vectorb); VS = trailHeightScalar(activeTrail); halfWidth = trailHalfWidth(activeTrail); scaleFactor = NLipsScaleFactor; //calc if (i == 0) { VECCOPY(&a0, &from); temp = halfWidth * scaleFactor; a1.x = a0.x - horiz[i].x * temp; a1.y = a0.y - horiz[i].y * temp; a1.z = a0.z - horiz[i].z * temp; a2.x = a0.x + horiz[i].x * temp; a2.y = a0.y + horiz[i].y * temp; a2.z = a0.z + horiz[i].z * temp; temp *= VS; a0lo.x = a0.x - vert[i].x * temp; a0lo.y = a0.y - vert[i].y * temp; a0lo.z = a0.z - vert[i].z * temp; a0hi.x = a0.x + vert[i].x * temp; a0hi.y = a0.y + vert[i].y * temp; a0hi.z = a0.z + vert[i].z * temp; cb = c; } else { VECCOPY(&a0, &b0); VECCOPY(&a1, &b1); VECCOPY(&a2, &b2); VECCOPY(&a0lo, &b0lo); VECCOPY(&a0hi, &b0hi); { color ct = cb; cb = c; c = ct; } } VECCOPY(&b0, &to); if (wide) { width = _HALFWIDTH * BURN_SIZESCALE; height = VS*_HALFWIDTH * BURN_SIZESCALE; if (BURN_COLORADJUST < 0) { cb = trailColorDesaturate(cb, -BURN_COLORADJUST); } else { cb = trailColorSaturate(cb, BURN_COLORADJUST); } } else { width = _HALFWIDTH; height = VS*_HALFWIDTH; } temp = width * scaleFactor; b1.x = b0.x - horiz[i+1].x * temp; b1.y = b0.y - horiz[i+1].y * temp; b1.z = b0.z - horiz[i+1].z * temp; b2.x = b0.x + horiz[i+1].x * temp; b2.y = b0.y + horiz[i+1].y * temp; b2.z = b0.z + horiz[i+1].z * temp; temp = height * scaleFactor; b0lo.x = b0.x - vert[i+1].x * temp; b0lo.y = b0.y - vert[i+1].y * temp; b0lo.z = b0.z - vert[i+1].z * temp; b0hi.x = b0.x + vert[i+1].x * temp; b0hi.y = b0.y + vert[i+1].y * temp; b0hi.z = b0.z + vert[i+1].z * temp; //render glEnable(GL_BLEND); glDepthMask(GL_FALSE); glBegin(GL_QUADS); //a COLx(c,PYRAMID_ALPHA_LO); VERT(a1); COLx(cb,PYRAMID_ALPHA_LO); VERT(b1); COLx(cb,PYRAMID_ALPHA_HI); VERT(b0hi); COLx(c,PYRAMID_ALPHA_HI); VERT(a0hi); //b VERT(a0hi); COLx(cb,PYRAMID_ALPHA_HI); VERT(b0hi); COLx(cb,PYRAMID_ALPHA_LO); VERT(b2); COLx(c,PYRAMID_ALPHA_LO); VERT(a2); //c VERT(a2); COLx(cb,PYRAMID_ALPHA_LO); VERT(b2); COLx(cb,PYRAMID_ALPHA_HI); VERT(b0lo); COLx(c,PYRAMID_ALPHA_HI); VERT(a0lo); //d VERT(a0lo); COLx(cb,PYRAMID_ALPHA_HI); VERT(b0lo); COLx(cb,PYRAMID_ALPHA_LO); VERT(b1); COLx(c,PYRAMID_ALPHA_LO); VERT(a1); glEnd(); glDepthMask(GL_TRUE); glDisable(GL_BLEND); } /*----------------------------------------------------------------------------- Name : trailLineFuzzySheath Description : draws a simplified pyramid (ie. flat) Inputs : LOD - the lod number i - trail segment number vectora, vectorb - from, to c - segment colour horiz - horizontal components of the coordinate space Outputs : Return : ----------------------------------------------------------------------------*/ void trailLineFuzzySheath(sdword LOD, sdword i, vector* vectora, vector* vectorb, color c, vector horiz[]) { vector from, to; real32 halfWidth, width, scalar = 0.9f; real32 scaleFactor, temp; static vector a0, a1, a2; static vector b0, b1, b2; static color cb; //setup scaleFactor = NLipsScaleFactor; VECCOPY(&from, vectora); VECCOPY(&to, vectorb); halfWidth = scalar * trailHalfWidth(activeTrail); //calc if (i == 0) { VECCOPY(&a0, &from); temp = halfWidth * scaleFactor; a1.x = a0.x - horiz[i].x * temp; a1.y = a0.y - horiz[i].y * temp; a1.z = a0.z - horiz[i].z * temp; a2.x = a0.x + horiz[i].x * temp; a2.y = a0.y + horiz[i].y * temp; a2.z = a0.z + horiz[i].z * temp; cb = c; } else { VECCOPY(&a0, &b0); VECCOPY(&a1, &b1); VECCOPY(&a2, &b2); { color ct = cb; cb = c; c = ct; } } VECCOPY(&b0, &to); width = scalar * _HALFWIDTH; temp = width * scaleFactor; b1.x = b0.x - horiz[i+1].x * temp; b1.y = b0.y - horiz[i+1].y * temp; b1.z = b0.z - horiz[i+1].z * temp; b2.x = b0.x + horiz[i+1].x * temp; b2.y = b0.y + horiz[i+1].y * temp; b2.z = b0.z + horiz[i+1].z * temp; //render glEnable(GL_BLEND); glDisable(GL_CULL_FACE); glDepthMask(GL_FALSE); glBegin(GL_QUADS); COLx(c, PYRAMID_ALPHA_MID); VERT(a1); COLx(cb, PYRAMID_ALPHA_MID); VERT(b1); VERT(b2); COLx(c, PYRAMID_ALPHA_MID); VERT(a2); glEnd(); glDisable(GL_BLEND); glEnable(GL_CULL_FACE); glDepthMask(GL_TRUE); } /*----------------------------------------------------------------------------- Name : trailLineBillboard Description : draws a pseudo-billboarded ribbon as an engine trail Inputs : LOD - lod number i - segment number vectora, vectorb - from, to vectors c - colour of the segment Outputs : Return : ----------------------------------------------------------------------------*/ void trailLineBillboard( sdword LOD, sdword i, vector* vectora, vector* vectorb, color c) { vector veye, vseg, vcross; vector from, fromHi; vector to, toHi; vector subAmount; ubyte alpha; static color cb; static vector lastTo, lastToHi; //setup VECCOPY(&from, vectora); VECCOPY(&to, vectorb); //calc vecSub(veye, from, mrCamera->eyeposition); vecSub(vseg, to, from); vecNormalize(&veye); vecNormalize(&vseg); vecCrossProduct(vcross, vseg, veye); vecNormalize(&vcross); vecScalarMultiply(fromHi, vcross, trailRibbonAdjust(activeTrail)*(_HALFHEIGHT+HALFWIDTH_FALLOFF)); vecAddTo(fromHi, from); vecScalarMultiply(toHi, vcross, trailRibbonAdjust(activeTrail)*_HALFHEIGHT); vecAddTo(toHi, to); vecScalarMultiply(subAmount, vcross, trailRibbonAdjust(activeTrail)*(_HALFHEIGHT+HALFWIDTH_FALLOFF)); vecSubFrom(from, subAmount); vecScalarMultiply(subAmount, vcross, trailRibbonAdjust(activeTrail)*_HALFHEIGHT); vecSubFrom(to, subAmount); //render if (_fastBlends) { glEnable(GL_BLEND); } glDisable(GL_CULL_FACE); glDepthMask(GL_FALSE); alpha = usingShader ? (ubyte)(127.0f * meshFadeAlpha) : 127; glBegin(GL_QUADS); if (i == 0) { cb = c; } else { VECCOPY(&from, &lastTo); VECCOPY(&fromHi, &lastToHi); } COLx(cb,alpha); VERT(from); VERT(fromHi); COLx(c,alpha); VERT(toHi); VERT(to); glEnd(); glEnable(GL_CULL_FACE); if (_fastBlends) { glDisable(GL_BLEND); } glDepthMask(GL_TRUE); VECCOPY(&lastTo, &to); VECCOPY(&lastToHi, &toHi); cb = c; } /*----------------------------------------------------------------------------- Name : trailSurpriseColorAdjust Description : adjusts a given colour with some random flair Inputs : index - trail segment number max - max number of trail segments c - colour of the segment Outputs : Return : ----------------------------------------------------------------------------*/ color trailSurpriseColorAdjust(sdword index, sdword max, color c) { sdword r, g, b; sdword randval; if ((COLOR_ADJUST == 0) || (COLORADJUST_FREQ == 0) || (index > (max - 5))) { return c; } if ((sdword)(ranRandom(RAN_Trails2) % 100) > COLORADJUST_FREQ) { return c; } r = colRed(c); g = colGreen(c); b = colBlue(c); randval = ranRandom(RAN_Trails3) % COLOR_ADJUST; if (ranRandom(RAN_Trails4) & 1) { r = MIN2(r+randval,255); g = MIN2(g+randval,255); b = MIN2(b+randval,255); } else { r = MAX2(r-randval,0); g = MAX2(g-randval,0); b = MAX2(b-randval,0); } return colRGB(r,g,b); } /*----------------------------------------------------------------------------- Name : trailLineSequence Description : render an engine trail that is merely a line Inputs : LOD - the LOD number n - number of segments vectors - segment position array segmentArray - array of segment colours Outputs : Return : ----------------------------------------------------------------------------*/ void trailLineSequence(sdword LOD, sdword n, vector vectors[], color* segmentArray) { sdword i; color c; ubyte alpha; if (_fastBlends) { alpha = 163; glEnable(GL_BLEND); } else { alpha = 255; } if (usingShader) { alpha = (ubyte)((real32)alpha * meshFadeAlpha); } if (LOD == 3) { glLineWidth(2.0f); } glBegin(GL_LINE_STRIP); for (i = 0; i < n; i++) { c = trailSurpriseColorAdjust(i, n, segmentArray[i+(i==0)]); glColor4ub(colRed(c), colGreen(c), colBlue(c), alpha); glVertex3fv((GLfloat*)(vectors + i)); } glEnd(); if (LOD == 3) { glLineWidth(1.0f); } if (_fastBlends) { glDisable(GL_BLEND); } } /*----------------------------------------------------------------------------- Name : trailLine Description : renders an engine trail, accounting for LOD [0..2] Inputs : ... Outputs : Return : ----------------------------------------------------------------------------*/ void trailLine(sdword LOD, sdword i, vector vectors[], color c, vector horiz[], vector vert[], bool wides[]) { if (bitTest(gDevcaps2, DEVSTAT2_NO_IALPHA)) { trailLineBillboard(LOD, i, vectors + i, vectors + i + 1, c); if (_fastBlends) { trailLineFuzzySheath(LOD, i, vectors + i, vectors + i + 1, c, horiz); } return; } switch (LOD) { case 0: case 1: trailLineBillboard(LOD, i, vectors + i, vectors + i + 1, c); if (_fastBlends) { if (activeTrail->style == 1) { trailLinePyramid(LOD, i, vectors + i, vectors + i + 1, c, horiz, vert, wides[i]); } } break; case 2: trailLineBillboard(LOD, i, vectors + i, vectors + i + 1, c); if (_fastBlends) { trailLineFuzzySheath(LOD, i, vectors + i, vectors + i + 1, c, horiz); } break; } } #undef VERT #undef COL #undef COLw /*----------------------------------------------------------------------------- Name : mistrailDrawLine Description : Inputs : Outputs : Return : ----------------------------------------------------------------------------*/ void mistrailDrawLine(vector* a, vector* b, sdword segment, sdword nSegments, color c0, color c1) { if (_fastBlends) { sdword alpha; alpha = (sdword)(255.0f * (1.0f - ((real32)segment / (real32)nSegments))); glBegin(GL_LINES); glColor4ub(colRed(c0), colGreen(c0), colBlue(c0), (GLubyte)alpha); glVertex3fv((GLfloat*)a); glColor4ub(colRed(c1), colGreen(c1), colBlue(c1), (GLubyte)alpha); glVertex3fv((GLfloat*)b); glEnd(); } else { glBegin(GL_LINES); glColor3ub(colRed(c0), colGreen(c0), colBlue(c0)); glVertex3fv((GLfloat*)a); glColor3ub(colRed(c1), colGreen(c1), colBlue(c1)); glVertex3fv((GLfloat*)b); glEnd(); } } /*----------------------------------------------------------------------------- Name : mistrailDraw Description : draw missile trails Inputs : Outputs : Return : ----------------------------------------------------------------------------*/ void mistrailDraw(vector* current, missiletrail* trail, sdword LOD, sdword teamIndex) { sdword index, count; vector* lastVector; trailstatic* trailStatic = trail->staticInfo; color prevColor; color* segmentArray; if (!enableTrails) { return; } dbgAssert(teamIndex >= 0 && teamIndex < MAX_MULTIPLAYER_PLAYERS); _fastBlends = glCapFastFeature(GL_BLEND); rndLightingEnable(FALSE); rndTextureEnable(FALSE); glShadeModel(GL_SMOOTH); index = trail->iHead <= 0 ? trailStatic->nSegments - 1 : trail->iHead - 1; lastVector = current; segmentArray = trail->staticInfo->segmentColor[teamIndex]; if (_fastBlends) { rndAdditiveBlends(TRUE); glEnable(GL_BLEND); } for (count = 1; count < trail->nLength; count++) { dbgAssert(index >= 0 && index < trailStatic->nSegments); if (count == 1) { prevColor = segmentArray[count]; } mistrailDrawLine(lastVector, &trail->segments[index].position, count, trail->nLength, prevColor, segmentArray[count]); prevColor = segmentArray[count]; lastVector = &trail->segments[index].position; index = index <= 1 ? trailStatic->nSegments - 1: index - 1; if (index == trail->iHead) { break; } } rndLightingEnable(TRUE); if (_fastBlends) { glDisable(GL_BLEND); rndAdditiveBlends(FALSE); } } /*----------------------------------------------------------------------------- Name : trailDraw Description : Draw ship trails Inputs : trail - pointer to ship trail LOD - level-of-detail to draw it at (typically the level of the ship it is attached to). current - current location of ship Outputs : Return : void ----------------------------------------------------------------------------*/ void trailDraw(vector *current, shiptrail *trail, sdword LOD, sdword teamIndex) { sdword index; // sdword count; trailsegment lastSegment, currentSegment; trailstatic *trailStatic = trail->staticInfo; color *segmentArray; real32 velratio, mag, maxvel; bool dontdrawtrail = FALSE; Ship* ship = (Ship*)trail->vship; NLipsScaleFactor = ship->magnitudeSquared; if (_isnan((double)NLipsScaleFactor)) { NLipsScaleFactor = 1.0f; } if (!enableTrails || !bTrailRender) { return; } if (((ship->posinfo.isMoving & ISMOVING_MOVING) == 0) || (ship->flags & (SOF_DontDrawTrails|SOF_Clamped|SOF_Disabled))) // clamped objs dont draw trails either { dontdrawtrail = TRUE; } else if ((ship->flags & SOF_NISShip) == 0) { // for non NIS ships, if they don't have a command or its NULL, don't draw engine trails if ((ship->isDodging) || (universe.totaltimeelapsed < ship->DodgeTime)) { CommandToDo *shipcommand = ship->command; if (shipcommand == NULL) { dontdrawtrail = TRUE; } else if ((shipcommand->ordertype.attributes & (COMMAND_IS_ATTACKINGANDMOVING|COMMAND_IS_HOLDINGPATTERN|COMMAND_IS_PROTECTING)) == 0) { switch (shipcommand->ordertype.order) { case COMMAND_NULL: case COMMAND_HALT: dontdrawtrail = TRUE; break; } } } } if (trail->style > 2) { if (dontdrawtrail) { switch (trail->state) { case TRAIL_CONTRACTING: break; case TRAIL_EXPANDED: case TRAIL_EXPANDING: trail->state = TRAIL_CONTRACTING; trail->counter = (ubyte)TRAIL_EXPANSION_TICKS - trail->counter; break; case TRAIL_CONTRACTED: return; } } else { switch (trail->state) { case TRAIL_CONTRACTED: trail->state = TRAIL_EXPANDING; trail->counter = (ubyte)TRAIL_EXPANSION_TICKS; break; case TRAIL_EXPANDED: case TRAIL_EXPANDING: break; case TRAIL_CONTRACTING: trail->state = TRAIL_EXPANDING; trail->counter = (ubyte)TRAIL_EXPANSION_TICKS - trail->counter; break; } } trailDrawCapitalGlow(trail, LOD); return; } if (dontdrawtrail) { trailZeroLength(trail); return; } _fastBlends = glCapFastFeature(GL_BLEND); trailGetNozzleOffset(lastSegment.position, trail); trailGetCoordsys(lastSegment.rotation, trail); trailGetTranslation(lastSegment.translation, trail); trail->segments[trail->iHead].wide = FALSE; if (LOD < 3) { real32 rad = ship->staticinfo->staticheader.staticCollInfo.collspheresize; segmentArray = trail->staticInfo->segmentColor[teamIndex]; _afterburning = FALSE; trailDrawBillboardedSquareThingz(trail, &lastSegment, rad, ship, segmentArray[2]); if (lastSegment.wide) { trail->segments[trail->iHead].wide = TRUE; } } lastSegment.position[2] -= 0.3f*XADJ; for (index = 0; index < 3; index++) { lastSegment.position[index] *= NLipsScaleFactor; } trailCopySegment(¤tSegment, &lastSegment); mag = fsqrt(vecMagnitudeSquared(ship->posinfo.velocity)); if (mag < 0.001f) //decision { return; } maxvel = tacticsGetShipsMaxVelocity(ship); velratio = mag / maxvel; dbgAssert(teamIndex >= 0 && teamIndex < MAX_MULTIPLAYER_PLAYERS); #if TRAIL_GATHER_STATS trailsRendered++; #endif index = trail->iHead <= 0 ? trailStatic->nSegments - 1 : trail->iHead - 1; index = index <= 1 ? trailStatic->nSegments - 1: index - 1; segmentArray = trail->staticInfo->segmentColor[teamIndex]; if (velratio > VELRATIO_CUTOFF) { velratio = 1.0f; } //actually display the trail now if (trail->nLength > 3) { vector segments[40]; vector horizontals[40]; vector verticals[40]; bool wides[40]; sdword i, n; /* real32 size = SIZE_MAJOR * velratio * NLipsScaleFactor; real32 sizeinc = SIZE_INC * velratio * NLipsScaleFactor; real32 sizeinc_inc = SIZEINC_INC * velratio * NLipsScaleFactor; real32 sizemax = SIZE_MAX * NLipsScaleFactor; */ n = MIN2(38, trail->nLength); if (LOD <= TRAIL_LINE_CUTOFF_LOD) { trailPositions(n, segments, horizontals, verticals, trail); VECCOPY(&horizontals[0], &horizontals[1]); VECCOPY(&verticals[0], &verticals[1]); } else { trailPositions(n, segments, NULL, NULL, trail); } wides[0] = wides[1] = FALSE; for (i = 0; i < n; i++) { wides[i] = trail->segments[i].wide; } wides[n] = FALSE; VECCOPY(&segments[n], &segments[n-1]); trailInplaceShipToWorld(currentSegment.position, currentSegment.rotation, currentSegment.translation); VECCOPY(&segments[0], currentSegment.position); //now draw if (bTrailDraw) { rndLightingEnable(FALSE); rndTextureEnable(FALSE); rndAdditiveBlends(TRUE); //initialize convenient globals trailLineInit(trail, n); //NaN checking (error correction) for (i = 0; i < n; i++) { if (_isnan((double)segments[i].x)) { n = (i == 0) ? 0 : i - 1; break; } } if (LOD > TRAIL_LINE_CUTOFF_LOD) { //trail is just a line trailLineSequence(LOD, n, segments, segmentArray); } else { //complex trail for (i = 0; i < (n-1); i++) { trailLine(LOD, i, segments, trailSurpriseColorAdjust(i, n, segmentArray[i+(i==0)]) // segmentArray[i+(i==0)] , horizontals, verticals, wides); if (_HALFWIDTH > HALFWIDTH_MIN) { _HALFWIDTH -= HALFWIDTH_FALLOFF; _HALFHEIGHT -= trailHeightScalar(activeTrail)*HALFWIDTH_FALLOFF; } } } rndLightingEnable(TRUE); glEnable(GL_CULL_FACE); glDisable(GL_BLEND); rndAdditiveBlends(FALSE); } //compute the acceleration with this if (trail->ranCounter == 0) { trail->prevvelsquared = trail->lastvelsquared; trail->lastvelsquared = vecMagnitudeSquared(ship->posinfo.velocity); } } } /*----------------------------------------------------------------------------- Name : trailMakeWobbly Description : enter/exit a trail/glow's "wobbly" state Inputs : ship - the ship whose trail(s)/glow(s) are to "wobble" state - TRUE or FALSE Outputs : Return : ----------------------------------------------------------------------------*/ void trailMakeWobbly(void* vship, bool state) { sdword i; Ship* ship = (Ship*)vship; dbgAssert(ship != NULL); for (i = 0; i < MAX_NUM_TRAILS; i++) { if (ship->trail[i] != NULL) { ship->trail[i]->wobbly = state; } } } /*============================================================================= Trail-static info parsing functions: =============================================================================*/ static sdword tpNSegments; static sdword tpGranularity; static color *tpColorArray[MAX_MULTIPLAYER_PLAYERS]; #define TP_InvalidColor colRGBA(255, 0, 255, 13) static void trailSegmentsRead(char *directory,char *field,void *dataToFillIn) { //read number of trail segments sdword nScanned, index, teamIndex; nScanned = sscanf(field, "%d", &tpNSegments); dbgAssert(nScanned == 1); dbgAssert(tpNSegments > 1 && tpNSegments < 1000); for (teamIndex = 0; teamIndex < MAX_MULTIPLAYER_PLAYERS; teamIndex++) { tpColorArray[teamIndex] = memAlloc(sizeof(color) * tpNSegments, "Temp. Color Array", 0); for (index = 0; index < tpNSegments; index++) { //clear all color entries tpColorArray[teamIndex][index] = TP_InvalidColor; } } } static void trailGranularityRead(char *directory,char *field,void *dataToFillIn) { //read trail granularity sdword nScanned; nScanned = sscanf(field, "%d", &tpGranularity); dbgAssert(nScanned == 1); } static void trailColorRead(char *directory,char *field,void *dataToFillIn) { sdword nScanned, index, teamIndex; udword red, green, blue; nScanned = sscanf(field, "%d,%d,%d,%d,%d", &teamIndex, &index, &red, &green, &blue); dbgAssert(nScanned == 5); dbgAssert(index >= 0 && index < tpNSegments); dbgAssert(tpColorArray[teamIndex] != NULL); tpColorArray[teamIndex][index] = colRGB(red, green, blue); } /*----------------------------------------------------------------------------- Name : trailStaticInfoParse Description : Parse a ship definition file and create a trailstatic structure Inputs : fileName - name of .shp file to load from Outputs : Potentially allocates new memory and initializes structure Return : pointer to new or default trailstatic structure or NULL if no info found. ----------------------------------------------------------------------------*/ trailstatic *trailStaticInfoParse(char *directory, char *fileName) { sdword index, j, lastIndex, teamIndex; udword red, green, blue; trailstatic *newStatic; tpGranularity = tpNSegments = 0; //set initial values for (teamIndex = 0; teamIndex < MAX_MULTIPLAYER_PLAYERS; teamIndex++) { tpColorArray[teamIndex] = NULL; } scriptSet(directory, fileName, trailStaticScriptTable); //read in the script if (tpColorArray[0] == NULL) //if nothing was loaded { return(NULL); } dbgAssert(tpGranularity > 0); dbgAssert(tpNSegments > 1 && tpGranularity < 1000); newStatic = memAlloc(trailStaticSize(tpNSegments), "Trail Static Info", NonVolatile); newStatic->granularity = tpGranularity; //init basic info newStatic->nSegments = tpNSegments; //now build the interpolated color table for (teamIndex = 0; teamIndex < MAX_MULTIPLAYER_PLAYERS; teamIndex++) { newStatic->segmentColor[teamIndex] = (color *)(((ubyte *)newStatic) + sizeof(trailstatic) + sizeof(color) * tpNSegments * teamIndex); for (index = 0; index < tpNSegments; index++) { //ensure first entry valid if (tpColorArray[teamIndex][index] != TP_InvalidColor) { tpColorArray[teamIndex][0] = tpColorArray[teamIndex][index]; break; } } #if TRAIL_ERROR_CHECKING if (tpColorArray[teamIndex][0] == TP_InvalidColor) { //if first entry still not set dbgFatalf(DBG_Loc, "No color specified for the trail of %s", fileName); } #endif for (index = tpNSegments - 1; index > 0; index--) { if (tpColorArray[teamIndex][index] != TP_InvalidColor) { tpColorArray[teamIndex][tpNSegments - 1] = tpColorArray[teamIndex][index]; break; } } #if TRAIL_ERROR_CHECKING if (tpColorArray[teamIndex][tpNSegments - 1] == TP_InvalidColor) { //if first entry still not set dbgFatalf(DBG_Loc, "No color specified for the trail of %s", fileName); } #endif for (index = lastIndex = 0; index < tpNSegments; index++) { //process entire list newStatic->segmentColor[teamIndex][index] = tpColorArray[teamIndex][index]; if (tpColorArray[teamIndex][index] != TP_InvalidColor) { //if this color is set // tpColorArray[teamIndex][index] &= TP_KeyColor; for (j = lastIndex + 1; j < index; j++) { //between last valid color to this one red = (colRed(tpColorArray[teamIndex][lastIndex]) * (index - j) + colRed(tpColorArray[teamIndex][index]) * (j - lastIndex)) / (index - lastIndex); green = (colGreen(tpColorArray[teamIndex][lastIndex]) * (index - j) + colGreen(tpColorArray[teamIndex][index]) * (j - lastIndex)) / (index - lastIndex); blue = (colBlue(tpColorArray[teamIndex][lastIndex]) * (index - j) + colBlue(tpColorArray[teamIndex][index]) * (j - lastIndex)) / (index - lastIndex); newStatic->segmentColor[teamIndex][j] = colRGB(red, green, blue);//interpolate the colors } newStatic->segmentColor[teamIndex][index] &= TP_KeyColor; lastIndex = index; } } memFree(tpColorArray[teamIndex]); //free old color array } return(newStatic); } /*----------------------------------------------------------------------------- Name : trailRecolorize Description : Recolorize the specified trail to reflect a change in team colors. Inputs : trailStatic - static info for trail to recolorize. Outputs : Trail colors will be re-interpolated to reflect new color changes. Return : void ----------------------------------------------------------------------------*/ void trailRecolorize(trailstatic *trailStatic) { sdword teamIndex, index, lastIndex, cpIndex, j; udword red, green, blue; for (teamIndex = 0; teamIndex < MAX_MULTIPLAYER_PLAYERS; teamIndex++) { //for each team for (index = lastIndex = cpIndex = 0; index < trailStatic->nSegments; index++) { //for each trail segment if ((trailStatic->segmentColor[teamIndex][index] & (~TP_KeyColor)) == 0) { //if this color is set trailStatic->segmentColor[teamIndex][index] = //set new control point color teColorSchemes[teamIndex].trailColors[cpIndex] & TP_KeyColor; cpIndex++; //update control point index dbgAssert(cpIndex <= TE_NumberTrailColors); for (j = lastIndex + 1; j < index; j++) { //between last valid color to this one red = (colRed(trailStatic->segmentColor[teamIndex][lastIndex]) * (index - j) + colRed(trailStatic->segmentColor[teamIndex][index]) * (j - lastIndex)) / (index - lastIndex); green = (colGreen(trailStatic->segmentColor[teamIndex][lastIndex]) * (index - j) + colGreen(trailStatic->segmentColor[teamIndex][index]) * (j - lastIndex)) / (index - lastIndex); blue = (colBlue(trailStatic->segmentColor[teamIndex][lastIndex]) * (index - j) + colBlue(trailStatic->segmentColor[teamIndex][index]) * (j - lastIndex)) / (index - lastIndex); trailStatic->segmentColor[teamIndex][j] = colRGB(red, green, blue);//interpolate the colors } lastIndex = index; } } } } /*----------------------------------------------------------------------------- Name : trailsRecolorize Description : Recolorize all ship trails to reflect a change in team colors. Inputs : void Outputs : ALL ships will have their trail colors re-interpolated to reflect new color changes. Return : void ----------------------------------------------------------------------------*/ void trailsRecolorize(void) { sdword shiprace, firstshiptype, lastshiptype, shiptype, i; ShipStaticInfo *shipstaticinfo; for (shiprace=0;shipracetrailStatic[i] != NULL) { trailRecolorize(shipstaticinfo->trailStatic[i]); } } //InitStatShipInfoPost(shipstaticinfo); } } } /*----------------------------------------------------------------------------- Name : mistrailsRecolorize Description : recolorize all missile trails to reflect a change in team colors Inputs : Outputs : ALL missiles will have their trail colors re-interpolated to reflect new color changes Return : ----------------------------------------------------------------------------*/ void mistrailsRecolorize(void) { sdword shiprace; for (shiprace = 0; shiprace < NUM_RACES; shiprace++) { if (missileStaticInfos[shiprace].trailStatic != NULL) { trailRecolorize(missileStaticInfos[shiprace].trailStatic); } } } /*----------------------------------------------------------------------------- Name : trailZeroLength Description : Make a trail zero segments long Inputs : trail - trail who's length we are to zero Outputs : Return : void ----------------------------------------------------------------------------*/ void trailZeroLength(shiptrail* trail) { dbgAssert(trail != NULL); trail->iHead = trail->iTail = trail->nLength = 0; trail->grainCounter = 0; } /*----------------------------------------------------------------------------- Name : trailMove Description : Moves the history of positions by a given amount. Inputs : trail - trail to move delta - amount to move by Outputs : Return : ----------------------------------------------------------------------------*/ void trailMove(shiptrail* trail, vector *delta) { sdword index; trailsegment *segment; dbgAssert(trail != NULL); segment = trail->segments; for (index = trail->staticInfo->nSegments; index >= 0; index--, segment++) { segment->position[0] += delta->x; segment->position[1] += delta->y; segment->position[2] += delta->z; } }