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