]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - plugins/entity/light.cpp
Remove some dead/debug code
[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         Plane3 lightProject[4];
1305
1306         Vector3 start = m_useLightStart && m_useLightEnd ? m_lightStart : vector3_normalised( m_lightTarget );
1307         Vector3 stop = m_useLightStart && m_useLightEnd ? m_lightEnd : m_lightTarget;
1308
1309         float rLen = vector3_length( m_lightRight );
1310         Vector3 right = vector3_divided( m_lightRight, rLen );
1311         float uLen = vector3_length( m_lightUp );
1312         Vector3 up = vector3_divided( m_lightUp, uLen );
1313         Vector3 normal = vector3_normalised( vector3_cross( up, right ) );
1314
1315         float dist = vector3_dot( m_lightTarget, normal );
1316         if ( dist < 0 ) {
1317                 dist = -dist;
1318                 normal = vector3_negated( normal );
1319         }
1320
1321         right *= ( 0.5f * dist ) / rLen;
1322         up *= -( 0.5f * dist ) / uLen;
1323
1324         lightProject[2] = Plane3( normal, 0 );
1325         lightProject[0] = Plane3( right, 0 );
1326         lightProject[1] = Plane3( up, 0 );
1327
1328         // now offset to center
1329         Vector4 targetGlobal( m_lightTarget, 1 );
1330         {
1331                 float a = vector4_dot( targetGlobal, plane3_to_vector4( lightProject[0] ) );
1332                 float b = vector4_dot( targetGlobal, plane3_to_vector4( lightProject[2] ) );
1333                 float ofs = 0.5f - a / b;
1334                 plane3_to_vector4( lightProject[0] ) += plane3_to_vector4( lightProject[2] ) * ofs;
1335         }
1336         {
1337                 float a = vector4_dot( targetGlobal, plane3_to_vector4( lightProject[1] ) );
1338                 float b = vector4_dot( targetGlobal, plane3_to_vector4( lightProject[2] ) );
1339                 float ofs = 0.5f - a / b;
1340                 plane3_to_vector4( lightProject[1] ) += plane3_to_vector4( lightProject[2] ) * ofs;
1341         }
1342
1343         // set the falloff vector
1344         Vector3 falloff = stop - start;
1345         float length = vector3_length( falloff );
1346         falloff = vector3_divided( falloff, length );
1347         if ( length <= 0 ) {
1348                 length = 1;
1349         }
1350         falloff *= ( 1.0f / length );
1351         lightProject[3] = Plane3( falloff, -vector3_dot( start, falloff ) );
1352
1353         // we want the planes of s=0, s=q, t=0, and t=q
1354         m_doom3Frustum.left = lightProject[0];
1355         m_doom3Frustum.bottom = lightProject[1];
1356         m_doom3Frustum.right = Plane3( lightProject[2].normal() - lightProject[0].normal(), lightProject[2].dist() - lightProject[0].dist() );
1357         m_doom3Frustum.top = Plane3( lightProject[2].normal() - lightProject[1].normal(), lightProject[2].dist() - lightProject[1].dist() );
1358
1359         // we want the planes of s=0 and s=1 for front and rear clipping planes
1360         m_doom3Frustum.front = lightProject[3];
1361
1362         m_doom3Frustum.back = lightProject[3];
1363         m_doom3Frustum.back.dist() -= 1.0f;
1364         m_doom3Frustum.back = plane3_flipped( m_doom3Frustum.back );
1365
1366         Matrix4 test( matrix4_from_planes( m_doom3Frustum.left, m_doom3Frustum.right, m_doom3Frustum.bottom, m_doom3Frustum.top, m_doom3Frustum.front, m_doom3Frustum.back ) );
1367         matrix4_multiply_by_matrix4( m_doom3Projection, test );
1368
1369         m_doom3Frustum.left = plane3_normalised( m_doom3Frustum.left );
1370         m_doom3Frustum.right = plane3_normalised( m_doom3Frustum.right );
1371         m_doom3Frustum.bottom = plane3_normalised( m_doom3Frustum.bottom );
1372         m_doom3Frustum.top = plane3_normalised( m_doom3Frustum.top );
1373         m_doom3Frustum.back = plane3_normalised( m_doom3Frustum.back );
1374         m_doom3Frustum.front = plane3_normalised( m_doom3Frustum.front );
1375
1376         return m_doom3Projection;
1377 }
1378
1379 Shader* getShader() const {
1380         return m_shader.get();
1381 }
1382 };
1383
1384 class LightInstance :
1385         public TargetableInstance,
1386         public TransformModifier,
1387         public Renderable,
1388         public SelectionTestable,
1389         public RendererLight,
1390         public PlaneSelectable,
1391         public ComponentSelectionTestable
1392 {
1393 class TypeCasts
1394 {
1395 InstanceTypeCastTable m_casts;
1396 public:
1397 TypeCasts(){
1398         m_casts = TargetableInstance::StaticTypeCasts::instance().get();
1399         InstanceContainedCast<LightInstance, Bounded>::install( m_casts );
1400         //InstanceContainedCast<LightInstance, Cullable>::install(m_casts);
1401         InstanceStaticCast<LightInstance, Renderable>::install( m_casts );
1402         InstanceStaticCast<LightInstance, SelectionTestable>::install( m_casts );
1403         InstanceStaticCast<LightInstance, Transformable>::install( m_casts );
1404         InstanceStaticCast<LightInstance, PlaneSelectable>::install( m_casts );
1405         InstanceStaticCast<LightInstance, ComponentSelectionTestable>::install( m_casts );
1406         InstanceIdentityCast<LightInstance>::install( m_casts );
1407 }
1408 InstanceTypeCastTable& get(){
1409         return m_casts;
1410 }
1411 };
1412
1413 Light& m_contained;
1414 DragPlanes m_dragPlanes;  // dragplanes for lightresizing using mousedrag
1415 public:
1416 typedef LazyStatic<TypeCasts> StaticTypeCasts;
1417
1418 Bounded& get( NullType<Bounded>){
1419         return m_contained;
1420 }
1421
1422 STRING_CONSTANT( Name, "LightInstance" );
1423
1424 LightInstance( const scene::Path& path, scene::Instance* parent, Light& contained ) :
1425         TargetableInstance( path, parent, this, StaticTypeCasts::instance().get(), contained.getEntity(), *this ),
1426         TransformModifier( Light::TransformChangedCaller( contained ), ApplyTransformCaller( *this ) ),
1427         m_contained( contained ),
1428         m_dragPlanes( SelectedChangedComponentCaller( *this ) ){
1429         m_contained.instanceAttach( Instance::path() );
1430
1431         if ( g_lightType == LIGHTTYPE_DOOM3 ) {
1432                 GlobalShaderCache().attach( *this );
1433                 m_contained.setLightChangedCallback( LightChangedCaller( *this ) );
1434         }
1435
1436         StaticRenderableConnectionLines::instance().attach( *this );
1437 }
1438 ~LightInstance(){
1439         StaticRenderableConnectionLines::instance().detach( *this );
1440
1441         if ( g_lightType == LIGHTTYPE_DOOM3 ) {
1442                 m_contained.setLightChangedCallback( Callback() );
1443                 GlobalShaderCache().detach( *this );
1444         }
1445
1446         m_contained.instanceDetach( Instance::path() );
1447 }
1448 void renderSolid( Renderer& renderer, const VolumeTest& volume ) const {
1449         m_contained.renderSolid( renderer, volume, Instance::localToWorld(), getSelectable().isSelected() );
1450 }
1451 void renderWireframe( Renderer& renderer, const VolumeTest& volume ) const {
1452         m_contained.renderWireframe( renderer, volume, Instance::localToWorld(), getSelectable().isSelected() );
1453 }
1454 void testSelect( Selector& selector, SelectionTest& test ){
1455         m_contained.testSelect( selector, test, Instance::localToWorld() );
1456 }
1457
1458 void selectPlanes( Selector& selector, SelectionTest& test, const PlaneCallback& selectedPlaneCallback ){
1459         test.BeginMesh( localToWorld() );
1460         m_dragPlanes.selectPlanes( m_contained.aabb(), selector, test, selectedPlaneCallback, rotation() );
1461 }
1462 void selectReversedPlanes( Selector& selector, const SelectedPlanes& selectedPlanes ){
1463         m_dragPlanes.selectReversedPlanes( m_contained.aabb(), selector, selectedPlanes, rotation() );
1464 }
1465
1466 bool isSelectedComponents() const {
1467         return m_dragPlanes.isSelected();
1468 }
1469 void setSelectedComponents( bool select, SelectionSystem::EComponentMode mode ){
1470         if ( mode == SelectionSystem::eFace ) {
1471                 m_dragPlanes.setSelected( false );
1472         }
1473 }
1474 void testSelectComponents( Selector& selector, SelectionTest& test, SelectionSystem::EComponentMode mode ){
1475 }
1476
1477 void selectedChangedComponent( const Selectable& selectable ){
1478         GlobalSelectionSystem().getObserver ( SelectionSystem::eComponent )( selectable );
1479         GlobalSelectionSystem().onComponentSelection( *this, selectable );
1480 }
1481 typedef MemberCaller1<LightInstance, const Selectable&, &LightInstance::selectedChangedComponent> SelectedChangedComponentCaller;
1482
1483 void evaluateTransform(){
1484         if ( getType() == TRANSFORM_PRIMITIVE ) {
1485                 m_contained.translate( getTranslation() );
1486                 m_contained.rotate( getRotation() );
1487         }
1488         else
1489         {
1490                 //globalOutputStream() << getTranslation() << "\n";
1491
1492                 m_dragPlanes.m_bounds = m_contained.aabb();
1493                 m_contained.setLightRadius( m_dragPlanes.evaluateResize( getTranslation(), rotation() ) );
1494         }
1495 }
1496 void applyTransform(){
1497         m_contained.revertTransform();
1498         evaluateTransform();
1499         m_contained.freezeTransform();
1500 }
1501 typedef MemberCaller<LightInstance, &LightInstance::applyTransform> ApplyTransformCaller;
1502
1503 void lightChanged(){
1504         GlobalShaderCache().changed( *this );
1505 }
1506 typedef MemberCaller<LightInstance, &LightInstance::lightChanged> LightChangedCaller;
1507
1508 Shader* getShader() const {
1509         return m_contained.getShader();
1510 }
1511 const AABB& aabb() const {
1512         return m_contained.aabb();
1513 }
1514 bool testAABB( const AABB& other ) const {
1515         return m_contained.testAABB( other );
1516 }
1517 const Matrix4& rotation() const {
1518         return m_contained.rotation();
1519 }
1520 const Vector3& offset() const {
1521         return m_contained.offset();
1522 }
1523 const Vector3& colour() const {
1524         return m_contained.colour();
1525 }
1526
1527 bool isProjected() const {
1528         return m_contained.isProjected();
1529 }
1530 const Matrix4& projection() const {
1531         return m_contained.projection();
1532 }
1533 };
1534
1535 class LightNode :
1536         public scene::Node::Symbiot,
1537         public scene::Instantiable,
1538         public scene::Cloneable,
1539         public scene::Traversable::Observer
1540 {
1541 class TypeCasts
1542 {
1543 NodeTypeCastTable m_casts;
1544 public:
1545 TypeCasts(){
1546         NodeStaticCast<LightNode, scene::Instantiable>::install( m_casts );
1547         NodeStaticCast<LightNode, scene::Cloneable>::install( m_casts );
1548         if ( g_lightType == LIGHTTYPE_DOOM3 ) {
1549                 NodeContainedCast<LightNode, scene::Traversable>::install( m_casts );
1550         }
1551         NodeContainedCast<LightNode, Editable>::install( m_casts );
1552         NodeContainedCast<LightNode, Snappable>::install( m_casts );
1553         NodeContainedCast<LightNode, TransformNode>::install( m_casts );
1554         NodeContainedCast<LightNode, Entity>::install( m_casts );
1555         NodeContainedCast<LightNode, Nameable>::install( m_casts );
1556         NodeContainedCast<LightNode, Namespaced>::install( m_casts );
1557 }
1558 NodeTypeCastTable& get(){
1559         return m_casts;
1560 }
1561 };
1562
1563
1564 scene::Node m_node;
1565 InstanceSet m_instances;
1566 Light m_contained;
1567
1568 void construct(){
1569         if ( g_lightType == LIGHTTYPE_DOOM3 ) {
1570                 m_contained.attach( this );
1571         }
1572 }
1573 void destroy(){
1574         if ( g_lightType == LIGHTTYPE_DOOM3 ) {
1575                 m_contained.detach( this );
1576         }
1577 }
1578 public:
1579 typedef LazyStatic<TypeCasts> StaticTypeCasts;
1580
1581 scene::Traversable& get( NullType<scene::Traversable>){
1582         return m_contained.getTraversable();
1583 }
1584 Editable& get( NullType<Editable>){
1585         return m_contained;
1586 }
1587 Snappable& get( NullType<Snappable>){
1588         return m_contained;
1589 }
1590 TransformNode& get( NullType<TransformNode>){
1591         return m_contained.getTransformNode();
1592 }
1593 Entity& get( NullType<Entity>){
1594         return m_contained.getEntity();
1595 }
1596 Nameable& get( NullType<Nameable>){
1597         return m_contained.getNameable();
1598 }
1599 Namespaced& get( NullType<Namespaced>){
1600         return m_contained.getNamespaced();
1601 }
1602
1603 LightNode( EntityClass* eclass ) :
1604         m_node( this, this, StaticTypeCasts::instance().get() ),
1605         m_contained( eclass, m_node, InstanceSet::TransformChangedCaller( m_instances ), InstanceSet::BoundsChangedCaller( m_instances ), InstanceSetEvaluateTransform<LightInstance>::Caller( m_instances ) ){
1606         construct();
1607 }
1608 LightNode( const LightNode& other ) :
1609         scene::Node::Symbiot( other ),
1610         scene::Instantiable( other ),
1611         scene::Cloneable( other ),
1612         scene::Traversable::Observer( other ),
1613         m_node( this, this, StaticTypeCasts::instance().get() ),
1614         m_contained( other.m_contained, m_node, InstanceSet::TransformChangedCaller( m_instances ), InstanceSet::BoundsChangedCaller( m_instances ), InstanceSetEvaluateTransform<LightInstance>::Caller( m_instances ) ){
1615         construct();
1616 }
1617 ~LightNode(){
1618         destroy();
1619 }
1620
1621 void release(){
1622         delete this;
1623 }
1624 scene::Node& node(){
1625         return m_node;
1626 }
1627
1628 scene::Node& clone() const {
1629         return ( new LightNode( *this ) )->node();
1630 }
1631
1632 void insert( scene::Node& child ){
1633         m_instances.insert( child );
1634 }
1635 void erase( scene::Node& child ){
1636         m_instances.erase( child );
1637 }
1638
1639 scene::Instance* create( const scene::Path& path, scene::Instance* parent ){
1640         return new LightInstance( path, parent, m_contained );
1641 }
1642 void forEachInstance( const scene::Instantiable::Visitor& visitor ){
1643         m_instances.forEachInstance( visitor );
1644 }
1645 void insert( scene::Instantiable::Observer* observer, const scene::Path& path, scene::Instance* instance ){
1646         m_instances.insert( observer, path, instance );
1647 }
1648 scene::Instance* erase( scene::Instantiable::Observer* observer, const scene::Path& path ){
1649         return m_instances.erase( observer, path );
1650 }
1651 };
1652
1653 void Light_Construct( LightType lightType ){
1654         g_lightType = lightType;
1655         if ( g_lightType == LIGHTTYPE_DOOM3 ) {
1656                 LightShader::m_defaultShader = "lights/defaultPointLight";
1657         }
1658         RenderLightRadiiFill::m_state = GlobalShaderCache().capture( "$Q3MAP2_LIGHT_SPHERE" );
1659         RenderLightCenter::m_state = GlobalShaderCache().capture( "$BIGPOINT" );
1660 }
1661 void Light_Destroy(){
1662         GlobalShaderCache().release( "$Q3MAP2_LIGHT_SPHERE" );
1663         GlobalShaderCache().release( "$BIGPOINT" );
1664 }
1665
1666 scene::Node& New_Light( EntityClass* eclass ){
1667         return ( new LightNode( eclass ) )->node();
1668 }