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