]> de.git.xonotic.org Git - xonotic/netradiant.git/blob - plugins/entity/light.cpp
Merge branch 'NateEag-master-patch-12920' into 'master'
[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 AABB m_aabb_light;
777
778 RenderLightRadiiWire m_radii_wire;
779 RenderLightRadiiFill m_radii_fill;
780 RenderLightRadiiBox m_radii_box;
781 RenderLightCenter m_render_center;
782 RenderableNamedEntity m_renderName;
783
784 Vector3 m_lightOrigin;
785 bool m_useLightOrigin;
786 Float9 m_lightRotation;
787 bool m_useLightRotation;
788
789 Vector3 m_lightTarget;
790 bool m_useLightTarget;
791 Vector3 m_lightUp;
792 bool m_useLightUp;
793 Vector3 m_lightRight;
794 bool m_useLightRight;
795 Vector3 m_lightStart;
796 bool m_useLightStart;
797 Vector3 m_lightEnd;
798 bool m_useLightEnd;
799
800 mutable AABB m_doom3AABB;
801 mutable Matrix4 m_doom3Rotation;
802 mutable Matrix4 m_doom3Projection;
803 mutable Frustum m_doom3Frustum;
804 mutable bool m_doom3ProjectionChanged;
805
806 RenderLightProjection m_renderProjection;
807
808 LightShader m_shader;
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_aabb_light( Vector3( 0, 0, 0 ), Vector3( 12, 12, 12 ) ),
986         m_radii_wire( m_radii, m_aabb_light.origin ),
987         m_radii_fill( m_radii, m_aabb_light.origin ),
988         m_radii_box( m_aabb_light.origin ),
989         m_render_center( m_doom3Radius.m_center, m_entity.getEntityClass() ),
990         m_renderName( m_named, m_aabb_light.origin ),
991         m_useLightOrigin( false ),
992         m_useLightRotation( false ),
993         m_renderProjection( m_doom3Projection ),
994         m_transformChanged( transformChanged ),
995         m_boundsChanged( boundsChanged ),
996         m_evaluateTransform( evaluateTransform ){
997         construct();
998 }
999 Light( const Light& other, scene::Node& node, const Callback<void()>& transformChanged, const Callback<void()>& boundsChanged, const Callback<void()>& evaluateTransform ) :
1000         m_entity( other.m_entity ),
1001         m_originKey( OriginChangedCaller( *this ) ),
1002         m_rotationKey( RotationChangedCaller( *this ) ),
1003         m_colour( Callback<void()>() ),
1004         m_filter( m_entity, node ),
1005         m_named( m_entity ),
1006         m_nameKeys( m_entity ),
1007         m_funcStaticOrigin( m_traverse, m_originKey.m_origin ),
1008         m_doom3Radius( EntityClass_valueForKey( m_entity.getEntityClass(), "light_radius" ) ),
1009         m_aabb_light( Vector3( 0, 0, 0 ), Vector3( 12, 12, 12 ) ),
1010         m_radii_wire( m_radii, m_aabb_light.origin ),
1011         m_radii_fill( m_radii, m_aabb_light.origin ),
1012         m_radii_box( m_aabb_light.origin ),
1013         m_render_center( m_doom3Radius.m_center, m_entity.getEntityClass() ),
1014         m_renderName( m_named, m_aabb_light.origin ),
1015         m_useLightOrigin( false ),
1016         m_useLightRotation( false ),
1017         m_renderProjection( m_doom3Projection ),
1018         m_transformChanged( transformChanged ),
1019         m_boundsChanged( boundsChanged ),
1020         m_evaluateTransform( evaluateTransform ){
1021         construct();
1022 }
1023 ~Light(){
1024         destroy();
1025 }
1026
1027 InstanceCounter m_instanceCounter;
1028 void instanceAttach( const scene::Path& path ){
1029         if ( ++m_instanceCounter.m_count == 1 ) {
1030                 m_filter.instanceAttach();
1031                 m_entity.instanceAttach( path_find_mapfile( path.begin(), path.end() ) );
1032                 if ( g_lightType == LIGHTTYPE_DOOM3 ) {
1033                         m_traverse.instanceAttach( path_find_mapfile( path.begin(), path.end() ) );
1034                 }
1035                 m_entity.attach( m_keyObservers );
1036
1037                 if ( g_lightType == LIGHTTYPE_DOOM3 ) {
1038                         m_funcStaticOrigin.enable();
1039                 }
1040         }
1041 }
1042 void instanceDetach( const scene::Path& path ){
1043         if ( --m_instanceCounter.m_count == 0 ) {
1044                 if ( g_lightType == LIGHTTYPE_DOOM3 ) {
1045                         m_funcStaticOrigin.disable();
1046                 }
1047
1048                 m_entity.detach( m_keyObservers );
1049                 if ( g_lightType == LIGHTTYPE_DOOM3 ) {
1050                         m_traverse.instanceDetach( path_find_mapfile( path.begin(), path.end() ) );
1051                 }
1052                 m_entity.instanceDetach( path_find_mapfile( path.begin(), path.end() ) );
1053                 m_filter.instanceDetach();
1054         }
1055 }
1056
1057 EntityKeyValues& getEntity(){
1058         return m_entity;
1059 }
1060 const EntityKeyValues& getEntity() const {
1061         return m_entity;
1062 }
1063
1064 scene::Traversable& getTraversable(){
1065         return m_traverse;
1066 }
1067 Namespaced& getNamespaced(){
1068         return m_nameKeys;
1069 }
1070 Nameable& getNameable(){
1071         return m_named;
1072 }
1073 TransformNode& getTransformNode(){
1074         return m_transform;
1075 }
1076
1077 void attach( scene::Traversable::Observer* observer ){
1078         m_traverseObservers.attach( *observer );
1079 }
1080 void detach( scene::Traversable::Observer* observer ){
1081         m_traverseObservers.detach( *observer );
1082 }
1083
1084 void render( RenderStateFlags state ) const {
1085         if ( !g_newLightDraw ) {
1086                 aabb_draw( m_aabb_light, state );
1087         }
1088         else
1089         {
1090                 light_draw( m_aabb_light, state );
1091         }
1092 }
1093
1094 VolumeIntersectionValue intersectVolume( const VolumeTest& volume, const Matrix4& localToWorld ) const {
1095         return volume.TestAABB( m_aabb_light, localToWorld );
1096 }
1097
1098 // cache
1099 const AABB& localAABB() const {
1100         return m_aabb_light;
1101 }
1102
1103
1104 mutable Matrix4 m_projectionOrientation;
1105
1106 void renderSolid( Renderer& renderer, const VolumeTest& volume, const Matrix4& localToWorld, bool selected ) const {
1107         renderer.SetState( m_entity.getEntityClass().m_state_wire, Renderer::eWireframeOnly );
1108         renderer.SetState( m_colour.state(), Renderer::eFullMaterials );
1109         renderer.addRenderable( *this, localToWorld );
1110
1111         if ( selected && g_lightRadii && string_empty( m_entity.getKeyValue( "target" ) ) ) {
1112                 if ( renderer.getStyle() == Renderer::eFullMaterials ) {
1113                         renderer.SetState( RenderLightRadiiFill::m_state, Renderer::eFullMaterials );
1114                         renderer.Highlight( Renderer::ePrimitive, false );
1115                         renderer.addRenderable( m_radii_fill, localToWorld );
1116                 }
1117                 else
1118                 {
1119                         renderer.addRenderable( m_radii_wire, localToWorld );
1120                 }
1121         }
1122
1123         renderer.SetState( m_entity.getEntityClass().m_state_wire, Renderer::eFullMaterials );
1124
1125         if ( g_lightType == LIGHTTYPE_DOOM3 && selected ) {
1126                 if ( isProjected() ) {
1127                         projection();
1128                         m_projectionOrientation = rotation();
1129                         vector4_to_vector3( m_projectionOrientation.t() ) = localAABB().origin;
1130                         renderer.addRenderable( m_renderProjection, m_projectionOrientation );
1131                 }
1132                 else
1133                 {
1134                         updateLightRadiiBox();
1135                         renderer.addRenderable( m_radii_box, localToWorld );
1136                 }
1137
1138                 //draw the center of the light
1139                 if ( m_doom3Radius.m_useCenterKey ) {
1140                         renderer.Highlight( Renderer::ePrimitive, false );
1141                         renderer.Highlight( Renderer::eFace, false );
1142                         renderer.SetState( m_render_center.m_state, Renderer::eFullMaterials );
1143                         renderer.SetState( m_render_center.m_state, Renderer::eWireframeOnly );
1144                         renderer.addRenderable( m_render_center, localToWorld );
1145                 }
1146         }
1147 }
1148 void renderWireframe( Renderer& renderer, const VolumeTest& volume, const Matrix4& localToWorld, bool selected ) const {
1149         renderSolid( renderer, volume, localToWorld, selected );
1150         if ( g_showNames && !string_equal( m_named.name(), "light" ) ) {
1151                 renderer.addRenderable( m_renderName, localToWorld );
1152         }
1153 }
1154
1155 void testSelect( Selector& selector, SelectionTest& test, const Matrix4& localToWorld ){
1156         test.BeginMesh( localToWorld );
1157
1158         SelectionIntersection best;
1159         aabb_testselect( m_aabb_light, test, best );
1160         if ( best.valid() ) {
1161                 selector.addIntersection( best );
1162         }
1163 }
1164
1165 void translate( const Vector3& translation ){
1166         m_aabb_light.origin = origin_translated( m_aabb_light.origin, translation );
1167 }
1168 void rotate( const Quaternion& rotation ){
1169         rotation_rotate( m_rotation, rotation );
1170 }
1171 void snapto( float snap ){
1172         if ( g_lightType == LIGHTTYPE_DOOM3 && !m_useLightOrigin && !m_traverse.empty() ) {
1173                 m_useLightOrigin = true;
1174                 m_lightOrigin = m_originKey.m_origin;
1175         }
1176
1177         if ( m_useLightOrigin ) {
1178                 m_lightOrigin = origin_snapped( m_lightOrigin, snap );
1179                 writeLightOrigin();
1180         }
1181         else
1182         {
1183                 m_originKey.m_origin = origin_snapped( m_originKey.m_origin, snap );
1184                 m_originKey.write( &m_entity );
1185         }
1186 }
1187 void setLightRadius( const AABB& aabb ){
1188         m_aabb_light.origin = aabb.origin;
1189         m_doom3Radius.m_radiusTransformed = aabb.extents;
1190 }
1191 void transformLightRadius( const Matrix4& transform ){
1192         matrix4_transform_point( transform, m_aabb_light.origin );
1193 }
1194 void revertTransform(){
1195         m_aabb_light.origin = m_useLightOrigin ? m_lightOrigin : m_originKey.m_origin;
1196         rotation_assign( m_rotation, m_useLightRotation ? m_lightRotation : m_rotationKey.m_rotation );
1197         m_doom3Radius.m_radiusTransformed = m_doom3Radius.m_radius;
1198 }
1199 void freezeTransform(){
1200         if ( g_lightType == LIGHTTYPE_DOOM3 && !m_useLightOrigin && !m_traverse.empty() ) {
1201                 m_useLightOrigin = true;
1202         }
1203
1204         if ( m_useLightOrigin ) {
1205                 m_lightOrigin = m_aabb_light.origin;
1206                 writeLightOrigin();
1207         }
1208         else
1209         {
1210                 m_originKey.m_origin = m_aabb_light.origin;
1211                 m_originKey.write( &m_entity );
1212         }
1213
1214         if ( g_lightType == LIGHTTYPE_DOOM3 ) {
1215                 if ( !m_useLightRotation && !m_traverse.empty() ) {
1216                         m_useLightRotation = true;
1217                 }
1218
1219                 if ( m_useLightRotation ) {
1220                         rotation_assign( m_lightRotation, m_rotation );
1221                         write_rotation( m_lightRotation, &m_entity, "light_rotation" );
1222                 }
1223
1224                 rotation_assign( m_rotationKey.m_rotation, m_rotation );
1225                 write_rotation( m_rotationKey.m_rotation, &m_entity );
1226
1227                 m_doom3Radius.m_radius = m_doom3Radius.m_radiusTransformed;
1228                 write_origin( m_doom3Radius.m_radius, &m_entity, "light_radius" );
1229         }
1230 }
1231 void transformChanged(){
1232         revertTransform();
1233         m_evaluateTransform();
1234         updateOrigin();
1235 }
1236 typedef MemberCaller<Light, void(), &Light::transformChanged> TransformChangedCaller;
1237
1238 mutable Matrix4 m_localPivot;
1239 const Matrix4& getLocalPivot() const {
1240         m_localPivot = rotation_toMatrix( m_rotation );
1241         vector4_to_vector3( m_localPivot.t() ) = m_aabb_light.origin;
1242         return m_localPivot;
1243 }
1244
1245 void setLightChangedCallback( const Callback<void()>& callback ){
1246         m_doom3Radius.m_changed = callback;
1247 }
1248
1249 const AABB& aabb() const {
1250         m_doom3AABB = AABB( m_aabb_light.origin, m_doom3Radius.m_radiusTransformed );
1251         return m_doom3AABB;
1252 }
1253 bool testAABB( const AABB& other ) const {
1254         if ( isProjected() ) {
1255                 Matrix4 transform = rotation();
1256                 vector4_to_vector3( transform.t() ) = localAABB().origin;
1257                 projection();
1258                 Frustum frustum( frustum_transformed( m_doom3Frustum, transform ) );
1259                 return frustum_test_aabb( frustum, other ) != c_volumeOutside;
1260         }
1261         // test against an AABB which contains the rotated bounds of this light.
1262         const AABB& bounds = aabb();
1263         return aabb_intersects_aabb( other, AABB(
1264                                                                          bounds.origin,
1265                                                                          Vector3(
1266                                                                                  static_cast<float>( fabs( m_rotation[0] * bounds.extents[0] )
1267                                                                                                                          + fabs( m_rotation[3] * bounds.extents[1] )
1268                                                                                                                          + fabs( m_rotation[6] * bounds.extents[2] ) ),
1269                                                                                  static_cast<float>( fabs( m_rotation[1] * bounds.extents[0] )
1270                                                                                                                          + fabs( m_rotation[4] * bounds.extents[1] )
1271                                                                                                                          + fabs( m_rotation[7] * bounds.extents[2] ) ),
1272                                                                                  static_cast<float>( fabs( m_rotation[2] * bounds.extents[0] )
1273                                                                                                                          + fabs( m_rotation[5] * bounds.extents[1] )
1274                                                                                                                          + fabs( m_rotation[8] * bounds.extents[2] ) )
1275                                                                                  )
1276                                                                          ) );
1277 }
1278
1279 const Matrix4& rotation() const {
1280         m_doom3Rotation = rotation_toMatrix( m_rotation );
1281         return m_doom3Rotation;
1282 }
1283 const Vector3& offset() const {
1284         return m_doom3Radius.m_center;
1285 }
1286 const Vector3& colour() const {
1287         return m_colour.m_colour;
1288 }
1289
1290 bool isProjected() const {
1291         return m_useLightTarget && m_useLightUp && m_useLightRight;
1292 }
1293 void projectionChanged(){
1294         m_doom3ProjectionChanged = true;
1295         m_doom3Radius.m_changed();
1296         SceneChangeNotify();
1297 }
1298
1299 const Matrix4& projection() const {
1300         if ( !m_doom3ProjectionChanged ) {
1301                 return m_doom3Projection;
1302         }
1303         m_doom3ProjectionChanged = false;
1304         m_doom3Projection = g_matrix4_identity;
1305         matrix4_translate_by_vec3( m_doom3Projection, Vector3( 0.5f, 0.5f, 0 ) );
1306         matrix4_scale_by_vec3( m_doom3Projection, Vector3( 0.5f, 0.5f, 1 ) );
1307
1308 #if 0
1309         Vector3 right = vector3_cross( m_lightUp, vector3_normalised( m_lightTarget ) );
1310         Vector3 up = vector3_cross( vector3_normalised( m_lightTarget ), m_lightRight );
1311         Vector3 target = m_lightTarget;
1312         Matrix4 test(
1313                 -right.x(), -right.y(), -right.z(), 0,
1314                 -up.x(), -up.y(), -up.z(), 0,
1315                 -target.x(), -target.y(), -target.z(), 0,
1316                 0, 0, 0, 1
1317                 );
1318         Matrix4 frustum = matrix4_frustum( -0.01, 0.01, -0.01, 0.01, 0.01, 1.0 );
1319         test = matrix4_full_inverse( test );
1320         matrix4_premultiply_by_matrix4( test, frustum );
1321         matrix4_multiply_by_matrix4( m_doom3Projection, test );
1322 #elif 0
1323         const float nearFar = 1 / 49.5f;
1324         Vector3 right = vector3_cross( m_lightUp, vector3_normalised( m_lightTarget + m_lightRight ) );
1325         Vector3 up = vector3_cross( vector3_normalised( m_lightTarget + m_lightUp ), m_lightRight );
1326         Vector3 target = vector3_negated( m_lightTarget * ( 1 + nearFar ) );
1327         float scale = -1 / vector3_length( m_lightTarget );
1328         Matrix4 test(
1329                 -inverse( right.x() ), -inverse( up.x() ), -inverse( target.x() ), 0,
1330                 -inverse( right.y() ), -inverse( up.y() ), -inverse( target.y() ), 0,
1331                 -inverse( right.z() ), -inverse( up.z() ), -inverse( target.z() ), scale,
1332                 0, 0, -nearFar, 0
1333                 );
1334         matrix4_multiply_by_matrix4( m_doom3Projection, test );
1335 #elif 0
1336         Vector3 leftA( m_lightTarget - m_lightRight );
1337         Vector3 leftB( m_lightRight + m_lightUp );
1338         Plane3 left( vector3_normalised( vector3_cross( leftA, leftB ) ) * ( 1.0 / 128 ), 0 );
1339         Vector3 rightA( m_lightTarget + m_lightRight );
1340         Vector3 rightB( vector3_cross( rightA, m_lightTarget ) );
1341         Plane3 right( vector3_normalised( vector3_cross( rightA, rightB ) ) * ( 1.0 / 128 ), 0 );
1342         Vector3 bottomA( m_lightTarget - m_lightUp );
1343         Vector3 bottomB( vector3_cross( bottomA, m_lightTarget ) );
1344         Plane3 bottom( vector3_normalised( vector3_cross( bottomA, bottomB ) ) * ( 1.0 / 128 ), 0 );
1345         Vector3 topA( m_lightTarget + m_lightUp );
1346         Vector3 topB( vector3_cross( topA, m_lightTarget ) );
1347         Plane3 top( vector3_normalised( vector3_cross( topA, topB ) ) * ( 1.0 / 128 ), 0 );
1348         Plane3 front( vector3_normalised( m_lightTarget ) * ( 1.0 / 128 ), 1 );
1349         Plane3 back( vector3_normalised( vector3_negated( m_lightTarget ) ) * ( 1.0 / 128 ), 0 );
1350         Matrix4 test( matrix4_from_planes( plane3_flipped( left ), plane3_flipped( right ), plane3_flipped( bottom ), plane3_flipped( top ), plane3_flipped( front ), plane3_flipped( back ) ) );
1351         matrix4_multiply_by_matrix4( m_doom3Projection, test );
1352 #else
1353
1354         Plane3 lightProject[4];
1355
1356         Vector3 start = m_useLightStart && m_useLightEnd ? m_lightStart : vector3_normalised( m_lightTarget );
1357         Vector3 stop = m_useLightStart && m_useLightEnd ? m_lightEnd : m_lightTarget;
1358
1359         float rLen = vector3_length( m_lightRight );
1360         Vector3 right = vector3_divided( m_lightRight, rLen );
1361         float uLen = vector3_length( m_lightUp );
1362         Vector3 up = vector3_divided( m_lightUp, uLen );
1363         Vector3 normal = vector3_normalised( vector3_cross( up, right ) );
1364
1365         float dist = vector3_dot( m_lightTarget, normal );
1366         if ( dist < 0 ) {
1367                 dist = -dist;
1368                 normal = vector3_negated( normal );
1369         }
1370
1371         right *= ( 0.5f * dist ) / rLen;
1372         up *= -( 0.5f * dist ) / uLen;
1373
1374         lightProject[2] = Plane3( normal, 0 );
1375         lightProject[0] = Plane3( right, 0 );
1376         lightProject[1] = Plane3( up, 0 );
1377
1378         // now offset to center
1379         Vector4 targetGlobal( m_lightTarget, 1 );
1380         {
1381                 float a = vector4_dot( targetGlobal, plane3_to_vector4( lightProject[0] ) );
1382                 float b = vector4_dot( targetGlobal, plane3_to_vector4( lightProject[2] ) );
1383                 float ofs = 0.5f - a / b;
1384                 plane3_to_vector4( lightProject[0] ) += plane3_to_vector4( lightProject[2] ) * ofs;
1385         }
1386         {
1387                 float a = vector4_dot( targetGlobal, plane3_to_vector4( lightProject[1] ) );
1388                 float b = vector4_dot( targetGlobal, plane3_to_vector4( lightProject[2] ) );
1389                 float ofs = 0.5f - a / b;
1390                 plane3_to_vector4( lightProject[1] ) += plane3_to_vector4( lightProject[2] ) * ofs;
1391         }
1392
1393         // set the falloff vector
1394         Vector3 falloff = stop - start;
1395         float length = vector3_length( falloff );
1396         falloff = vector3_divided( falloff, length );
1397         if ( length <= 0 ) {
1398                 length = 1;
1399         }
1400         falloff *= ( 1.0f / length );
1401         lightProject[3] = Plane3( falloff, -vector3_dot( start, falloff ) );
1402
1403         // we want the planes of s=0, s=q, t=0, and t=q
1404         m_doom3Frustum.left = lightProject[0];
1405         m_doom3Frustum.bottom = lightProject[1];
1406         m_doom3Frustum.right = Plane3( lightProject[2].normal() - lightProject[0].normal(), lightProject[2].dist() - lightProject[0].dist() );
1407         m_doom3Frustum.top = Plane3( lightProject[2].normal() - lightProject[1].normal(), lightProject[2].dist() - lightProject[1].dist() );
1408
1409         // we want the planes of s=0 and s=1 for front and rear clipping planes
1410         m_doom3Frustum.front = lightProject[3];
1411
1412         m_doom3Frustum.back = lightProject[3];
1413         m_doom3Frustum.back.dist() -= 1.0f;
1414         m_doom3Frustum.back = plane3_flipped( m_doom3Frustum.back );
1415
1416         Matrix4 test( matrix4_from_planes( m_doom3Frustum.left, m_doom3Frustum.right, m_doom3Frustum.bottom, m_doom3Frustum.top, m_doom3Frustum.front, m_doom3Frustum.back ) );
1417         matrix4_multiply_by_matrix4( m_doom3Projection, test );
1418
1419         m_doom3Frustum.left = plane3_normalised( m_doom3Frustum.left );
1420         m_doom3Frustum.right = plane3_normalised( m_doom3Frustum.right );
1421         m_doom3Frustum.bottom = plane3_normalised( m_doom3Frustum.bottom );
1422         m_doom3Frustum.top = plane3_normalised( m_doom3Frustum.top );
1423         m_doom3Frustum.back = plane3_normalised( m_doom3Frustum.back );
1424         m_doom3Frustum.front = plane3_normalised( m_doom3Frustum.front );
1425 #endif
1426         //matrix4_scale_by_vec3(m_doom3Projection, Vector3(1.0 / 128, 1.0 / 128, 1.0 / 128));
1427         return m_doom3Projection;
1428 }
1429
1430 Shader* getShader() const {
1431         return m_shader.get();
1432 }
1433 };
1434
1435 class LightInstance :
1436         public TargetableInstance,
1437         public TransformModifier,
1438         public Renderable,
1439         public SelectionTestable,
1440         public RendererLight,
1441         public PlaneSelectable,
1442         public ComponentSelectionTestable
1443 {
1444 class TypeCasts
1445 {
1446 InstanceTypeCastTable m_casts;
1447 public:
1448 TypeCasts(){
1449         m_casts = TargetableInstance::StaticTypeCasts::instance().get();
1450         InstanceContainedCast<LightInstance, Bounded>::install( m_casts );
1451         //InstanceContainedCast<LightInstance, Cullable>::install(m_casts);
1452         InstanceStaticCast<LightInstance, Renderable>::install( m_casts );
1453         InstanceStaticCast<LightInstance, SelectionTestable>::install( m_casts );
1454         InstanceStaticCast<LightInstance, Transformable>::install( m_casts );
1455         InstanceStaticCast<LightInstance, PlaneSelectable>::install( m_casts );
1456         InstanceStaticCast<LightInstance, ComponentSelectionTestable>::install( m_casts );
1457         InstanceIdentityCast<LightInstance>::install( m_casts );
1458 }
1459 InstanceTypeCastTable& get(){
1460         return m_casts;
1461 }
1462 };
1463
1464 Light& m_contained;
1465 DragPlanes m_dragPlanes;  // dragplanes for lightresizing using mousedrag
1466 public:
1467 typedef LazyStatic<TypeCasts> StaticTypeCasts;
1468
1469 Bounded& get( NullType<Bounded>){
1470         return m_contained;
1471 }
1472
1473 STRING_CONSTANT( Name, "LightInstance" );
1474
1475 LightInstance( const scene::Path& path, scene::Instance* parent, Light& contained ) :
1476         TargetableInstance( path, parent, this, StaticTypeCasts::instance().get(), contained.getEntity(), *this ),
1477         TransformModifier( Light::TransformChangedCaller( contained ), ApplyTransformCaller( *this ) ),
1478         m_contained( contained ),
1479         m_dragPlanes( SelectedChangedComponentCaller( *this ) ){
1480         m_contained.instanceAttach( Instance::path() );
1481
1482         if ( g_lightType == LIGHTTYPE_DOOM3 ) {
1483                 GlobalShaderCache().attach( *this );
1484                 m_contained.setLightChangedCallback( LightChangedCaller( *this ) );
1485         }
1486
1487         StaticRenderableConnectionLines::instance().attach( *this );
1488 }
1489 ~LightInstance(){
1490         StaticRenderableConnectionLines::instance().detach( *this );
1491
1492         if ( g_lightType == LIGHTTYPE_DOOM3 ) {
1493                 m_contained.setLightChangedCallback( Callback<void()>() );
1494                 GlobalShaderCache().detach( *this );
1495         }
1496
1497         m_contained.instanceDetach( Instance::path() );
1498 }
1499 void renderSolid( Renderer& renderer, const VolumeTest& volume ) const {
1500         m_contained.renderSolid( renderer, volume, Instance::localToWorld(), getSelectable().isSelected() );
1501 }
1502 void renderWireframe( Renderer& renderer, const VolumeTest& volume ) const {
1503         m_contained.renderWireframe( renderer, volume, Instance::localToWorld(), getSelectable().isSelected() );
1504 }
1505 void testSelect( Selector& selector, SelectionTest& test ){
1506         m_contained.testSelect( selector, test, Instance::localToWorld() );
1507 }
1508
1509 void selectPlanes( Selector& selector, SelectionTest& test, const PlaneCallback& selectedPlaneCallback ){
1510         test.BeginMesh( localToWorld() );
1511         m_dragPlanes.selectPlanes( m_contained.aabb(), selector, test, selectedPlaneCallback, rotation() );
1512 }
1513 void selectReversedPlanes( Selector& selector, const SelectedPlanes& selectedPlanes ){
1514         m_dragPlanes.selectReversedPlanes( m_contained.aabb(), selector, selectedPlanes, rotation() );
1515 }
1516
1517 bool isSelectedComponents() const {
1518         return m_dragPlanes.isSelected();
1519 }
1520 void setSelectedComponents( bool select, SelectionSystem::EComponentMode mode ){
1521         if ( mode == SelectionSystem::eFace ) {
1522                 m_dragPlanes.setSelected( false );
1523         }
1524 }
1525 void testSelectComponents( Selector& selector, SelectionTest& test, SelectionSystem::EComponentMode mode ){
1526 }
1527
1528 void selectedChangedComponent( const Selectable& selectable ){
1529         GlobalSelectionSystem().getObserver ( SelectionSystem::eComponent )( selectable );
1530         GlobalSelectionSystem().onComponentSelection( *this, selectable );
1531 }
1532 typedef MemberCaller<LightInstance, void(const Selectable&), &LightInstance::selectedChangedComponent> SelectedChangedComponentCaller;
1533
1534 void evaluateTransform(){
1535         if ( getType() == TRANSFORM_PRIMITIVE ) {
1536                 m_contained.translate( getTranslation() );
1537                 m_contained.rotate( getRotation() );
1538         }
1539         else
1540         {
1541                 //globalOutputStream() << getTranslation() << "\n";
1542
1543                 m_dragPlanes.m_bounds = m_contained.aabb();
1544                 m_contained.setLightRadius( m_dragPlanes.evaluateResize( getTranslation(), rotation() ) );
1545         }
1546 }
1547 void applyTransform(){
1548         m_contained.revertTransform();
1549         evaluateTransform();
1550         m_contained.freezeTransform();
1551 }
1552 typedef MemberCaller<LightInstance, void(), &LightInstance::applyTransform> ApplyTransformCaller;
1553
1554 void lightChanged(){
1555         GlobalShaderCache().changed( *this );
1556 }
1557 typedef MemberCaller<LightInstance, void(), &LightInstance::lightChanged> LightChangedCaller;
1558
1559 Shader* getShader() const {
1560         return m_contained.getShader();
1561 }
1562 const AABB& aabb() const {
1563         return m_contained.aabb();
1564 }
1565 bool testAABB( const AABB& other ) const {
1566         return m_contained.testAABB( other );
1567 }
1568 const Matrix4& rotation() const {
1569         return m_contained.rotation();
1570 }
1571 const Vector3& offset() const {
1572         return m_contained.offset();
1573 }
1574 const Vector3& colour() const {
1575         return m_contained.colour();
1576 }
1577
1578 bool isProjected() const {
1579         return m_contained.isProjected();
1580 }
1581 const Matrix4& projection() const {
1582         return m_contained.projection();
1583 }
1584 };
1585
1586 class LightNode :
1587         public scene::Node::Symbiot,
1588         public scene::Instantiable,
1589         public scene::Cloneable,
1590         public scene::Traversable::Observer
1591 {
1592 class TypeCasts
1593 {
1594 NodeTypeCastTable m_casts;
1595 public:
1596 TypeCasts(){
1597         NodeStaticCast<LightNode, scene::Instantiable>::install( m_casts );
1598         NodeStaticCast<LightNode, scene::Cloneable>::install( m_casts );
1599         if ( g_lightType == LIGHTTYPE_DOOM3 ) {
1600                 NodeContainedCast<LightNode, scene::Traversable>::install( m_casts );
1601         }
1602         NodeContainedCast<LightNode, Editable>::install( m_casts );
1603         NodeContainedCast<LightNode, Snappable>::install( m_casts );
1604         NodeContainedCast<LightNode, TransformNode>::install( m_casts );
1605         NodeContainedCast<LightNode, Entity>::install( m_casts );
1606         NodeContainedCast<LightNode, Nameable>::install( m_casts );
1607         NodeContainedCast<LightNode, Namespaced>::install( m_casts );
1608 }
1609 NodeTypeCastTable& get(){
1610         return m_casts;
1611 }
1612 };
1613
1614
1615 scene::Node m_node;
1616 InstanceSet m_instances;
1617 Light m_contained;
1618
1619 void construct(){
1620         if ( g_lightType == LIGHTTYPE_DOOM3 ) {
1621                 m_contained.attach( this );
1622         }
1623 }
1624 void destroy(){
1625         if ( g_lightType == LIGHTTYPE_DOOM3 ) {
1626                 m_contained.detach( this );
1627         }
1628 }
1629 public:
1630 typedef LazyStatic<TypeCasts> StaticTypeCasts;
1631
1632 scene::Traversable& get( NullType<scene::Traversable>){
1633         return m_contained.getTraversable();
1634 }
1635 Editable& get( NullType<Editable>){
1636         return m_contained;
1637 }
1638 Snappable& get( NullType<Snappable>){
1639         return m_contained;
1640 }
1641 TransformNode& get( NullType<TransformNode>){
1642         return m_contained.getTransformNode();
1643 }
1644 Entity& get( NullType<Entity>){
1645         return m_contained.getEntity();
1646 }
1647 Nameable& get( NullType<Nameable>){
1648         return m_contained.getNameable();
1649 }
1650 Namespaced& get( NullType<Namespaced>){
1651         return m_contained.getNamespaced();
1652 }
1653
1654 LightNode( EntityClass* eclass ) :
1655         m_node( this, this, StaticTypeCasts::instance().get() ),
1656         m_contained( eclass, m_node, InstanceSet::TransformChangedCaller( m_instances ), InstanceSet::BoundsChangedCaller( m_instances ), InstanceSetEvaluateTransform<LightInstance>::Caller( m_instances ) ){
1657         construct();
1658 }
1659 LightNode( const LightNode& other ) :
1660         scene::Node::Symbiot( other ),
1661         scene::Instantiable( other ),
1662         scene::Cloneable( other ),
1663         scene::Traversable::Observer( other ),
1664         m_node( this, this, StaticTypeCasts::instance().get() ),
1665         m_contained( other.m_contained, m_node, InstanceSet::TransformChangedCaller( m_instances ), InstanceSet::BoundsChangedCaller( m_instances ), InstanceSetEvaluateTransform<LightInstance>::Caller( m_instances ) ){
1666         construct();
1667 }
1668 ~LightNode(){
1669         destroy();
1670 }
1671
1672 void release(){
1673         delete this;
1674 }
1675 scene::Node& node(){
1676         return m_node;
1677 }
1678
1679 scene::Node& clone() const {
1680         return ( new LightNode( *this ) )->node();
1681 }
1682
1683 void insert( scene::Node& child ){
1684         m_instances.insert( child );
1685 }
1686 void erase( scene::Node& child ){
1687         m_instances.erase( child );
1688 }
1689
1690 scene::Instance* create( const scene::Path& path, scene::Instance* parent ){
1691         return new LightInstance( path, parent, m_contained );
1692 }
1693 void forEachInstance( const scene::Instantiable::Visitor& visitor ){
1694         m_instances.forEachInstance( visitor );
1695 }
1696 void insert( scene::Instantiable::Observer* observer, const scene::Path& path, scene::Instance* instance ){
1697         m_instances.insert( observer, path, instance );
1698 }
1699 scene::Instance* erase( scene::Instantiable::Observer* observer, const scene::Path& path ){
1700         return m_instances.erase( observer, path );
1701 }
1702 };
1703
1704 void Light_Construct( LightType lightType ){
1705         g_lightType = lightType;
1706         if ( g_lightType == LIGHTTYPE_DOOM3 ) {
1707                 LightShader::m_defaultShader = "lights/defaultPointLight";
1708 #if 0
1709                 LightShader::m_defaultShader = "lights/defaultProjectedLight";
1710 #endif
1711         }
1712         RenderLightRadiiFill::m_state = GlobalShaderCache().capture( "$Q3MAP2_LIGHT_SPHERE" );
1713         RenderLightCenter::m_state = GlobalShaderCache().capture( "$BIGPOINT" );
1714 }
1715 void Light_Destroy(){
1716         GlobalShaderCache().release( "$Q3MAP2_LIGHT_SPHERE" );
1717         GlobalShaderCache().release( "$BIGPOINT" );
1718 }
1719
1720 scene::Node& New_Light( EntityClass* eclass ){
1721         return ( new LightNode( eclass ) )->node();
1722 }