#include "plugin.h" #include "entity.h" #include "light.h" void DrawSphere(vec3_t center, float radius, int sides, int nGLState) { int i, j; float dt = (float) (2 * Q_PI / (float) sides); float dp = (float) (Q_PI / (float) sides); float t, p; vec3_t v; if (radius <= 0) return; g_QglTable.m_pfn_qglBegin(GL_TRIANGLES); for (i = 0; i <= sides - 1; i++) { for (j = 0; j <= sides - 2; j++) { t = i * dt; p = (float) ((j * dp) - (Q_PI / 2)); VectorPolar(v, radius, t, p); VectorAdd(v, center, v); g_QglTable.m_pfn_qglVertex3fv(v); VectorPolar(v, radius, t, p + dp); VectorAdd(v, center, v); g_QglTable.m_pfn_qglVertex3fv(v); VectorPolar(v, radius, t + dt, p + dp); VectorAdd(v, center, v); g_QglTable.m_pfn_qglVertex3fv(v); VectorPolar(v, radius, t, p); VectorAdd(v, center, v); g_QglTable.m_pfn_qglVertex3fv(v); VectorPolar(v, radius, t + dt, p + dp); VectorAdd(v, center, v); g_QglTable.m_pfn_qglVertex3fv(v); VectorPolar(v, radius, t + dt, p); VectorAdd(v, center, v); g_QglTable.m_pfn_qglVertex3fv(v); } } p = (float) ((sides - 1) * dp - (Q_PI / 2)); for (i = 0; i <= sides - 1; i++) { t = i * dt; VectorPolar(v, radius, t, p); VectorAdd(v, center, v); g_QglTable.m_pfn_qglVertex3fv(v); VectorPolar(v, radius, t + dt, p + dp); VectorAdd(v, center, v); g_QglTable.m_pfn_qglVertex3fv(v); VectorPolar(v, radius, t + dt, p); VectorAdd(v, center, v); g_QglTable.m_pfn_qglVertex3fv(v); } g_QglTable.m_pfn_qglEnd(); } #define LIGHT_ATTEN_LINEAR 1 #define LIGHT_ATTEN_ANGLE 2 #define LIGHT_ATTEN_DISTANCE 4 #define LIGHT_Q3A_DEFAULT (LIGHT_ATTEN_ANGLE | LIGHT_ATTEN_DISTANCE) #define LIGHT_WOLF_DEFAULT (LIGHT_ATTEN_LINEAR | LIGHT_ATTEN_DISTANCE) float CalculateEnvelopeForLight(entity_t * e, float fFalloffTolerance) { float fEnvelope = 0.f; int iSpawnFlags = atoi(ValueForKey(e, "spawnflags")); int iLightFlags = 0; float fFade = 1.f; float fIntensity, fPhotons; float fScale; const char *gameFile = g_FuncTable.m_pfnGetGameFile(); // These variables are tweakable on the q3map2 console, setting to q3map2 // default here as there is no way to find out what the user actually uses // right now. Maybe move them to worldspawn? float fPointScale = 7500.f; float fLinearScale = 1.f / 8000.f; //float fFalloffTolerance = 1.f; // Need it as parameter // Arnout: HACK for per-game radii - really need to move this to a per-game module? if( !strcmp( gameFile, "wolf.game" ) || !strcmp( gameFile, "et.game" ) ) { // Spawnflags : // 1: nonlinear // 2: angle // set default flags iLightFlags = LIGHT_WOLF_DEFAULT; // inverse distance squared attenuation? if (iSpawnFlags & 1) { iLightFlags &= ~LIGHT_ATTEN_LINEAR; iLightFlags |= LIGHT_ATTEN_ANGLE; } // angle attenuate if (iSpawnFlags & 2) iLightFlags |= LIGHT_ATTEN_ANGLE; } else { // Spawnflags : // 1: linear // 2: no angle // set default flags iLightFlags = LIGHT_Q3A_DEFAULT; // linear attenuation? if (iSpawnFlags & 1) { iLightFlags |= LIGHT_ATTEN_LINEAR; iLightFlags &= ~LIGHT_ATTEN_ANGLE; } // no angle attenuate? if (iSpawnFlags & 2) iLightFlags &= ~LIGHT_ATTEN_ANGLE; } // set fade key (from wolf) if (iLightFlags & LIGHT_ATTEN_LINEAR) { fFade = FloatForKey(e, "fade"); if (fFade <= 0.f) fFade = 1.f; } // set light intensity fIntensity = FloatForKey(e, "_light"); if (fIntensity == 0.f) fIntensity = FloatForKey(e, "light"); if (fIntensity == 0.f) fIntensity = 300.f; // set light scale (sof2) fScale = FloatForKey(e, "scale"); if (fScale <= 0.f) fScale = 1.f; fIntensity *= fScale; // amount of photons fPhotons = fIntensity * fPointScale; // calculate envelope // solve distance for non-distance lights if (!(iLightFlags & LIGHT_ATTEN_DISTANCE)) //!\todo (spog) can't access global objects in a module - globals are EVIL - solution: API for querying global settings. fEnvelope = 131072/*g_MaxWorldCoord * 2.f*/; // solve distance for linear lights else if (iLightFlags & LIGHT_ATTEN_LINEAR) fEnvelope = ((fPhotons * fLinearScale) - fFalloffTolerance) / fFade; // solve for inverse square falloff else fEnvelope = sqrt(fPhotons / fFalloffTolerance) /* + fRadius */ ; // Arnout radius is always 0, only for area lights return fEnvelope; } float CalculateLightRadius(entity_t * e, bool outer) { float fEnvelope = 0.f; int iSpawnFlags = atoi(ValueForKey(e, "spawnflags")); float fIntensity; float fScale; const char *gameFile = g_FuncTable.m_pfnGetGameFile(); fIntensity = FloatForKey(e, "light"); if (fIntensity == 0.f) fIntensity = 300.f; // Arnout: HACK for per-game radii - really need to move this to a per-game module if( !strcmp( gameFile, "sof2.game" ) || !strcmp( gameFile, "jk2.game" ) || !strcmp( gameFile, "ja.game" )) { // Spawnflags : // 1: linear // 2: noincidence if (!outer) { if (iSpawnFlags & 2) fIntensity *= .9; else fIntensity *= .25f; } // set light scale (sof2) fScale = FloatForKey(e, "scale"); if (fScale <= 0.f) fScale = 1.f; fIntensity *= fScale; fEnvelope = fIntensity; } else { float fPointScale = 7500.f; if (outer) fEnvelope = sqrt(fIntensity * fPointScale / 48.f); else fEnvelope = sqrt(fIntensity * fPointScale / 255.f); } return fEnvelope; } void Light_OnIntensityChanged(entity_t* e) { e->fLightEnvelope1[0] = CalculateEnvelopeForLight(e, 1.f); e->fLightEnvelope1[1] = CalculateEnvelopeForLight(e, 48.f); e->fLightEnvelope1[2] = CalculateEnvelopeForLight(e, 255.f); e->fLightEnvelope2[0] = CalculateLightRadius(e, TRUE); e->fLightEnvelope2[1] = CalculateLightRadius(e, FALSE); } void Light_OnKeyValueChanged(entity_t *e, const char *key, const char* value) { if(strcmp(key,"_color") == 0) { if (sscanf(ValueForKey(e, "_color"),"%f %f %f", &e->color[0], &e->color[1], &e->color[2]) != 3) VectorSet(e->color, 1, 1, 1); } else if(strcmp(key,"spawnflags") == 0 || strcmp(key,"fade") == 0 || strcmp(key,"_light") == 0 || strcmp(key,"light") == 0 || strcmp(key,"scale") == 0) { Light_OnIntensityChanged(e); } } bool Entity_IsLight(entity_t *e) { return e->eclass != NULL && e->eclass->nShowFlags & ECLASS_LIGHT;//strncmp(ValueforKey(e, "classname"), "light") == 0 } static void DrawLightSphere(entity_t * e, int nGLState, int pref) { const char *target = ValueForKey(e, "target"); bool bIsSpotLight = !!target[0]; //!\todo Write an API for modules to register preference settings, and make this preference module-specific. int nPasses = pref == 1 ? 3 : 2; g_QglTable.m_pfn_qglPushAttrib(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT); g_QglTable.m_pfn_qglDepthMask(GL_FALSE); g_QglTable.m_pfn_qglEnable(GL_BLEND); g_QglTable.m_pfn_qglBlendFunc(GL_ONE, GL_ONE); // Arnout: TODO: spotlight rendering if (!(bIsSpotLight)) { switch(pref) { case 1: g_QglTable.m_pfn_qglColor3f(e->color[0] * .05f, e->color[1] * .05f, e->color[2] * .05f); DrawSphere(e->origin, e->fLightEnvelope1[0], 16, nGLState); DrawSphere(e->origin, e->fLightEnvelope1[1], 16, nGLState); DrawSphere(e->origin, e->fLightEnvelope1[2], 16, nGLState); break; case 2: g_QglTable.m_pfn_qglColor3f(e->color[0] * .15f * .95f, e->color[1] * .15f * .95f, e->color[2] * .15f * .95f); DrawSphere(e->origin, e->fLightEnvelope2[0], 16, nGLState); DrawSphere(e->origin, e->fLightEnvelope2[1], 16, nGLState); break; } } g_QglTable.m_pfn_qglPopAttrib(); } float F = 0.70710678f; // North, East, South, West vec3_t normals[8] = { { 0, F, F }, { F, 0, F }, { 0,-F, F }, {-F, 0, F }, { 0, F,-F }, { F, 0,-F }, { 0,-F,-F }, {-F, 0,-F } }; unsigned short indices[24] = { 0, 2, 3, 0, 3, 4, 0, 4, 5, 0, 5, 2, 1, 2, 5, 1, 5, 4, 1, 4, 3, 1, 3, 2 }; void DrawLight(entity_t* e, int nGLState, int pref, int nViewType) { int i; // top, bottom, tleft, tright, bright, bleft vec3_t points[6]; vec3_t vMid, vMin, vMax; VectorAdd(e->origin, e->eclass->mins, vMin); VectorAdd(e->origin, e->eclass->maxs, vMax); vMid[0] = (vMin[0] + vMax[0]) * 0.5; vMid[1] = (vMin[1] + vMax[1]) * 0.5; vMid[2] = (vMin[2] + vMax[2]) * 0.5; VectorSet(points[0], vMid[0], vMid[1], vMax[2]); VectorSet(points[1], vMid[0], vMid[1], vMin[2]); VectorSet(points[2], vMin[0], vMax[1], vMid[2]); VectorSet(points[3], vMax[0], vMax[1], vMid[2]); VectorSet(points[4], vMax[0], vMin[1], vMid[2]); VectorSet(points[5], vMin[0], vMin[1], vMid[2]); if (nGLState & DRAW_GL_LIGHTING)// && g_PrefsDlg.m_bGLLighting) { g_QglTable.m_pfn_qglBegin(GL_TRIANGLES);// NOTE: comment to use gl_triangle_fan instead //g_QglTable.m_pfn_qglBegin(GL_TRIANGLE_FAN); g_QglTable.m_pfn_qglVertex3fv(points[0]); g_QglTable.m_pfn_qglVertex3fv(points[2]); g_QglTable.m_pfn_qglNormal3fv(normals[0]); g_QglTable.m_pfn_qglVertex3fv(points[3]); g_QglTable.m_pfn_qglVertex3fv(points[0]);// g_QglTable.m_pfn_qglVertex3fv(points[3]);// g_QglTable.m_pfn_qglNormal3fv(normals[1]); g_QglTable.m_pfn_qglVertex3fv(points[4]); g_QglTable.m_pfn_qglVertex3fv(points[0]);// g_QglTable.m_pfn_qglVertex3fv(points[4]);// g_QglTable.m_pfn_qglNormal3fv(normals[2]); g_QglTable.m_pfn_qglVertex3fv(points[5]); g_QglTable.m_pfn_qglVertex3fv(points[0]);// g_QglTable.m_pfn_qglVertex3fv(points[5]);// g_QglTable.m_pfn_qglNormal3fv(normals[3]); g_QglTable.m_pfn_qglVertex3fv(points[2]); //g_QglTable.m_pfn_qglEnd(); //g_QglTable.m_pfn_qglBegin(GL_TRIANGLE_FAN); g_QglTable.m_pfn_qglVertex3fv(points[1]); g_QglTable.m_pfn_qglVertex3fv(points[2]); g_QglTable.m_pfn_qglNormal3fv(normals[7]); g_QglTable.m_pfn_qglVertex3fv(points[5]); g_QglTable.m_pfn_qglVertex3fv(points[1]);// g_QglTable.m_pfn_qglVertex3fv(points[5]);// g_QglTable.m_pfn_qglNormal3fv(normals[6]); g_QglTable.m_pfn_qglVertex3fv(points[4]); g_QglTable.m_pfn_qglVertex3fv(points[1]);// g_QglTable.m_pfn_qglVertex3fv(points[4]);// g_QglTable.m_pfn_qglNormal3fv(normals[5]); g_QglTable.m_pfn_qglVertex3fv(points[3]); g_QglTable.m_pfn_qglVertex3fv(points[1]);// g_QglTable.m_pfn_qglVertex3fv(points[3]);// g_QglTable.m_pfn_qglNormal3fv(normals[4]); g_QglTable.m_pfn_qglVertex3fv(points[2]); g_QglTable.m_pfn_qglEnd(); } else if (nGLState & DRAW_GL_FILL) { vec3_t colors[4]; VectorScale(e->color, 0.95, colors[0]); VectorScale(colors[0], 0.95, colors[1]); VectorScale(colors[1], 0.95, colors[2]); VectorScale(colors[2], 0.95, colors[3]); g_QglTable.m_pfn_qglBegin(GL_TRIANGLES);// NOTE: comment to use gl_triangle_fan instead //g_QglTable.m_pfn_qglBegin(GL_TRIANGLE_FAN); g_QglTable.m_pfn_qglColor3fv(colors[0]); g_QglTable.m_pfn_qglVertex3fv(points[0]); g_QglTable.m_pfn_qglVertex3fv(points[2]); g_QglTable.m_pfn_qglVertex3fv(points[3]); g_QglTable.m_pfn_qglColor3fv(colors[1]); g_QglTable.m_pfn_qglVertex3fv(points[0]);// g_QglTable.m_pfn_qglVertex3fv(points[3]);// g_QglTable.m_pfn_qglVertex3fv(points[4]); g_QglTable.m_pfn_qglColor3fv(colors[2]); g_QglTable.m_pfn_qglVertex3fv(points[0]);// g_QglTable.m_pfn_qglVertex3fv(points[4]);// g_QglTable.m_pfn_qglVertex3fv(points[5]); g_QglTable.m_pfn_qglColor3fv(colors[3]); g_QglTable.m_pfn_qglVertex3fv(points[0]);// g_QglTable.m_pfn_qglVertex3fv(points[5]);// g_QglTable.m_pfn_qglVertex3fv(points[2]); //g_QglTable.m_pfn_qglEnd(); //g_QglTable.m_pfn_qglBegin(GL_TRIANGLE_FAN); g_QglTable.m_pfn_qglColor3fv(colors[0]); g_QglTable.m_pfn_qglVertex3fv(points[1]); g_QglTable.m_pfn_qglVertex3fv(points[2]); g_QglTable.m_pfn_qglVertex3fv(points[5]); g_QglTable.m_pfn_qglColor3fv(colors[1]); g_QglTable.m_pfn_qglVertex3fv(points[1]);// g_QglTable.m_pfn_qglVertex3fv(points[5]);// g_QglTable.m_pfn_qglVertex3fv(points[4]); g_QglTable.m_pfn_qglColor3fv(colors[2]); g_QglTable.m_pfn_qglVertex3fv(points[1]);// g_QglTable.m_pfn_qglVertex3fv(points[4]);// g_QglTable.m_pfn_qglVertex3fv(points[3]); g_QglTable.m_pfn_qglColor3fv(colors[3]); g_QglTable.m_pfn_qglVertex3fv(points[1]);// g_QglTable.m_pfn_qglVertex3fv(points[3]);// g_QglTable.m_pfn_qglVertex3fv(points[2]); g_QglTable.m_pfn_qglEnd(); } else { g_QglTable.m_pfn_qglVertexPointer(3, GL_FLOAT, 0, points); g_QglTable.m_pfn_qglDrawElements(GL_TRIANGLES, 24, GL_UNSIGNED_SHORT, indices); } // NOTE: prolly not relevant until some time.. // check for DOOM lights if (strlen(ValueForKey(e, "light_right")) > 0) { vec3_t vRight, vUp, vTarget, vTemp; GetVectorForKey (e, "light_right", vRight); GetVectorForKey (e, "light_up", vUp); GetVectorForKey (e, "light_target", vTarget); g_QglTable.m_pfn_qglColor3f(0, 1, 0); g_QglTable.m_pfn_qglBegin(GL_LINE_LOOP); VectorAdd(vTarget, e->origin, vTemp); VectorAdd(vTemp, vRight, vTemp); VectorAdd(vTemp, vUp, vTemp); g_QglTable.m_pfn_qglVertex3fv(e->origin); g_QglTable.m_pfn_qglVertex3fv(vTemp); VectorAdd(vTarget, e->origin, vTemp); VectorAdd(vTemp, vUp, vTemp); VectorSubtract(vTemp, vRight, vTemp); g_QglTable.m_pfn_qglVertex3fv(e->origin); g_QglTable.m_pfn_qglVertex3fv(vTemp); VectorAdd(vTarget, e->origin, vTemp); VectorAdd(vTemp, vRight, vTemp); VectorSubtract(vTemp, vUp, vTemp); g_QglTable.m_pfn_qglVertex3fv(e->origin); g_QglTable.m_pfn_qglVertex3fv(vTemp); VectorAdd(vTarget, e->origin, vTemp); VectorSubtract(vTemp, vUp, vTemp); VectorSubtract(vTemp, vRight, vTemp); g_QglTable.m_pfn_qglVertex3fv(e->origin); g_QglTable.m_pfn_qglVertex3fv(vTemp); g_QglTable.m_pfn_qglEnd(); } if(nGLState & DRAW_GL_FILL) { DrawLightSphere(e, nGLState, pref); } else { // Arnout: FIXME: clean this up a bit // now draw lighting radius stuff... if (pref) { bool bDrawSpotlightArc = false; int nPasses = pref == 1 ? 3 : 2; const char *target = ValueForKey(e, "target"); bool bIsSpotLight = !!target[0]; /*!\todo Spotlight.. if (bIsSpotLight) { // find the origin of the target... entity_t *e = FindEntity("targetname", target); if (e) bDrawSpotlightArc = true; } */ g_QglTable.m_pfn_qglPushAttrib(GL_LINE_BIT); g_QglTable.m_pfn_qglLineStipple(8, 0xAAAA); g_QglTable.m_pfn_qglEnable(GL_LINE_STIPPLE); float* envelope = (pref == 1) ? e->fLightEnvelope1 : e->fLightEnvelope2; for (int iPass = 0; iPass < nPasses; iPass++) { float fRadius = envelope[iPass]; g_QglTable.m_pfn_qglBegin(GL_LINE_LOOP); if (bIsSpotLight) { if (bDrawSpotlightArc) { // I give up on this, it's beyond me } } else { if (fRadius > 0) { int i; float ds, dc; for (i = 0; i <= 24; i++) { ds = sin((i * 2 * Q_PI) / 24); dc = cos((i * 2 * Q_PI) / 24); switch (nViewType) { case 2: g_QglTable.m_pfn_qglVertex3f(e->origin[0] + fRadius * dc, e->origin[1] + fRadius * ds, e->origin[2]); break; case 1: g_QglTable.m_pfn_qglVertex3f(e->origin[0] + fRadius * dc, e->origin[1], e->origin[2] + fRadius * ds); break; case 0: g_QglTable.m_pfn_qglVertex3f(e->origin[0], e->origin[1] + fRadius * dc, e->origin[2] + fRadius * ds); break; } } } } g_QglTable.m_pfn_qglEnd(); } g_QglTable.m_pfn_qglPopAttrib(); } } }