- some small adjustments to the lighting
[supertux.git] / src / video / drawing_context.cpp
1 //  $Id$
2 //
3 //  SuperTux
4 //  Copyright (C) 2006 Matthias Braun <matze@braunis.de>
5 //
6 //  This program is free software; you can redistribute it and/or
7 //  modify it under the terms of the GNU General Public License
8 //  as published by the Free Software Foundation; either version 2
9 //  of the License, or (at your option) any later version.
10 //
11 //  This program is distributed in the hope that it will be useful,
12 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
13 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 //  GNU General Public License for more details.
15 //
16 //  You should have received a copy of the GNU General Public License
17 //  along with this program; if not, write to the Free Software
18 //  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
19
20 #include <config.h>
21
22 #include <algorithm>
23 #include <cassert>
24 #include <iostream>
25 #include <SDL_image.h>
26 #include <GL/gl.h>
27
28 #include "drawing_context.hpp"
29 #include "surface.hpp"
30 #include "font.hpp"
31 #include "main.hpp"
32 #include "gameconfig.hpp"
33 #include "glutil.hpp"
34 #include "texture.hpp"
35 #include "texture_manager.hpp"
36
37 #define LIGHTMAP_DIV 4
38
39 static inline int next_po2(int val)
40 {
41   int result = 1;
42   while(result < val)
43     result *= 2;
44   
45   return result;
46 }
47
48 DrawingContext::DrawingContext()
49 {
50   screen = SDL_GetVideoSurface();
51
52   lightmap_width = screen->w / LIGHTMAP_DIV;
53   lightmap_height = screen->h / LIGHTMAP_DIV;
54   unsigned int width = next_po2(lightmap_width);
55   unsigned int height = next_po2(lightmap_height);
56
57   lightmap = new Texture(width, height, GL_RGB);
58
59   lightmap_uv_right = static_cast<float>(lightmap_width) / static_cast<float>(width);
60   lightmap_uv_bottom = static_cast<float>(lightmap_height) / static_cast<float>(height);
61   texture_manager->register_texture(lightmap);
62
63   requests = &drawing_requests;
64 }
65
66 DrawingContext::~DrawingContext()
67 {
68   texture_manager->remove_texture(lightmap);
69   delete lightmap;
70 }
71
72 void
73 DrawingContext::draw_surface(const Surface* surface, const Vector& position, float angle,
74     int layer)
75 {
76   assert(surface != 0);
77   
78   DrawingRequest request;
79
80   request.type = SURFACE;
81   request.pos = transform.apply(position);
82
83   if(request.pos.x >= SCREEN_WIDTH || request.pos.y >= SCREEN_HEIGHT
84       || request.pos.x + surface->get_width() < 0 
85       || request.pos.y + surface->get_height() < 0)
86     return;
87
88   request.layer = layer;
89   request.drawing_effect = transform.drawing_effect;
90   request.alpha = transform.alpha;
91   request.angle = angle;
92   request.request_data = const_cast<Surface*> (surface);  
93
94   requests->push_back(request);
95 }
96
97 void
98 DrawingContext::draw_surface(const Surface* surface, const Vector& position, 
99     int layer)
100 {
101   draw_surface(surface, position, 0.0f, layer);
102 }
103
104 void
105 DrawingContext::draw_surface_part(const Surface* surface, const Vector& source,
106     const Vector& size, const Vector& dest, int layer)
107 {
108   assert(surface != 0);
109
110   DrawingRequest request;
111
112   request.type = SURFACE_PART;
113   request.pos = transform.apply(dest);
114   request.layer = layer;
115   request.drawing_effect = transform.drawing_effect;
116   request.alpha = transform.alpha;
117   
118   SurfacePartRequest* surfacepartrequest = new SurfacePartRequest();
119   surfacepartrequest->size = size;
120   surfacepartrequest->source = source;
121   surfacepartrequest->surface = surface;
122
123   // clip on screen borders
124   if(request.pos.x < 0) {
125     surfacepartrequest->size.x += request.pos.x;
126     if(surfacepartrequest->size.x <= 0)
127       return;
128     surfacepartrequest->source.x -= request.pos.x;
129     request.pos.x = 0;
130   }
131   if(request.pos.y < 0) {
132     surfacepartrequest->size.y += request.pos.y;
133     if(surfacepartrequest->size.y <= 0)
134       return;
135     surfacepartrequest->source.y -= request.pos.y;
136     request.pos.y = 0;
137   }
138   request.request_data = surfacepartrequest;
139
140   requests->push_back(request);
141 }
142
143 void
144 DrawingContext::draw_text(const Font* font, const std::string& text,
145     const Vector& position, FontAlignment alignment, int layer)
146 {
147   DrawingRequest request;
148
149   request.type = TEXT;
150   request.pos = transform.apply(position);
151   request.layer = layer;
152   request.drawing_effect = transform.drawing_effect;
153   request.alpha = transform.alpha;
154
155   TextRequest* textrequest = new TextRequest;
156   textrequest->font = font;
157   textrequest->text = text;
158   textrequest->alignment = alignment;
159   request.request_data = textrequest;
160
161   requests->push_back(request);
162 }
163
164 void
165 DrawingContext::draw_center_text(const Font* font, const std::string& text,
166     const Vector& position, int layer)
167 {
168   draw_text(font, text, Vector(position.x + SCREEN_WIDTH/2, position.y),
169       CENTER_ALLIGN, layer);
170 }
171
172 void
173 DrawingContext::draw_gradient(const Color& top, const Color& bottom, int layer)
174 {
175   DrawingRequest request;
176
177   request.type = GRADIENT;
178   request.pos = Vector(0,0);
179   request.layer = layer;
180
181   request.drawing_effect = transform.drawing_effect;
182   request.alpha = transform.alpha;
183
184   GradientRequest* gradientrequest = new GradientRequest;
185   gradientrequest->top = top;
186   gradientrequest->bottom = bottom;
187   request.request_data = gradientrequest;
188
189   requests->push_back(request);
190 }
191
192 void
193 DrawingContext::draw_filled_rect(const Vector& topleft, const Vector& size,
194                                  const Color& color, int layer)
195 {
196   DrawingRequest request;
197
198   request.type = FILLRECT;
199   request.pos = transform.apply(topleft);
200   request.layer = layer;
201
202   request.drawing_effect = transform.drawing_effect;
203   request.alpha = transform.alpha;                    
204
205   FillRectRequest* fillrectrequest = new FillRectRequest;
206   fillrectrequest->size = size;
207   fillrectrequest->color = color;
208   fillrectrequest->color.alpha = color.alpha * transform.alpha;
209   request.request_data = fillrectrequest;
210
211   requests->push_back(request);
212 }
213
214 void
215 DrawingContext::draw_filled_rect(const Rect& rect, const Color& color,
216                                  int layer)
217 {
218   DrawingRequest request;
219
220   request.type = FILLRECT;
221   request.pos = transform.apply(rect.p1);
222   request.layer = layer;
223
224   request.drawing_effect = transform.drawing_effect;
225   request.alpha = transform.alpha;                    
226
227   FillRectRequest* fillrectrequest = new FillRectRequest;
228   fillrectrequest->size = Vector(rect.get_width(), rect.get_height());
229   fillrectrequest->color = color;
230   fillrectrequest->color.alpha = color.alpha * transform.alpha;
231   request.request_data = fillrectrequest;
232
233   requests->push_back(request);
234 }
235
236 void
237 DrawingContext::draw_surface_part(DrawingRequest& request)
238 {
239   SurfacePartRequest* surfacepartrequest
240     = (SurfacePartRequest*) request.request_data;
241
242   surfacepartrequest->surface->draw_part(
243       surfacepartrequest->source.x, surfacepartrequest->source.y,
244       request.pos.x, request.pos.y,
245       surfacepartrequest->size.x, surfacepartrequest->size.y,
246       request.alpha, request.drawing_effect);
247
248   delete surfacepartrequest;
249 }
250
251 void
252 DrawingContext::draw_gradient(DrawingRequest& request)
253 {
254   GradientRequest* gradientrequest = (GradientRequest*) request.request_data;
255   const Color& top = gradientrequest->top;
256   const Color& bottom = gradientrequest->bottom;
257   
258   glDisable(GL_TEXTURE_2D);
259   glBegin(GL_QUADS);
260   glColor4f(top.red, top.green, top.blue, top.alpha);
261   glVertex2f(0, 0);
262   glVertex2f(SCREEN_WIDTH, 0);
263   glColor4f(bottom.red, bottom.green, bottom.blue, bottom.alpha);
264   glVertex2f(SCREEN_WIDTH, SCREEN_HEIGHT);
265   glVertex2f(0, SCREEN_HEIGHT);
266   glEnd();
267   glEnable(GL_TEXTURE_2D);
268
269   delete gradientrequest;
270 }
271
272 void
273 DrawingContext::draw_text(DrawingRequest& request)
274 {
275   TextRequest* textrequest = (TextRequest*) request.request_data;
276
277   textrequest->font->draw(textrequest->text, request.pos,
278       textrequest->alignment, request.drawing_effect, request.alpha);
279
280   delete textrequest;
281 }
282
283 void
284 DrawingContext::draw_filled_rect(DrawingRequest& request)
285 {
286   FillRectRequest* fillrectrequest = (FillRectRequest*) request.request_data;
287
288   float x = request.pos.x;
289   float y = request.pos.y;
290   float w = fillrectrequest->size.x;
291   float h = fillrectrequest->size.y;
292
293   glDisable(GL_TEXTURE_2D);
294   glColor4f(fillrectrequest->color.red, fillrectrequest->color.green,
295             fillrectrequest->color.blue, fillrectrequest->color.alpha);
296  
297   glBegin(GL_QUADS);
298   glVertex2f(x, y);
299   glVertex2f(x+w, y);
300   glVertex2f(x+w, y+h);
301   glVertex2f(x, y+h);
302   glEnd();
303   glEnable(GL_TEXTURE_2D);
304
305   delete fillrectrequest;
306 }
307
308 void
309 DrawingContext::do_drawing()
310 {
311 #ifdef DEBUG
312   assert(transformstack.empty());
313   assert(target_stack.empty());
314 #endif
315   transformstack.clear();
316   target_stack.clear();
317
318   bool use_lightmap = lightmap_requests.size() != 0;
319   
320   // PART1: create lightmap
321   if(use_lightmap) {
322     glViewport(0, screen->h - lightmap_height, lightmap_width, lightmap_height);
323     glMatrixMode(GL_PROJECTION);
324     glLoadIdentity();               
325     glOrtho(0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, -1.0, 1.0);
326     glMatrixMode(GL_MODELVIEW);
327     glLoadIdentity();
328
329     // FIXME: Add ambient light support here
330     glClearColor(0.3, 0.3, 0.4, 1);
331     glClear(GL_COLOR_BUFFER_BIT);
332     handle_drawing_requests(lightmap_requests);
333     lightmap_requests.clear();
334   
335     glDisable(GL_BLEND);
336     glBindTexture(GL_TEXTURE_2D, lightmap->get_handle());
337     glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, screen->h - lightmap_height, lightmap_width, lightmap_height);
338
339     glViewport(0, 0, screen->w, screen->h);
340     glMatrixMode(GL_PROJECTION);
341     glLoadIdentity();               
342     glOrtho(0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, -1.0, 1.0);
343     glMatrixMode(GL_MODELVIEW);    
344     glLoadIdentity();
345     glEnable(GL_BLEND);
346   }
347
348   //glClear(GL_COLOR_BUFFER_BIT);
349   handle_drawing_requests(drawing_requests);
350   drawing_requests.clear();
351
352   if(use_lightmap) {
353     // multiple the lightmap with the framebuffer
354     glBlendFunc(GL_DST_COLOR, GL_ZERO);
355      
356     glBindTexture(GL_TEXTURE_2D, lightmap->get_handle());
357     glBegin(GL_QUADS);
358
359     glTexCoord2f(0, lightmap_uv_bottom);
360     glVertex2f(0, 0);
361
362     glTexCoord2f(lightmap_uv_right, lightmap_uv_bottom);
363     glVertex2f(SCREEN_WIDTH, 0);
364
365     glTexCoord2f(lightmap_uv_right, 0);
366     glVertex2f(SCREEN_WIDTH, SCREEN_HEIGHT);
367
368     glTexCoord2f(0, 0);
369     glVertex2f(0, SCREEN_HEIGHT);
370     
371     glEnd();
372
373     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
374   }
375
376   assert_gl("drawing");
377
378   SDL_GL_SwapBuffers();
379 }
380
381 void
382 DrawingContext::handle_drawing_requests(DrawingRequests& requests)
383 {
384   std::stable_sort(requests.begin(), requests.end());
385   
386   for(DrawingRequests::iterator i = requests.begin();
387       i != requests.end(); ++i) {
388     switch(i->type) {
389       case SURFACE:
390       {
391         const Surface* surface = (const Surface*) i->request_data;
392         if (i->angle == 0.0f)
393           surface->draw(i->pos.x, i->pos.y, i->alpha, i->drawing_effect);
394         else
395           surface->draw(i->pos.x, i->pos.y, i->alpha, i->angle, i->drawing_effect);
396         break;
397       }
398       case SURFACE_PART:
399         draw_surface_part(*i);
400         break;
401       case GRADIENT:
402         draw_gradient(*i);
403         break;
404       case TEXT:
405         draw_text(*i);
406         break;
407       case FILLRECT:
408         draw_filled_rect(*i);
409         break;
410     }
411   }
412 }
413
414 void
415 DrawingContext::push_transform()
416 {
417   transformstack.push_back(transform);
418 }
419
420 void
421 DrawingContext::pop_transform()
422 {
423   assert(!transformstack.empty());
424
425   transform = transformstack.back();
426   transformstack.pop_back();
427 }
428
429 void
430 DrawingContext::set_drawing_effect(DrawingEffect effect)
431 {
432   transform.drawing_effect = effect;
433 }
434
435 DrawingEffect
436 DrawingContext::get_drawing_effect() const
437 {
438   return transform.drawing_effect;
439 }
440
441 void
442 DrawingContext::set_alpha(float alpha)
443 {
444   transform.alpha = alpha;
445 }
446
447 float
448 DrawingContext::get_alpha() const
449 {
450   return transform.alpha;
451 }
452
453 void
454 DrawingContext::push_target()
455 {
456   target_stack.push_back(target);
457 }
458
459 void
460 DrawingContext::pop_target()
461 {
462   set_target(target_stack.back());
463   target_stack.pop_back();
464 }
465
466 void
467 DrawingContext::set_target(Target target)
468 {
469   this->target = target;
470   if(target == LIGHTMAP)
471     requests = &lightmap_requests;
472   else
473     requests = &drawing_requests;
474 }
475