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