/*============================================================================= Name : nebulae.c Purpose : management and display of nebulae Created 3/9/1998 by khent Copyright Relic Entertainment, Inc. All rights reserved. =============================================================================*/ #ifndef SW_Render #include #endif #include #include #include #include "types.h" #include "memory.h" #include "clouds.h" #include "randy.h" #include "glinc.h" #include "fastmath.h" #include "render.h" #include "debug.h" #include "univupdate.h" #include "statscript.h" #include "matrix.h" #include "universe.h" #include "nebulae.h" #include "mainrgn.h" #include "autolod.h" #include "shader.h" #include "glcaps.h" #include "SaveGame.h" ubyte nebColor[4]; #define NEB_RENDER RGL_SPECULAR2_RENDER #define TENDRILBRIGHTEN 60 #define TENDRILCOLOR0(a) \ { \ ubyte b = a; \ if (!_bright) \ { \ b += TENDRILBRIGHTEN; \ } \ nebColor[0] = (ubyte)(NEB_FOG_RED * 255.0f); \ nebColor[1] = (ubyte)(NEB_FOG_GREEN * 255.0f); \ nebColor[2] = (ubyte)(NEB_FOG_BLUE * 255.0f); \ nebColor[3] = b; \ glColor4ub(nebColor[0], nebColor[1], nebColor[2], nebColor[3]); \ } #define TENDRILCOLOR(t,a) \ { \ ubyte b = a; \ if (!_bright) \ { \ b += TENDRILBRIGHTEN; \ } \ nebColor[0] = colRed(t->colour); \ nebColor[1] = colGreen(t->colour); \ nebColor[2] = colBlue(t->colour); \ nebColor[3] = b; \ glColor4ub(nebColor[0], nebColor[1], nebColor[2], nebColor[3]); \ } // ----- // DATA // ----- static bool _bright = TRUE; static ubyte TENDRILALPHA = 163; #define NEB_CHUNK_RADIUS 800.0f static real32 nebFogColor[4]; real32 nebFogSum = 0.0f; static udword _alloc; static udword neb_frame_counter; real32 NEB_RADIUS = 100.0f; //initialize to reasonable defaults static real32 NEB_FOG_RED = 0.1f; static real32 NEB_FOG_GREEN = 0.3f; static real32 NEB_FOG_BLUE = 0.7f; static real32 NEB_BG_SCALAR = 0.2f; static real32 NEB_TENDRIL_RED_DEV = 0.05f; static real32 NEB_TENDRIL_GREEN_DEV = 0.05f; static real32 NEB_TENDRIL_BLUE_DEV = 0.1f; static real32 NEB_DISTANCE_DESATURATION = 127.0f; static real32 NEB_MAX_TENDRIL_ANGULAR_DEVIATION = 45.0f; static real32 NEB_MAX_TENDRIL_DISTANCE = 80000.0f; static real32 NEB_PICK_DISTANCE_THRESHOLD = 12000.0f; static real32 NEB_PICK_DISTANCE_MINTHRESH = 500.0f; static real32 NEB_SHUFFLE_DISTANCE = 550.0f; static real32 NEB_VEL_RAN_MULTIPLIER = 0.4f; static real32 NEB_RAN_RANGE = 3.0f; static real32 NEB_VEL_BASE = 0.4f; static real32 NEB_TENDRIL_RADIUS_BASE = 200.0f; static real32 NEB_TENDRIL_RADIUS_RANGE = 400.0f; scriptEntry NebulaeTweaks[] = { makeEntry(NEB_RADIUS, scriptSetReal32CB), makeEntry(NEB_FOG_RED, scriptSetReal32CB), makeEntry(NEB_FOG_GREEN, scriptSetReal32CB), makeEntry(NEB_FOG_BLUE, scriptSetReal32CB), makeEntry(NEB_BG_SCALAR, scriptSetReal32CB), makeEntry(NEB_TENDRIL_RED_DEV, scriptSetReal32CB), makeEntry(NEB_TENDRIL_GREEN_DEV, scriptSetReal32CB), makeEntry(NEB_TENDRIL_BLUE_DEV, scriptSetReal32CB), makeEntry(NEB_DISTANCE_DESATURATION, scriptSetReal32CB), makeEntry(NEB_MAX_TENDRIL_ANGULAR_DEVIATION, scriptSetReal32CB), makeEntry(NEB_MAX_TENDRIL_DISTANCE, scriptSetReal32CB), makeEntry(NEB_PICK_DISTANCE_THRESHOLD, scriptSetReal32CB), makeEntry(NEB_PICK_DISTANCE_MINTHRESH, scriptSetReal32CB), makeEntry(NEB_SHUFFLE_DISTANCE, scriptSetReal32CB), makeEntry(NEB_VEL_RAN_MULTIPLIER, scriptSetReal32CB), makeEntry(NEB_RAN_RANGE, scriptSetReal32CB), makeEntry(NEB_VEL_BASE, scriptSetReal32CB), makeEntry(NEB_TENDRIL_RADIUS_BASE, scriptSetReal32CB), makeEntry(NEB_TENDRIL_RADIUS_RANGE, scriptSetReal32CB), endEntry }; nebulae_t nebNebulae[NEB_MAX_NEBULAE]; udword numNebulae = 0; void nebRenderNebula(nebulae_t* neb); // ----- // HELPERS // ----- real32 realRand(real32 n) { if (n == 0.0f) { return 0.0f; } else { return((real32)(ranRandom(RAN_Nebulae) % (sdword)(n * 10000.0f)) / 10000.0f); } } #if 1 real32 nebRealDist(real32 n, real32 d) { real32 twod = 2.0f*d; return(n + (realRand(twod) - d)); } #else real32 nebRealDist(real32 n, real32 d) { real32 r = (real32)((real64)(ranRandom(RAN_Nebulae) % 100000) / 100000.0); real32 sign = (ranRandom(RAN_Nebulae) % 2 == 0) ? -1.0f : 1.0f; if (d < 0.0f) { sign = -1.0f; } return n + sign*r*d; } #endif real32 _fabs(real32 n) { return (n < 0.0f) ? -n : n; } static real32 fsin(real32 a) { return (real32)sin((double)a); } static real32 fcos(real32 a) { return (real32)cos((double)a); } void nebHomogenize(hvector* h) { real32 oneOverW; if (h->w == 0.0f || h->w == 1.0f) return; oneOverW = 1.0f / h->w; h->x *= oneOverW; h->y *= oneOverW; h->z *= oneOverW; } static void _clearcolorUniverse() { color c = universe.backgroundColor; if (!smSensorsActive) { rndSetClearColor(colRGBA(colRed(c), colGreen(c), colBlue(c), 255)); } } // ----- // FUNCTIONS // ----- void nebColorInit() { nebFogColor[0] = NEB_FOG_RED; nebFogColor[1] = NEB_FOG_GREEN; nebFogColor[2] = NEB_FOG_BLUE; nebFogColor[3] = 1.0f; } /*----------------------------------------------------------------------------- Name : nebStartup Description : initializes necessary things. different from actually creating a chunk distribution and linking 'em up with tendrils, however Inputs : Outputs : Return : ----------------------------------------------------------------------------*/ void nebStartup() { sdword i; neb_frame_counter = 0; _alloc = 0; for (i = 0; i < NEB_MAX_NEBULAE; i++) { nebNebulae[i].numChunks = 0; nebNebulae[i].numTendrils = 0; nebNebulae[i].chunkTable = NULL; nebNebulae[i].tendrilTable = NULL; } ranParametersReset(RAN_Nebulae); nebColorInit(); _bright = glCapFastFeature(GL_BLEND); } /*----------------------------------------------------------------------------- Name : nebReset Description : resets nebulae Inputs : Outputs : Return : ----------------------------------------------------------------------------*/ void nebReset() { ranParametersReset(RAN_Nebulae); nebShutdown(); _clearcolorUniverse(); rndFogOn = FALSE; } /*----------------------------------------------------------------------------- Name : nebShutdown Description : frees memory &c Inputs : Outputs : Return : ----------------------------------------------------------------------------*/ void nebShutdown() { sdword i; if (numNebulae > 0) { for (i = 0; i < numNebulae; i++) { nebFreeChunks(&nebNebulae[i]); nebFreeTendrils(&nebNebulae[i]); } numNebulae = 0; } #if 0 if (_alloc > 0) { FILE* out = fopen("neb.dat", "wt"); if (out != NULL) { fprintf(out, "bytes %d (%dK)\n", _alloc, _alloc >> 10); fclose(out); } } #endif _alloc = 0; } /*----------------------------------------------------------------------------- Name : nebAllocateChunks Description : allocates memory for the global chunk table Inputs : num - number of chunks to create space for Outputs : Return : ----------------------------------------------------------------------------*/ void nebAllocateChunks(nebulae_t* neb, udword num) { if (neb->chunkTable != NULL) { nebFreeChunks(neb); } dbgAssert(num > 1); neb->chunkTable = memAlloc(num * sizeof(nebChunk), "nebula chunkTable", 0); neb->numChunks = num; } /*----------------------------------------------------------------------------- Name : nebAllocateTendrils Description : allocates memory for the global tendril table Inputs : num - number of tendrils to create space for Outputs : Return : ----------------------------------------------------------------------------*/ void nebAllocateTendrils(nebulae_t* neb, udword num) { udword i; nebTendril* tendril; dbgAssert(num > 0); if (neb->tendrilTable != NULL) { nebFreeTendrils(neb); } neb->tendrilTable = memAlloc(num * sizeof(nebTendril), "nebula tendrilTable", 0); _alloc += num * sizeof(nebTendril); neb->numTendrils = num; for (i = 0, tendril = neb->tendrilTable; i < neb->numTendrils; i++, tendril++) { nebInitTendril(tendril); } } /*----------------------------------------------------------------------------- Name : nebFreeChunks Description : frees memory allocated to nebChunks in the chunkTable Inputs : Outputs : Return : ----------------------------------------------------------------------------*/ void nebFreeChunks(nebulae_t* neb) { if (neb->chunkTable != NULL) { memFree(neb->chunkTable); neb->chunkTable = NULL; neb->numChunks = 0; } } /*----------------------------------------------------------------------------- Name : nebFreeTendrils Description : frees memory allocated to nebTendrils in the tendrilTable Inputs : Outputs : Return : ----------------------------------------------------------------------------*/ void nebFreeTendrils(nebulae_t* neb) { udword i, j; nebTendril* tendril; if (neb->tendrilTable != NULL) { for (i = 0, tendril = neb->tendrilTable; i < neb->numTendrils; i++, tendril++) { for (j = 0; j < NUM_NEBTENDRIL_LODS; j++) { if (tendril->lod[j].vertices != NULL) { memFree(tendril->lod[j].vertices); tendril->lod[j].vertices = NULL; tendril->lod[j].numVerts = 0; } if (tendril->lod[j].normals != NULL) { memFree(tendril->lod[j].normals); tendril->lod[j].normals = NULL; } if (tendril->lod[j].faces != NULL) { memFree(tendril->lod[j].faces); tendril->lod[j].faces = NULL; tendril->lod[j].numFaces = 0; } } } memFree(neb->tendrilTable); neb->tendrilTable = NULL; neb->numTendrils = 0; } } /*----------------------------------------------------------------------------- Name : chunkRandomVelocity Description : applies a random velocity to a chunk Inputs : the chunk Outputs : Return : ----------------------------------------------------------------------------*/ void chunkRandomVelocity(nebChunk* chunk) { real32 chunkVelocity = NEB_VEL_RAN_MULTIPLIER*(real32)(ranRandom(RAN_Nebulae) % (udword)NEB_RAN_RANGE) + NEB_VEL_BASE; dbgAssert(chunk != NULL); cloudRandomSphericalPoint(&chunk->velocity); vecMultiplyByScalar(chunk->velocity, chunkVelocity); } /*----------------------------------------------------------------------------- Name : nebInitChunk Description : inits a blank chunk Inputs : chunk - the chunk to init Outputs : an init'ed chunk Return : ----------------------------------------------------------------------------*/ void nebInitChunk(nebChunk* chunk) { udword i; dbgAssert(chunk != NULL); chunk->flags = 0; chunk->counter = 0; chunk->numTendrils = 0; chunk->innerRadius = 160.0f; chunk->outerRadius = 245.0f; vecSet(chunk->position, 0.0f, 0.0f, 0.0f); chunkRandomVelocity(chunk); vecSet(chunk->dPos, 0.0f, 0.0f, 0.0f); for (i = 0; i < NEB_MAX_TENDRILS; i++) { chunk->tendrils[i] = NULL; chunk->tFlags[i] = 0; } } /*----------------------------------------------------------------------------- Name : nebInitTendril Description : inits a blank tendril Inputs : tendril - the tendril to init Outputs : an init'ed tendril Return : ----------------------------------------------------------------------------*/ void nebInitTendril(nebTendril* tendril) { udword i; dbgAssert(tendril != NULL); tendril->flags = 0; tendril->counter = 0; tendril->a = NULL; tendril->b = NULL; tendril->prev = NULL; tendril->next = NULL; vecSet(tendril->direction, 0.0f, 0.0f, 0.0f); for (i = 0; i < NUM_NEBTENDRIL_LODS; i++) { tendril->lod[i].numVerts = 0; tendril->lod[i].numFaces = 0; tendril->lod[i].vertices = NULL; tendril->lod[i].normals = NULL; tendril->lod[i].faces = NULL; } { sdword r, g, b, a; r = (sdword)(255.0f * nebRealDist(NEB_FOG_RED, NEB_TENDRIL_RED_DEV)); g = (sdword)(255.0f * nebRealDist(NEB_FOG_GREEN, NEB_TENDRIL_GREEN_DEV)); b = (sdword)(255.0f * nebRealDist(NEB_FOG_BLUE, NEB_TENDRIL_BLUE_DEV)); a = TENDRILALPHA; if (r > 255) r = 255; else if (r < 0) r = 0; if (g > 255) g = 255; else if (g < 0) g = 0; if (b > 255) b = 255; else if (b < 0) b = 0; tendril->realColour = colRGBA(r,g,b,a); tendril->maxAlpha = (ubyte)a; } tendril->fadeFactor = 1.0f; } /*----------------------------------------------------------------------------- Name : nebDistributeChunks Description : throws out a bunch of chunks Inputs : origin - centre of distribution radius - maximum extent of a chunk wrt origin Outputs : Return : ----------------------------------------------------------------------------*/ void nebDistributeChunks(nebulae_t* neb, vector* origin, vector* dimension) { udword i; nebChunk* chunk; for (i = 0, chunk = neb->chunkTable; i < neb->numChunks; i++, chunk++) { nebInitChunk(chunk); chunk->position.x = realRand(dimension->x) - 0.5f*dimension->x; chunk->position.y = realRand(dimension->y) - 0.5f*dimension->y; chunk->position.z = realRand(dimension->z) - 0.5f*dimension->z; vecAddTo(chunk->position, *origin); //radius of chunk left at default for now } } /*----------------------------------------------------------------------------- Name : nebGetFreshTendril Description : returns a free tendril freshly initialized, if it can Inputs : Outputs : Return : a freshly initialized tendril, or NULL ----------------------------------------------------------------------------*/ nebTendril* nebGetFreshTendril(nebulae_t* neb) { udword i; nebTendril* tendril; for (i = 0, tendril = neb->tendrilTable; i < neb->numTendrils; i++, tendril++) { if (tendril->a == NULL && tendril->b == NULL) { nebInitTendril(tendril); return tendril; } } return NULL; } /*----------------------------------------------------------------------------- Name : nebNumSimilarTendrils Description : returns the number of active tendrils which have == endpoints Inputs : chunka, chunkb - endpoints Outputs : Return : ----------------------------------------------------------------------------*/ udword nebNumSimilarTendrils(nebulae_t* neb, nebChunk* chunka, nebChunk* chunkb) { udword i, hits; nebTendril* tendril; for (i = hits = 0, tendril = neb->tendrilTable; i < neb->numTendrils; i++, tendril++) { if ((tendril->a == chunka && tendril->b == chunkb) || (tendril->a == chunkb && tendril->b == chunka)) { hits++; } } return hits; } /*----------------------------------------------------------------------------- Name : nebAngularDeviation Description : returns the angular deviation from previous path given a new chunk Inputs : newChunk - the possible next step in the tendril path (new endpoint) oldChunka - previous startpoint oldChunkb - previous endpoint Outputs : Return : ----------------------------------------------------------------------------*/ real32 nebAngularDeviation(nebChunk* newChunk, nebChunk* oldChunka, nebChunk* oldChunkb) { vector lastVec; vector nextVec; real32 val; vecSub(lastVec, oldChunkb->position, oldChunka->position); vecSub(nextVec, newChunk->position, oldChunkb->position); vecNormalize(&lastVec); vecNormalize(&nextVec); val = vecDotProduct(nextVec, lastVec); return _fabs(RAD_TO_DEG((real32)acos((real64)val))); } /*----------------------------------------------------------------------------- Name : nebPickChunk Description : picks a new chunk out of the chunkTable subject to the constraints of the given chunk type (leading chunk or otherwise) and the possibly provided previous chunk(s) Inputs : type - NEB_INITIAL_CHUNK or NEB_MEDIAN_CHUNK constrainta, constraintb - either a chunk or NULL Outputs : Return : a desired chunk or NULL ----------------------------------------------------------------------------*/ /* * Logic Description: * * a chunk is "good" at the initial stage if it has < max tendrils. * thereafter a chunk is "good" if it doesn't deviate more than a certain threshold * from a straight line. a tendril would rather die than deviate * * initial chunks may be selected with a weighting favouring a tendrilled chunk. * * chunk-picking logic: gather a few chunks, weight un/favourably wrt numTendrils & * distance, select only one * * absolutely won't return a chunk if the chunk doesn't have a free tendril slot * * should have a separate mode given only 1 constraint: distance limiting */ nebChunk* nebPickChunk(nebulae_t* neb, sdword type, nebChunk* constrainta, nebChunk* constraintb) { #define NUM_CHUNKS 16 nebChunk* chunk = NULL; nebChunk* chunks[NUM_CHUNKS]; udword onChunk = 0; real32 minDist = 1e20f; real32 tempDist; udword i, maxTendrils; switch (type) { case NEB_INITIAL_CHUNK: for (i = 0, chunk = neb->chunkTable; i < neb->numChunks; i++, chunk++) { if (bitTest(chunk->flags, NEB_CHUNK_PICKED)) { continue; } if (chunk->numTendrils + 1 < NEB_MAX_TENDRILS) { chunks[onChunk++] = chunk; if (onChunk == NUM_CHUNKS) { break; } } } if (onChunk == 0) { return NULL; } chunk = chunks[0]; maxTendrils = chunks[0]->numTendrils; for (i = 0/*silly*/; i < onChunk; i++) { if (chunks[i]->numTendrils > maxTendrils) { chunk = chunks[i]; maxTendrils = chunks[i]->numTendrils; } } //the most tendrilled chunk we found return chunk; case NEB_MEDIAN_CHUNK: if (constrainta == NULL) { for (i = 0, chunk = neb->chunkTable; i < neb->numChunks; i++, chunk++) { if (bitTest(chunk->flags, NEB_CHUNK_PICKED)) { continue; } if (chunk->numTendrils + 1 < NEB_MAX_TENDRILS) { return chunk; } } } else if (constraintb == NULL) { for (i = 0, chunk = neb->chunkTable; i < neb->numChunks; i++, chunk++) { if (bitTest(chunk->flags, NEB_CHUNK_PICKED)) { continue; } if ((chunk->numTendrils + 1 < NEB_MAX_TENDRILS) && (nebChunkDistance(constrainta, chunk) < NEB_PICK_DISTANCE_THRESHOLD) && (nebNumSimilarTendrils(neb, chunk, constrainta) == 0)) { return chunk; } } } else { //pick a few, and return the closest w/in straightness constraint for (i = 0, chunk = neb->chunkTable; i < neb->numChunks; i++, chunk++) { if (bitTest(chunk->flags, NEB_CHUNK_PICKED)) { continue; } if (chunk->numTendrils + 1 < NEB_MAX_TENDRILS) { tempDist = nebChunkDistance(constraintb, chunk); if ((nebAngularDeviation(chunk, constrainta, constraintb) < NEB_MAX_TENDRIL_ANGULAR_DEVIATION) && (tempDist < NEB_PICK_DISTANCE_THRESHOLD) && (tempDist > NEB_PICK_DISTANCE_MINTHRESH) && (nebNumSimilarTendrils(neb, chunk, constraintb) == 0)) { chunks[onChunk++] = chunk; if (onChunk == NUM_CHUNKS) { break; } } } } if (onChunk == 0) { return NULL; } //locate nearest chunk for (i = 0; i < onChunk; i++) { tempDist = nebChunkDistance(chunks[i], constraintb); if (tempDist != 0.0f && tempDist < minDist) { minDist = tempDist; chunk = chunks[i]; } } return chunk; } return NULL; default: return NULL; } #undef NUM_CHUNKS } /*----------------------------------------------------------------------------- Name : nebChunkDistance Description : returns the distance between 2 chunks Inputs : a, b - the chunks Outputs : Return : a real value representing distance ----------------------------------------------------------------------------*/ real32 nebChunkDistance(nebChunk* a, nebChunk* b) { if (a == NULL || b == NULL) { return 0.0f; } else { vector dist; vecSub(dist, a->position, b->position); return fsqrt(vecMagnitudeSquared(dist)); } } /*----------------------------------------------------------------------------- Name : nebChunkPicked Description : set a flag indicating whether a chunk is available for picking Inputs : chunk - the chunk to set/clear the flag on v - the boolean value Outputs : Return : ----------------------------------------------------------------------------*/ void nebChunkPicked(nebChunk* chunk, bool8 v) { dbgAssert(chunk != NULL); if (v) { bitSet(chunk->flags, NEB_CHUNK_PICKED); } else { bitClear(chunk->flags, NEB_CHUNK_PICKED); } } /*----------------------------------------------------------------------------- Name : nebUnpickAllChunks Description : unpicks all chunks Inputs : Outputs : Return : ----------------------------------------------------------------------------*/ void nebUnpickAllChunks(nebulae_t* neb) { udword i; nebChunk* chunk; for (i = 0, chunk = neb->chunkTable; i < neb->numChunks; i++, chunk++) { nebChunkPicked(chunk, FALSE); } } /*----------------------------------------------------------------------------- Name : nebAddTendril Description : adds a tendril to a chunk Inputs : chunk - the chunk to add the tendril to tendril - the tendril to add flags - NEB_CHUNK_FORE or NEB_CHUNK_AFT Outputs : Return : ----------------------------------------------------------------------------*/ void nebAddTendril(nebChunk* chunk, nebTendril* tendril, udword flags) { dbgAssert(chunk != NULL); chunk->tFlags[chunk->numTendrils] = flags; chunk->tendrils[chunk->numTendrils++] = (void*)tendril; } /*----------------------------------------------------------------------------- Name : nebTendrilDirection Description : precalculates the direction the tendril goes Inputs : tendril - the tendril Outputs : Return : ----------------------------------------------------------------------------*/ void nebTendrilDirection(nebTendril* tendril) { dbgAssert(tendril != NULL); dbgAssert(tendril->a != NULL); dbgAssert(tendril->b != NULL); vecSub(tendril->direction, tendril->b->position, tendril->a->position); vecNormalize(&tendril->direction); } /*----------------------------------------------------------------------------- Name : nebAttachTendrils Description : attaches tendrils to bare chunks Inputs : Outputs : Return : ----------------------------------------------------------------------------*/ void nebAttachTendrils(nebulae_t* neb) { nebChunk* chunka; nebChunk* chunkb; nebChunk* lastchunk; nebTendril* tendril; nebTendril* lastTendril; real32 distanceLeft; udword count = 0; for (;;) { nebUnpickAllChunks(neb); //attempt to get a fresh tendril tendril = nebGetFreshTendril(neb); if (tendril == NULL) { //no tendrils left, our work is done break; } //pick initial 2 chunks REPICK: chunka = nebPickChunk(neb, NEB_INITIAL_CHUNK, NULL, NULL); nebChunkPicked(chunka, TRUE); chunkb = nebPickChunk(neb, NEB_MEDIAN_CHUNK, chunka, NULL); if (chunkb == NULL) { dbgAssert(++count < 100); // nebChunkPicked(chunka, FALSE); goto REPICK; } count = 0; nebChunkPicked(chunkb, TRUE); nebAddTendril(chunka, tendril, NEB_CHUNK_FORE); nebAddTendril(chunkb, tendril, NEB_CHUNK_AFT); //setup the tendril tendril->a = chunka; tendril->b = chunkb; nebTendrilDirection(tendril); lastTendril = tendril; bitSet(tendril->flags, NEB_TENDRIL_LEADING); //travel distance remaining distanceLeft = NEB_MAX_TENDRIL_DISTANCE - nebChunkDistance(chunka, chunkb); //repeat until we've gone far enough while (distanceLeft > 0.0f) { if (chunkb->numTendrils + 1 >= NEB_MAX_TENDRILS) { //too many tendrils on this guy, abort break; } //save the previous chunka lastchunk = chunka; //last endpoint becomes new startpoint chunka = chunkb; //get an endpoint, subject to constraints of previous chunk chunkb = nebPickChunk(neb, NEB_MEDIAN_CHUNK, lastchunk, chunka); if (chunkb != NULL) { nebChunkPicked(chunkb, TRUE); distanceLeft -= nebChunkDistance(chunka, chunkb); //try to get a new tendril for these chunks tendril = nebGetFreshTendril(neb); if (tendril != NULL) { //got one, set it up tendril->a = chunka; tendril->b = chunkb; lastTendril->next = tendril; tendril->prev = lastTendril; lastTendril = tendril; nebAddTendril(chunka, tendril, NEB_CHUNK_FORE); nebAddTendril(chunkb, tendril, NEB_CHUNK_AFT); nebTendrilDirection(tendril); } else { //couldn't find a tendril, so end this tendril and then //the whole deal distanceLeft = 0.0f; } } else { //couldn't find a suitable endpoint, so end this tendril distanceLeft = 0.0f; } } bitSet(lastTendril->flags, NEB_TENDRIL_TRAILING); } nebUnpickAllChunks(neb); } /*----------------------------------------------------------------------------- Name : nebGo Description : creates chunks and tendrils; we call this a "nebula" Inputs : origin - where the centre of the nebula is located in space radius - maximum extent of the nebula about the origin numOfChunks - the number of chunks that comprise the nebula numOfTendrils - the number of tendrils that comprise the nebula Outputs : Return : Notes : numOfChunks will probably be determined by a provided macro that uses the radius and a density factor to cook up a value. likewise numOfTendrils wrt numOfChunks ----------------------------------------------------------------------------*/ void nebGo(vector* origin, vector* dimension, udword numOfChunks, udword numOfTendrils) { nebulae_t* neb; neb = &nebNebulae[numNebulae++]; nebAllocateChunks(neb, numOfChunks); nebAllocateTendrils(neb, numOfTendrils); nebDistributeChunks(neb, origin, dimension); nebAttachTendrils(neb); nebDeactivateEmptyChunksAndFixup(neb); } /*----------------------------------------------------------------------------- Name : nebDeactivateEmptyChunksAndFixup Description : deactivate chunks with no attached tendrils, generate minimally sized tables, and fixup data Inputs : Outputs : Return : ----------------------------------------------------------------------------*/ void nebDeactivateEmptyChunksAndFixup(nebulae_t* neb) { udword i, t, hits; nebChunk* chunk; nebChunk* newTable; nebChunk* newChunk; nebTendril* tendril; //PHASE 1: set a flag on tendril-less chunks for (i = hits = 0, chunk = neb->chunkTable; i < neb->numChunks; i++, chunk++) { if (chunk->numTendrils == 0) { bitSet(chunk->flags, NEB_CHUNK_INACTIVE); } else { hits++; } } dbgAssert(hits != 0); //PHASE 2: create a new chunk structure that lacks bare chunks newTable = (nebChunk*)memAlloc(hits * sizeof(nebChunk), "nebulae chunks", 0); _alloc += hits * sizeof(nebChunk); for (i = 0, chunk = neb->chunkTable, newChunk = newTable; i < neb->numChunks; i++, chunk++) { if (!bitTest(chunk->flags, NEB_CHUNK_INACTIVE)) { //valid chunk, so copy it over to the new table memcpy(newChunk, chunk, sizeof(nebChunk)); //parent pointer newChunk->nebulae = (void*)neb; //now update all the tendrils for (t = 0, tendril = neb->tendrilTable; t < neb->numTendrils; t++, tendril++) { if (tendril->a == chunk) { tendril->a = newChunk; } if (tendril->b == chunk) { tendril->b = newChunk; } } //add a resource to the game (void)univAddNebula(Nebula0, &newChunk->position, (void*)newChunk); //goto next chunk in the new table newChunk++; } } //housekeeping neb->numChunks = hits; memFree(neb->chunkTable); neb->chunkTable = newTable; //PHASE 3: remove tendrils that are both leading and trailing for (i = 0, tendril = neb->tendrilTable; i < neb->numTendrils; i++, tendril++) { if (bitTest(tendril->flags, NEB_TENDRIL_LEADING) && bitTest(tendril->flags, NEB_TENDRIL_TRAILING)) { //flag 'em bitSet(tendril->flags, NEB_TENDRIL_INACTIVE); } } //PHASE 4: check to see if there's >1 tendril traversing the same 2 chunks for (i = hits = 0, tendril = neb->tendrilTable; i < neb->numTendrils; i++, tendril++) { nebTendril* otherTendril; udword j; for (j = 0, otherTendril = neb->tendrilTable; j < neb->numTendrils; j++, otherTendril++) { if (otherTendril == tendril) { continue; } if (tendril->a == otherTendril->a && tendril->b == otherTendril->b) { dbgMessagef("\ntendril == otherTendril (%d)", hits++); } if (tendril->a == otherTendril->b && tendril->b == otherTendril->a) { dbgMessagef("\ntendril ~= otherTendril (%d)", hits++); } } } //FIXME [PHASE 5]: fixup trailing/leading tendril flags if a tendril has //docked/departed from a chunk that has an/other tendril(s) attached } /*----------------------------------------------------------------------------- Name : nebTransformNormal Description : transforms a normal from object -> world space Inputs : vec - the target vector m - the hmatrix that effects the object->world transform nx, ny, nz - normal components Outputs : vec - the final (unit) vector Return : ----------------------------------------------------------------------------*/ void nebTransformNormal(vector* vec, real32* m, real32 nx, real32 ny, real32 nz) { #if 1 vec->x = nx*m[0] + ny*m[1] + nz*m[2]; vec->y = nx*m[4] + ny*m[5] + nz*m[6]; vec->z = nx*m[8] + ny*m[9] + nz*m[10]; #else vec->x = nx; vec->y = ny; vec->z = nz; #endif vecNormalize(vec); } /*----------------------------------------------------------------------------- Name : nebTransformVert Description : transforms a vertex from object -> world space Inputs : vecOut - the target vector hmat - the hmatrix that effects the object->world transform x, y, z - components of the source vector Outputs : vecOut - the final (transformed) vector Return : ----------------------------------------------------------------------------*/ void nebTransformVert(vector* vecOut, real32* hmat, real32 x, real32 y, real32 z) { hvector hvecIn, hvecOut; hvecIn.x = x; hvecIn.y = y; hvecIn.z = z; hvecIn.w = 1.0f; hmatMultiplyHMatByHVec(&hvecOut, (hmatrix*)hmat, &hvecIn); nebHomogenize(&hvecOut); vecOut->x = hvecOut.x; vecOut->y = hvecOut.y; vecOut->z = hvecOut.z; } /*----------------------------------------------------------------------------- Name : nebGetTendrilNormal Description : retrieves requested normal from a tendril's soup Inputs : tendril - the tendril whichNormal - normal number lod - lod number normal - space for the target Outputs : normal - the requested normal Return : ----------------------------------------------------------------------------*/ void nebGetTendrilNormal(nebTendril* tendril, sdword whichNormal, sdword lod, vector* normal) { real32* np = tendril->lod[lod].normals; np += 3 * whichNormal; normal->x = np[0]; normal->y = np[1]; normal->z = np[2]; } /*----------------------------------------------------------------------------- Name : nebGetTendrilVert Description : retrieves requested vertex of a tendril Inputs : tendril - the tendril whichVert - vertex number lod - lod number vert - space for the target Outputs : vert - the requested vertex Return : ----------------------------------------------------------------------------*/ void nebGetTendrilVert(nebTendril* tendril, sdword whichVert, sdword lod, vector* vert) { real32* vp = tendril->lod[lod].vertices; vp += 3 * whichVert; vert->x = vp[0]; vert->y = vp[1]; vert->z = vp[2]; } /*----------------------------------------------------------------------------- Name : nebColourAdjust Description : Inputs : Outputs : Return : ----------------------------------------------------------------------------*/ void nebColourAdjust(vector* vert, vector* norm, real32* m, real32* minv) { if (usingShader) { ubyte color[4]; color[0] = nebColor[0]; color[1] = nebColor[1]; color[2] = nebColor[2]; color[3] = nebColor[3]; shSpecularColour(1, 0, vert, norm, color, m, minv); glColor4ub(color[0], color[1], color[2], color[3]); } } /*----------------------------------------------------------------------------- Name : nebDrawChunk2 Description : draws a trivial joiner between the tendrils of a chunk Ot(2) Inputs : chunk - the chunk lod - lod of the chunk Outputs : Return : ----------------------------------------------------------------------------*/ void nebDrawChunk2(nebChunk* chunk, sdword lod) { #define COLOUR(c) \ { \ GLubyte a = colAlpha(c); \ if (!_bright) \ { \ a += TENDRILBRIGHTEN; \ } \ nebColor[0] = colRed(c); \ nebColor[1] = colGreen(c); \ nebColor[2] = colBlue(c); \ nebColor[3] = a; \ glColor4ub(nebColor[0], nebColor[1], nebColor[2], nebColor[3]); \ } nebTendril* ta; nebTendril* tb; nebTendril* tendril; vector verta0, vertb0, verta1, vertb1; vector norma0, normb0, norma1, normb1; udword i, ia0, ib0, ia1, ib1; sdword a, b; color cola, colb; real32 m[16], minv[16]; if (usingShader) { glGetFloatv(GL_MODELVIEW_MATRIX, m); shInvertMatrix(minv, m); } dbgAssert(chunk != NULL); a = b = -1; for (i = 0; i < chunk->numTendrils; i++) { tendril = (nebTendril*)chunk->tendrils[i]; if (!bitTest(tendril->flags, NEB_TENDRIL_INACTIVE) && tendril->lod[lod].numVerts > 0) { if (a == -1) { a = i; } else { b = i; break; } } } dbgAssert(a != -1); dbgAssert(b != -1); ta = (nebTendril*)chunk->tendrils[a]; tb = (nebTendril*)chunk->tendrils[b]; dbgAssert(ta != NULL); dbgAssert(tb != NULL); cola = ta->colour; colb = tb->colour; if (ta->lod[lod].numVerts == 0 || tb->lod[lod].numVerts == 0) { return; } for (i = 0; i < ta->lod[lod].slices; i++) { ia0 = ta->lod[lod].numVerts - ta->lod[lod].slices + i; ib0 = i; ia1 = ta->lod[lod].numVerts - ta->lod[lod].slices + ((i + 1) % ta->lod[lod].slices); ib1 = (ib0 + 1) % tb->lod[lod].slices; nebGetTendrilVert(ta, ia0, lod, &verta0); nebGetTendrilVert(tb, ib0, lod, &vertb0); nebGetTendrilVert(ta, ia1, lod, &verta1); nebGetTendrilVert(tb, ib1, lod, &vertb1); vecAddTo(verta0, chunk->dPos); vecAddTo(vertb0, chunk->dPos); vecAddTo(verta1, chunk->dPos); vecAddTo(vertb1, chunk->dPos); nebGetTendrilNormal(ta, ia0, lod, &norma0); nebGetTendrilNormal(tb, ib0, lod, &normb0); nebGetTendrilNormal(ta, ia1, lod, &norma1); nebGetTendrilNormal(tb, ib1, lod, &normb1); glBegin(GL_QUADS); COLOUR(cola); glNormal3f(norma0.x, norma0.y, norma0.z); nebColourAdjust(&verta0, &norma0, m, minv); glVertex3fv((GLfloat*)&verta0); COLOUR(colb); glNormal3f(normb0.x, normb0.y, normb0.z); nebColourAdjust(&vertb0, &normb0, m, minv); glVertex3fv((GLfloat*)&vertb0); COLOUR(colb); glNormal3f(normb1.x, normb1.y, normb1.z); nebColourAdjust(&vertb1, &normb1, m, minv); glVertex3fv((GLfloat*)&vertb1); COLOUR(cola); glNormal3f(norma1.x, norma1.y, norma1.z); nebColourAdjust(&verta1, &norma1, m, minv); glVertex3fv((GLfloat*)&verta1); glEnd(); } alodIncPolys(ta->lod[lod].slices); #undef COLOUR } /*----------------------------------------------------------------------------- Name : nebRenderChunk Description : renders a chunk if it hasn't already been rendered Inputs : chunk - the chunk lod - lod number Outputs : Return : ----------------------------------------------------------------------------*/ void nebRenderChunk(nebChunk* chunk, sdword lod) { vector origin = {0.0f, 0.0f, 0.0f}; udword i, actualTendrils; nebTendril* tendril; dbgAssert(chunk != NULL); if (RGL && !usingShader) rglFeature(NEB_RENDER); TENDRILCOLOR0(TENDRILALPHA); if (chunk->counter < neb_frame_counter) { //render this guy, he hasn't been rendered yet this frame chunk->counter = neb_frame_counter; for (i = actualTendrils = 0; i < chunk->numTendrils; i++) { tendril = (nebTendril*)chunk->tendrils[i]; if (!bitTest(tendril->flags, NEB_TENDRIL_INACTIVE) && tendril->lod[lod].numVerts > 0) { actualTendrils++; } } switch (actualTendrils) { case 0: case 1: break; case 2: nebDrawChunk2(chunk, lod); break; } } if (RGL && !usingShader) rglFeature(RGL_NORMAL_RENDER); TENDRILCOLOR0(TENDRILALPHA); } /*----------------------------------------------------------------------------- Name : nebDrawTendril Description : draws an already-allocated tendril Inputs : tendril - the tendril to draw lod - lod number Outputs : Return : ----------------------------------------------------------------------------*/ void nebDrawTendril(nebTendril* tendril, sdword lod) { udword i, j, t; vector vert, norm; vector dPosA, dPosB; real32 m[16], minv[16]; if (usingShader) { glGetFloatv(GL_MODELVIEW_MATRIX, m); shInvertMatrix(minv, m); } dPosA = tendril->a->dPos; dPosB = tendril->b->dPos; if (RGL && !usingShader) rglFeature(NEB_RENDER); glBegin(GL_QUADS); for (j = 1; j <= tendril->lod[lod].stacks; j++) { for (i = 0; i < tendril->lod[lod].slices; i++) { nebGetTendrilNormal(tendril, (j-1)*tendril->lod[lod].slices + i, lod, &norm); glNormal3f(norm.x, norm.y, norm.z); nebGetTendrilVert(tendril, (j-1)*tendril->lod[lod].slices + i, lod, &vert); if (j == 1) { vecAddTo(vert, dPosA); if (bitTest(tendril->flags, NEB_TENDRIL_LEADING)) { TENDRILCOLOR(tendril,0); } } nebColourAdjust(&vert, &norm, m, minv); glVertex3fv((GLfloat*)&vert); TENDRILCOLOR(tendril,colAlpha(tendril->colour)); nebGetTendrilNormal(tendril, j*tendril->lod[lod].slices + i, lod, &norm); glNormal3f(norm.x, norm.y, norm.z); nebGetTendrilVert(tendril, j*tendril->lod[lod].slices + i, lod, &vert); if (j == tendril->lod[lod].stacks) { vecAddTo(vert, dPosB); if (bitTest(tendril->flags, NEB_TENDRIL_TRAILING)) { TENDRILCOLOR(tendril,0); } } nebColourAdjust(&vert, &norm, m, minv); glVertex3fv((GLfloat*)&vert); TENDRILCOLOR(tendril,colAlpha(tendril->colour)); t = (i+1) % tendril->lod[lod].slices; nebGetTendrilNormal(tendril, j*tendril->lod[lod].slices + t, lod, &norm); glNormal3f(norm.x, norm.y, norm.z); nebGetTendrilVert(tendril, j*tendril->lod[lod].slices + t, lod, &vert); if (j == tendril->lod[lod].stacks) { vecAddTo(vert, dPosB); if (bitTest(tendril->flags, NEB_TENDRIL_TRAILING)) { TENDRILCOLOR(tendril,0); } } nebColourAdjust(&vert, &norm, m, minv); glVertex3fv((GLfloat*)&vert); TENDRILCOLOR(tendril,colAlpha(tendril->colour)); nebGetTendrilNormal(tendril, (j-1)*tendril->lod[lod].slices + t, lod, &norm); glNormal3f(norm.x, norm.y, norm.z); nebGetTendrilVert(tendril, (j-1)*tendril->lod[lod].slices + t, lod, &vert); if (j == 1) { vecAddTo(vert, dPosA); if (bitTest(tendril->flags, NEB_TENDRIL_LEADING)) { TENDRILCOLOR(tendril,0); } } nebColourAdjust(&vert, &norm, m, minv); glVertex3fv((GLfloat*)&vert); TENDRILCOLOR(tendril,colAlpha(tendril->colour)); } } glEnd(); alodIncPolys(tendril->lod[lod].stacks * tendril->lod[lod].slices); if (RGL && !usingShader) rglFeature(RGL_NORMAL_RENDER); } /*----------------------------------------------------------------------------- Name : nebTendrilBounds Description : calculates a bounding sphere around a tendril Inputs : tendril - the tendril Outputs : modified the tendril structure elements corresponding to the bsphere Return : ----------------------------------------------------------------------------*/ void nebTendrilBounds(nebTendril* tendril) { sdword i; real32 dx, dy, dz; real32 rad_sq, xspan, yspan, zspan, maxspan; real32 old_to_p, old_to_p_sq, old_to_new; vector xmin, xmax, ymin, ymax, zmin, zmax, dia1, dia2; vector caller_p; real32 BIGNUMBER = 1E9f; real32 rad; vector cen; sdword lod = 1; xmin.x = ymin.y = zmin.z = BIGNUMBER; xmax.x = ymax.y = zmax.z = -BIGNUMBER; for (i = 0; i < (sdword)tendril->lod[lod].numVerts; i++) { nebGetTendrilVert(tendril, i, lod, &caller_p); if (caller_p.x < xmin.x) xmin = caller_p; if (caller_p.x > xmax.x) xmax = caller_p; if (caller_p.y < ymin.y) ymin = caller_p; if (caller_p.y > ymax.y) ymax = caller_p; if (caller_p.z < zmin.z) zmin = caller_p; if (caller_p.z > zmax.z) zmax = caller_p; } dx = xmax.x - xmin.x; dy = xmax.y - xmin.y; dz = xmax.z - xmin.z; xspan = dx*dx + dy*dy + dz*dz; dx = ymax.x - ymin.x; dy = ymax.y - ymin.y; dz = ymax.z - ymin.z; yspan = dx*dx + dy*dy + dz*dz; dx = zmax.x - zmin.x; dy = zmax.y - zmin.y; dz = zmax.z - zmin.z; zspan = dx*dx + dy*dy + dz*dz; dia1 = xmin; dia2 = xmax; maxspan = xspan; if (yspan > maxspan) { maxspan = yspan; dia1 = ymin; dia2 = ymax; } if (zspan > maxspan) { dia1 = zmin; dia2 = zmax; } cen.x = 0.5f * (dia1.x + dia2.x); cen.y = 0.5f * (dia1.y + dia2.y); cen.z = 0.5f * (dia1.z + dia2.z); dx = dia2.x - cen.x; dy = dia2.y - cen.y; dz = dia2.z - cen.z; rad_sq = dx*dx + dy*dy + dz*dz; rad = fsqrt(rad_sq); for (i = 0; i < (sdword)tendril->lod[lod].numVerts; i++) { nebGetTendrilVert(tendril, i, lod, &caller_p); dx = caller_p.x - cen.x; dy = caller_p.y - cen.y; dz = caller_p.z - cen.z; old_to_p_sq = dx*dx + dy*dy + dz*dz; if (old_to_p_sq > rad_sq) { old_to_p = fsqrt(old_to_p_sq); rad = 0.5f * (rad + old_to_p); rad_sq = rad*rad; old_to_new = old_to_p - rad; cen.x = (rad*cen.x + old_to_new*caller_p.x) / old_to_p; cen.y = (rad*cen.y + old_to_new*caller_p.y) / old_to_p; cen.z = (rad*cen.z + old_to_new*caller_p.z) / old_to_p; } } tendril->radius = rad; tendril->midpoint = cen; } /*----------------------------------------------------------------------------- Name : nebCreateCylinder Description : creates the data necessary for rendering a tendril Inputs : tendril - the tendril hmat - the local -> world hmatrix for this tendril baseRadius - radius at the base topRadius - radius at the top height - height/length of the tendril slices - number of slices in the tendril stacks - number of stacks in the tendril lod - lod number Outputs : Return : ----------------------------------------------------------------------------*/ void nebCreateCylinder( nebTendril* tendril, real32* hmat, real32 baseRadius, real32 topRadius, real32 height, sdword slices, sdword stacks, sdword lod) { real32* vp; real32* np; sdword i, j; real32 da, dr, dz, nz, z, r; real32 x1, y1; real32 sign; vector vert, normal; if (bitTest(tendril->flags, NEB_TENDRIL_GEOMETRY)) { return; } for (i = 0; i < 16; i++) { tendril->lod[lod].hmat[i] = hmat[i]; } tendril->lod[lod].numVerts = slices * (stacks + 1); tendril->lod[lod].numFaces = 0; //3 components per vertex tendril->lod[lod].vertices = (real32*)memAlloc(tendril->lod[lod].numVerts*3*sizeof(real32), "tendril vertices", 0); _alloc += tendril->lod[lod].numVerts*3*sizeof(real32); //3 components per normal tendril->lod[lod].normals = (real32*)memAlloc(tendril->lod[lod].numVerts*3*sizeof(real32), "tendril normals", 0); _alloc += tendril->lod[lod].numVerts*3*sizeof(real32); tendril->lod[lod].faces = NULL; tendril->lod[lod].slices = slices; tendril->lod[lod].stacks = stacks; vp = tendril->lod[lod].vertices; np = tendril->lod[lod].normals; da = 2.0f * 3.14159265f / slices; dr = (topRadius - baseRadius) / slices; dz = height / stacks; nz = (baseRadius - topRadius) / height; z = 0.0f; if (ranRandom(RAN_Nebulae) % 2 == 0) { sign = 1.0f; } else { sign = -1.0f; } if (stacks == 5) { for (j = 0; j <= stacks; j++) { r = baseRadius; for (i = 0; i < slices; i++, vp += 3, np += 3) { x1 = -fsin(i * da); y1 = fcos(i * da); if (j == 2) { real32 r2 = r + sign*r*0.15f; nebTransformVert(&vert, hmat, x1*r2, y1*r2, z); } else if (j == 1 || j == 3) { real32 r2 = r + sign*r*0.1f; nebTransformVert(&vert, hmat, x1*r2, y1*r2, z); } else { nebTransformVert(&vert, hmat, x1*r, y1*r, z); } *(vp+0) = vert.x; *(vp+1) = vert.y; *(vp+2) = vert.z; nebTransformNormal(&normal, hmat, x1, y1, nz); *(np+0) = normal.x; *(np+1) = normal.y; *(np+2) = normal.z; r += dr; } z += dz; } } else { for (j = 0; j <= stacks; j++) { r = baseRadius; for (i = 0; i < slices; i++, vp += 3, np += 3) { x1 = -fsin(i * da); y1 = fcos(i * da); nebTransformVert(&vert, hmat, x1*r, y1*r, z); *(vp+0) = vert.x; *(vp+1) = vert.y; *(vp+2) = vert.z; nebTransformNormal(&normal, hmat, x1, y1, nz); *(np+0) = normal.x; *(np+1) = normal.y; *(np+2) = normal.z; r += dr; } z += dz; } } nebTendrilBounds(tendril); } /*----------------------------------------------------------------------------- Name : nebIndexOfNeb Description : returns the index of the particular nebulae structure Inputs : Outputs : Return : ----------------------------------------------------------------------------*/ sdword nebIndexOfNeb(nebulae_t* neb) { sdword i; for (i = 0; i < NEB_MAX_NEBULAE; i++) { if (neb == &nebNebulae[i]) { return i; } } return -1; } /*----------------------------------------------------------------------------- Name : nebNewNeb Description : callback from univupdate, sets a spaceobj ptr in the chunk Inputs : Outputs : Return : ----------------------------------------------------------------------------*/ void nebNewNeb(struct Nebula* neb) { nebChunk* chunk = (nebChunk*)neb->stub; chunk->spaceobj = (void*)neb; neb->nebNebulaeIndex = nebIndexOfNeb(chunk->nebulae); } /*----------------------------------------------------------------------------- Name : nebLOD Description : returns an lod given 2 chunks. for tendrils. Inputs : chunka, chunkb - the chunks Outputs : Return : 0 or 1, representing the lod number ----------------------------------------------------------------------------*/ sdword nebLOD(nebChunk* chunka, nebChunk* chunkb) { bool a = univSpaceObjInRenderList((SpaceObj*)chunka->spaceobj); bool b = univSpaceObjInRenderList((SpaceObj*)chunkb->spaceobj); return (a || b) ? 0 : 1; } /*----------------------------------------------------------------------------- Name : nebRenderTendril Description : renders a tendril Inputs : tendril - the tendril lod - lod number Outputs : Return : ----------------------------------------------------------------------------*/ void nebRenderTendril(nebTendril* tendril, sdword lod) { vector a, b, direction; dbgAssert(tendril != NULL); dbgAssert(tendril->a != NULL); dbgAssert(tendril->b != NULL); dbgAssert(tendril->a != tendril->b); if (tendril->counter >= neb_frame_counter) { return; } tendril->counter = neb_frame_counter; a = tendril->a->position; b = tendril->b->position; //leave room for the chunk to grow towards this tendril direction = tendril->direction; vecMultiplyByScalar(direction, NEB_SHUFFLE_DISTANCE); vecAddTo(a, direction); direction = tendril->direction; vecMultiplyByScalar(direction, NEB_SHUFFLE_DISTANCE); vecSubFrom(b, direction); /* want a cylinder around the tendril's ideal line. a->b is forward vector of tendril's coordinate system. 'z' is up vector of coordinate system. a->b X 'z' is right vector of coordinate system. */ { vector forward, up, right; vector a0, b0; real32 length; a0 = a; b0 = b; vecSub(forward, b, a); length = fsqrt(vecMagnitudeSquared(forward)); vecNormalize(&forward); direction = forward; vecSet(up, 0.0f, 0.0f, 1.0f); vecCrossProduct(right, forward, up); if (!bitTest(tendril->flags, NEB_TENDRIL_GEOMETRY)) { real32 hmat[16]; real32 radius, rand; hmat[0] = right.x; hmat[1] = right.y; hmat[2] = right.z; hmat[3] = 0.0f; hmat[4] = up.x; hmat[5] = up.y; hmat[6] = up.z; hmat[7] = 0.0f; hmat[8] = forward.x; hmat[9] = forward.y; hmat[10] = forward.z; hmat[11] = 0.0f; hmat[12] = a0.x; hmat[13] = a0.y; hmat[14] = a0.z; hmat[15] = 1.0f; rand = realRand(NEB_TENDRIL_RADIUS_RANGE); if (ranRandom(RAN_Nebulae) % 2 == 0) { rand = -rand; } radius = NEB_TENDRIL_RADIUS_BASE + rand; nebCreateCylinder(tendril, hmat, radius, radius, length, 6, 5, 0); nebCreateCylinder(tendril, hmat, radius, radius, length, 6, 1, 1); bitSet(tendril->flags, NEB_TENDRIL_GEOMETRY); } nebDrawTendril(tendril, lod); } } /*----------------------------------------------------------------------------- Name : nebDistanceFromCamera Description : returns the distance from the current cameraposition Inputs : pos - position in worldspace Outputs : Return : scalar distance from camera ----------------------------------------------------------------------------*/ real32 nebDistanceFromCamera(vector* pos) { vector dist; vecSub(dist, *pos, mrCamera->eyeposition); return fsqrt(vecMagnitudeSquared(dist)); } /*----------------------------------------------------------------------------- Name : nebDistanceFromCameraSquared Description : returns the distance squared from the current cameraposition Inputs : pos - position in worldspace Outputs : Return : scalar distance squared from camera ----------------------------------------------------------------------------*/ real32 nebDistanceFromCameraSquared(vector* pos) { vector dist; vecSub(dist, *pos, mrCamera->eyeposition); return vecMagnitudeSquared(dist); } /*----------------------------------------------------------------------------- Name : nebColorDarkenAndDesaturate Description : darkens and desaturates a color by provided amounts Inputs : c - the color to modify dark - [0..255] amount to darken desat - [0..255] amount to desaturate Outputs : Return : modified color value ----------------------------------------------------------------------------*/ sdword nebColorDarkenAndDesaturate(color c, sdword dark, sdword desat) { real32 h, s, v; real32 red = colUbyteToReal(colRed(c)); real32 green = colUbyteToReal(colGreen(c)); real32 blue = colUbyteToReal(colBlue(c)); colRGBToHSV(&h, &s, &v, red, green, blue); v -= colUbyteToReal(dark); if (v < 0.0f) v = 0.0f; s -= colUbyteToReal(desat); if (s < 0.0f) s = 0.0f; colHSVToRGB(&red, &green, &blue, h, s, v); return colRGBA(colRealToUbyte(red), colRealToUbyte(green), colRealToUbyte(blue), colAlpha(c)); } /*----------------------------------------------------------------------------- Name : nebColourTendril Description : colours a tendril based on distance Inputs : tendril - the tendril to colour lod - lod number of the tendril Outputs : sets tendril->colour to a modified (by 1/d) tendril->realColour Return : ----------------------------------------------------------------------------*/ void nebColourTendril(nebTendril* tendril, sdword lod) { real32 dA, dB, d; static real32 MAXD = 40000.0f; dbgAssert(tendril != NULL); #if 0 tendril->colour = tendril->realColour; #else dA = nebDistanceFromCamera(&tendril->a->position); dB = nebDistanceFromCamera(&tendril->b->position); if (dA > MAXD) dA = MAXD; if (dB > MAXD) dB = MAXD; d = 0.5f * (dA + dB); d /= MAXD; d *= NEB_DISTANCE_DESATURATION; tendril->colour = nebColorDarkenAndDesaturate(tendril->realColour, (sdword)(0.75f * d), (sdword)d); tendril->colour = colRGBA(colRed(tendril->colour), colGreen(tendril->colour), colBlue(tendril->colour), (ubyte)(tendril->fadeFactor * (real32)colAlpha(tendril->colour))); #endif } /*----------------------------------------------------------------------------- Name : nebIsClipped Description : determines whether to render a tendril and its chunks Inputs : tendril - the tendril Outputs : Return : TRUE or FALSE, depending ----------------------------------------------------------------------------*/ bool nebIsClipped(nebTendril* tendril) { #if 0 //FIXME: dammit, this is buggy. holy moley it's fast, though real32 r = 1.5f * tendril->radius; return rglIsClipped((GLfloat*)&tendril->midpoint, r, r, r); #else vector veye, vobj; vecSub(veye, mrCamera->eyeposition, mrCamera->lookatpoint); vecSub(vobj, mrCamera->eyeposition, tendril->midpoint); return (vecDotProduct(veye, vobj) < 0.0f); #endif } /*----------------------------------------------------------------------------- Name : nebRender Description : renders the nebulae Inputs : Outputs : Return : ----------------------------------------------------------------------------*/ void nebRender(void) { sdword i; for (i = 0; i < numNebulae; i++) { nebRenderNebula(&nebNebulae[i]); } } /*----------------------------------------------------------------------------- Name : nebRenderNebula Description : renders a nebula Inputs : Outputs : Return : ----------------------------------------------------------------------------*/ void nebRenderNebula(nebulae_t* neb) { udword i; nebChunk* chunk; nebTendril* tendril; bool fogOn, atOn, cullOff; real32 maxDist; if (neb->numTendrils == 0 || smSensorsActive) { return; } _bright = glCapFastFeature(GL_BLEND); fogOn = glIsEnabled(GL_FOG); atOn = glIsEnabled(GL_ALPHA_TEST); cullOff = !glIsEnabled(GL_CULL_FACE); glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_FALSE); if (fogOn) glDisable(GL_FOG); if (RGL && !usingShader) { rndLightingEnable(TRUE); } else { rndLightingEnable(FALSE); } rndTextureEnable(FALSE); glEnable(GL_BLEND); rndAdditiveBlends(FALSE); glDepthMask(GL_FALSE); if (atOn) glDisable(GL_ALPHA_TEST); if (cullOff) glEnable(GL_CULL_FACE); TENDRILCOLOR0(TENDRILALPHA); neb_frame_counter++; if (neb_frame_counter == 0) { //overflow has occurred, fix it neb_frame_counter = 1; for (i = 0, chunk = neb->chunkTable; i < neb->numChunks; i++, chunk++) { chunk->counter = 0; } for (i = 0, tendril = neb->tendrilTable; i < neb->numTendrils; i++, tendril++) { tendril->counter = 0; } } maxDist = 1.1f * mrCamera->clipPlaneFar; maxDist *= maxDist; //squared for (i = 0, tendril = neb->tendrilTable; i < neb->numTendrils; i++, tendril++) { sdword lod; //check for activity if (!bitTest(tendril->flags, NEB_TENDRIL_INACTIVE)) { if (nebIsClipped(tendril)) { continue; } lod = nebLOD(tendril->a, tendril->b); nebColourTendril(tendril, lod); //too far to be visible? if ((nebDistanceFromCameraSquared(&tendril->a->position) > maxDist) && (nebDistanceFromCameraSquared(&tendril->b->position) > maxDist)) { continue; } if (!bitTest(tendril->flags, NEB_TENDRIL_LEADING)) { //render chunka if this is not a leading tendril nebRenderChunk(tendril->a, lod); } if (!bitTest(tendril->flags, NEB_TENDRIL_TRAILING)) { //render chunkb if this is not a trailing tendril nebRenderChunk(tendril->b, lod); } //render the tendril itself nebRenderTendril(tendril, lod); } } glDepthMask(GL_TRUE); glDisable(GL_BLEND); if (fogOn) glEnable(GL_FOG); if (atOn) glEnable(GL_ALPHA_TEST); if (cullOff) glDisable(GL_CULL_FACE); if (usingShader) { rndLightingEnable(TRUE); } } //MUST also remove itself from the resource list, &c void nebDeleteChunkSimply(nebChunk* chunk) { dbgAssert(chunk != NULL); bitSet(chunk->flags, NEB_CHUNK_DEAD); } /*----------------------------------------------------------------------------- Name : nebRemoveTendril Description : wipes out a tendril after a chunk in its link has been harvested Inputs : tendril - the tendril chunk - the chunk that was deleted causing our deletion Outputs : Return : ----------------------------------------------------------------------------*/ /* * this fn handles the following conditions * o the tendril is a leading tendril, in which case it is set inactive and * any chunks on it (that aren't the passed chunk) are told of the departure * o the tendril is a trailing tendril, in which case it is set inactive and * any chunks on it (that aren't the passed chunk) are told of the departure * o the tendril is a median tendril, in which case nothing yet happens */ void nebRemoveTendril(nebTendril* tendril, nebChunk* chunk) { #if 0 if (bitSet(tendril->flags, NEB_TENDRIL_LEADING) || bitSet(tendril->flags, NEB_TENDRIL_TRAILING)) { if (tendril->a != chunk) { nebDeleteChunkSimply(tendril->a); } if (tendril->b != chunk) { nebDeleteChunkSimply(tendril->b); } } else { dbgAssert(tendril != NULL); dbgAssert(chunk != NULL); } #else bitSet(tendril->flags, NEB_TENDRIL_INACTIVE); if (tendril->prev != NULL) { bitSet(tendril->prev->flags, NEB_TENDRIL_TRAILING); } if (tendril->next != NULL) { bitSet(tendril->next->flags, NEB_TENDRIL_LEADING); } #endif } /*----------------------------------------------------------------------------- Name : nebDeleteChunk Description : handles a chunk that has been resourced to death Inputs : chunk - the chunk Outputs : Return : ----------------------------------------------------------------------------*/ void nebDeleteChunk(nebChunk* chunk) { udword i; nebulae_t* neb; if (numNebulae == 0) { //already deleted return; } neb = (nebulae_t*)chunk->nebulae; dbgAssert(neb != NULL); if (neb->numChunks == 0) { //already deleted return; } dbgAssert(chunk != NULL); bitSet(chunk->flags, NEB_CHUNK_DEAD); for (i = 0; i < chunk->numTendrils; i++) { nebRemoveTendril(chunk->tendrils[i], chunk); } } /*----------------------------------------------------------------------------- Name : nebFadeAttachedTendrils Description : fade tendrils attached to a chunk that's being resourced Inputs : chunk - the chunk factor - [0..1] fraction of maximum RU's Outputs : Return : ----------------------------------------------------------------------------*/ /* * issues: * o should fade only endpoints of a tendril actually attached to the chunk * o chunks should fade at the attachment point */ void nebFadeAttachedTendrils(nebChunk* chunk, real32 factor) { udword i; nebulae_t* neb; neb = (nebulae_t*)chunk->nebulae; dbgAssert(neb != NULL); if (neb->numChunks == 0) { //nothing to fade - what's going on? dbgFatal(DBG_Loc, "numChunks == 0 in nebFadeAttachedTendrils"); } dbgAssert(chunk != NULL); for (i = 0; i < chunk->numTendrils; i++) { nebTendril* tendril; tendril = chunk->tendrils[i]; tendril->fadeFactor = factor; } } /*----------------------------------------------------------------------------- Name : nebUpdateChunk Description : adds wiggle, sloosh to a chunk Inputs : chunk - the chunk to move around Outputs : Return : ----------------------------------------------------------------------------*/ /* * a chunk picks a random direction to move in and just keeps going until it * has exceeded the bounds of its outerRadius, in which case it picks another * direction and goes on about its business */ void nebUpdateChunk(nebChunk* chunk) { dbgAssert(chunk != NULL); //add velocity to dPos vecAddTo(chunk->dPos, chunk->velocity); //check magnitude if (fsqrt(vecMagnitudeSquared(chunk->dPos)) > (chunk->outerRadius - chunk->innerRadius)) { //from whence we came vecSubFrom(chunk->dPos, chunk->velocity); //go somewhere new chunkRandomVelocity(chunk); } } static real32 nebradius = NEB_CHUNK_RADIUS*2.0f; /*----------------------------------------------------------------------------- Name : nebSetFog Description : decides what to do about fog Inputs : Outputs : Return : ----------------------------------------------------------------------------*/ void nebSetFog() { Node* node; SpaceObj* spaceobj; real32 sumOf = 0.0f, dist; real32 MIN_SUMOF = 0.1f; real32 MAX_SUMOF = 0.57f; if (dontNebulate || numNebulae == 0) { //nothing to do, or we're not allowed to do anything return; } //gather linear proximity info node = universe.RenderList.head; while (node != NULL) { spaceobj = (SpaceObj*)listGetStructOfNode(node); if (spaceobj->objtype == OBJ_NebulaType) { dist = fsqrt(spaceobj->cameraDistanceSquared); if (dist != 0.0f) { sumOf += nebradius / dist; } nebUpdateChunk((nebChunk*)((Nebula*)spaceobj)->stub); } node = node->next; } sumOf *= 0.15f; nebFogSum = sumOf; if (sumOf > 0.86f) { sumOf = 0.86f; } if (sumOf > 0.1f) { rndFogOn = TRUE; glFogfv(GL_FOG_COLOR, nebFogColor); glFogf(GL_FOG_DENSITY, sumOf); } else { rndFogOn = FALSE; } if (!smSensorsActive) { if (sumOf > 0.0f) { int r, g, b; sumOf *= 1.5f; if (sumOf > MAX_SUMOF) sumOf = MAX_SUMOF; else if (sumOf < MIN_SUMOF) sumOf = MIN_SUMOF; nebFogSum = sumOf; if (!_bright) sumOf *= 1.4f; r = (int)(NEB_FOG_RED * sumOf * NEB_BG_SCALAR * 255.0f); g = (int)(NEB_FOG_GREEN * sumOf * NEB_BG_SCALAR * 255.0f); b = (int)(NEB_FOG_BLUE * sumOf * NEB_BG_SCALAR * 255.0f); r += colRed(universe.backgroundColor); g += colGreen(universe.backgroundColor); b += colBlue(universe.backgroundColor); if (r > 255) r = 255; if (g > 255) g = 255; if (b > 255) b = 255; rndSetClearColor(colRGBA(r,g,b,255)); } } } /*============================================================================= Save functions for nebula =============================================================================*/ #pragma warning( 4 : 4047) // turns off "different levels of indirection warning" sdword nebChunkPtrToNum(nebulae_t* neb, nebChunk *nebChunk) { sdword i; for (i=0;inumChunks;i++) { if (nebChunk == &neb->chunkTable[i]) { return i; } } return -1; } nebChunk *nebNumToChunkPtr(nebulae_t* neb, sdword num) { if (num == -1) { return NULL; } else { dbgAssert(num < neb->numChunks); return &neb->chunkTable[num]; } } sdword nebTendrilPtrToNum(nebulae_t* neb, nebTendril *nebTendril) { sdword i; for (i=0;inumTendrils;i++) { if (nebTendril == &neb->tendrilTable[i]) { return i; } } return -1; } nebTendril *nebNumToTendrilPtr(nebulae_t* neb, sdword num) { if (num == -1) { return NULL; } else { dbgAssert(num < neb->numTendrils); return &neb->tendrilTable[num]; } } void nebSave_nebTendrilLOD(nebTendrilLOD *nebtendrilLOD) { if (nebtendrilLOD->vertices != NULL) { SaveChunk *chunk = CreateChunk(BASIC_STRUCTURE,sizeof(real32)*nebtendrilLOD->numVerts*3,nebtendrilLOD->vertices); SaveThisChunk(chunk); memFree(chunk); } if (nebtendrilLOD->normals != NULL) { SaveChunk *chunk = CreateChunk(BASIC_STRUCTURE,sizeof(real32)*nebtendrilLOD->numVerts*3,nebtendrilLOD->normals); SaveThisChunk(chunk); memFree(chunk); } dbgAssert(nebtendrilLOD->faces == NULL); } void nebSave_nebTendril(nebulae_t* neb, nebTendril *nebtendril) { sdword i; SaveChunk *chunk; nebTendril *savecontents; chunk = CreateChunk(BASIC_STRUCTURE|SAVE_NEBTENDRIL,sizeof(nebTendril),nebtendril); savecontents = chunkContents(chunk); savecontents->a = nebChunkPtrToNum(neb, savecontents->a); savecontents->b = nebChunkPtrToNum(neb, savecontents->b); savecontents->prev = nebTendrilPtrToNum(neb, savecontents->prev); savecontents->next = nebTendrilPtrToNum(neb, savecontents->next); SaveThisChunk(chunk); memFree(chunk); for (i=0;ilod[i]); } } void nebSave_nebChunk(nebulae_t* neb, nebChunk *nebchunk) { sdword i; SaveChunk *chunk; nebChunk *savecontents; chunk = CreateChunk(BASIC_STRUCTURE|SAVE_NEBCHUNK,sizeof(nebChunk),nebchunk); savecontents = chunkContents(chunk); for (i=0;itendrils[i] = nebTendrilPtrToNum(neb, savecontents->tendrils[i]); } savecontents->spaceobj = SpaceObjRegistryGetID(savecontents->spaceobj); savecontents->nebulae = nebIndexOfNeb(neb); SaveThisChunk(chunk); memFree(chunk); } void nebSave_Nebula(void) { sdword i, nebIndex; nebulae_t* neb; SaveInfoNumber(neb_frame_counter); SaveInfoNumber(numNebulae); for (nebIndex = 0; nebIndex < numNebulae; nebIndex++) { neb = &nebNebulae[nebIndex]; SaveInfoNumber(neb->numChunks); for (i=0;inumChunks;i++) { nebSave_nebChunk(neb, &neb->chunkTable[i]); } SaveInfoNumber(neb->numTendrils); for (i=0;inumTendrils;i++) { nebSave_nebTendril(neb, &neb->tendrilTable[i]); } } } void nebLoad_nebChunk(nebulae_t* neb, nebChunk *nebchunk) { SaveChunk *chunk; chunk = LoadNextChunk(); VerifyChunk(chunk,BASIC_STRUCTURE|SAVE_NEBCHUNK,sizeof(nebChunk)); memcpy(nebchunk,chunkContents(chunk),sizeof(nebChunk)); memFree(chunk); } void nebFix_nebChunk(nebulae_t* neb, nebChunk *nebchunk) { sdword i; for (i=0;itendrils[i] = nebNumToTendrilPtr(neb, (sdword)nebchunk->tendrils[i]); } nebchunk->spaceobj = SpaceObjRegistryGetObj((sdword)nebchunk->spaceobj); nebchunk->nebulae = neb; } void nebLoad_nebTendrilLOD(nebulae_t* neb, nebTendrilLOD *nebtendrilLOD) { if (nebtendrilLOD->vertices != NULL) { SaveChunk *chunk = LoadNextChunk(); sdword size = sizeof(real32)*nebtendrilLOD->numVerts*3; VerifyChunk(chunk,BASIC_STRUCTURE,size); nebtendrilLOD->vertices = memAlloc(size, "tendril vertices", 0); memcpy(nebtendrilLOD->vertices,chunkContents(chunk),size); memFree(chunk); } if (nebtendrilLOD->normals != NULL) { SaveChunk *chunk = LoadNextChunk(); sdword size = sizeof(real32)*nebtendrilLOD->numVerts*3; VerifyChunk(chunk,BASIC_STRUCTURE,size); nebtendrilLOD->normals = memAlloc(size, "tendril normals", 0); memcpy(nebtendrilLOD->normals,chunkContents(chunk),size); memFree(chunk); } dbgAssert(nebtendrilLOD->faces == NULL); } void nebLoad_nebTendril(nebulae_t* neb, nebTendril *nebtendril) { SaveChunk *chunk; sdword i; chunk = LoadNextChunk(); VerifyChunk(chunk,BASIC_STRUCTURE|SAVE_NEBTENDRIL,sizeof(nebTendril)); memcpy(nebtendril,chunkContents(chunk),sizeof(nebTendril)); memFree(chunk); for (i=0;ilod[i]); } } void nebFix_nebTendril(nebulae_t* neb, nebTendril *nebtendril) { nebtendril->a = nebNumToChunkPtr(neb, (sdword)nebtendril->a); nebtendril->b = nebNumToChunkPtr(neb, (sdword)nebtendril->b); nebtendril->prev = nebNumToTendrilPtr(neb, (sdword)nebtendril->prev); nebtendril->next = nebNumToTendrilPtr(neb, (sdword)nebtendril->next); } void nebLoad_Nebula(void) { sdword i, nebIndex; nebulae_t* neb; nebColorInit(); neb_frame_counter = LoadInfoNumber(); numNebulae = LoadInfoNumber(); for (nebIndex = 0; nebIndex < numNebulae; nebIndex++) { neb = &nebNebulae[nebIndex]; neb->numChunks = LoadInfoNumber(); if (neb->numChunks != 0) { neb->chunkTable = memAlloc(neb->numChunks * sizeof(nebChunk), "nebula chunkTable", 0); } for (i=0;inumChunks;i++) { nebLoad_nebChunk(neb, &neb->chunkTable[i]); } neb->numTendrils = LoadInfoNumber(); if (neb->numTendrils != 0) { neb->tendrilTable = memAlloc(neb->numTendrils * sizeof(nebTendril), "nebula tendrilTable", 0); } for (i=0;inumTendrils;i++) { nebLoad_nebTendril(neb, &neb->tendrilTable[i]); } // now fix everything up for (i=0;inumChunks;i++) { nebFix_nebChunk(neb, &neb->chunkTable[i]); } for (i=0;inumTendrils;i++) { nebFix_nebTendril(neb, &neb->tendrilTable[i]); } } } #pragma warning( 2 : 4047) // turn back on "different levels of indirection warning"