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