]> icculus.org git repositories - divverent/netradiant.git/blob - plugins/entity/light.cpp
support "target2" etc. name keys for Q3A maps too
[divverent/netradiant.git] / plugins / entity / light.cpp
1 /*
2 Copyright (C) 2001-2006, William Joseph.
3 All Rights Reserved.
4
5 This file is part of GtkRadiant.
6
7 GtkRadiant is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
11
12 GtkRadiant is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 GNU General Public License for more details.
16
17 You should have received a copy of the GNU General Public License
18 along with GtkRadiant; if not, write to the Free Software
19 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20 */
21
22 ///\file
23 ///\brief Represents any light entity (e.g. light).
24 ///
25 /// This entity dislays a special 'light' model.
26 /// The "origin" key directly controls the position of the light model in local space.
27 /// The "_color" key controls the colour of the light model.
28 /// The "light" key is visualised with a sphere representing the approximate coverage of the light (except Doom3).
29 /// Doom3 special behaviour:
30 /// The entity behaves as a group.
31 /// The "origin" key is the translation to be applied to all brushes (not patches) grouped under this entity.
32 /// The "light_center" and "light_radius" keys are visualised with a point and a box when the light is selected.
33 /// The "rotation" key directly controls the orientation of the light bounding box in local space.
34 /// The "light_origin" key controls the position of the light independently of the "origin" key if it is specified.
35 /// The "light_rotation" key duplicates the behaviour of the "rotation" key if it is specified. This appears to be an unfinished feature in Doom3.
36
37 #include "light.h"
38
39 #include <stdlib.h>
40
41 #include "cullable.h"
42 #include "renderable.h"
43 #include "editable.h"
44
45 #include "math/frustum.h"
46 #include "selectionlib.h"
47 #include "instancelib.h"
48 #include "transformlib.h"
49 #include "entitylib.h"
50 #include "render.h"
51 #include "eclasslib.h"
52 #include "render.h"
53 #include "stringio.h"
54 #include "traverselib.h"
55 #include "dragplanes.h"
56
57 #include "targetable.h"
58 #include "origin.h"
59 #include "colour.h"
60 #include "filters.h"
61 #include "namedentity.h"
62 #include "keyobservers.h"
63 #include "namekeys.h"
64 #include "rotation.h"
65
66 #include "entity.h"
67 extern bool g_newLightDraw;
68
69
70 void sphere_draw_fill(const Vector3& origin, float radius, int sides)
71 {
72   if (radius <= 0)
73     return;
74
75   const double dt = c_2pi / static_cast<double>(sides);
76   const double dp = c_pi / static_cast<double>(sides);
77
78   glBegin(GL_TRIANGLES);
79   for (int i = 0; i <= sides - 1; ++i)
80   {
81     for (int j = 0; j <= sides - 2; ++j)
82     {
83       const double t = i * dt;
84       const double p = (j * dp) - (c_pi / 2.0);
85
86       {
87         Vector3 v(vector3_added(origin, vector3_scaled(vector3_for_spherical(t, p), radius)));
88         glVertex3fv(vector3_to_array(v));
89       }
90
91       {
92         Vector3 v(vector3_added(origin, vector3_scaled(vector3_for_spherical(t, p + dp), radius)));
93         glVertex3fv(vector3_to_array(v));
94       }
95
96       {
97         Vector3 v(vector3_added(origin, vector3_scaled(vector3_for_spherical(t + dt, p + dp), radius)));
98         glVertex3fv(vector3_to_array(v));
99       }
100
101       {
102         Vector3 v(vector3_added(origin, vector3_scaled(vector3_for_spherical(t, p), radius)));
103         glVertex3fv(vector3_to_array(v));
104       }
105
106       {
107         Vector3 v(vector3_added(origin, vector3_scaled(vector3_for_spherical(t + dt, p + dp), radius)));
108         glVertex3fv(vector3_to_array(v));
109       }
110
111       {
112         Vector3 v(vector3_added(origin, vector3_scaled(vector3_for_spherical(t + dt, p), radius)));
113         glVertex3fv(vector3_to_array(v));
114       }
115     }
116   }
117
118   {
119     const double p = (sides - 1) * dp - (c_pi / 2.0);
120     for (int i = 0; i <= sides - 1; ++i)
121     {
122       const double t = i * dt;
123
124       {
125         Vector3 v(vector3_added(origin, vector3_scaled(vector3_for_spherical(t, p), radius)));
126         glVertex3fv(vector3_to_array(v));
127       }
128
129       {
130         Vector3 v(vector3_added(origin, vector3_scaled(vector3_for_spherical(t + dt, p + dp), radius)));
131         glVertex3fv(vector3_to_array(v));
132       }
133
134       {
135         Vector3 v(vector3_added(origin, vector3_scaled(vector3_for_spherical(t + dt, p), radius)));
136         glVertex3fv(vector3_to_array(v));
137       }
138     }
139   }
140   glEnd();
141 }
142
143 void sphere_draw_wire(const Vector3& origin, float radius, int sides)
144 {
145   {
146     glBegin(GL_LINE_LOOP);
147
148     for (int i = 0; i <= sides; i++)
149     {
150       double ds = sin((i * 2 * c_pi) / sides);
151       double dc = cos((i * 2 * c_pi) / sides);
152
153       glVertex3f(
154         static_cast<float>(origin[0] + radius * dc),
155         static_cast<float>(origin[1] + radius * ds),
156         origin[2]
157       );
158     }
159
160     glEnd();
161   }
162
163   {
164     glBegin(GL_LINE_LOOP);
165
166     for (int i = 0; i <= sides; i++)
167     {
168       double ds = sin((i * 2 * c_pi) / sides);
169       double dc = cos((i * 2 * c_pi) / sides);
170
171       glVertex3f(
172         static_cast<float>(origin[0] + radius * dc),
173         origin[1],
174         static_cast<float>(origin[2] + radius * ds)
175       );
176     }
177
178     glEnd();
179   }
180
181   {
182     glBegin(GL_LINE_LOOP);
183
184     for (int i = 0; i <= sides; i++)
185     {
186       double ds = sin((i * 2 * c_pi) / sides);
187       double dc = cos((i * 2 * c_pi) / sides);
188
189       glVertex3f(
190         origin[0],
191         static_cast<float>(origin[1] + radius * dc),
192         static_cast<float>(origin[2] + radius * ds)
193       );
194     }
195
196     glEnd();
197   }
198 }
199
200 void light_draw_box_lines(const Vector3& origin, const Vector3 points[8])
201 {
202   //draw lines from the center of the bbox to the corners
203   glBegin(GL_LINES);
204
205   glVertex3fv(vector3_to_array(origin));
206   glVertex3fv(vector3_to_array(points[1]));
207
208   glVertex3fv(vector3_to_array(origin));
209   glVertex3fv(vector3_to_array(points[5]));
210
211   glVertex3fv(vector3_to_array(origin));
212   glVertex3fv(vector3_to_array(points[2]));
213
214   glVertex3fv(vector3_to_array(origin));
215   glVertex3fv(vector3_to_array(points[6]));
216
217   glVertex3fv(vector3_to_array(origin));
218   glVertex3fv(vector3_to_array(points[0]));
219
220   glVertex3fv(vector3_to_array(origin));
221   glVertex3fv(vector3_to_array(points[4]));
222
223   glVertex3fv(vector3_to_array(origin));
224   glVertex3fv(vector3_to_array(points[3]));
225
226   glVertex3fv(vector3_to_array(origin));
227   glVertex3fv(vector3_to_array(points[7]));
228
229   glEnd();
230 }
231
232 void light_draw_radius_wire(const Vector3& origin, const float envelope[3])
233 {
234   if(envelope[0] > 0)
235     sphere_draw_wire(origin, envelope[0], 24);
236   if(envelope[1] > 0)
237     sphere_draw_wire(origin, envelope[1], 24);
238   if(envelope[2] > 0)
239     sphere_draw_wire(origin, envelope[2], 24);
240 }
241
242 void light_draw_radius_fill(const Vector3& origin, const float envelope[3])
243 {
244   if(envelope[0] > 0)
245     sphere_draw_fill(origin, envelope[0], 16);
246   if(envelope[1] > 0)
247     sphere_draw_fill(origin, envelope[1], 16);
248   if(envelope[2] > 0)
249     sphere_draw_fill(origin, envelope[2], 16);
250 }
251
252 void light_vertices(const AABB& aabb_light, Vector3 points[6])
253 {
254   Vector3 max(vector3_added(aabb_light.origin, aabb_light.extents));
255   Vector3 min(vector3_subtracted(aabb_light.origin, aabb_light.extents));
256   Vector3 mid(aabb_light.origin);
257
258   // top, bottom, tleft, tright, bright, bleft
259   points[0] = Vector3(mid[0], mid[1], max[2]);
260   points[1] = Vector3(mid[0], mid[1], min[2]);
261   points[2] = Vector3(min[0], max[1], mid[2]);
262   points[3] = Vector3(max[0], max[1], mid[2]);
263   points[4] = Vector3(max[0], min[1], mid[2]);
264   points[5] = Vector3(min[0], min[1], mid[2]);
265 }
266
267 void light_draw(const AABB& aabb_light, RenderStateFlags state)
268 {
269   Vector3 points[6];
270   light_vertices(aabb_light, points);
271
272   if(state & RENDER_LIGHTING)
273   {
274     const float f = 0.70710678f;
275     // North, East, South, West
276     const Vector3 normals[8] = {
277       Vector3( 0, f, f ),
278       Vector3( f, 0, f ),
279       Vector3( 0,-f, f ),
280       Vector3(-f, 0, f ),
281       Vector3( 0, f,-f ),
282       Vector3( f, 0,-f ),
283       Vector3( 0,-f,-f ),
284       Vector3(-f, 0,-f ),
285     };
286
287 #if !defined(USE_TRIANGLE_FAN)
288     glBegin(GL_TRIANGLES);
289 #else
290     glBegin(GL_TRIANGLE_FAN);
291 #endif
292     glVertex3fv(vector3_to_array(points[0]));
293     glVertex3fv(vector3_to_array(points[2]));
294     glNormal3fv(vector3_to_array(normals[0]));
295     glVertex3fv(vector3_to_array(points[3]));
296
297 #if !defined(USE_TRIANGLE_FAN)
298     glVertex3fv(vector3_to_array(points[0]));
299     glVertex3fv(vector3_to_array(points[3]));
300 #endif
301     glNormal3fv(vector3_to_array(normals[1]));
302     glVertex3fv(vector3_to_array(points[4]));
303
304 #if !defined(USE_TRIANGLE_FAN)
305     glVertex3fv(vector3_to_array(points[0]));
306     glVertex3fv(vector3_to_array(points[4]));
307 #endif
308     glNormal3fv(vector3_to_array(normals[2]));
309     glVertex3fv(vector3_to_array(points[5]));
310 #if !defined(USE_TRIANGLE_FAN)
311     glVertex3fv(vector3_to_array(points[0]));
312     glVertex3fv(vector3_to_array(points[5]));
313 #endif
314     glNormal3fv(vector3_to_array(normals[3]));
315     glVertex3fv(vector3_to_array(points[2]));
316 #if defined(USE_TRIANGLE_FAN)
317     glEnd();
318     glBegin(GL_TRIANGLE_FAN);
319 #endif
320
321     glVertex3fv(vector3_to_array(points[1]));
322     glVertex3fv(vector3_to_array(points[2]));
323     glNormal3fv(vector3_to_array(normals[7]));
324     glVertex3fv(vector3_to_array(points[5]));
325
326 #if !defined(USE_TRIANGLE_FAN)
327     glVertex3fv(vector3_to_array(points[1]));
328     glVertex3fv(vector3_to_array(points[5]));
329 #endif
330     glNormal3fv(vector3_to_array(normals[6]));
331     glVertex3fv(vector3_to_array(points[4]));
332
333 #if !defined(USE_TRIANGLE_FAN)
334     glVertex3fv(vector3_to_array(points[1]));
335     glVertex3fv(vector3_to_array(points[4]));
336 #endif
337     glNormal3fv(vector3_to_array(normals[5]));
338     glVertex3fv(vector3_to_array(points[3]));
339
340 #if !defined(USE_TRIANGLE_FAN)
341     glVertex3fv(vector3_to_array(points[1]));
342     glVertex3fv(vector3_to_array(points[3]));
343 #endif
344     glNormal3fv(vector3_to_array(normals[4]));
345     glVertex3fv(vector3_to_array(points[2]));
346
347     glEnd();
348   }
349   else
350   {
351     typedef unsigned int index_t;
352     const index_t indices[24] = {
353       0, 2, 3,
354       0, 3, 4,
355       0, 4, 5,
356       0, 5, 2,
357       1, 2, 5,
358       1, 5, 4,
359       1, 4, 3,
360       1, 3, 2
361     };
362 #if 1
363     glVertexPointer(3, GL_FLOAT, 0, points);
364     glDrawElements(GL_TRIANGLES, sizeof(indices)/sizeof(index_t), RenderIndexTypeID, indices);
365 #else
366     glBegin(GL_TRIANGLES);
367     for(unsigned int i = 0; i < sizeof(indices)/sizeof(index_t); ++i)
368     {
369       glVertex3fv(points[indices[i]]);
370     }
371     glEnd();
372 #endif
373   }
374
375
376   // NOTE: prolly not relevant until some time..
377   // check for DOOM lights
378 #if 0
379   if (strlen(ValueForKey(e, "light_right")) > 0) {
380     vec3_t vRight, vUp, vTarget, vTemp;
381     GetVectorForKey (e, "light_right", vRight);
382     GetVectorForKey (e, "light_up", vUp);
383     GetVectorForKey (e, "light_target", vTarget);
384
385     glColor3f(0, 1, 0);
386     glBegin(GL_LINE_LOOP);
387     VectorAdd(vTarget, e->origin, vTemp);
388     VectorAdd(vTemp, vRight, vTemp);
389     VectorAdd(vTemp, vUp, vTemp);
390     glVertex3fv(e->origin);
391     glVertex3fv(vTemp);
392     VectorAdd(vTarget, e->origin, vTemp);
393     VectorAdd(vTemp, vUp, vTemp);
394     VectorSubtract(vTemp, vRight, vTemp);
395     glVertex3fv(e->origin);
396     glVertex3fv(vTemp);
397     VectorAdd(vTarget, e->origin, vTemp);
398     VectorAdd(vTemp, vRight, vTemp);
399     VectorSubtract(vTemp, vUp, vTemp);
400     glVertex3fv(e->origin);
401     glVertex3fv(vTemp);
402     VectorAdd(vTarget, e->origin, vTemp);
403     VectorSubtract(vTemp, vUp, vTemp);
404     VectorSubtract(vTemp, vRight, vTemp);
405     glVertex3fv(e->origin);
406     glVertex3fv(vTemp);
407     glEnd();
408
409   }
410 #endif
411 }
412
413 // These variables are tweakable on the q3map2 console, setting to q3map2
414 // default here as there is no way to find out what the user actually uses
415 // right now. Maybe move them to worldspawn?
416 float fPointScale = 7500.f;
417 float fLinearScale = 1.f / 8000.f;
418
419 float light_radius_linear(float fIntensity, float fFalloffTolerance)
420 {
421   return ((fIntensity * fPointScale * fLinearScale) - fFalloffTolerance);
422 }
423
424 float light_radius(float fIntensity, float fFalloffTolerance)
425 {
426   return sqrt(fIntensity * fPointScale / fFalloffTolerance);
427 }
428
429
430 LightType g_lightType = LIGHTTYPE_DEFAULT;
431
432
433 bool spawnflags_linear(int flags)
434 {
435   if( g_lightType == LIGHTTYPE_RTCW )
436   {
437     // Spawnflags :
438     // 1: nonlinear
439     // 2: angle
440
441     return !(flags & 1);
442   }
443   else
444   {
445     // Spawnflags :
446     // 1: linear
447     // 2: no angle
448
449     return (flags & 1);
450   }
451 }
452
453 class LightRadii
454 {
455 public:
456   float m_radii[3];
457
458 private:
459   float m_primaryIntensity;
460   float m_secondaryIntensity;
461   int m_flags;
462   float m_fade;
463   float m_scale;
464
465   void calculateRadii()
466   {
467     float intensity = 300.0f;
468
469     if(m_primaryIntensity != 0.0f)
470     {
471       intensity = m_primaryIntensity;
472     }
473     else if(m_secondaryIntensity != 0.0f)
474     {
475       intensity = m_secondaryIntensity;
476     }
477
478     intensity *= m_scale;
479
480     if(spawnflags_linear(m_flags))
481     {
482       m_radii[0] = light_radius_linear(intensity, 1.0f) / m_fade;
483       m_radii[1] = light_radius_linear(intensity, 48.0f) / m_fade;
484       m_radii[2] = light_radius_linear(intensity, 255.0f) / m_fade;
485     }
486     else
487     {
488       m_radii[0] = light_radius(intensity, 1.0f);
489       m_radii[1] = light_radius(intensity, 48.0f);
490       m_radii[2] = light_radius(intensity, 255.0f);
491     }
492   }
493
494 public:
495   LightRadii() : m_primaryIntensity(0), m_secondaryIntensity(0), m_flags(0), m_fade(1), m_scale(1)
496   {
497   }
498
499  
500   void primaryIntensityChanged(const char* value)
501   {
502     m_primaryIntensity = string_read_float(value);
503     calculateRadii();
504   }
505   typedef MemberCaller1<LightRadii, const char*, &LightRadii::primaryIntensityChanged> PrimaryIntensityChangedCaller;
506   void secondaryIntensityChanged(const char* value)
507   {
508     m_secondaryIntensity = string_read_float(value);
509     calculateRadii();
510   }
511   typedef MemberCaller1<LightRadii, const char*, &LightRadii::secondaryIntensityChanged> SecondaryIntensityChangedCaller;
512   void scaleChanged(const char* value)
513   {
514     m_scale = string_read_float(value);
515     if(m_scale <= 0.0f)
516     {
517       m_scale = 1.0f;
518     }
519     calculateRadii();
520   }
521   typedef MemberCaller1<LightRadii, const char*, &LightRadii::scaleChanged> ScaleChangedCaller;
522   void fadeChanged(const char* value)
523   {
524     m_fade = string_read_float(value);
525     if(m_fade <= 0.0f)
526     {
527       m_fade = 1.0f;
528     }
529     calculateRadii();
530   }
531   typedef MemberCaller1<LightRadii, const char*, &LightRadii::fadeChanged> FadeChangedCaller;
532   void flagsChanged(const char* value)
533   {
534     m_flags = string_read_int(value);
535     calculateRadii();
536   }
537   typedef MemberCaller1<LightRadii, const char*, &LightRadii::flagsChanged> FlagsChangedCaller;
538 };
539
540 class Doom3LightRadius
541 {
542 public:
543   Vector3 m_defaultRadius;
544   Vector3 m_radius;
545   Vector3 m_radiusTransformed;
546   Vector3 m_center;
547   Callback m_changed;
548   bool m_useCenterKey;
549
550   Doom3LightRadius(const char* defaultRadius) : m_defaultRadius(300, 300, 300), m_center(0, 0, 0), m_useCenterKey(false)
551   {
552     if(!string_parse_vector3(defaultRadius, m_defaultRadius))
553     {
554       globalErrorStream() << "Doom3LightRadius: failed to parse default light radius\n";
555     }
556     m_radius = m_defaultRadius;
557   }
558
559   void lightRadiusChanged(const char* value)
560   {
561     if(!string_parse_vector3(value, m_radius))
562     {
563       m_radius = m_defaultRadius;
564     }
565     m_radiusTransformed = m_radius;
566     m_changed();
567     SceneChangeNotify();
568   }
569   typedef MemberCaller1<Doom3LightRadius, const char*, &Doom3LightRadius::lightRadiusChanged> LightRadiusChangedCaller;
570
571   void lightCenterChanged(const char* value)
572   {
573     m_useCenterKey = string_parse_vector3(value, m_center);
574     if(!m_useCenterKey)
575     {
576       m_center = Vector3(0, 0, 0);
577     }
578     SceneChangeNotify();
579   }
580   typedef MemberCaller1<Doom3LightRadius, const char*, &Doom3LightRadius::lightCenterChanged> LightCenterChangedCaller;
581 };
582
583 class RenderLightRadiiWire : public OpenGLRenderable
584 {
585   LightRadii& m_radii;
586   const Vector3& m_origin;
587 public:
588   RenderLightRadiiWire(LightRadii& radii, const Vector3& origin) : m_radii(radii), m_origin(origin)
589   {
590   }
591   void render(RenderStateFlags state) const
592   {
593     light_draw_radius_wire(m_origin, m_radii.m_radii);
594   }
595 };
596
597 class RenderLightRadiiFill : public OpenGLRenderable
598 {
599   LightRadii& m_radii;
600   const Vector3& m_origin;
601 public:
602   static Shader* m_state;
603
604   RenderLightRadiiFill(LightRadii& radii, const Vector3& origin) : m_radii(radii), m_origin(origin)
605   {
606   }
607   void render(RenderStateFlags state) const
608   {
609     light_draw_radius_fill(m_origin, m_radii.m_radii);
610   }
611 };
612
613 class RenderLightRadiiBox : public OpenGLRenderable
614 {
615   const Vector3& m_origin;
616 public:
617   mutable Vector3 m_points[8];
618   static Shader* m_state;
619
620   RenderLightRadiiBox(const Vector3& origin) : m_origin(origin)
621   {
622   }
623   void render(RenderStateFlags state) const
624   {
625     //draw the bounding box of light based on light_radius key
626     if((state & RENDER_FILL) != 0)
627     {
628       aabb_draw_flatshade(m_points);
629     }
630     else
631     {
632       aabb_draw_wire(m_points);
633     }
634
635   #if 1    //disable if you dont want lines going from the center of the light bbox to the corners
636     light_draw_box_lines(m_origin, m_points);
637   #endif
638   }
639 };
640
641 Shader* RenderLightRadiiFill::m_state = 0;
642
643 class RenderLightCenter : public OpenGLRenderable
644 {
645   const Vector3& m_center;
646   EntityClass& m_eclass;
647 public:
648   static Shader* m_state;
649
650   RenderLightCenter(const Vector3& center, EntityClass& eclass) : m_center(center), m_eclass(eclass)
651   {
652   }
653   void render(RenderStateFlags state) const
654   {
655     glBegin(GL_POINTS);
656     glColor3fv(vector3_to_array(m_eclass.color));
657     glVertex3fv(vector3_to_array(m_center));
658     glEnd();
659   }
660 };
661
662 Shader* RenderLightCenter::m_state = 0;
663
664 class RenderLightProjection : public OpenGLRenderable
665 {
666   const Matrix4& m_projection;
667 public:
668
669   RenderLightProjection(const Matrix4& projection) : m_projection(projection)
670   {
671   }
672   void render(RenderStateFlags state) const
673   {
674     Matrix4 unproject(matrix4_full_inverse(m_projection));
675     Vector3 points[8];
676     aabb_corners(AABB(Vector3(0.5f, 0.5f, 0.5f), Vector3(0.5f, 0.5f, 0.5f)), points);
677     points[0] = vector4_projected(matrix4_transformed_vector4(unproject, Vector4(points[0], 1)));
678     points[1] = vector4_projected(matrix4_transformed_vector4(unproject, Vector4(points[1], 1)));
679     points[2] = vector4_projected(matrix4_transformed_vector4(unproject, Vector4(points[2], 1)));
680     points[3] = vector4_projected(matrix4_transformed_vector4(unproject, Vector4(points[3], 1)));
681     points[4] = vector4_projected(matrix4_transformed_vector4(unproject, Vector4(points[4], 1)));
682     points[5] = vector4_projected(matrix4_transformed_vector4(unproject, Vector4(points[5], 1)));
683     points[6] = vector4_projected(matrix4_transformed_vector4(unproject, Vector4(points[6], 1)));
684     points[7] = vector4_projected(matrix4_transformed_vector4(unproject, Vector4(points[7], 1)));
685     Vector4 test1 = matrix4_transformed_vector4(unproject, Vector4(0.5f, 0.5f, 0.5f, 1));
686     Vector3 test2 = vector4_projected(test1);
687     aabb_draw_wire(points);
688   }
689 };
690
691 inline void default_extents(Vector3& extents)
692 {
693   extents = Vector3(8, 8, 8);
694 }
695
696 class ShaderRef
697 {
698   CopiedString m_name;
699   Shader* m_shader;
700   void capture()
701   {
702     m_shader = GlobalShaderCache().capture(m_name.c_str());
703   }
704   void release()
705   {
706     GlobalShaderCache().release(m_name.c_str());
707   }
708 public:
709   ShaderRef()
710   {
711     capture();
712   }
713   ~ShaderRef()
714   {
715     release();
716   }
717   void setName(const char* name)
718   {
719     release();
720     m_name = name;
721     capture();
722   }
723   Shader* get() const
724   {
725     return m_shader;
726   }
727 };
728
729 class LightShader
730 {
731   ShaderRef m_shader;
732   void setDefault()
733   {
734     m_shader.setName(m_defaultShader);
735   }
736 public:
737   static const char* m_defaultShader;
738
739   LightShader()
740   {
741     setDefault();
742   }
743   void valueChanged(const char* value)
744   {
745     if(string_empty(value))
746     {
747       setDefault();
748     }
749     else
750     {
751       m_shader.setName(value);
752     }
753     SceneChangeNotify();
754   }
755   typedef MemberCaller1<LightShader, const char*, &LightShader::valueChanged> ValueChangedCaller;
756
757   Shader* get() const
758   {
759     return m_shader.get();
760   }
761 };
762
763 const char* LightShader::m_defaultShader = "";
764
765 inline const BasicVector4<double>& plane3_to_vector4(const Plane3& self)
766 {
767   return reinterpret_cast<const BasicVector4<double>&>(self);
768 }
769
770 inline BasicVector4<double>& plane3_to_vector4(Plane3& self)
771 {
772   return reinterpret_cast<BasicVector4<double>&>(self);
773 }
774
775 inline Matrix4 matrix4_from_planes(const Plane3& left, const Plane3& right, const Plane3& bottom, const Plane3& top, const Plane3& front, const Plane3& back)
776 {
777   return Matrix4(
778     (right.a - left.a) / 2,
779     (top.a - bottom.a) / 2,
780     (back.a - front.a) / 2,
781     right.a - (right.a - left.a) / 2,
782     (right.b - left.b) / 2,
783     (top.b - bottom.b) / 2,
784     (back.b - front.b) / 2,
785     right.b - (right.b - left.b) / 2,
786     (right.c - left.c) / 2,
787     (top.c - bottom.c) / 2,
788     (back.c - front.c) / 2,
789     right.c - (right.c - left.c) / 2,
790     (right.d - left.d) / 2,
791     (top.d - bottom.d) / 2,
792     (back.d - front.d) / 2,
793     right.d - (right.d - left.d) / 2
794   );
795 }
796
797 class Light :
798   public OpenGLRenderable,
799   public Cullable,
800   public Bounded,
801   public Editable,
802   public Snappable
803 {
804   EntityKeyValues m_entity;
805   KeyObserverMap m_keyObservers;
806   TraversableNodeSet m_traverse;
807   IdentityTransform m_transform;
808
809   OriginKey m_originKey;
810   RotationKey m_rotationKey;
811   Float9 m_rotation;
812   Colour m_colour;
813
814   ClassnameFilter m_filter;
815   NamedEntity m_named;
816   NameKeys m_nameKeys;
817   TraversableObserverPairRelay m_traverseObservers;
818   Doom3GroupOrigin m_funcStaticOrigin;
819
820   LightRadii m_radii;
821   Doom3LightRadius m_doom3Radius;
822
823   RenderLightRadiiWire m_radii_wire;
824   RenderLightRadiiFill m_radii_fill;
825   RenderLightRadiiBox m_radii_box;
826   RenderLightCenter m_render_center;
827   RenderableNamedEntity m_renderName;
828
829   Vector3 m_lightOrigin;
830   bool m_useLightOrigin;
831   Float9 m_lightRotation;
832   bool m_useLightRotation;
833
834   Vector3 m_lightTarget;
835   bool m_useLightTarget;
836   Vector3 m_lightUp;
837   bool m_useLightUp;
838   Vector3 m_lightRight;
839   bool m_useLightRight;
840   Vector3 m_lightStart;
841   bool m_useLightStart;
842   Vector3 m_lightEnd;
843   bool m_useLightEnd;
844
845   mutable AABB m_doom3AABB;
846   mutable Matrix4 m_doom3Rotation;
847   mutable Matrix4 m_doom3Projection;
848   mutable Frustum m_doom3Frustum;
849   mutable bool m_doom3ProjectionChanged;
850
851   RenderLightProjection m_renderProjection;
852
853   LightShader m_shader;
854
855   AABB m_aabb_light;
856
857   Callback m_transformChanged;
858   Callback m_boundsChanged;
859   Callback m_evaluateTransform;
860
861   void construct()
862   {
863     default_rotation(m_rotation);
864     m_aabb_light.origin = Vector3(0, 0, 0);
865     default_extents(m_aabb_light.extents);
866
867     m_keyObservers.insert("classname", ClassnameFilter::ClassnameChangedCaller(m_filter));
868     m_keyObservers.insert(Static<KeyIsName>::instance().m_nameKey, NamedEntity::IdentifierChangedCaller(m_named));
869     m_keyObservers.insert("_color", Colour::ColourChangedCaller(m_colour));
870     m_keyObservers.insert("origin", OriginKey::OriginChangedCaller(m_originKey));
871     m_keyObservers.insert("_light", LightRadii::PrimaryIntensityChangedCaller(m_radii));
872     m_keyObservers.insert("light", LightRadii::SecondaryIntensityChangedCaller(m_radii));
873     m_keyObservers.insert("fade", LightRadii::FadeChangedCaller(m_radii));
874     m_keyObservers.insert("scale", LightRadii::ScaleChangedCaller(m_radii));
875     m_keyObservers.insert("spawnflags", LightRadii::FlagsChangedCaller(m_radii));
876
877     if(g_lightType == LIGHTTYPE_DOOM3)
878     {
879       m_keyObservers.insert("angle", RotationKey::AngleChangedCaller(m_rotationKey));
880       m_keyObservers.insert("rotation", RotationKey::RotationChangedCaller(m_rotationKey));
881       m_keyObservers.insert("light_radius", Doom3LightRadius::LightRadiusChangedCaller(m_doom3Radius));
882       m_keyObservers.insert("light_center", Doom3LightRadius::LightCenterChangedCaller(m_doom3Radius));
883       m_keyObservers.insert("light_origin", Light::LightOriginChangedCaller(*this));
884       m_keyObservers.insert("light_rotation", Light::LightRotationChangedCaller(*this));
885       m_keyObservers.insert("light_target", Light::LightTargetChangedCaller(*this));
886       m_keyObservers.insert("light_up", Light::LightUpChangedCaller(*this));
887       m_keyObservers.insert("light_right", Light::LightRightChangedCaller(*this));
888       m_keyObservers.insert("light_start", Light::LightStartChangedCaller(*this));
889       m_keyObservers.insert("light_end", Light::LightEndChangedCaller(*this));
890       m_keyObservers.insert("texture", LightShader::ValueChangedCaller(m_shader));
891       m_useLightTarget = m_useLightUp = m_useLightRight = m_useLightStart = m_useLightEnd = false;
892       m_doom3ProjectionChanged = true;
893     }
894
895     if(g_lightType == LIGHTTYPE_DOOM3)
896     {
897       m_traverse.attach(&m_traverseObservers);
898       m_traverseObservers.attach(m_funcStaticOrigin);
899
900       m_entity.m_isContainer = true;
901     }
902   }
903   void destroy()
904   {
905     if(g_lightType == LIGHTTYPE_DOOM3)
906     {
907       m_traverseObservers.detach(m_funcStaticOrigin);
908       m_traverse.detach(&m_traverseObservers);
909     }
910   }
911   
912 // vc 2k5 compiler fix
913 #if _MSC_VER >= 1400
914         public:
915 #endif
916
917   void updateOrigin()
918   {
919     m_boundsChanged();
920
921     if(g_lightType == LIGHTTYPE_DOOM3)
922     {
923       m_funcStaticOrigin.originChanged();
924     }
925
926     m_doom3Radius.m_changed();
927
928     GlobalSelectionSystem().pivotChanged();
929   }
930
931   void originChanged()
932   {
933     m_aabb_light.origin = m_useLightOrigin ? m_lightOrigin : m_originKey.m_origin;
934     updateOrigin();
935   }
936   typedef MemberCaller<Light, &Light::originChanged> OriginChangedCaller;
937
938   void lightOriginChanged(const char* value)
939   {
940     m_useLightOrigin = !string_empty(value);
941     if(m_useLightOrigin)
942     {
943       read_origin(m_lightOrigin, value);
944     }
945     originChanged();
946   }
947   typedef MemberCaller1<Light, const char*, &Light::lightOriginChanged> LightOriginChangedCaller;
948
949   void lightTargetChanged(const char* value)
950   {
951     m_useLightTarget = !string_empty(value);
952     if(m_useLightTarget)
953     {
954       read_origin(m_lightTarget, value);
955     }
956     projectionChanged();
957   }
958   typedef MemberCaller1<Light, const char*, &Light::lightTargetChanged> LightTargetChangedCaller;
959   void lightUpChanged(const char* value)
960   {
961     m_useLightUp = !string_empty(value);
962     if(m_useLightUp)
963     {
964       read_origin(m_lightUp, value);
965     }
966     projectionChanged();
967   }
968   typedef MemberCaller1<Light, const char*, &Light::lightUpChanged> LightUpChangedCaller;
969   void lightRightChanged(const char* value)
970   {
971     m_useLightRight = !string_empty(value);
972     if(m_useLightRight)
973     {
974       read_origin(m_lightRight, value);
975     }
976     projectionChanged();
977   }
978   typedef MemberCaller1<Light, const char*, &Light::lightRightChanged> LightRightChangedCaller;
979   void lightStartChanged(const char* value)
980   {
981     m_useLightStart = !string_empty(value);
982     if(m_useLightStart)
983     {
984       read_origin(m_lightStart, value);
985     }
986     projectionChanged();
987   }
988   typedef MemberCaller1<Light, const char*, &Light::lightStartChanged> LightStartChangedCaller;
989   void lightEndChanged(const char* value)
990   {
991     m_useLightEnd = !string_empty(value);
992     if(m_useLightEnd)
993     {
994       read_origin(m_lightEnd, value);
995     }
996     projectionChanged();
997   }
998   typedef MemberCaller1<Light, const char*, &Light::lightEndChanged> LightEndChangedCaller;
999
1000   void writeLightOrigin()
1001   {
1002     write_origin(m_lightOrigin, &m_entity, "light_origin");
1003   }
1004
1005   void updateLightRadiiBox() const
1006   {
1007     const Matrix4& rotation = rotation_toMatrix(m_rotation);
1008     aabb_corners(AABB(Vector3(0, 0, 0), m_doom3Radius.m_radiusTransformed), m_radii_box.m_points);
1009     matrix4_transform_point(rotation, m_radii_box.m_points[0]);
1010     vector3_add(m_radii_box.m_points[0], m_aabb_light.origin);
1011     matrix4_transform_point(rotation, m_radii_box.m_points[1]);
1012     vector3_add(m_radii_box.m_points[1], m_aabb_light.origin);
1013     matrix4_transform_point(rotation, m_radii_box.m_points[2]);
1014     vector3_add(m_radii_box.m_points[2], m_aabb_light.origin);
1015     matrix4_transform_point(rotation, m_radii_box.m_points[3]);
1016     vector3_add(m_radii_box.m_points[3], m_aabb_light.origin);
1017     matrix4_transform_point(rotation, m_radii_box.m_points[4]);
1018     vector3_add(m_radii_box.m_points[4], m_aabb_light.origin);
1019     matrix4_transform_point(rotation, m_radii_box.m_points[5]);
1020     vector3_add(m_radii_box.m_points[5], m_aabb_light.origin);
1021     matrix4_transform_point(rotation, m_radii_box.m_points[6]);
1022     vector3_add(m_radii_box.m_points[6], m_aabb_light.origin);
1023     matrix4_transform_point(rotation, m_radii_box.m_points[7]);
1024     vector3_add(m_radii_box.m_points[7], m_aabb_light.origin);
1025   }
1026
1027   void rotationChanged()
1028   {
1029     rotation_assign(m_rotation, m_useLightRotation ? m_lightRotation : m_rotationKey.m_rotation);
1030     GlobalSelectionSystem().pivotChanged();
1031   }
1032   typedef MemberCaller<Light, &Light::rotationChanged> RotationChangedCaller;
1033
1034   void lightRotationChanged(const char* value)
1035   {
1036     m_useLightRotation = !string_empty(value);
1037     if(m_useLightRotation)
1038     {
1039       read_rotation(m_lightRotation, value);
1040     }
1041     rotationChanged();
1042   }
1043   typedef MemberCaller1<Light, const char*, &Light::lightRotationChanged> LightRotationChangedCaller;
1044
1045 public:
1046
1047   Light(EntityClass* eclass, scene::Node& node, const Callback& transformChanged, const Callback& boundsChanged, const Callback& evaluateTransform) :
1048     m_entity(eclass),
1049     m_originKey(OriginChangedCaller(*this)),
1050     m_rotationKey(RotationChangedCaller(*this)),
1051     m_colour(Callback()),
1052     m_filter(m_entity, node),
1053     m_named(m_entity),
1054     m_nameKeys(m_entity),
1055     m_funcStaticOrigin(m_traverse, m_originKey.m_origin),
1056     m_doom3Radius(EntityClass_valueForKey(m_entity.getEntityClass(), "light_radius")),
1057     m_radii_wire(m_radii, m_aabb_light.origin),
1058     m_radii_fill(m_radii, m_aabb_light.origin),
1059     m_radii_box(m_aabb_light.origin),
1060     m_render_center(m_doom3Radius.m_center, m_entity.getEntityClass()), 
1061     m_renderName(m_named, m_aabb_light.origin),
1062     m_useLightOrigin(false),
1063     m_useLightRotation(false),
1064     m_renderProjection(m_doom3Projection),
1065     m_transformChanged(transformChanged),
1066     m_boundsChanged(boundsChanged),
1067     m_evaluateTransform(evaluateTransform)
1068   {
1069     construct();
1070   }
1071   Light(const Light& other, scene::Node& node, const Callback& transformChanged, const Callback& boundsChanged, const Callback& evaluateTransform) :
1072     m_entity(other.m_entity),
1073     m_originKey(OriginChangedCaller(*this)),
1074     m_rotationKey(RotationChangedCaller(*this)),
1075     m_colour(Callback()),
1076     m_filter(m_entity, node),
1077     m_named(m_entity),
1078     m_nameKeys(m_entity),
1079     m_funcStaticOrigin(m_traverse, m_originKey.m_origin),
1080     m_doom3Radius(EntityClass_valueForKey(m_entity.getEntityClass(), "light_radius")),
1081     m_radii_wire(m_radii, m_aabb_light.origin),
1082     m_radii_fill(m_radii, m_aabb_light.origin),
1083     m_radii_box(m_aabb_light.origin),
1084     m_render_center(m_doom3Radius.m_center, m_entity.getEntityClass()), 
1085     m_renderName(m_named, m_aabb_light.origin),
1086     m_useLightOrigin(false),
1087     m_useLightRotation(false),
1088     m_renderProjection(m_doom3Projection),
1089     m_transformChanged(transformChanged),
1090     m_boundsChanged(boundsChanged),
1091     m_evaluateTransform(evaluateTransform)
1092   {
1093     construct();
1094   }
1095   ~Light()
1096   {
1097     destroy();
1098   }
1099
1100   InstanceCounter m_instanceCounter;
1101   void instanceAttach(const scene::Path& path)
1102   {
1103     if(++m_instanceCounter.m_count == 1)
1104     {
1105       m_filter.instanceAttach();
1106       m_entity.instanceAttach(path_find_mapfile(path.begin(), path.end()));
1107       if(g_lightType == LIGHTTYPE_DOOM3)
1108       {
1109         m_traverse.instanceAttach(path_find_mapfile(path.begin(), path.end()));
1110       }
1111       m_entity.attach(m_keyObservers);
1112
1113       if(g_lightType == LIGHTTYPE_DOOM3)
1114       {
1115         m_funcStaticOrigin.enable();
1116       }
1117     }
1118   }
1119   void instanceDetach(const scene::Path& path)
1120   {
1121     if(--m_instanceCounter.m_count == 0)
1122     {
1123       if(g_lightType == LIGHTTYPE_DOOM3)
1124       {
1125         m_funcStaticOrigin.disable();
1126       }
1127
1128       m_entity.detach(m_keyObservers);
1129       if(g_lightType == LIGHTTYPE_DOOM3)
1130       {
1131         m_traverse.instanceDetach(path_find_mapfile(path.begin(), path.end()));
1132       }
1133       m_entity.instanceDetach(path_find_mapfile(path.begin(), path.end()));
1134       m_filter.instanceDetach();
1135     }
1136   }
1137
1138   EntityKeyValues& getEntity()
1139   {
1140     return m_entity;
1141   }
1142   const EntityKeyValues& getEntity() const
1143   {
1144     return m_entity;
1145   }
1146
1147   scene::Traversable& getTraversable()
1148   {
1149     return m_traverse;
1150   }
1151   Namespaced& getNamespaced()
1152   {
1153     return m_nameKeys;
1154   }
1155   Nameable& getNameable()
1156   {
1157     return m_named;
1158   }
1159   TransformNode& getTransformNode()
1160   {
1161     return m_transform;
1162   }
1163
1164   void attach(scene::Traversable::Observer* observer)
1165   {
1166     m_traverseObservers.attach(*observer);
1167   }
1168   void detach(scene::Traversable::Observer* observer)
1169   {
1170     m_traverseObservers.detach(*observer);
1171   }
1172
1173   void render(RenderStateFlags state) const
1174   {
1175     if(!g_newLightDraw)
1176     {
1177       aabb_draw(m_aabb_light, state);
1178     }
1179     else
1180     {
1181       light_draw(m_aabb_light, state);
1182     }
1183   }
1184
1185   VolumeIntersectionValue intersectVolume(const VolumeTest& volume, const Matrix4& localToWorld) const
1186   {
1187     return volume.TestAABB(m_aabb_light, localToWorld);
1188   }
1189
1190   // cache
1191   const AABB& localAABB() const
1192   {
1193     return m_aabb_light;
1194   }
1195
1196
1197   mutable Matrix4 m_projectionOrientation;
1198
1199   void renderSolid(Renderer& renderer, const VolumeTest& volume, const Matrix4& localToWorld, bool selected) const
1200   {
1201     renderer.SetState(m_entity.getEntityClass().m_state_wire, Renderer::eWireframeOnly);
1202     renderer.SetState(m_colour.state(), Renderer::eFullMaterials);
1203     renderer.addRenderable(*this, localToWorld);
1204
1205     if(selected && g_lightRadii && string_empty(m_entity.getKeyValue("target")))
1206     {
1207       if(renderer.getStyle() == Renderer::eFullMaterials)
1208       {
1209         renderer.SetState(RenderLightRadiiFill::m_state, Renderer::eFullMaterials);
1210         renderer.Highlight(Renderer::ePrimitive, false);
1211         renderer.addRenderable(m_radii_fill, localToWorld);
1212       }
1213       else
1214       {
1215         renderer.addRenderable(m_radii_wire, localToWorld);
1216       }
1217     }
1218
1219     renderer.SetState(m_entity.getEntityClass().m_state_wire, Renderer::eFullMaterials);
1220
1221     if(g_lightType == LIGHTTYPE_DOOM3 && selected)
1222     {
1223       if(isProjected())
1224       {
1225         projection();
1226         m_projectionOrientation = rotation();
1227         vector4_to_vector3(m_projectionOrientation.t()) = localAABB().origin;
1228         renderer.addRenderable(m_renderProjection, m_projectionOrientation);
1229       }
1230       else
1231       {
1232         updateLightRadiiBox();
1233         renderer.addRenderable(m_radii_box, localToWorld);
1234       }
1235
1236       //draw the center of the light
1237       if(m_doom3Radius.m_useCenterKey)
1238       {
1239         renderer.Highlight(Renderer::ePrimitive, false);
1240         renderer.Highlight(Renderer::eFace, false);
1241         renderer.SetState(m_render_center.m_state, Renderer::eFullMaterials);
1242         renderer.SetState(m_render_center.m_state, Renderer::eWireframeOnly);
1243         renderer.addRenderable(m_render_center, localToWorld);
1244       }
1245     }
1246   }
1247   void renderWireframe(Renderer& renderer, const VolumeTest& volume, const Matrix4& localToWorld, bool selected) const
1248   {
1249     renderSolid(renderer, volume, localToWorld, selected);
1250     if(g_showNames)
1251     {
1252       renderer.addRenderable(m_renderName, localToWorld);
1253     }
1254   }
1255
1256   void testSelect(Selector& selector, SelectionTest& test, const Matrix4& localToWorld)
1257   {
1258     test.BeginMesh(localToWorld);
1259
1260     SelectionIntersection best;
1261     aabb_testselect(m_aabb_light, test, best);
1262     if(best.valid())
1263     {
1264       selector.addIntersection(best);
1265     }
1266   }
1267   
1268   void translate(const Vector3& translation)
1269   {
1270     m_aabb_light.origin = origin_translated(m_aabb_light.origin, translation);
1271   }
1272   void rotate(const Quaternion& rotation)
1273   {
1274     rotation_rotate(m_rotation, rotation);
1275   }
1276   void snapto(float snap)
1277   {
1278     if(g_lightType == LIGHTTYPE_DOOM3 && !m_useLightOrigin && !m_traverse.empty())
1279     {
1280       m_useLightOrigin = true;
1281       m_lightOrigin = m_originKey.m_origin;
1282     }
1283
1284     if(m_useLightOrigin)
1285     {
1286       m_lightOrigin = origin_snapped(m_lightOrigin, snap);
1287       writeLightOrigin();
1288     }
1289     else
1290     {
1291       m_originKey.m_origin = origin_snapped(m_originKey.m_origin, snap);
1292       m_originKey.write(&m_entity);
1293     }
1294   }
1295   void setLightRadius(const AABB& aabb)
1296   {
1297     m_aabb_light.origin = aabb.origin;
1298     m_doom3Radius.m_radiusTransformed = aabb.extents;
1299   }
1300   void transformLightRadius(const Matrix4& transform)
1301   {
1302     matrix4_transform_point(transform, m_aabb_light.origin);
1303   }
1304   void revertTransform()
1305   {
1306     m_aabb_light.origin = m_useLightOrigin ? m_lightOrigin : m_originKey.m_origin;
1307     rotation_assign(m_rotation, m_useLightRotation ? m_lightRotation : m_rotationKey.m_rotation);
1308     m_doom3Radius.m_radiusTransformed = m_doom3Radius.m_radius;
1309   }
1310   void freezeTransform()
1311   {
1312     if(g_lightType == LIGHTTYPE_DOOM3 && !m_useLightOrigin && !m_traverse.empty())
1313     {
1314       m_useLightOrigin = true;
1315     }
1316
1317     if(m_useLightOrigin)
1318     {
1319       m_lightOrigin = m_aabb_light.origin;
1320       writeLightOrigin();
1321     }
1322     else
1323     {
1324       m_originKey.m_origin = m_aabb_light.origin;
1325       m_originKey.write(&m_entity);
1326     }
1327
1328     if(g_lightType == LIGHTTYPE_DOOM3)
1329     {
1330       if(!m_useLightRotation && !m_traverse.empty())
1331       {
1332         m_useLightRotation = true;
1333       }
1334
1335       if(m_useLightRotation)
1336       {
1337         rotation_assign(m_lightRotation, m_rotation);
1338         write_rotation(m_lightRotation, &m_entity, "light_rotation");
1339       }
1340
1341       rotation_assign(m_rotationKey.m_rotation, m_rotation);
1342       write_rotation(m_rotationKey.m_rotation, &m_entity);
1343
1344       m_doom3Radius.m_radius = m_doom3Radius.m_radiusTransformed;
1345       write_origin(m_doom3Radius.m_radius, &m_entity, "light_radius");
1346     }
1347   }
1348   void transformChanged()
1349   {
1350     revertTransform();
1351     m_evaluateTransform();
1352     updateOrigin();
1353   }
1354   typedef MemberCaller<Light, &Light::transformChanged> TransformChangedCaller;
1355
1356   mutable Matrix4 m_localPivot;
1357   const Matrix4& getLocalPivot() const
1358   {
1359     m_localPivot = rotation_toMatrix(m_rotation);
1360     vector4_to_vector3(m_localPivot.t()) = m_aabb_light.origin;
1361     return m_localPivot;
1362   }
1363
1364   void setLightChangedCallback(const Callback& callback)
1365   {
1366     m_doom3Radius.m_changed = callback;
1367   }
1368
1369   const AABB& aabb() const
1370   {
1371     m_doom3AABB = AABB(m_aabb_light.origin, m_doom3Radius.m_radiusTransformed);
1372     return m_doom3AABB;
1373   }
1374   bool testAABB(const AABB& other) const
1375   {
1376     if(isProjected())
1377     {
1378       Matrix4 transform = rotation();
1379       vector4_to_vector3(transform.t()) = localAABB().origin;
1380       projection();
1381       Frustum frustum(frustum_transformed(m_doom3Frustum, transform));
1382       return frustum_test_aabb(frustum, other) != c_volumeOutside;
1383     }
1384     // test against an AABB which contains the rotated bounds of this light.
1385     const AABB& bounds = aabb();
1386     return aabb_intersects_aabb(other, AABB(
1387       bounds.origin,
1388       Vector3(
1389         static_cast<float>(fabs(m_rotation[0] * bounds.extents[0])
1390                             + fabs(m_rotation[3] * bounds.extents[1])
1391                             + fabs(m_rotation[6] * bounds.extents[2])),
1392         static_cast<float>(fabs(m_rotation[1] * bounds.extents[0])
1393                             + fabs(m_rotation[4] * bounds.extents[1])
1394                             + fabs(m_rotation[7] * bounds.extents[2])),
1395         static_cast<float>(fabs(m_rotation[2] * bounds.extents[0])
1396                             + fabs(m_rotation[5] * bounds.extents[1])
1397                             + fabs(m_rotation[8] * bounds.extents[2]))
1398       )
1399     ));
1400   }
1401
1402   const Matrix4& rotation() const
1403   {
1404     m_doom3Rotation = rotation_toMatrix(m_rotation);
1405     return m_doom3Rotation;
1406   }
1407   const Vector3& offset() const
1408   {
1409     return m_doom3Radius.m_center;
1410   }
1411   const Vector3& colour() const
1412   {
1413     return m_colour.m_colour;
1414   }
1415
1416   bool isProjected() const
1417   {
1418     return m_useLightTarget && m_useLightUp && m_useLightRight;
1419   }
1420   void projectionChanged()
1421   {
1422     m_doom3ProjectionChanged = true;
1423     m_doom3Radius.m_changed();
1424     SceneChangeNotify();
1425   }
1426
1427   const Matrix4& projection() const
1428   {
1429     if(!m_doom3ProjectionChanged)
1430     {
1431       return m_doom3Projection;
1432     }
1433     m_doom3ProjectionChanged = false;
1434     m_doom3Projection = g_matrix4_identity;
1435     matrix4_translate_by_vec3(m_doom3Projection, Vector3(0.5f, 0.5f, 0));
1436     matrix4_scale_by_vec3(m_doom3Projection, Vector3(0.5f, 0.5f, 1));
1437
1438 #if 0
1439     Vector3 right = vector3_cross(m_lightUp, vector3_normalised(m_lightTarget));
1440     Vector3 up = vector3_cross(vector3_normalised(m_lightTarget), m_lightRight);
1441     Vector3 target = m_lightTarget;
1442     Matrix4 test(
1443       -right.x(), -right.y(), -right.z(), 0,
1444       -up.x(), -up.y(), -up.z(), 0,
1445       -target.x(), -target.y(), -target.z(), 0,
1446       0, 0, 0, 1
1447     );
1448     Matrix4 frustum = matrix4_frustum(-0.01, 0.01, -0.01, 0.01, 0.01, 1.0);
1449     test = matrix4_full_inverse(test);
1450     matrix4_premultiply_by_matrix4(test, frustum);
1451     matrix4_multiply_by_matrix4(m_doom3Projection, test);
1452 #elif 0
1453     const float nearFar = 1 / 49.5f;
1454     Vector3 right = vector3_cross(m_lightUp, vector3_normalised(m_lightTarget + m_lightRight));
1455     Vector3 up = vector3_cross(vector3_normalised(m_lightTarget + m_lightUp), m_lightRight);
1456     Vector3 target = vector3_negated(m_lightTarget * (1 + nearFar));
1457     float scale = -1 / vector3_length(m_lightTarget);
1458     Matrix4 test(
1459       -inverse(right.x()), -inverse(up.x()), -inverse(target.x()), 0,
1460       -inverse(right.y()), -inverse(up.y()), -inverse(target.y()), 0,
1461       -inverse(right.z()), -inverse(up.z()), -inverse(target.z()), scale,
1462       0, 0, -nearFar, 0
1463     );
1464     matrix4_multiply_by_matrix4(m_doom3Projection, test);
1465 #elif 0
1466     Vector3 leftA(m_lightTarget - m_lightRight);
1467     Vector3 leftB(m_lightRight + m_lightUp);
1468     Plane3 left(vector3_normalised(vector3_cross(leftA, leftB)) * (1.0 / 128), 0);
1469     Vector3 rightA(m_lightTarget + m_lightRight);
1470     Vector3 rightB(vector3_cross(rightA, m_lightTarget));
1471     Plane3 right(vector3_normalised(vector3_cross(rightA, rightB)) * (1.0 / 128), 0);
1472     Vector3 bottomA(m_lightTarget - m_lightUp);
1473     Vector3 bottomB(vector3_cross(bottomA, m_lightTarget));
1474     Plane3 bottom(vector3_normalised(vector3_cross(bottomA, bottomB)) * (1.0 / 128), 0);
1475     Vector3 topA(m_lightTarget + m_lightUp);
1476     Vector3 topB(vector3_cross(topA, m_lightTarget));
1477     Plane3 top(vector3_normalised(vector3_cross(topA, topB)) * (1.0 / 128), 0);
1478     Plane3 front(vector3_normalised(m_lightTarget) * (1.0 / 128), 1);
1479     Plane3 back(vector3_normalised(vector3_negated(m_lightTarget)) * (1.0 / 128), 0);
1480     Matrix4 test(matrix4_from_planes(plane3_flipped(left), plane3_flipped(right), plane3_flipped(bottom), plane3_flipped(top), plane3_flipped(front), plane3_flipped(back)));
1481     matrix4_multiply_by_matrix4(m_doom3Projection, test);
1482 #else
1483
1484     Plane3 lightProject[4];
1485
1486     Vector3 start = m_useLightStart && m_useLightEnd ? m_lightStart : vector3_normalised(m_lightTarget);
1487     Vector3 stop = m_useLightStart && m_useLightEnd ? m_lightEnd : m_lightTarget;
1488
1489           float rLen = vector3_length(m_lightRight);
1490           Vector3 right = vector3_divided(m_lightRight, rLen);
1491           float uLen = vector3_length(m_lightUp);
1492           Vector3 up = vector3_divided(m_lightUp, uLen);
1493           Vector3 normal = vector3_normalised(vector3_cross(up, right));
1494
1495           float dist = vector3_dot(m_lightTarget, normal);
1496           if ( dist < 0 ) {
1497                   dist = -dist;
1498                   normal = vector3_negated(normal);
1499           }
1500
1501           right *= ( 0.5f * dist ) / rLen;
1502           up *= -( 0.5f * dist ) / uLen;
1503
1504           lightProject[2] = Plane3(normal, 0);
1505           lightProject[0] = Plane3(right, 0);
1506           lightProject[1] = Plane3(up, 0);
1507
1508           // now offset to center
1509           Vector4 targetGlobal(m_lightTarget, 1);
1510     {
1511       float a = vector4_dot(targetGlobal, plane3_to_vector4(lightProject[0]));
1512       float b = vector4_dot(targetGlobal, plane3_to_vector4(lightProject[2]));
1513             float ofs = 0.5f - a / b;
1514             plane3_to_vector4(lightProject[0]) += plane3_to_vector4(lightProject[2]) * ofs;
1515     }
1516     {
1517       float a = vector4_dot(targetGlobal, plane3_to_vector4(lightProject[1]));
1518       float b = vector4_dot(targetGlobal, plane3_to_vector4(lightProject[2]));
1519             float ofs = 0.5f - a / b;
1520             plane3_to_vector4(lightProject[1]) += plane3_to_vector4(lightProject[2]) * ofs;
1521     }
1522
1523           // set the falloff vector
1524           Vector3 falloff = stop - start;
1525           float length = vector3_length(falloff);
1526     falloff = vector3_divided(falloff, length);
1527           if ( length <= 0 ) {
1528                   length = 1;
1529           }
1530     falloff *= (1.0f / length);
1531           lightProject[3] = Plane3(falloff, -vector3_dot(start, falloff));
1532
1533           // we want the planes of s=0, s=q, t=0, and t=q
1534           m_doom3Frustum.left = lightProject[0];
1535           m_doom3Frustum.bottom = lightProject[1];
1536           m_doom3Frustum.right = Plane3(lightProject[2].normal() - lightProject[0].normal(), lightProject[2].dist() - lightProject[0].dist());
1537           m_doom3Frustum.top = Plane3(lightProject[2].normal() - lightProject[1].normal(), lightProject[2].dist() - lightProject[1].dist());
1538
1539           // we want the planes of s=0 and s=1 for front and rear clipping planes
1540           m_doom3Frustum.front = lightProject[3];
1541
1542           m_doom3Frustum.back = lightProject[3];
1543           m_doom3Frustum.back.dist() -= 1.0f;
1544           m_doom3Frustum.back = plane3_flipped(m_doom3Frustum.back);
1545
1546     Matrix4 test(matrix4_from_planes(m_doom3Frustum.left, m_doom3Frustum.right, m_doom3Frustum.bottom, m_doom3Frustum.top, m_doom3Frustum.front, m_doom3Frustum.back));
1547     matrix4_multiply_by_matrix4(m_doom3Projection, test);
1548
1549     m_doom3Frustum.left = plane3_normalised(m_doom3Frustum.left);
1550     m_doom3Frustum.right = plane3_normalised(m_doom3Frustum.right);
1551     m_doom3Frustum.bottom = plane3_normalised(m_doom3Frustum.bottom);
1552     m_doom3Frustum.top = plane3_normalised(m_doom3Frustum.top);
1553     m_doom3Frustum.back = plane3_normalised(m_doom3Frustum.back);
1554     m_doom3Frustum.front = plane3_normalised(m_doom3Frustum.front);
1555 #endif
1556     //matrix4_scale_by_vec3(m_doom3Projection, Vector3(1.0 / 128, 1.0 / 128, 1.0 / 128));
1557     return m_doom3Projection;
1558   }
1559
1560   Shader* getShader() const
1561   {
1562     return m_shader.get();
1563   }
1564 };
1565
1566 class LightInstance :
1567   public TargetableInstance,
1568   public TransformModifier,
1569   public Renderable,
1570   public SelectionTestable,
1571   public RendererLight,
1572   public PlaneSelectable,
1573   public ComponentSelectionTestable
1574 {
1575   class TypeCasts
1576   {
1577     InstanceTypeCastTable m_casts;
1578   public:
1579     TypeCasts()
1580     {
1581       m_casts = TargetableInstance::StaticTypeCasts::instance().get();
1582       InstanceContainedCast<LightInstance, Bounded>::install(m_casts);
1583       //InstanceContainedCast<LightInstance, Cullable>::install(m_casts);
1584       InstanceStaticCast<LightInstance, Renderable>::install(m_casts);
1585       InstanceStaticCast<LightInstance, SelectionTestable>::install(m_casts);
1586       InstanceStaticCast<LightInstance, Transformable>::install(m_casts);
1587       InstanceStaticCast<LightInstance, PlaneSelectable>::install(m_casts);
1588       InstanceStaticCast<LightInstance, ComponentSelectionTestable>::install(m_casts);
1589       InstanceIdentityCast<LightInstance>::install(m_casts);
1590     }
1591     InstanceTypeCastTable& get()
1592     {
1593       return m_casts;
1594     }
1595   };
1596
1597   Light& m_contained;
1598   DragPlanes m_dragPlanes;// dragplanes for lightresizing using mousedrag
1599 public:
1600   typedef LazyStatic<TypeCasts> StaticTypeCasts;
1601
1602   Bounded& get(NullType<Bounded>)
1603   {
1604     return m_contained;
1605   }
1606
1607   STRING_CONSTANT(Name, "LightInstance");
1608
1609   LightInstance(const scene::Path& path, scene::Instance* parent, Light& contained) :
1610     TargetableInstance(path, parent, this, StaticTypeCasts::instance().get(), contained.getEntity(), *this),
1611     TransformModifier(Light::TransformChangedCaller(contained), ApplyTransformCaller(*this)),
1612     m_contained(contained),
1613     m_dragPlanes(SelectedChangedComponentCaller(*this))
1614   {
1615     m_contained.instanceAttach(Instance::path());
1616
1617     if(g_lightType == LIGHTTYPE_DOOM3)
1618     {
1619       GlobalShaderCache().attach(*this);
1620       m_contained.setLightChangedCallback(LightChangedCaller(*this));
1621     }
1622
1623     StaticRenderableConnectionLines::instance().attach(*this);
1624   }
1625   ~LightInstance()
1626   {
1627     StaticRenderableConnectionLines::instance().detach(*this);
1628
1629     if(g_lightType == LIGHTTYPE_DOOM3)
1630     {
1631       m_contained.setLightChangedCallback(Callback());
1632       GlobalShaderCache().detach(*this);
1633     }
1634
1635     m_contained.instanceDetach(Instance::path());
1636   }
1637   void renderSolid(Renderer& renderer, const VolumeTest& volume) const
1638   {
1639     m_contained.renderSolid(renderer, volume, Instance::localToWorld(), getSelectable().isSelected());
1640   }
1641   void renderWireframe(Renderer& renderer, const VolumeTest& volume) const
1642   {
1643     m_contained.renderWireframe(renderer, volume, Instance::localToWorld(), getSelectable().isSelected());
1644   }
1645   void testSelect(Selector& selector, SelectionTest& test)
1646   {
1647     m_contained.testSelect(selector, test, Instance::localToWorld());
1648   }
1649
1650   void selectPlanes(Selector& selector, SelectionTest& test, const PlaneCallback& selectedPlaneCallback)
1651   {
1652     test.BeginMesh(localToWorld());
1653     m_dragPlanes.selectPlanes(m_contained.aabb(), selector, test, selectedPlaneCallback, rotation());
1654   }
1655   void selectReversedPlanes(Selector& selector, const SelectedPlanes& selectedPlanes)
1656   {
1657     m_dragPlanes.selectReversedPlanes(m_contained.aabb(), selector, selectedPlanes, rotation());
1658   }
1659   
1660   bool isSelectedComponents() const
1661   {
1662     return m_dragPlanes.isSelected();
1663   }
1664   void setSelectedComponents(bool select, SelectionSystem::EComponentMode mode)
1665   {
1666     if(mode == SelectionSystem::eFace)
1667     {
1668       m_dragPlanes.setSelected(false);
1669     }
1670   }
1671   void testSelectComponents(Selector& selector, SelectionTest& test, SelectionSystem::EComponentMode mode)
1672   {
1673   }
1674
1675   void selectedChangedComponent(const Selectable& selectable)
1676   {
1677     GlobalSelectionSystem().getObserver(SelectionSystem::eComponent)(selectable);
1678     GlobalSelectionSystem().onComponentSelection(*this, selectable);
1679   }
1680   typedef MemberCaller1<LightInstance, const Selectable&, &LightInstance::selectedChangedComponent> SelectedChangedComponentCaller;
1681
1682   void evaluateTransform()
1683   {
1684     if(getType() == TRANSFORM_PRIMITIVE)
1685     {
1686       m_contained.translate(getTranslation());
1687       m_contained.rotate(getRotation());
1688     }
1689     else
1690     {
1691       //globalOutputStream() << getTranslation() << "\n";
1692
1693       m_dragPlanes.m_bounds = m_contained.aabb();
1694       m_contained.setLightRadius(m_dragPlanes.evaluateResize(getTranslation(), rotation()));
1695     }
1696   }
1697   void applyTransform()
1698   {
1699     m_contained.revertTransform();
1700     evaluateTransform();
1701     m_contained.freezeTransform();
1702   }
1703   typedef MemberCaller<LightInstance, &LightInstance::applyTransform> ApplyTransformCaller;
1704
1705   void lightChanged()
1706   {
1707     GlobalShaderCache().changed(*this);
1708   }
1709   typedef MemberCaller<LightInstance, &LightInstance::lightChanged> LightChangedCaller;
1710
1711   Shader* getShader() const
1712   {
1713     return m_contained.getShader();
1714   }
1715   const AABB& aabb() const
1716   {
1717     return m_contained.aabb();
1718   }
1719   bool testAABB(const AABB& other) const
1720   {
1721     return m_contained.testAABB(other);
1722   }
1723   const Matrix4& rotation() const
1724   {
1725     return m_contained.rotation();
1726   }
1727   const Vector3& offset() const
1728   {
1729     return m_contained.offset();
1730   }
1731   const Vector3& colour() const
1732   {
1733     return m_contained.colour();
1734   }
1735
1736   bool isProjected() const
1737   {
1738     return m_contained.isProjected();
1739   }
1740   const Matrix4& projection() const
1741   {
1742     return m_contained.projection();
1743   }
1744 };
1745
1746 class LightNode :
1747   public scene::Node::Symbiot,
1748   public scene::Instantiable,
1749   public scene::Cloneable,
1750   public scene::Traversable::Observer
1751 {
1752   class TypeCasts
1753   {
1754     NodeTypeCastTable m_casts;
1755   public:
1756     TypeCasts()
1757     {
1758       NodeStaticCast<LightNode, scene::Instantiable>::install(m_casts);
1759       NodeStaticCast<LightNode, scene::Cloneable>::install(m_casts);
1760       if(g_lightType == LIGHTTYPE_DOOM3)
1761       {
1762         NodeContainedCast<LightNode, scene::Traversable>::install(m_casts);
1763       }
1764       NodeContainedCast<LightNode, Editable>::install(m_casts);
1765       NodeContainedCast<LightNode, Snappable>::install(m_casts);
1766       NodeContainedCast<LightNode, TransformNode>::install(m_casts);
1767       NodeContainedCast<LightNode, Entity>::install(m_casts);
1768       NodeContainedCast<LightNode, Nameable>::install(m_casts);
1769       NodeContainedCast<LightNode, Namespaced>::install(m_casts);
1770     }
1771     NodeTypeCastTable& get()
1772     {
1773       return m_casts;
1774     }
1775   };
1776
1777
1778   scene::Node m_node;
1779   InstanceSet m_instances;
1780   Light m_contained;
1781
1782   void construct()
1783   {
1784     if(g_lightType == LIGHTTYPE_DOOM3)
1785     {
1786       m_contained.attach(this);
1787     }
1788   }
1789   void destroy()
1790   {
1791     if(g_lightType == LIGHTTYPE_DOOM3)
1792     {
1793       m_contained.detach(this);
1794     }
1795   }
1796 public:
1797   typedef LazyStatic<TypeCasts> StaticTypeCasts;
1798
1799   scene::Traversable& get(NullType<scene::Traversable>)
1800   {
1801     return m_contained.getTraversable();
1802   }
1803   Editable& get(NullType<Editable>)
1804   {
1805     return m_contained;
1806   }
1807   Snappable& get(NullType<Snappable>)
1808   {
1809     return m_contained;
1810   }
1811   TransformNode& get(NullType<TransformNode>)
1812   {
1813     return m_contained.getTransformNode();
1814   }
1815   Entity& get(NullType<Entity>)
1816   {
1817     return m_contained.getEntity();
1818   }
1819   Nameable& get(NullType<Nameable>)
1820   {
1821     return m_contained.getNameable();
1822   }
1823   Namespaced& get(NullType<Namespaced>)
1824   {
1825     return m_contained.getNamespaced();
1826   }
1827
1828   LightNode(EntityClass* eclass) :
1829     m_node(this, this, StaticTypeCasts::instance().get()),
1830     m_contained(eclass, m_node, InstanceSet::TransformChangedCaller(m_instances), InstanceSet::BoundsChangedCaller(m_instances), InstanceSetEvaluateTransform<LightInstance>::Caller(m_instances))
1831   {
1832     construct();
1833   }
1834   LightNode(const LightNode& other) :
1835     scene::Node::Symbiot(other),
1836     scene::Instantiable(other),
1837     scene::Cloneable(other),
1838     scene::Traversable::Observer(other),
1839     m_node(this, this, StaticTypeCasts::instance().get()),
1840     m_contained(other.m_contained, m_node, InstanceSet::TransformChangedCaller(m_instances), InstanceSet::BoundsChangedCaller(m_instances), InstanceSetEvaluateTransform<LightInstance>::Caller(m_instances))
1841   {
1842     construct();
1843   }
1844   ~LightNode()
1845   {
1846     destroy();
1847   }
1848
1849   void release()
1850   {
1851     delete this;
1852   }
1853   scene::Node& node()
1854   {
1855     return m_node;
1856   }
1857
1858   scene::Node& clone() const
1859   {
1860     return (new LightNode(*this))->node();
1861   }
1862
1863   void insert(scene::Node& child)
1864   {
1865     m_instances.insert(child);
1866   }
1867   void erase(scene::Node& child)
1868   {
1869     m_instances.erase(child);
1870   }
1871
1872   scene::Instance* create(const scene::Path& path, scene::Instance* parent)
1873   {
1874     return new LightInstance(path, parent, m_contained);
1875   }
1876   void forEachInstance(const scene::Instantiable::Visitor& visitor)
1877   {
1878     m_instances.forEachInstance(visitor);
1879   }
1880   void insert(scene::Instantiable::Observer* observer, const scene::Path& path, scene::Instance* instance)
1881   {
1882     m_instances.insert(observer, path, instance);
1883   }
1884   scene::Instance* erase(scene::Instantiable::Observer* observer, const scene::Path& path)
1885   {
1886     return m_instances.erase(observer, path);
1887   }
1888 };
1889
1890 void Light_Construct(LightType lightType)
1891 {
1892   g_lightType = lightType;
1893   if(g_lightType == LIGHTTYPE_DOOM3)
1894   {
1895     LightShader::m_defaultShader = "lights/defaultPointLight";
1896 #if 0
1897     LightShader::m_defaultShader = "lights/defaultProjectedLight";
1898 #endif
1899   }
1900   RenderLightRadiiFill::m_state = GlobalShaderCache().capture("$Q3MAP2_LIGHT_SPHERE");
1901   RenderLightCenter::m_state = GlobalShaderCache().capture("$BIGPOINT");
1902 }
1903 void Light_Destroy()
1904 {
1905   GlobalShaderCache().release("$Q3MAP2_LIGHT_SPHERE");
1906   GlobalShaderCache().release("$BIGPOINT");
1907 }
1908
1909 scene::Node& New_Light(EntityClass* eclass)
1910 {
1911   return (new LightNode(eclass))->node();
1912 }