1 /**
2  * Contains the base class for all light types, Light, as well as
3  * AmbientLight, DirectionalLight, PointLight, SpotLight.
4  */
5 module dash.components.lights;
6 import dash.core, dash.components, dash.graphics;
7 import dash.utility;
8 
9 import derelict.opengl3.gl3;
10 import std.math;
11 
12 mixin( registerComponents!() );
13 
14 /**
15  * Base class for lights.
16  */
17 abstract class Light : Component
18 {
19 public:
20     /// The color the light gives off.
21     @rename( "Color" ) @optional
22     vec3f color;
23     /// If it should cast shadows
24     @rename( "CastShadows" ) @optional
25     bool castShadows;
26 
27     this( vec3f color )
28     {
29         this.color = color;
30         castShadows = false;
31     }
32 
33     override void update() { }
34     override void shutdown() { }
35 }
36 
37 /**
38  * Ambient Light
39  */
40 class AmbientLight : Light
41 {
42     this( vec3f color = vec3f( 1.0f ) )
43     {
44         super( color );
45     }
46 }
47 
48 /**
49  * Directional Light
50  */
51 class DirectionalLight : Light
52 {
53 private:
54     uint _shadowMapFrameBuffer;
55     uint _shadowMapTexture;
56     mat4f _projView;
57     int _shadowMapSize;
58 
59 public:
60     /// The direction the light points in.
61     @rename( "Direction" ) @optional
62     vec3f direction;
63     /// The FrameBuffer for the shadowmap.
64     mixin( Property!( _shadowMapFrameBuffer ) );
65     /// The shadow map's depth texture.
66     mixin( Property!( _shadowMapTexture ) );
67     mixin( Property!( _projView ) );
68     mixin( Property!( _shadowMapSize ) );
69 
70     this( vec3f color = vec3f( 1.0f ), vec3f direction = vec3f( 0.0f ), bool castShadows = false )
71     {
72         this.direction = direction;
73         super( color );
74         this.castShadows = castShadows;
75     }
76 
77     /// Initializes the lights.
78     override void initialize()
79     {
80         if( castShadows )
81         {
82             // generate framebuffer for shadow map
83             shadowMapFrameBuffer = 0;
84             glGenFramebuffers( 1, cast(uint*)&_shadowMapFrameBuffer );
85             glBindFramebuffer( GL_FRAMEBUFFER, _shadowMapFrameBuffer );
86 
87             // generate depth texture of shadow map
88             shadowMapSize = 2048;
89             glGenTextures( 1, cast(uint*)&_shadowMapTexture );
90             glBindTexture( GL_TEXTURE_2D, _shadowMapTexture );
91             glTexImage2D( GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT16, shadowMapSize, shadowMapSize, 0, GL_DEPTH_COMPONENT, GL_FLOAT, null );
92             glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
93             glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
94             glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
95             glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
96 
97             glFramebufferTexture2D( GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, _shadowMapTexture, 0 );
98 
99             // don't want any info besides depth
100             glDrawBuffer( GL_NONE );
101             // don't want to read from gpu
102             glReadBuffer( GL_NONE );
103 
104             // check for success
105             if( glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE )
106             {
107                 fatal( "Shadow map frame buffer failure." );
108                 assert(false);
109             }
110         }
111     }
112 
113     /**
114      * calculates the light's projection and view matrices, and combines them
115      */
116     void calculateProjView( box3f frustum )
117     {
118         // determine the center of the frustum
119         vec3f center = vec3f( ( frustum.min + frustum.max ).x/2.0f,
120                                           ( frustum.min + frustum.max ).y/2.0f,
121                                           ( frustum.min + frustum.max ).z/2.0f );
122 
123         // determine the rotation for the viewing axis
124         // adapted from http://lolengine.net/blog/2013/09/18/beautiful-maths-quaternion-from-vectors
125         vec3f lDirNorm = direction.normalized;
126         vec3f baseAxis = vec3f( 0, 0, -1 );
127         float cosTheta = dot( lDirNorm, baseAxis );
128         float halfCosX2 = sqrt( 0.5f * (1.0f + cosTheta) ) * 2.0f;
129         vec3f w = cross( lDirNorm, baseAxis );
130         quatf rotation = quatf( halfCosX2/2, w.x / halfCosX2, w.y / halfCosX2, w.z / halfCosX2 );
131 
132         // determine the x,y,z axes
133         vec3f eulers = rotation.toEulerAngles();
134         float cosPitch = cos( eulers.x );
135         float sinPitch = sin( eulers.x );
136         float cosYaw = cos( eulers.y );
137         float sinYaw = sin( eulers.y );
138         vec3f xaxis = vec3f( cosYaw, 0.0f, -sinYaw );
139         vec3f yaxis = vec3f( sinYaw * sinPitch, cosPitch, cosYaw * sinPitch );
140         vec3f zaxis = vec3f( sinYaw * cosPitch, -sinPitch, cosPitch * cosYaw );
141 
142         // build the view matrix
143         mat4f viewMatrix;
144         ///*
145         viewMatrix[ 0 ] = vec4f( xaxis, -xaxis.dot( center ) ).vector;
146         viewMatrix[ 1 ] = vec4f( yaxis, -yaxis.dot( center ) ).vector;
147         viewMatrix[ 2 ] = vec4f( zaxis, -zaxis.dot( center ) ).vector;
148         viewMatrix[ 3 ] = vec4f( 0, 0, 0, 1 ).vector;
149         /*/
150         // using lookAt works for everying but a light direction of (0,+/-1,0)
151         light.view = Camera.lookAt( center - light.direction.normalized, center ); //*/
152 
153         // get frustum in view space
154         frustum.min = (viewMatrix * vec4f(frustum.min,1.0f)).xyz;
155         frustum.max = (viewMatrix * vec4f(frustum.max,1.0f)).xyz;
156 
157         // get mins and maxes in view space
158         vec3f mins, maxes;
159         for( int i = 0; i < 3; i++ )
160         {
161             if( frustum.min.vector[ i ] < frustum.max.vector[ i ] )
162             {
163                 mins.vector[ i ] = frustum.min.vector[ i ];
164                 maxes.vector[ i ] = frustum.max.vector[ i ];
165             }
166             else
167             {
168                 mins.vector[ i ] = frustum.max.vector[ i ];
169                 maxes.vector[ i ] = frustum.min.vector[ i ];
170             }
171         }
172 
173         float magicNumber = 1.5f; // literally the worst
174         projView = mat4f.orthographic( mins.x * magicNumber, maxes.x* magicNumber, mins.y* magicNumber , maxes.y* magicNumber, maxes.z* magicNumber, mins.z* magicNumber ) * viewMatrix;
175     }
176 }
177 
178 /**
179  * Point Light
180  */
181 class PointLight : Light
182 {
183 private:
184     mat4f _matrix;
185 
186 public:
187     /// The area that lighting will be calculated for.
188     @rename( "Radius" )
189     float radius;
190     /// The light's exponential attenuation modifier.
191     @rename( "FalloffRate" )
192     float falloffRate;
193 
194     this( vec3f color = vec3f( 1.0f ), float radius = 0.0f, float falloffRate = 0.0f )
195     {
196         this.radius = radius;
197         this.falloffRate = falloffRate;
198         super( color );
199     }
200 
201     /**
202      * TODO
203      *
204      * Params:
205      *
206      * Returns:
207      */
208     public mat4f getTransform()
209     {
210         _matrix = mat4f.identity;
211         // Scale
212         _matrix[ 0 ][ 0 ] = radius;
213         _matrix[ 1 ][ 1 ] = radius;
214         _matrix[ 2 ][ 2 ] = radius;
215         // Translate
216         vec3f position = owner.transform.worldPosition;
217         _matrix[ 0 ][ 3 ] = position.x;
218         _matrix[ 1 ][ 3 ] = position.y;
219         _matrix[ 2 ][ 3 ] = position.z;
220         return _matrix;
221     }
222 
223 }
224 
225 /**
226  * SpotLight Stub
227  */
228 class SpotLight : Light
229 {
230 public:
231     this( vec3f color = vec3f() )
232     {
233         super( color );
234     }
235 }