more eol-style
[xonotic/netradiant.git] / plugins / entity / light.cpp
1
2 #include "plugin.h"
3 #include "entity.h"
4 #include "light.h"
5
6 void DrawSphere(vec3_t center, float radius, int sides, int nGLState)
7 {
8   int i, j;
9   float dt = (float) (2 * Q_PI / (float) sides);
10   float dp = (float) (Q_PI / (float) sides);
11   float t, p;
12   vec3_t v;
13
14   if (radius <= 0)
15     return;
16
17   g_QglTable.m_pfn_qglBegin(GL_TRIANGLES);
18   for (i = 0; i <= sides - 1; i++) {
19     for (j = 0; j <= sides - 2; j++) {
20       t = i * dt;
21       p = (float) ((j * dp) - (Q_PI / 2));
22
23       VectorPolar(v, radius, t, p);
24       VectorAdd(v, center, v);
25       g_QglTable.m_pfn_qglVertex3fv(v);
26
27       VectorPolar(v, radius, t, p + dp);
28       VectorAdd(v, center, v);
29       g_QglTable.m_pfn_qglVertex3fv(v);
30
31       VectorPolar(v, radius, t + dt, p + dp);
32       VectorAdd(v, center, v);
33       g_QglTable.m_pfn_qglVertex3fv(v);
34
35       VectorPolar(v, radius, t, p);
36       VectorAdd(v, center, v);
37       g_QglTable.m_pfn_qglVertex3fv(v);
38
39       VectorPolar(v, radius, t + dt, p + dp);
40       VectorAdd(v, center, v);
41       g_QglTable.m_pfn_qglVertex3fv(v);
42
43       VectorPolar(v, radius, t + dt, p);
44       VectorAdd(v, center, v);
45       g_QglTable.m_pfn_qglVertex3fv(v);
46     }
47   }
48
49   p = (float) ((sides - 1) * dp - (Q_PI / 2));
50   for (i = 0; i <= sides - 1; i++) {
51     t = i * dt;
52
53     VectorPolar(v, radius, t, p);
54     VectorAdd(v, center, v);
55     g_QglTable.m_pfn_qglVertex3fv(v);
56
57     VectorPolar(v, radius, t + dt, p + dp);
58     VectorAdd(v, center, v);
59     g_QglTable.m_pfn_qglVertex3fv(v);
60
61     VectorPolar(v, radius, t + dt, p);
62     VectorAdd(v, center, v);
63     g_QglTable.m_pfn_qglVertex3fv(v);
64   }
65   g_QglTable.m_pfn_qglEnd();
66 }
67
68 #define LIGHT_ATTEN_LINEAR      1
69 #define LIGHT_ATTEN_ANGLE               2
70 #define LIGHT_ATTEN_DISTANCE    4
71
72 #define LIGHT_Q3A_DEFAULT               (LIGHT_ATTEN_ANGLE | LIGHT_ATTEN_DISTANCE)
73 #define LIGHT_WOLF_DEFAULT      (LIGHT_ATTEN_LINEAR | LIGHT_ATTEN_DISTANCE)
74
75 float CalculateEnvelopeForLight(entity_t * e, float fFalloffTolerance)
76 {
77   float fEnvelope = 0.f;
78   int iSpawnFlags = atoi(ValueForKey(e, "spawnflags"));
79   int iLightFlags = 0;
80   float fFade = 1.f;
81   float fIntensity, fPhotons;
82   float fScale;
83   const char *gameFile = g_FuncTable.m_pfnGetGameFile();
84
85   // These variables are tweakable on the q3map2 console, setting to q3map2
86   // default here as there is no way to find out what the user actually uses
87   // right now. Maybe move them to worldspawn?
88   float fPointScale = 7500.f;
89   float fLinearScale = 1.f / 8000.f;
90   //float fFalloffTolerance = 1.f;  // Need it as parameter
91
92   // Arnout: HACK for per-game radii - really need to move this to a per-game module?
93   if( !strcmp( gameFile, "wolf.game" ) || !strcmp( gameFile, "et.game" ) ) {
94     // Spawnflags :
95     // 1: nonlinear
96     // 2: angle
97
98     // set default flags
99     iLightFlags = LIGHT_WOLF_DEFAULT;
100
101     // inverse distance squared attenuation?
102     if (iSpawnFlags & 1) {
103       iLightFlags &= ~LIGHT_ATTEN_LINEAR;
104       iLightFlags |= LIGHT_ATTEN_ANGLE;
105     }
106     // angle attenuate
107     if (iSpawnFlags & 2)
108       iLightFlags |= LIGHT_ATTEN_ANGLE;
109   } else {
110     // Spawnflags :
111     // 1: linear
112     // 2: no angle
113
114     // set default flags
115     iLightFlags = LIGHT_Q3A_DEFAULT;
116
117     // linear attenuation?
118     if (iSpawnFlags & 1) {
119       iLightFlags |= LIGHT_ATTEN_LINEAR;
120       iLightFlags &= ~LIGHT_ATTEN_ANGLE;
121     }
122     // no angle attenuate?
123     if (iSpawnFlags & 2)
124       iLightFlags &= ~LIGHT_ATTEN_ANGLE;
125   }
126
127   // set fade key (from wolf)
128   if (iLightFlags & LIGHT_ATTEN_LINEAR) {
129     fFade = FloatForKey(e, "fade");
130     if (fFade <= 0.f)
131       fFade = 1.f;
132   }
133   // set light intensity
134   fIntensity = FloatForKey(e, "_light");
135   if (fIntensity == 0.f)
136     fIntensity = FloatForKey(e, "light");
137   if (fIntensity == 0.f)
138     fIntensity = 300.f;
139
140   // set light scale (sof2)
141   fScale = FloatForKey(e, "scale");
142   if (fScale <= 0.f)
143     fScale = 1.f;
144   fIntensity *= fScale;
145
146   // amount of photons
147   fPhotons = fIntensity * fPointScale;
148
149   // calculate envelope
150
151   // solve distance for non-distance lights
152   if (!(iLightFlags & LIGHT_ATTEN_DISTANCE))
153     //!\todo (spog) can't access global objects in a module - globals are EVIL - solution: API for querying global settings.
154     fEnvelope = 131072/*g_MaxWorldCoord * 2.f*/;
155   // solve distance for linear lights
156   else if (iLightFlags & LIGHT_ATTEN_LINEAR)
157     fEnvelope = ((fPhotons * fLinearScale) - fFalloffTolerance) / fFade;
158   // solve for inverse square falloff
159   else
160     fEnvelope = sqrt(fPhotons / fFalloffTolerance) /* + fRadius */ ;  // Arnout radius is always 0, only for area lights
161
162   return fEnvelope;
163 }
164
165 float CalculateLightRadius(entity_t * e, bool outer)
166 {
167   float fEnvelope = 0.f;
168   int iSpawnFlags = atoi(ValueForKey(e, "spawnflags"));
169   float fIntensity;
170   float fScale;
171   const char *gameFile = g_FuncTable.m_pfnGetGameFile();
172
173   fIntensity = FloatForKey(e, "light");
174   if (fIntensity == 0.f)
175     fIntensity = 300.f;
176
177   // Arnout: HACK for per-game radii - really need to move this to a per-game module
178   if( !strcmp( gameFile, "sof2.game" ) || !strcmp( gameFile, "jk2.game" ) || !strcmp( gameFile, "ja.game" )) {
179     // Spawnflags :
180     // 1: linear
181     // 2: noincidence
182
183     if (!outer) {
184       if (iSpawnFlags & 2)
185         fIntensity *= .9;
186       else
187         fIntensity *= .25f;
188     }
189     // set light scale (sof2)
190     fScale = FloatForKey(e, "scale");
191     if (fScale <= 0.f)
192       fScale = 1.f;
193     fIntensity *= fScale;
194
195     fEnvelope = fIntensity;
196   } else {
197     float fPointScale = 7500.f;
198
199     if (outer)
200       fEnvelope = sqrt(fIntensity * fPointScale / 48.f);
201     else
202       fEnvelope = sqrt(fIntensity * fPointScale / 255.f);
203   }
204
205   return fEnvelope;
206 }
207
208 void Light_OnIntensityChanged(entity_t* e)
209 {
210   e->fLightEnvelope1[0] = CalculateEnvelopeForLight(e, 1.f);
211   e->fLightEnvelope1[1] = CalculateEnvelopeForLight(e, 48.f);
212   e->fLightEnvelope1[2] = CalculateEnvelopeForLight(e, 255.f);
213
214   e->fLightEnvelope2[0] = CalculateLightRadius(e, TRUE);
215   e->fLightEnvelope2[1] = CalculateLightRadius(e, FALSE);
216 }
217
218 void Light_OnKeyValueChanged(entity_t *e, const char *key, const char* value)
219 {
220   if(strcmp(key,"_color") == 0)
221   {
222     if (sscanf(ValueForKey(e, "_color"),"%f %f %f",
223       &e->color[0], &e->color[1], &e->color[2]) != 3)
224       VectorSet(e->color, 1, 1, 1);
225   }
226   else if(strcmp(key,"spawnflags") == 0 ||
227           strcmp(key,"fade") == 0 ||
228           strcmp(key,"_light") == 0 ||
229           strcmp(key,"light") == 0 ||
230           strcmp(key,"scale") == 0)
231   {
232     Light_OnIntensityChanged(e);
233   }
234 }
235
236 bool Entity_IsLight(entity_t *e)
237 {
238   return e->eclass != NULL && e->eclass->nShowFlags & ECLASS_LIGHT;//strncmp(ValueforKey(e, "classname"), "light") == 0
239 }
240
241 static void DrawLightSphere(entity_t * e, int nGLState, int pref)
242 {
243   const char *target = ValueForKey(e, "target");
244   bool bIsSpotLight = !!target[0];
245   //!\todo Write an API for modules to register preference settings, and make this preference module-specific.
246   int nPasses = pref == 1 ? 3 : 2;
247
248   g_QglTable.m_pfn_qglPushAttrib(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
249   g_QglTable.m_pfn_qglDepthMask(GL_FALSE);
250   g_QglTable.m_pfn_qglEnable(GL_BLEND);
251   g_QglTable.m_pfn_qglBlendFunc(GL_ONE, GL_ONE);
252
253   // Arnout: TODO: spotlight rendering
254   if (!(bIsSpotLight))
255   {
256     switch(pref)
257     {
258     case 1:
259       g_QglTable.m_pfn_qglColor3f(e->color[0] * .05f,
260         e->color[1] * .05f,
261         e->color[2] * .05f);
262       DrawSphere(e->origin, e->fLightEnvelope1[0], 16, nGLState);
263       DrawSphere(e->origin, e->fLightEnvelope1[1], 16, nGLState);
264       DrawSphere(e->origin, e->fLightEnvelope1[2], 16, nGLState);
265       break;
266     case 2:
267       g_QglTable.m_pfn_qglColor3f(e->color[0] * .15f * .95f,
268         e->color[1] * .15f * .95f,
269         e->color[2] * .15f * .95f);
270       DrawSphere(e->origin, e->fLightEnvelope2[0], 16, nGLState);
271       DrawSphere(e->origin, e->fLightEnvelope2[1], 16, nGLState);
272       break;
273
274     }
275   }
276
277   g_QglTable.m_pfn_qglPopAttrib();
278 }
279
280 float F = 0.70710678f;
281 // North, East, South, West
282 vec3_t normals[8] = { { 0, F, F }, { F, 0, F }, { 0,-F, F }, {-F, 0, F },
283                                                                                         { 0, F,-F }, { F, 0,-F }, { 0,-F,-F }, {-F, 0,-F } };
284
285 unsigned short indices[24] = { 0, 2, 3, 0, 3, 4, 0, 4, 5, 0, 5, 2,
286                                1, 2, 5, 1, 5, 4, 1, 4, 3, 1, 3, 2 };
287
288 void DrawLight(entity_t* e, int nGLState, int pref, int nViewType)
289 {
290   int i;
291   // top, bottom, tleft, tright, bright, bleft
292   vec3_t points[6];
293   vec3_t vMid, vMin, vMax;
294   VectorAdd(e->origin, e->eclass->mins, vMin);
295   VectorAdd(e->origin, e->eclass->maxs, vMax);
296   vMid[0] = (vMin[0] + vMax[0]) * 0.5;
297   vMid[1] = (vMin[1] + vMax[1]) * 0.5;
298   vMid[2] = (vMin[2] + vMax[2]) * 0.5;
299
300   VectorSet(points[0], vMid[0], vMid[1], vMax[2]);
301   VectorSet(points[1], vMid[0], vMid[1], vMin[2]);
302   VectorSet(points[2], vMin[0], vMax[1], vMid[2]);
303   VectorSet(points[3], vMax[0], vMax[1], vMid[2]);
304   VectorSet(points[4], vMax[0], vMin[1], vMid[2]);
305   VectorSet(points[5], vMin[0], vMin[1], vMid[2]);
306
307   if (nGLState & DRAW_GL_LIGHTING)// && g_PrefsDlg.m_bGLLighting)
308   {
309     g_QglTable.m_pfn_qglBegin(GL_TRIANGLES);// NOTE: comment to use gl_triangle_fan instead
310           //g_QglTable.m_pfn_qglBegin(GL_TRIANGLE_FAN);
311     g_QglTable.m_pfn_qglVertex3fv(points[0]);
312     g_QglTable.m_pfn_qglVertex3fv(points[2]);
313           g_QglTable.m_pfn_qglNormal3fv(normals[0]);
314           g_QglTable.m_pfn_qglVertex3fv(points[3]);
315
316           g_QglTable.m_pfn_qglVertex3fv(points[0]);//
317     g_QglTable.m_pfn_qglVertex3fv(points[3]);//
318           g_QglTable.m_pfn_qglNormal3fv(normals[1]);
319           g_QglTable.m_pfn_qglVertex3fv(points[4]);
320
321           g_QglTable.m_pfn_qglVertex3fv(points[0]);//
322     g_QglTable.m_pfn_qglVertex3fv(points[4]);//
323           g_QglTable.m_pfn_qglNormal3fv(normals[2]);
324           g_QglTable.m_pfn_qglVertex3fv(points[5]);
325
326           g_QglTable.m_pfn_qglVertex3fv(points[0]);//
327     g_QglTable.m_pfn_qglVertex3fv(points[5]);//
328           g_QglTable.m_pfn_qglNormal3fv(normals[3]);
329           g_QglTable.m_pfn_qglVertex3fv(points[2]);
330
331           //g_QglTable.m_pfn_qglEnd();
332           //g_QglTable.m_pfn_qglBegin(GL_TRIANGLE_FAN);
333
334           g_QglTable.m_pfn_qglVertex3fv(points[1]);
335           g_QglTable.m_pfn_qglVertex3fv(points[2]);
336     g_QglTable.m_pfn_qglNormal3fv(normals[7]);
337           g_QglTable.m_pfn_qglVertex3fv(points[5]);
338
339           g_QglTable.m_pfn_qglVertex3fv(points[1]);//
340           g_QglTable.m_pfn_qglVertex3fv(points[5]);//
341     g_QglTable.m_pfn_qglNormal3fv(normals[6]);
342           g_QglTable.m_pfn_qglVertex3fv(points[4]);
343
344           g_QglTable.m_pfn_qglVertex3fv(points[1]);//
345           g_QglTable.m_pfn_qglVertex3fv(points[4]);//
346     g_QglTable.m_pfn_qglNormal3fv(normals[5]);
347           g_QglTable.m_pfn_qglVertex3fv(points[3]);
348
349           g_QglTable.m_pfn_qglVertex3fv(points[1]);//
350           g_QglTable.m_pfn_qglVertex3fv(points[3]);//
351     g_QglTable.m_pfn_qglNormal3fv(normals[4]);
352           g_QglTable.m_pfn_qglVertex3fv(points[2]);
353
354     g_QglTable.m_pfn_qglEnd();
355   }
356   else if (nGLState & DRAW_GL_FILL)
357   {
358     vec3_t colors[4];
359     VectorScale(e->color, 0.95, colors[0]);
360     VectorScale(colors[0], 0.95, colors[1]);
361     VectorScale(colors[1], 0.95, colors[2]);
362     VectorScale(colors[2], 0.95, colors[3]);
363     g_QglTable.m_pfn_qglBegin(GL_TRIANGLES);// NOTE: comment to use gl_triangle_fan instead
364           //g_QglTable.m_pfn_qglBegin(GL_TRIANGLE_FAN);
365     g_QglTable.m_pfn_qglColor3fv(colors[0]);
366     g_QglTable.m_pfn_qglVertex3fv(points[0]);
367     g_QglTable.m_pfn_qglVertex3fv(points[2]);
368           g_QglTable.m_pfn_qglVertex3fv(points[3]);
369
370     g_QglTable.m_pfn_qglColor3fv(colors[1]);
371           g_QglTable.m_pfn_qglVertex3fv(points[0]);//
372     g_QglTable.m_pfn_qglVertex3fv(points[3]);//
373           g_QglTable.m_pfn_qglVertex3fv(points[4]);
374
375     g_QglTable.m_pfn_qglColor3fv(colors[2]);
376           g_QglTable.m_pfn_qglVertex3fv(points[0]);//
377     g_QglTable.m_pfn_qglVertex3fv(points[4]);//
378           g_QglTable.m_pfn_qglVertex3fv(points[5]);
379
380     g_QglTable.m_pfn_qglColor3fv(colors[3]);
381           g_QglTable.m_pfn_qglVertex3fv(points[0]);//
382     g_QglTable.m_pfn_qglVertex3fv(points[5]);//
383           g_QglTable.m_pfn_qglVertex3fv(points[2]);
384
385           //g_QglTable.m_pfn_qglEnd();
386           //g_QglTable.m_pfn_qglBegin(GL_TRIANGLE_FAN);
387
388     g_QglTable.m_pfn_qglColor3fv(colors[0]);
389           g_QglTable.m_pfn_qglVertex3fv(points[1]);
390           g_QglTable.m_pfn_qglVertex3fv(points[2]);
391           g_QglTable.m_pfn_qglVertex3fv(points[5]);
392
393     g_QglTable.m_pfn_qglColor3fv(colors[1]);
394           g_QglTable.m_pfn_qglVertex3fv(points[1]);//
395           g_QglTable.m_pfn_qglVertex3fv(points[5]);//
396           g_QglTable.m_pfn_qglVertex3fv(points[4]);
397
398     g_QglTable.m_pfn_qglColor3fv(colors[2]);
399           g_QglTable.m_pfn_qglVertex3fv(points[1]);//
400           g_QglTable.m_pfn_qglVertex3fv(points[4]);//
401           g_QglTable.m_pfn_qglVertex3fv(points[3]);
402
403     g_QglTable.m_pfn_qglColor3fv(colors[3]);
404           g_QglTable.m_pfn_qglVertex3fv(points[1]);//
405           g_QglTable.m_pfn_qglVertex3fv(points[3]);//
406           g_QglTable.m_pfn_qglVertex3fv(points[2]);
407
408     g_QglTable.m_pfn_qglEnd();
409   }
410   else
411   {
412           g_QglTable.m_pfn_qglVertexPointer(3, GL_FLOAT, 0, points);
413           g_QglTable.m_pfn_qglDrawElements(GL_TRIANGLES, 24, GL_UNSIGNED_SHORT, indices);
414   }
415
416
417   // NOTE: prolly not relevant until some time..
418   // check for DOOM lights
419   if (strlen(ValueForKey(e, "light_right")) > 0) {
420     vec3_t vRight, vUp, vTarget, vTemp;
421     GetVectorForKey (e, "light_right", vRight);
422     GetVectorForKey (e, "light_up", vUp);
423     GetVectorForKey (e, "light_target", vTarget);
424
425     g_QglTable.m_pfn_qglColor3f(0, 1, 0);
426                 g_QglTable.m_pfn_qglBegin(GL_LINE_LOOP);
427     VectorAdd(vTarget, e->origin, vTemp);
428     VectorAdd(vTemp, vRight, vTemp);
429     VectorAdd(vTemp, vUp, vTemp);
430     g_QglTable.m_pfn_qglVertex3fv(e->origin);
431     g_QglTable.m_pfn_qglVertex3fv(vTemp);
432     VectorAdd(vTarget, e->origin, vTemp);
433     VectorAdd(vTemp, vUp, vTemp);
434     VectorSubtract(vTemp, vRight, vTemp);
435     g_QglTable.m_pfn_qglVertex3fv(e->origin);
436     g_QglTable.m_pfn_qglVertex3fv(vTemp);
437     VectorAdd(vTarget, e->origin, vTemp);
438     VectorAdd(vTemp, vRight, vTemp);
439     VectorSubtract(vTemp, vUp, vTemp);
440     g_QglTable.m_pfn_qglVertex3fv(e->origin);
441     g_QglTable.m_pfn_qglVertex3fv(vTemp);
442     VectorAdd(vTarget, e->origin, vTemp);
443     VectorSubtract(vTemp, vUp, vTemp);
444     VectorSubtract(vTemp, vRight, vTemp);
445     g_QglTable.m_pfn_qglVertex3fv(e->origin);
446     g_QglTable.m_pfn_qglVertex3fv(vTemp);
447     g_QglTable.m_pfn_qglEnd();
448
449   }
450
451   if(nGLState & DRAW_GL_FILL)
452   {
453     DrawLightSphere(e, nGLState, pref);
454   }
455   else
456   {
457     // Arnout: FIXME: clean this up a bit
458     // now draw lighting radius stuff...
459     if (pref)
460     {
461       bool bDrawSpotlightArc = false;
462       int nPasses = pref == 1 ? 3 : 2;
463
464       const char *target = ValueForKey(e, "target");
465       bool bIsSpotLight = !!target[0];
466
467       /*!\todo Spotlight..
468       if (bIsSpotLight)
469       {
470         // find the origin of the target...
471         entity_t *e = FindEntity("targetname", target);
472
473         if (e)
474           bDrawSpotlightArc = true;
475       }
476       */
477
478       g_QglTable.m_pfn_qglPushAttrib(GL_LINE_BIT);
479       g_QglTable.m_pfn_qglLineStipple(8, 0xAAAA);
480       g_QglTable.m_pfn_qglEnable(GL_LINE_STIPPLE);
481
482       float* envelope = (pref == 1) ? e->fLightEnvelope1 : e->fLightEnvelope2; 
483       for (int iPass = 0; iPass < nPasses; iPass++)
484       {
485         float fRadius = envelope[iPass];
486
487         g_QglTable.m_pfn_qglBegin(GL_LINE_LOOP);
488
489         if (bIsSpotLight)
490         {
491           if (bDrawSpotlightArc)
492           {
493             // I give up on this, it's beyond me
494           }
495         }
496         else
497         {
498           if (fRadius > 0)
499           {
500             int i;
501             float ds, dc;
502
503             for (i = 0; i <= 24; i++)
504             {
505               ds = sin((i * 2 * Q_PI) / 24);
506               dc = cos((i * 2 * Q_PI) / 24);
507
508               switch (nViewType)
509               {
510               case 2:
511                 g_QglTable.m_pfn_qglVertex3f(e->origin[0] + fRadius * dc,
512                   e->origin[1] + fRadius * ds,
513                   e->origin[2]);
514                 break;
515               case 1:
516                 g_QglTable.m_pfn_qglVertex3f(e->origin[0] + fRadius * dc,
517                    e->origin[1],
518                   e->origin[2] + fRadius * ds);
519                 break;
520               case 0:
521                 g_QglTable.m_pfn_qglVertex3f(e->origin[0],
522                   e->origin[1] + fRadius * dc,
523                   e->origin[2] + fRadius * ds);
524                 break;
525               }
526             }
527           }
528         }
529         g_QglTable.m_pfn_qglEnd();
530       }
531       g_QglTable.m_pfn_qglPopAttrib();
532     }
533   }
534 }
535
536