]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - plugins/entity/light.cpp
limited win32 stack-trace size
[xonotic/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 const Vector3 c_defaultDoom3LightRadius = Vector3(300, 300, 300);
541 class Doom3LightRadius
542 {
543 public:
544   Vector3 m_radius;
545   Vector3 m_radiusTransformed;
546   Vector3 m_center;
547   Callback m_changed;
548   bool m_useCenterKey;
549
550   Doom3LightRadius() : m_radius(c_defaultDoom3LightRadius), m_center(0, 0, 0), m_useCenterKey(false)
551   {
552   }
553
554   void lightRadiusChanged(const char* value)
555   {
556     if(!string_parse_vector3(value, m_radius))
557     {
558       m_radius = c_defaultDoom3LightRadius;
559     }
560     m_radiusTransformed = m_radius;
561     m_changed();
562     SceneChangeNotify();
563   }
564   typedef MemberCaller1<Doom3LightRadius, const char*, &Doom3LightRadius::lightRadiusChanged> LightRadiusChangedCaller;
565
566   void lightCenterChanged(const char* value)
567   {
568     m_useCenterKey = string_parse_vector3(value, m_center);
569     if(!m_useCenterKey)
570     {
571       m_center = Vector3(0, 0, 0);
572     }
573     SceneChangeNotify();
574   }
575   typedef MemberCaller1<Doom3LightRadius, const char*, &Doom3LightRadius::lightCenterChanged> LightCenterChangedCaller;
576 };
577
578 class RenderLightRadiiWire : public OpenGLRenderable
579 {
580   LightRadii& m_radii;
581   const Vector3& m_origin;
582 public:
583   RenderLightRadiiWire(LightRadii& radii, const Vector3& origin) : m_radii(radii), m_origin(origin)
584   {
585   }
586   void render(RenderStateFlags state) const
587   {
588     light_draw_radius_wire(m_origin, m_radii.m_radii);
589   }
590 };
591
592 class RenderLightRadiiFill : public OpenGLRenderable
593 {
594   LightRadii& m_radii;
595   const Vector3& m_origin;
596 public:
597   static Shader* m_state;
598
599   RenderLightRadiiFill(LightRadii& radii, const Vector3& origin) : m_radii(radii), m_origin(origin)
600   {
601   }
602   void render(RenderStateFlags state) const
603   {
604     light_draw_radius_fill(m_origin, m_radii.m_radii);
605   }
606 };
607
608 class RenderLightRadiiBox : public OpenGLRenderable
609 {
610   const Vector3& m_origin;
611 public:
612   mutable Vector3 m_points[8];
613   static Shader* m_state;
614
615   RenderLightRadiiBox(const Vector3& origin) : m_origin(origin)
616   {
617   }
618   void render(RenderStateFlags state) const
619   {
620     //draw the bounding box of light based on light_radius key
621     if((state & RENDER_FILL) != 0)
622     {
623       aabb_draw_flatshade(m_points);
624     }
625     else
626     {
627       aabb_draw_wire(m_points);
628     }
629
630   #if 1    //disable if you dont want lines going from the center of the light bbox to the corners
631     light_draw_box_lines(m_origin, m_points);
632   #endif
633   }
634 };
635
636 Shader* RenderLightRadiiFill::m_state = 0;
637
638 class RenderLightCenter : public OpenGLRenderable
639 {
640   const Vector3& m_center;
641   EntityClass& m_eclass;
642 public:
643   static Shader* m_state;
644
645   RenderLightCenter(const Vector3& center, EntityClass& eclass) : m_center(center), m_eclass(eclass)
646   {
647   }
648   void render(RenderStateFlags state) const
649   {
650     glBegin(GL_POINTS);
651     glColor3fv(vector3_to_array(m_eclass.color));
652     glVertex3fv(vector3_to_array(m_center));
653     glEnd();
654   }
655 };
656
657 Shader* RenderLightCenter::m_state = 0;
658
659 class RenderLightProjection : public OpenGLRenderable
660 {
661   const Matrix4& m_projection;
662 public:
663
664   RenderLightProjection(const Matrix4& projection) : m_projection(projection)
665   {
666   }
667   void render(RenderStateFlags state) const
668   {
669     Matrix4 unproject(matrix4_full_inverse(m_projection));
670     Vector3 points[8];
671     aabb_corners(AABB(Vector3(0.5f, 0.5f, 0.5f), Vector3(0.5f, 0.5f, 0.5f)), points);
672     points[0] = vector4_projected(matrix4_transformed_vector4(unproject, Vector4(points[0], 1)));
673     points[1] = vector4_projected(matrix4_transformed_vector4(unproject, Vector4(points[1], 1)));
674     points[2] = vector4_projected(matrix4_transformed_vector4(unproject, Vector4(points[2], 1)));
675     points[3] = vector4_projected(matrix4_transformed_vector4(unproject, Vector4(points[3], 1)));
676     points[4] = vector4_projected(matrix4_transformed_vector4(unproject, Vector4(points[4], 1)));
677     points[5] = vector4_projected(matrix4_transformed_vector4(unproject, Vector4(points[5], 1)));
678     points[6] = vector4_projected(matrix4_transformed_vector4(unproject, Vector4(points[6], 1)));
679     points[7] = vector4_projected(matrix4_transformed_vector4(unproject, Vector4(points[7], 1)));
680     Vector4 test1 = matrix4_transformed_vector4(unproject, Vector4(0.5f, 0.5f, 0.5f, 1));
681     Vector3 test2 = vector4_projected(test1);
682     aabb_draw_wire(points);
683   }
684 };
685
686 inline void default_extents(Vector3& extents)
687 {
688   extents = Vector3(8, 8, 8);
689 }
690
691 class ShaderRef
692 {
693   CopiedString m_name;
694   Shader* m_shader;
695   void capture()
696   {
697     m_shader = GlobalShaderCache().capture(m_name.c_str());
698   }
699   void release()
700   {
701     GlobalShaderCache().release(m_name.c_str());
702   }
703 public:
704   ShaderRef()
705   {
706     capture();
707   }
708   ~ShaderRef()
709   {
710     release();
711   }
712   void setName(const char* name)
713   {
714     release();
715     m_name = name;
716     capture();
717   }
718   Shader* get() const
719   {
720     return m_shader;
721   }
722 };
723
724 class LightShader
725 {
726   ShaderRef m_shader;
727   void setDefault()
728   {
729     m_shader.setName(m_defaultShader);
730   }
731 public:
732   static const char* m_defaultShader;
733
734   LightShader()
735   {
736     setDefault();
737   }
738   void valueChanged(const char* value)
739   {
740     if(string_empty(value))
741     {
742       setDefault();
743     }
744     else
745     {
746       m_shader.setName(value);
747     }
748     SceneChangeNotify();
749   }
750   typedef MemberCaller1<LightShader, const char*, &LightShader::valueChanged> ValueChangedCaller;
751
752   Shader* get() const
753   {
754     return m_shader.get();
755   }
756 };
757
758 const char* LightShader::m_defaultShader = "";
759
760 inline const BasicVector4<double>& plane3_to_vector4(const Plane3& self)
761 {
762   return reinterpret_cast<const BasicVector4<double>&>(self);
763 }
764
765 inline BasicVector4<double>& plane3_to_vector4(Plane3& self)
766 {
767   return reinterpret_cast<BasicVector4<double>&>(self);
768 }
769
770 inline Matrix4 matrix4_from_planes(const Plane3& left, const Plane3& right, const Plane3& bottom, const Plane3& top, const Plane3& front, const Plane3& back)
771 {
772   return Matrix4(
773     (right.a - left.a) / 2,
774     (top.a - bottom.a) / 2,
775     (back.a - front.a) / 2,
776     right.a - (right.a - left.a) / 2,
777     (right.b - left.b) / 2,
778     (top.b - bottom.b) / 2,
779     (back.b - front.b) / 2,
780     right.b - (right.b - left.b) / 2,
781     (right.c - left.c) / 2,
782     (top.c - bottom.c) / 2,
783     (back.c - front.c) / 2,
784     right.c - (right.c - left.c) / 2,
785     (right.d - left.d) / 2,
786     (top.d - bottom.d) / 2,
787     (back.d - front.d) / 2,
788     right.d - (right.d - left.d) / 2
789   );
790 }
791
792 class Light :
793   public OpenGLRenderable,
794   public Cullable,
795   public Bounded,
796   public Editable,
797   public Snappable
798 {
799   EntityKeyValues m_entity;
800   KeyObserverMap m_keyObservers;
801   TraversableNodeSet m_traverse;
802   IdentityTransform m_transform;
803
804   OriginKey m_originKey;
805   RotationKey m_rotationKey;
806   Float9 m_rotation;
807   Colour m_colour;
808
809   ClassnameFilter m_filter;
810   NamedEntity m_named;
811   NameKeys m_nameKeys;
812   TraversableObserverPairRelay m_traverseObservers;
813   Doom3GroupOrigin m_funcStaticOrigin;
814
815   LightRadii m_radii;
816   Doom3LightRadius m_doom3Radius;
817
818   RenderLightRadiiWire m_radii_wire;
819   RenderLightRadiiFill m_radii_fill;
820   RenderLightRadiiBox m_radii_box;
821   RenderLightCenter m_render_center;
822   RenderableNamedEntity m_renderName;
823
824   Vector3 m_lightOrigin;
825   bool m_useLightOrigin;
826   Float9 m_lightRotation;
827   bool m_useLightRotation;
828
829   Vector3 m_lightTarget;
830   bool m_useLightTarget;
831   Vector3 m_lightUp;
832   bool m_useLightUp;
833   Vector3 m_lightRight;
834   bool m_useLightRight;
835   Vector3 m_lightStart;
836   bool m_useLightStart;
837   Vector3 m_lightEnd;
838   bool m_useLightEnd;
839
840   mutable AABB m_doom3AABB;
841   mutable Matrix4 m_doom3Rotation;
842   mutable Matrix4 m_doom3Projection;
843   mutable Frustum m_doom3Frustum;
844   mutable bool m_doom3ProjectionChanged;
845
846   RenderLightProjection m_renderProjection;
847
848   LightShader m_shader;
849
850   AABB m_aabb_light;
851
852   Callback m_transformChanged;
853   Callback m_boundsChanged;
854   Callback m_evaluateTransform;
855
856   void construct()
857   {
858     default_rotation(m_rotation);
859     m_aabb_light.origin = Vector3(0, 0, 0);
860     default_extents(m_aabb_light.extents);
861
862     m_keyObservers.insert("classname", ClassnameFilter::ClassnameChangedCaller(m_filter));
863     m_keyObservers.insert(Static<KeyIsName>::instance().m_nameKey, NamedEntity::IdentifierChangedCaller(m_named));
864     m_keyObservers.insert("_color", Colour::ColourChangedCaller(m_colour));
865     m_keyObservers.insert("origin", OriginKey::OriginChangedCaller(m_originKey));
866     m_keyObservers.insert("_light", LightRadii::PrimaryIntensityChangedCaller(m_radii));
867     m_keyObservers.insert("light", LightRadii::SecondaryIntensityChangedCaller(m_radii));
868     m_keyObservers.insert("fade", LightRadii::FadeChangedCaller(m_radii));
869     m_keyObservers.insert("scale", LightRadii::ScaleChangedCaller(m_radii));
870     m_keyObservers.insert("spawnflags", LightRadii::FlagsChangedCaller(m_radii));
871
872     if(g_lightType == LIGHTTYPE_DOOM3)
873     {
874       m_keyObservers.insert("angle", RotationKey::AngleChangedCaller(m_rotationKey));
875       m_keyObservers.insert("rotation", RotationKey::RotationChangedCaller(m_rotationKey));
876       m_keyObservers.insert("light_radius", Doom3LightRadius::LightRadiusChangedCaller(m_doom3Radius));
877       m_keyObservers.insert("light_center", Doom3LightRadius::LightCenterChangedCaller(m_doom3Radius));
878       m_keyObservers.insert("light_origin", Light::LightOriginChangedCaller(*this));
879       m_keyObservers.insert("light_rotation", Light::LightRotationChangedCaller(*this));
880       m_keyObservers.insert("light_target", Light::LightTargetChangedCaller(*this));
881       m_keyObservers.insert("light_up", Light::LightUpChangedCaller(*this));
882       m_keyObservers.insert("light_right", Light::LightRightChangedCaller(*this));
883       m_keyObservers.insert("light_start", Light::LightStartChangedCaller(*this));
884       m_keyObservers.insert("light_end", Light::LightEndChangedCaller(*this));
885       m_keyObservers.insert("texture", LightShader::ValueChangedCaller(m_shader));
886       m_useLightTarget = m_useLightUp = m_useLightRight = m_useLightStart = m_useLightEnd = false;
887       m_doom3ProjectionChanged = true;
888     }
889
890     if(g_lightType == LIGHTTYPE_DOOM3)
891     {
892       m_traverse.attach(&m_traverseObservers);
893       m_traverseObservers.attach(m_funcStaticOrigin);
894
895       m_entity.m_isContainer = true;
896     }
897   }
898   void destroy()
899   {
900     if(g_lightType == LIGHTTYPE_DOOM3)
901     {
902       m_traverseObservers.detach(m_funcStaticOrigin);
903       m_traverse.detach(&m_traverseObservers);
904     }
905   }
906
907   void updateOrigin()
908   {
909     m_boundsChanged();
910
911     if(g_lightType == LIGHTTYPE_DOOM3)
912     {
913       m_funcStaticOrigin.originChanged();
914     }
915
916     m_doom3Radius.m_changed();
917
918     GlobalSelectionSystem().pivotChanged();
919   }
920
921   void originChanged()
922   {
923     m_aabb_light.origin = m_useLightOrigin ? m_lightOrigin : m_originKey.m_origin;
924     updateOrigin();
925   }
926   typedef MemberCaller<Light, &Light::originChanged> OriginChangedCaller;
927
928   void lightOriginChanged(const char* value)
929   {
930     m_useLightOrigin = !string_empty(value);
931     if(m_useLightOrigin)
932     {
933       read_origin(m_lightOrigin, value);
934     }
935     originChanged();
936   }
937   typedef MemberCaller1<Light, const char*, &Light::lightOriginChanged> LightOriginChangedCaller;
938
939   void lightTargetChanged(const char* value)
940   {
941     m_useLightTarget = !string_empty(value);
942     if(m_useLightTarget)
943     {
944       read_origin(m_lightTarget, value);
945     }
946     projectionChanged();
947   }
948   typedef MemberCaller1<Light, const char*, &Light::lightTargetChanged> LightTargetChangedCaller;
949   void lightUpChanged(const char* value)
950   {
951     m_useLightUp = !string_empty(value);
952     if(m_useLightUp)
953     {
954       read_origin(m_lightUp, value);
955     }
956     projectionChanged();
957   }
958   typedef MemberCaller1<Light, const char*, &Light::lightUpChanged> LightUpChangedCaller;
959   void lightRightChanged(const char* value)
960   {
961     m_useLightRight = !string_empty(value);
962     if(m_useLightRight)
963     {
964       read_origin(m_lightRight, value);
965     }
966     projectionChanged();
967   }
968   typedef MemberCaller1<Light, const char*, &Light::lightRightChanged> LightRightChangedCaller;
969   void lightStartChanged(const char* value)
970   {
971     m_useLightStart = !string_empty(value);
972     if(m_useLightStart)
973     {
974       read_origin(m_lightStart, value);
975     }
976     projectionChanged();
977   }
978   typedef MemberCaller1<Light, const char*, &Light::lightStartChanged> LightStartChangedCaller;
979   void lightEndChanged(const char* value)
980   {
981     m_useLightEnd = !string_empty(value);
982     if(m_useLightEnd)
983     {
984       read_origin(m_lightEnd, value);
985     }
986     projectionChanged();
987   }
988   typedef MemberCaller1<Light, const char*, &Light::lightEndChanged> LightEndChangedCaller;
989
990   void writeLightOrigin()
991   {
992     write_origin(m_lightOrigin, &m_entity, "light_origin");
993   }
994
995   void updateLightRadiiBox() const
996   {
997     const Matrix4& rotation = rotation_toMatrix(m_rotation);
998     aabb_corners(AABB(Vector3(0, 0, 0), m_doom3Radius.m_radiusTransformed), m_radii_box.m_points);
999     matrix4_transform_point(rotation, m_radii_box.m_points[0]);
1000     vector3_add(m_radii_box.m_points[0], m_aabb_light.origin);
1001     matrix4_transform_point(rotation, m_radii_box.m_points[1]);
1002     vector3_add(m_radii_box.m_points[1], m_aabb_light.origin);
1003     matrix4_transform_point(rotation, m_radii_box.m_points[2]);
1004     vector3_add(m_radii_box.m_points[2], m_aabb_light.origin);
1005     matrix4_transform_point(rotation, m_radii_box.m_points[3]);
1006     vector3_add(m_radii_box.m_points[3], m_aabb_light.origin);
1007     matrix4_transform_point(rotation, m_radii_box.m_points[4]);
1008     vector3_add(m_radii_box.m_points[4], m_aabb_light.origin);
1009     matrix4_transform_point(rotation, m_radii_box.m_points[5]);
1010     vector3_add(m_radii_box.m_points[5], m_aabb_light.origin);
1011     matrix4_transform_point(rotation, m_radii_box.m_points[6]);
1012     vector3_add(m_radii_box.m_points[6], m_aabb_light.origin);
1013     matrix4_transform_point(rotation, m_radii_box.m_points[7]);
1014     vector3_add(m_radii_box.m_points[7], m_aabb_light.origin);
1015   }
1016
1017   void rotationChanged()
1018   {
1019     rotation_assign(m_rotation, m_useLightRotation ? m_lightRotation : m_rotationKey.m_rotation);
1020     GlobalSelectionSystem().pivotChanged();
1021   }
1022   typedef MemberCaller<Light, &Light::rotationChanged> RotationChangedCaller;
1023
1024   void lightRotationChanged(const char* value)
1025   {
1026     m_useLightRotation = !string_empty(value);
1027     if(m_useLightRotation)
1028     {
1029       read_rotation(m_lightRotation, value);
1030     }
1031     rotationChanged();
1032   }
1033   typedef MemberCaller1<Light, const char*, &Light::lightRotationChanged> LightRotationChangedCaller;
1034
1035 public:
1036
1037   Light(EntityClass* eclass, scene::Node& node, const Callback& transformChanged, const Callback& boundsChanged, const Callback& evaluateTransform) :
1038     m_entity(eclass),
1039     m_originKey(OriginChangedCaller(*this)),
1040     m_rotationKey(RotationChangedCaller(*this)),
1041     m_colour(Callback()),
1042     m_filter(m_entity, node),
1043     m_named(m_entity),
1044     m_nameKeys(m_entity),
1045     m_funcStaticOrigin(m_traverse, m_originKey.m_origin),
1046     m_radii_wire(m_radii, m_aabb_light.origin),
1047     m_radii_fill(m_radii, m_aabb_light.origin),
1048     m_radii_box(m_aabb_light.origin),
1049     m_render_center(m_doom3Radius.m_center, m_entity.getEntityClass()), 
1050     m_renderName(m_named, m_aabb_light.origin),
1051     m_useLightOrigin(false),
1052     m_useLightRotation(false),
1053     m_renderProjection(m_doom3Projection),
1054     m_transformChanged(transformChanged),
1055     m_boundsChanged(boundsChanged),
1056     m_evaluateTransform(evaluateTransform)
1057   {
1058     construct();
1059   }
1060   Light(const Light& other, scene::Node& node, const Callback& transformChanged, const Callback& boundsChanged, const Callback& evaluateTransform) :
1061     m_entity(other.m_entity),
1062     m_originKey(OriginChangedCaller(*this)),
1063     m_rotationKey(RotationChangedCaller(*this)),
1064     m_colour(Callback()),
1065     m_filter(m_entity, node),
1066     m_named(m_entity),
1067     m_nameKeys(m_entity),
1068     m_funcStaticOrigin(m_traverse, m_originKey.m_origin),
1069     m_radii_wire(m_radii, m_aabb_light.origin),
1070     m_radii_fill(m_radii, m_aabb_light.origin),
1071     m_radii_box(m_aabb_light.origin),
1072     m_render_center(m_doom3Radius.m_center, m_entity.getEntityClass()), 
1073     m_renderName(m_named, m_aabb_light.origin),
1074     m_useLightOrigin(false),
1075     m_useLightRotation(false),
1076     m_renderProjection(m_doom3Projection),
1077     m_transformChanged(transformChanged),
1078     m_boundsChanged(boundsChanged),
1079     m_evaluateTransform(evaluateTransform)
1080   {
1081     construct();
1082   }
1083   ~Light()
1084   {
1085     destroy();
1086   }
1087
1088   InstanceCounter m_instanceCounter;
1089   void instanceAttach(const scene::Path& path)
1090   {
1091     if(++m_instanceCounter.m_count == 1)
1092     {
1093       m_filter.instanceAttach();
1094       m_entity.instanceAttach(path_find_mapfile(path.begin(), path.end()));
1095       if(g_lightType == LIGHTTYPE_DOOM3)
1096       {
1097         m_traverse.instanceAttach(path_find_mapfile(path.begin(), path.end()));
1098       }
1099       m_entity.attach(m_keyObservers);
1100
1101       if(g_lightType == LIGHTTYPE_DOOM3)
1102       {
1103         m_funcStaticOrigin.enable();
1104       }
1105     }
1106   }
1107   void instanceDetach(const scene::Path& path)
1108   {
1109     if(--m_instanceCounter.m_count == 0)
1110     {
1111       if(g_lightType == LIGHTTYPE_DOOM3)
1112       {
1113         m_funcStaticOrigin.disable();
1114       }
1115
1116       m_entity.detach(m_keyObservers);
1117       if(g_lightType == LIGHTTYPE_DOOM3)
1118       {
1119         m_traverse.instanceDetach(path_find_mapfile(path.begin(), path.end()));
1120       }
1121       m_entity.instanceDetach(path_find_mapfile(path.begin(), path.end()));
1122       m_filter.instanceDetach();
1123     }
1124   }
1125
1126   EntityKeyValues& getEntity()
1127   {
1128     return m_entity;
1129   }
1130   const EntityKeyValues& getEntity() const
1131   {
1132     return m_entity;
1133   }
1134
1135   scene::Traversable& getTraversable()
1136   {
1137     return m_traverse;
1138   }
1139   Namespaced& getNamespaced()
1140   {
1141     return m_nameKeys;
1142   }
1143   Nameable& getNameable()
1144   {
1145     return m_named;
1146   }
1147   TransformNode& getTransformNode()
1148   {
1149     return m_transform;
1150   }
1151
1152   void attach(scene::Traversable::Observer* observer)
1153   {
1154     m_traverseObservers.attach(*observer);
1155   }
1156   void detach(scene::Traversable::Observer* observer)
1157   {
1158     m_traverseObservers.detach(*observer);
1159   }
1160
1161   void render(RenderStateFlags state) const
1162   {
1163     if(!g_newLightDraw)
1164     {
1165       aabb_draw(m_aabb_light, state);
1166     }
1167     else
1168     {
1169       light_draw(m_aabb_light, state);
1170     }
1171   }
1172
1173   VolumeIntersectionValue intersectVolume(const VolumeTest& volume, const Matrix4& localToWorld) const
1174   {
1175     return volume.TestAABB(m_aabb_light, localToWorld);
1176   }
1177
1178   // cache
1179   const AABB& localAABB() const
1180   {
1181     return m_aabb_light;
1182   }
1183
1184
1185   mutable Matrix4 m_projectionOrientation;
1186
1187   void renderSolid(Renderer& renderer, const VolumeTest& volume, const Matrix4& localToWorld, bool selected) const
1188   {
1189     renderer.SetState(m_entity.getEntityClass().m_state_wire, Renderer::eWireframeOnly);
1190     renderer.SetState(m_colour.state(), Renderer::eFullMaterials);
1191     renderer.addRenderable(*this, localToWorld);
1192
1193     if(selected && g_lightRadii && string_empty(m_entity.getKeyValue("target")))
1194     {
1195       if(renderer.getStyle() == Renderer::eFullMaterials)
1196       {
1197         renderer.SetState(RenderLightRadiiFill::m_state, Renderer::eFullMaterials);
1198         renderer.Highlight(Renderer::ePrimitive, false);
1199         renderer.addRenderable(m_radii_fill, localToWorld);
1200       }
1201       else
1202       {
1203         renderer.addRenderable(m_radii_wire, localToWorld);
1204       }
1205     }
1206
1207     renderer.SetState(m_entity.getEntityClass().m_state_wire, Renderer::eFullMaterials);
1208
1209     if(g_lightType == LIGHTTYPE_DOOM3 && selected)
1210     {
1211       if(isProjected())
1212       {
1213         projection();
1214         m_projectionOrientation = rotation();
1215         vector4_to_vector3(m_projectionOrientation.t()) = localAABB().origin;
1216         renderer.addRenderable(m_renderProjection, m_projectionOrientation);
1217       }
1218       else
1219       {
1220         updateLightRadiiBox();
1221         renderer.addRenderable(m_radii_box, localToWorld);
1222       }
1223
1224       //draw the center of the light
1225       if(m_doom3Radius.m_useCenterKey)
1226       {
1227         renderer.Highlight(Renderer::ePrimitive, false);
1228         renderer.Highlight(Renderer::eFace, false);
1229         renderer.SetState(m_render_center.m_state, Renderer::eFullMaterials);
1230         renderer.SetState(m_render_center.m_state, Renderer::eWireframeOnly);
1231         renderer.addRenderable(m_render_center, localToWorld);
1232       }
1233     }
1234   }
1235   void renderWireframe(Renderer& renderer, const VolumeTest& volume, const Matrix4& localToWorld, bool selected) const
1236   {
1237     renderSolid(renderer, volume, localToWorld, selected);
1238     if(g_showNames)
1239     {
1240       renderer.addRenderable(m_renderName, localToWorld);
1241     }
1242   }
1243
1244   void testSelect(Selector& selector, SelectionTest& test, const Matrix4& localToWorld)
1245   {
1246     test.BeginMesh(localToWorld);
1247
1248     SelectionIntersection best;
1249     aabb_testselect(m_aabb_light, test, best);
1250     if(best.valid())
1251     {
1252       selector.addIntersection(best);
1253     }
1254   }
1255   
1256   void translate(const Vector3& translation)
1257   {
1258     m_aabb_light.origin = origin_translated(m_aabb_light.origin, translation);
1259   }
1260   void rotate(const Quaternion& rotation)
1261   {
1262     rotation_rotate(m_rotation, rotation);
1263   }
1264   void snapto(float snap)
1265   {
1266     if(g_lightType == LIGHTTYPE_DOOM3 && !m_useLightOrigin && !m_traverse.empty())
1267     {
1268       m_useLightOrigin = true;
1269       m_lightOrigin = m_originKey.m_origin;
1270     }
1271
1272     if(m_useLightOrigin)
1273     {
1274       m_lightOrigin = origin_snapped(m_lightOrigin, snap);
1275       writeLightOrigin();
1276     }
1277     else
1278     {
1279       m_originKey.m_origin = origin_snapped(m_originKey.m_origin, snap);
1280       m_originKey.write(&m_entity);
1281     }
1282   }
1283   void setLightRadius(const AABB& aabb)
1284   {
1285     m_aabb_light.origin = aabb.origin;
1286     m_doom3Radius.m_radiusTransformed = aabb.extents;
1287   }
1288   void transformLightRadius(const Matrix4& transform)
1289   {
1290     matrix4_transform_point(transform, m_aabb_light.origin);
1291   }
1292   void revertTransform()
1293   {
1294     m_aabb_light.origin = m_useLightOrigin ? m_lightOrigin : m_originKey.m_origin;
1295     rotation_assign(m_rotation, m_useLightRotation ? m_lightRotation : m_rotationKey.m_rotation);
1296     m_doom3Radius.m_radiusTransformed = m_doom3Radius.m_radius;
1297   }
1298   void freezeTransform()
1299   {
1300     if(g_lightType == LIGHTTYPE_DOOM3 && !m_useLightOrigin && !m_traverse.empty())
1301     {
1302       m_useLightOrigin = true;
1303     }
1304
1305     if(m_useLightOrigin)
1306     {
1307       m_lightOrigin = m_aabb_light.origin;
1308       writeLightOrigin();
1309     }
1310     else
1311     {
1312       m_originKey.m_origin = m_aabb_light.origin;
1313       m_originKey.write(&m_entity);
1314     }
1315
1316     if(g_lightType == LIGHTTYPE_DOOM3)
1317     {
1318       if(!m_useLightRotation && !m_traverse.empty())
1319       {
1320         m_useLightRotation = true;
1321       }
1322
1323       if(m_useLightRotation)
1324       {
1325         rotation_assign(m_lightRotation, m_rotation);
1326         write_rotation(m_lightRotation, &m_entity, "light_rotation");
1327       }
1328
1329       rotation_assign(m_rotationKey.m_rotation, m_rotation);
1330       write_rotation(m_rotationKey.m_rotation, &m_entity);
1331
1332       m_doom3Radius.m_radius = m_doom3Radius.m_radiusTransformed;
1333       if(m_doom3Radius.m_radius == c_defaultDoom3LightRadius)
1334       {
1335         m_entity.setKeyValue("light_radius", "");
1336       }
1337       else
1338       {
1339         write_origin(m_doom3Radius.m_radius, &m_entity, "light_radius");
1340       }
1341     }
1342   }
1343   void transformChanged()
1344   {
1345     revertTransform();
1346     m_evaluateTransform();
1347     updateOrigin();
1348   }
1349   typedef MemberCaller<Light, &Light::transformChanged> TransformChangedCaller;
1350
1351   mutable Matrix4 m_localPivot;
1352   const Matrix4& getLocalPivot() const
1353   {
1354     m_localPivot = rotation_toMatrix(m_rotation);
1355     vector4_to_vector3(m_localPivot.t()) = m_aabb_light.origin;
1356     return m_localPivot;
1357   }
1358
1359   void setLightChangedCallback(const Callback& callback)
1360   {
1361     m_doom3Radius.m_changed = callback;
1362   }
1363
1364   const AABB& aabb() const
1365   {
1366     m_doom3AABB = AABB(m_aabb_light.origin, m_doom3Radius.m_radiusTransformed);
1367     return m_doom3AABB;
1368   }
1369   bool testAABB(const AABB& other) const
1370   {
1371     if(isProjected())
1372     {
1373       Matrix4 transform = rotation();
1374       vector4_to_vector3(transform.t()) = localAABB().origin;
1375       projection();
1376       Frustum frustum(frustum_transformed(m_doom3Frustum, transform));
1377       return frustum_test_aabb(frustum, other) != c_volumeOutside;
1378     }
1379     // test against an AABB which contains the rotated bounds of this light.
1380     const AABB& bounds = aabb();
1381     return aabb_intersects_aabb(other, AABB(
1382       bounds.origin,
1383       Vector3(
1384         static_cast<float>(fabs(m_rotation[0] * bounds.extents[0])
1385                             + fabs(m_rotation[3] * bounds.extents[1])
1386                             + fabs(m_rotation[6] * bounds.extents[2])),
1387         static_cast<float>(fabs(m_rotation[1] * bounds.extents[0])
1388                             + fabs(m_rotation[4] * bounds.extents[1])
1389                             + fabs(m_rotation[7] * bounds.extents[2])),
1390         static_cast<float>(fabs(m_rotation[2] * bounds.extents[0])
1391                             + fabs(m_rotation[5] * bounds.extents[1])
1392                             + fabs(m_rotation[8] * bounds.extents[2]))
1393       )
1394     ));
1395   }
1396
1397   const Matrix4& rotation() const
1398   {
1399     m_doom3Rotation = rotation_toMatrix(m_rotation);
1400     return m_doom3Rotation;
1401   }
1402   const Vector3& offset() const
1403   {
1404     return m_doom3Radius.m_center;
1405   }
1406   const Vector3& colour() const
1407   {
1408     return m_colour.m_colour;
1409   }
1410
1411   bool isProjected() const
1412   {
1413     return m_useLightTarget && m_useLightUp && m_useLightRight;
1414   }
1415   void projectionChanged()
1416   {
1417     m_doom3ProjectionChanged = true;
1418     m_doom3Radius.m_changed();
1419     SceneChangeNotify();
1420   }
1421
1422   const Matrix4& projection() const
1423   {
1424     if(!m_doom3ProjectionChanged)
1425     {
1426       return m_doom3Projection;
1427     }
1428     m_doom3ProjectionChanged = false;
1429     m_doom3Projection = g_matrix4_identity;
1430     matrix4_translate_by_vec3(m_doom3Projection, Vector3(0.5f, 0.5f, 0));
1431     matrix4_scale_by_vec3(m_doom3Projection, Vector3(0.5f, 0.5f, 1));
1432
1433 #if 0
1434     Vector3 right = vector3_cross(m_lightUp, vector3_normalised(m_lightTarget));
1435     Vector3 up = vector3_cross(vector3_normalised(m_lightTarget), m_lightRight);
1436     Vector3 target = m_lightTarget;
1437     Matrix4 test(
1438       -right.x(), -right.y(), -right.z(), 0,
1439       -up.x(), -up.y(), -up.z(), 0,
1440       -target.x(), -target.y(), -target.z(), 0,
1441       0, 0, 0, 1
1442     );
1443     Matrix4 frustum = matrix4_frustum(-0.01, 0.01, -0.01, 0.01, 0.01, 1.0);
1444     test = matrix4_full_inverse(test);
1445     matrix4_premultiply_by_matrix4(test, frustum);
1446     matrix4_multiply_by_matrix4(m_doom3Projection, test);
1447 #elif 0
1448     const float nearFar = 1 / 49.5f;
1449     Vector3 right = vector3_cross(m_lightUp, vector3_normalised(m_lightTarget + m_lightRight));
1450     Vector3 up = vector3_cross(vector3_normalised(m_lightTarget + m_lightUp), m_lightRight);
1451     Vector3 target = vector3_negated(m_lightTarget * (1 + nearFar));
1452     float scale = -1 / vector3_length(m_lightTarget);
1453     Matrix4 test(
1454       -inverse(right.x()), -inverse(up.x()), -inverse(target.x()), 0,
1455       -inverse(right.y()), -inverse(up.y()), -inverse(target.y()), 0,
1456       -inverse(right.z()), -inverse(up.z()), -inverse(target.z()), scale,
1457       0, 0, -nearFar, 0
1458     );
1459     matrix4_multiply_by_matrix4(m_doom3Projection, test);
1460 #elif 0
1461     Vector3 leftA(m_lightTarget - m_lightRight);
1462     Vector3 leftB(m_lightRight + m_lightUp);
1463     Plane3 left(vector3_normalised(vector3_cross(leftA, leftB)) * (1.0 / 128), 0);
1464     Vector3 rightA(m_lightTarget + m_lightRight);
1465     Vector3 rightB(vector3_cross(rightA, m_lightTarget));
1466     Plane3 right(vector3_normalised(vector3_cross(rightA, rightB)) * (1.0 / 128), 0);
1467     Vector3 bottomA(m_lightTarget - m_lightUp);
1468     Vector3 bottomB(vector3_cross(bottomA, m_lightTarget));
1469     Plane3 bottom(vector3_normalised(vector3_cross(bottomA, bottomB)) * (1.0 / 128), 0);
1470     Vector3 topA(m_lightTarget + m_lightUp);
1471     Vector3 topB(vector3_cross(topA, m_lightTarget));
1472     Plane3 top(vector3_normalised(vector3_cross(topA, topB)) * (1.0 / 128), 0);
1473     Plane3 front(vector3_normalised(m_lightTarget) * (1.0 / 128), 1);
1474     Plane3 back(vector3_normalised(vector3_negated(m_lightTarget)) * (1.0 / 128), 0);
1475     Matrix4 test(matrix4_from_planes(plane3_flipped(left), plane3_flipped(right), plane3_flipped(bottom), plane3_flipped(top), plane3_flipped(front), plane3_flipped(back)));
1476     matrix4_multiply_by_matrix4(m_doom3Projection, test);
1477 #else
1478
1479     Plane3 lightProject[4];
1480
1481     Vector3 start = m_useLightStart && m_useLightEnd ? m_lightStart : vector3_normalised(m_lightTarget);
1482     Vector3 stop = m_useLightStart && m_useLightEnd ? m_lightEnd : m_lightTarget;
1483
1484           float rLen = vector3_length(m_lightRight);
1485           Vector3 right = vector3_divided(m_lightRight, rLen);
1486           float uLen = vector3_length(m_lightUp);
1487           Vector3 up = vector3_divided(m_lightUp, uLen);
1488           Vector3 normal = vector3_normalised(vector3_cross(up, right));
1489
1490           float dist = vector3_dot(m_lightTarget, normal);
1491           if ( dist < 0 ) {
1492                   dist = -dist;
1493                   normal = vector3_negated(normal);
1494           }
1495
1496           right *= ( 0.5f * dist ) / rLen;
1497           up *= -( 0.5f * dist ) / uLen;
1498
1499           lightProject[2] = Plane3(normal, 0);
1500           lightProject[0] = Plane3(right, 0);
1501           lightProject[1] = Plane3(up, 0);
1502
1503           // now offset to center
1504           Vector4 targetGlobal(m_lightTarget, 1);
1505     {
1506       float a = vector4_dot(targetGlobal, plane3_to_vector4(lightProject[0]));
1507       float b = vector4_dot(targetGlobal, plane3_to_vector4(lightProject[2]));
1508             float ofs = 0.5f - a / b;
1509             plane3_to_vector4(lightProject[0]) += plane3_to_vector4(lightProject[2]) * ofs;
1510     }
1511     {
1512       float a = vector4_dot(targetGlobal, plane3_to_vector4(lightProject[1]));
1513       float b = vector4_dot(targetGlobal, plane3_to_vector4(lightProject[2]));
1514             float ofs = 0.5f - a / b;
1515             plane3_to_vector4(lightProject[1]) += plane3_to_vector4(lightProject[2]) * ofs;
1516     }
1517
1518           // set the falloff vector
1519           Vector3 falloff = stop - start;
1520           float length = vector3_length(falloff);
1521     falloff = vector3_divided(falloff, length);
1522           if ( length <= 0 ) {
1523                   length = 1;
1524           }
1525     falloff *= (1.0f / length);
1526           lightProject[3] = Plane3(falloff, -vector3_dot(start, falloff));
1527
1528           // we want the planes of s=0, s=q, t=0, and t=q
1529           m_doom3Frustum.left = lightProject[0];
1530           m_doom3Frustum.bottom = lightProject[1];
1531           m_doom3Frustum.right = Plane3(lightProject[2].normal() - lightProject[0].normal(), lightProject[2].dist() - lightProject[0].dist());
1532           m_doom3Frustum.top = Plane3(lightProject[2].normal() - lightProject[1].normal(), lightProject[2].dist() - lightProject[1].dist());
1533
1534           // we want the planes of s=0 and s=1 for front and rear clipping planes
1535           m_doom3Frustum.front = lightProject[3];
1536
1537           m_doom3Frustum.back = lightProject[3];
1538           m_doom3Frustum.back.dist() -= 1.0f;
1539           m_doom3Frustum.back = plane3_flipped(m_doom3Frustum.back);
1540
1541     Matrix4 test(matrix4_from_planes(m_doom3Frustum.left, m_doom3Frustum.right, m_doom3Frustum.bottom, m_doom3Frustum.top, m_doom3Frustum.front, m_doom3Frustum.back));
1542     matrix4_multiply_by_matrix4(m_doom3Projection, test);
1543
1544     m_doom3Frustum.left = plane3_normalised(m_doom3Frustum.left);
1545     m_doom3Frustum.right = plane3_normalised(m_doom3Frustum.right);
1546     m_doom3Frustum.bottom = plane3_normalised(m_doom3Frustum.bottom);
1547     m_doom3Frustum.top = plane3_normalised(m_doom3Frustum.top);
1548     m_doom3Frustum.back = plane3_normalised(m_doom3Frustum.back);
1549     m_doom3Frustum.front = plane3_normalised(m_doom3Frustum.front);
1550 #endif
1551     //matrix4_scale_by_vec3(m_doom3Projection, Vector3(1.0 / 128, 1.0 / 128, 1.0 / 128));
1552     return m_doom3Projection;
1553   }
1554
1555   Shader* getShader() const
1556   {
1557     return m_shader.get();
1558   }
1559 };
1560
1561 class LightInstance :
1562   public TargetableInstance,
1563   public TransformModifier,
1564   public Renderable,
1565   public SelectionTestable,
1566   public RendererLight,
1567   public PlaneSelectable,
1568   public ComponentSelectionTestable
1569 {
1570   class TypeCasts
1571   {
1572     InstanceTypeCastTable m_casts;
1573   public:
1574     TypeCasts()
1575     {
1576       m_casts = TargetableInstance::StaticTypeCasts::instance().get();
1577       InstanceContainedCast<LightInstance, Bounded>::install(m_casts);
1578       //InstanceContainedCast<LightInstance, Cullable>::install(m_casts);
1579       InstanceStaticCast<LightInstance, Renderable>::install(m_casts);
1580       InstanceStaticCast<LightInstance, SelectionTestable>::install(m_casts);
1581       InstanceStaticCast<LightInstance, Transformable>::install(m_casts);
1582       InstanceStaticCast<LightInstance, PlaneSelectable>::install(m_casts);
1583       InstanceStaticCast<LightInstance, ComponentSelectionTestable>::install(m_casts);
1584       InstanceIdentityCast<LightInstance>::install(m_casts);
1585     }
1586     InstanceTypeCastTable& get()
1587     {
1588       return m_casts;
1589     }
1590   };
1591
1592   Light& m_contained;
1593   DragPlanes m_dragPlanes;// dragplanes for lightresizing using mousedrag
1594 public:
1595   typedef LazyStatic<TypeCasts> StaticTypeCasts;
1596
1597   Bounded& get(NullType<Bounded>)
1598   {
1599     return m_contained;
1600   }
1601
1602   STRING_CONSTANT(Name, "LightInstance");
1603
1604   LightInstance(const scene::Path& path, scene::Instance* parent, Light& contained) :
1605     TargetableInstance(path, parent, this, StaticTypeCasts::instance().get(), contained.getEntity(), *this),
1606     TransformModifier(Light::TransformChangedCaller(contained), ApplyTransformCaller(*this)),
1607     m_contained(contained),
1608     m_dragPlanes(SelectedChangedComponentCaller(*this))
1609   {
1610     m_contained.instanceAttach(Instance::path());
1611
1612     if(g_lightType == LIGHTTYPE_DOOM3)
1613     {
1614       GlobalShaderCache().attach(*this);
1615       m_contained.setLightChangedCallback(LightChangedCaller(*this));
1616     }
1617
1618     StaticRenderableConnectionLines::instance().attach(*this);
1619   }
1620   ~LightInstance()
1621   {
1622     StaticRenderableConnectionLines::instance().detach(*this);
1623
1624     if(g_lightType == LIGHTTYPE_DOOM3)
1625     {
1626       m_contained.setLightChangedCallback(Callback());
1627       GlobalShaderCache().detach(*this);
1628     }
1629
1630     m_contained.instanceDetach(Instance::path());
1631   }
1632   void renderSolid(Renderer& renderer, const VolumeTest& volume) const
1633   {
1634     m_contained.renderSolid(renderer, volume, Instance::localToWorld(), getSelectable().isSelected());
1635   }
1636   void renderWireframe(Renderer& renderer, const VolumeTest& volume) const
1637   {
1638     m_contained.renderWireframe(renderer, volume, Instance::localToWorld(), getSelectable().isSelected());
1639   }
1640   void testSelect(Selector& selector, SelectionTest& test)
1641   {
1642     m_contained.testSelect(selector, test, Instance::localToWorld());
1643   }
1644
1645   void selectPlanes(Selector& selector, SelectionTest& test, const PlaneCallback& selectedPlaneCallback)
1646   {
1647     test.BeginMesh(localToWorld());
1648     m_dragPlanes.selectPlanes(m_contained.aabb(), selector, test, selectedPlaneCallback, rotation());
1649   }
1650   void selectReversedPlanes(Selector& selector, const SelectedPlanes& selectedPlanes)
1651   {
1652     m_dragPlanes.selectReversedPlanes(m_contained.aabb(), selector, selectedPlanes, rotation());
1653   }
1654   
1655   bool isSelectedComponents() const
1656   {
1657     return m_dragPlanes.isSelected();
1658   }
1659   void setSelectedComponents(bool select, SelectionSystem::EComponentMode mode)
1660   {
1661     if(mode == SelectionSystem::eFace)
1662     {
1663       m_dragPlanes.setSelected(false);
1664     }
1665   }
1666   void testSelectComponents(Selector& selector, SelectionTest& test, SelectionSystem::EComponentMode mode)
1667   {
1668   }
1669
1670   void selectedChangedComponent(const Selectable& selectable)
1671   {
1672     GlobalSelectionSystem().getObserver(SelectionSystem::eComponent)(selectable);
1673     GlobalSelectionSystem().onComponentSelection(*this, selectable);
1674   }
1675   typedef MemberCaller1<LightInstance, const Selectable&, &LightInstance::selectedChangedComponent> SelectedChangedComponentCaller;
1676
1677   void evaluateTransform()
1678   {
1679     if(getType() == TRANSFORM_PRIMITIVE)
1680     {
1681       m_contained.translate(getTranslation());
1682       m_contained.rotate(getRotation());
1683     }
1684     else
1685     {
1686       //globalOutputStream() << getTranslation() << "\n";
1687
1688       m_dragPlanes.m_bounds = m_contained.aabb();
1689       m_contained.setLightRadius(m_dragPlanes.evaluateResize(getTranslation(), rotation()));
1690     }
1691   }
1692   void applyTransform()
1693   {
1694     m_contained.revertTransform();
1695     evaluateTransform();
1696     m_contained.freezeTransform();
1697   }
1698   typedef MemberCaller<LightInstance, &LightInstance::applyTransform> ApplyTransformCaller;
1699
1700   void lightChanged()
1701   {
1702     GlobalShaderCache().changed(*this);
1703   }
1704   typedef MemberCaller<LightInstance, &LightInstance::lightChanged> LightChangedCaller;
1705
1706   Shader* getShader() const
1707   {
1708     return m_contained.getShader();
1709   }
1710   const AABB& aabb() const
1711   {
1712     return m_contained.aabb();
1713   }
1714   bool testAABB(const AABB& other) const
1715   {
1716     return m_contained.testAABB(other);
1717   }
1718   const Matrix4& rotation() const
1719   {
1720     return m_contained.rotation();
1721   }
1722   const Vector3& offset() const
1723   {
1724     return m_contained.offset();
1725   }
1726   const Vector3& colour() const
1727   {
1728     return m_contained.colour();
1729   }
1730
1731   bool isProjected() const
1732   {
1733     return m_contained.isProjected();
1734   }
1735   const Matrix4& projection() const
1736   {
1737     return m_contained.projection();
1738   }
1739 };
1740
1741 class LightNode :
1742   public scene::Node::Symbiot,
1743   public scene::Instantiable,
1744   public scene::Cloneable,
1745   public scene::Traversable::Observer
1746 {
1747   class TypeCasts
1748   {
1749     NodeTypeCastTable m_casts;
1750   public:
1751     TypeCasts()
1752     {
1753       NodeStaticCast<LightNode, scene::Instantiable>::install(m_casts);
1754       NodeStaticCast<LightNode, scene::Cloneable>::install(m_casts);
1755       if(g_lightType == LIGHTTYPE_DOOM3)
1756       {
1757         NodeContainedCast<LightNode, scene::Traversable>::install(m_casts);
1758       }
1759       NodeContainedCast<LightNode, Editable>::install(m_casts);
1760       NodeContainedCast<LightNode, Snappable>::install(m_casts);
1761       NodeContainedCast<LightNode, TransformNode>::install(m_casts);
1762       NodeContainedCast<LightNode, Entity>::install(m_casts);
1763       NodeContainedCast<LightNode, Nameable>::install(m_casts);
1764       NodeContainedCast<LightNode, Namespaced>::install(m_casts);
1765     }
1766     NodeTypeCastTable& get()
1767     {
1768       return m_casts;
1769     }
1770   };
1771
1772
1773   scene::Node m_node;
1774   InstanceSet m_instances;
1775   Light m_contained;
1776
1777   void construct()
1778   {
1779     if(g_lightType == LIGHTTYPE_DOOM3)
1780     {
1781       m_contained.attach(this);
1782     }
1783   }
1784   void destroy()
1785   {
1786     if(g_lightType == LIGHTTYPE_DOOM3)
1787     {
1788       m_contained.detach(this);
1789     }
1790   }
1791 public:
1792   typedef LazyStatic<TypeCasts> StaticTypeCasts;
1793
1794   scene::Traversable& get(NullType<scene::Traversable>)
1795   {
1796     return m_contained.getTraversable();
1797   }
1798   Editable& get(NullType<Editable>)
1799   {
1800     return m_contained;
1801   }
1802   Snappable& get(NullType<Snappable>)
1803   {
1804     return m_contained;
1805   }
1806   TransformNode& get(NullType<TransformNode>)
1807   {
1808     return m_contained.getTransformNode();
1809   }
1810   Entity& get(NullType<Entity>)
1811   {
1812     return m_contained.getEntity();
1813   }
1814   Nameable& get(NullType<Nameable>)
1815   {
1816     return m_contained.getNameable();
1817   }
1818   Namespaced& get(NullType<Namespaced>)
1819   {
1820     return m_contained.getNamespaced();
1821   }
1822
1823   LightNode(EntityClass* eclass) :
1824     m_node(this, this, StaticTypeCasts::instance().get()),
1825     m_contained(eclass, m_node, InstanceSet::TransformChangedCaller(m_instances), InstanceSet::BoundsChangedCaller(m_instances), InstanceSetEvaluateTransform<LightInstance>::Caller(m_instances))
1826   {
1827     construct();
1828   }
1829   LightNode(const LightNode& other) :
1830     scene::Node::Symbiot(other),
1831     scene::Instantiable(other),
1832     scene::Cloneable(other),
1833     scene::Traversable::Observer(other),
1834     m_node(this, this, StaticTypeCasts::instance().get()),
1835     m_contained(other.m_contained, m_node, InstanceSet::TransformChangedCaller(m_instances), InstanceSet::BoundsChangedCaller(m_instances), InstanceSetEvaluateTransform<LightInstance>::Caller(m_instances))
1836   {
1837     construct();
1838   }
1839   ~LightNode()
1840   {
1841     destroy();
1842   }
1843
1844   void release()
1845   {
1846     delete this;
1847   }
1848   scene::Node& node()
1849   {
1850     return m_node;
1851   }
1852
1853   scene::Node& clone() const
1854   {
1855     return (new LightNode(*this))->node();
1856   }
1857
1858   void insert(scene::Node& child)
1859   {
1860     m_instances.insert(child);
1861   }
1862   void erase(scene::Node& child)
1863   {
1864     m_instances.erase(child);
1865   }
1866
1867   scene::Instance* create(const scene::Path& path, scene::Instance* parent)
1868   {
1869     return new LightInstance(path, parent, m_contained);
1870   }
1871   void forEachInstance(const scene::Instantiable::Visitor& visitor)
1872   {
1873     m_instances.forEachInstance(visitor);
1874   }
1875   void insert(scene::Instantiable::Observer* observer, const scene::Path& path, scene::Instance* instance)
1876   {
1877     m_instances.insert(observer, path, instance);
1878   }
1879   scene::Instance* erase(scene::Instantiable::Observer* observer, const scene::Path& path)
1880   {
1881     return m_instances.erase(observer, path);
1882   }
1883 };
1884
1885 void Light_Construct(LightType lightType)
1886 {
1887   g_lightType = lightType;
1888   if(g_lightType == LIGHTTYPE_DOOM3)
1889   {
1890     LightShader::m_defaultShader = "lights/defaultPointLight";
1891 #if 0
1892     LightShader::m_defaultShader = "lights/defaultProjectedLight";
1893 #endif
1894   }
1895   RenderLightRadiiFill::m_state = GlobalShaderCache().capture("$Q3MAP2_LIGHT_SPHERE");
1896   RenderLightCenter::m_state = GlobalShaderCache().capture("$BIGPOINT");
1897 }
1898 void Light_Destroy()
1899 {
1900   GlobalShaderCache().release("$Q3MAP2_LIGHT_SPHERE");
1901   GlobalShaderCache().release("$BIGPOINT");
1902 }
1903
1904 scene::Node& New_Light(EntityClass* eclass)
1905 {
1906   return (new LightNode(eclass))->node();
1907 }