fix lightmap-in-front-of-menu bug
[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 #include <config.h>
20
21 #include <functional>
22 #include <algorithm>
23 #include <cassert>
24 #include <iostream>
25 #include <SDL_image.h>
26 #include <sstream>
27 #include <iomanip>
28 #include <physfs.h>
29
30 #include "drawing_context.hpp"
31 #include "drawing_request.hpp"
32 #include "video_systems.hpp"
33 #include "renderer.hpp"
34 #include "lightmap.hpp"
35 #include "surface.hpp"
36 #include "main.hpp"
37 #include "gameconfig.hpp"
38 #include "texture.hpp"
39 #include "texture_manager.hpp"
40 #include "obstack/obstackpp.hpp"
41
42 static inline int next_po2(int val)
43 {
44   int result = 1;
45   while(result < val)
46     result *= 2;
47
48   return result;
49 }
50
51 DrawingContext::DrawingContext() :
52   renderer(0), lightmap(0), ambient_color(1.0f, 1.0f, 1.0f, 1.0f), target(NORMAL), screenshot_requested(false)
53 {
54   requests = &drawing_requests;
55   obstack_init(&obst);
56 }
57
58 DrawingContext::~DrawingContext()
59 {
60   delete renderer;
61   delete lightmap;
62
63   obstack_free(&obst, NULL);
64 }
65
66 void
67 DrawingContext::init_renderer()
68 {
69   delete renderer;
70   delete lightmap;
71
72   renderer = new_renderer();
73   lightmap = new_lightmap();
74 }
75
76 void
77 DrawingContext::draw_surface(const Surface* surface, const Vector& position,
78                              float angle, const Color& color, const Blend& blend,
79                              int layer)
80 {
81   assert(surface != 0);
82
83   DrawingRequest* request = new(obst) DrawingRequest();
84
85   request->target = target;
86   request->type = SURFACE;
87   request->pos = transform.apply(position);
88
89   if(request->pos.x >= SCREEN_WIDTH || request->pos.y >= SCREEN_HEIGHT
90       || request->pos.x + surface->get_width() < 0
91       || request->pos.y + surface->get_height() < 0)
92     return;
93
94   request->layer = layer;
95   request->drawing_effect = transform.drawing_effect;
96   request->alpha = transform.alpha;
97   request->angle = angle;
98   request->color = color;
99   request->blend = blend;
100
101   request->request_data = const_cast<Surface*> (surface);
102
103   requests->push_back(request);
104 }
105
106 void
107 DrawingContext::draw_surface(const Surface* surface, const Vector& position,
108     int layer)
109 {
110   draw_surface(surface, position, 0.0f, Color(1.0f, 1.0f, 1.0f), Blend(), layer);
111 }
112
113 void
114 DrawingContext::draw_surface_part(const Surface* surface, const Vector& source,
115     const Vector& size, const Vector& dest, int layer)
116 {
117   assert(surface != 0);
118
119   DrawingRequest* request = new(obst) DrawingRequest();
120
121   request->target = target;
122   request->type = SURFACE_PART;
123   request->pos = transform.apply(dest);
124   request->layer = layer;
125   request->drawing_effect = transform.drawing_effect;
126   request->alpha = transform.alpha;
127
128   SurfacePartRequest* surfacepartrequest = new(obst) SurfacePartRequest();
129   surfacepartrequest->size = size;
130   surfacepartrequest->source = source;
131   surfacepartrequest->surface = surface;
132
133   // clip on screen borders
134   if(request->pos.x < 0) {
135     surfacepartrequest->size.x += request->pos.x;
136     if(surfacepartrequest->size.x <= 0)
137       return;
138     surfacepartrequest->source.x -= request->pos.x;
139     request->pos.x = 0;
140   }
141   if(request->pos.y < 0) {
142     surfacepartrequest->size.y += request->pos.y;
143     if(surfacepartrequest->size.y <= 0)
144       return;
145     surfacepartrequest->source.y -= request->pos.y;
146     request->pos.y = 0;
147   }
148   request->request_data = surfacepartrequest;
149
150   requests->push_back(request);
151 }
152
153 void
154 DrawingContext::draw_text(const Font* font, const std::string& text,
155     const Vector& position, FontAlignment alignment, int layer)
156 {
157   DrawingRequest* request = new(obst) DrawingRequest();
158
159   request->target = target;
160   request->type = TEXT;
161   request->pos = transform.apply(position);
162   request->layer = layer;
163   request->drawing_effect = transform.drawing_effect;
164   request->alpha = transform.alpha;
165
166   TextRequest* textrequest = new(obst) TextRequest();
167   textrequest->font = font;
168   textrequest->text = text;
169   textrequest->alignment = alignment;
170   request->request_data = textrequest;
171
172   requests->push_back(request);
173 }
174
175 void
176 DrawingContext::draw_center_text(const Font* font, const std::string& text,
177     const Vector& position, int layer)
178 {
179   draw_text(font, text, Vector(position.x + SCREEN_WIDTH/2, position.y),
180       ALIGN_CENTER, layer);
181 }
182
183 void
184 DrawingContext::draw_gradient(const Color& top, const Color& bottom, int layer)
185 {
186   DrawingRequest* request = new(obst) DrawingRequest();
187
188   request->target = target;
189   request->type = GRADIENT;
190   request->pos = Vector(0,0);
191   request->layer = layer;
192
193   request->drawing_effect = transform.drawing_effect;
194   request->alpha = transform.alpha;
195
196   GradientRequest* gradientrequest = new(obst) GradientRequest();
197   gradientrequest->top = top;
198   gradientrequest->bottom = bottom;
199   request->request_data = gradientrequest;
200
201   requests->push_back(request);
202 }
203
204 void
205 DrawingContext::draw_filled_rect(const Vector& topleft, const Vector& size,
206                                  const Color& color, int layer)
207 {
208   DrawingRequest* request = new(obst) DrawingRequest();
209
210   request->target = target;
211   request->type = FILLRECT;
212   request->pos = transform.apply(topleft);
213   request->layer = layer;
214
215   request->drawing_effect = transform.drawing_effect;
216   request->alpha = transform.alpha;
217
218   FillRectRequest* fillrectrequest = new(obst) FillRectRequest();
219   fillrectrequest->size = size;
220   fillrectrequest->color = color;
221   fillrectrequest->color.alpha = color.alpha * transform.alpha;
222   fillrectrequest->radius = 0.0f;
223   request->request_data = fillrectrequest;
224
225   requests->push_back(request);
226 }
227
228 void
229 DrawingContext::draw_filled_rect(const Rect& rect, const Color& color,
230                                  int layer)
231 {
232   draw_filled_rect(rect, color, 0.0f, layer);
233 }
234
235 void
236 DrawingContext::draw_filled_rect(const Rect& rect, const Color& color, float radius, int layer)
237 {
238   DrawingRequest* request = new(obst) DrawingRequest();
239
240   request->target = target;
241   request->type   = FILLRECT;
242   request->pos    = transform.apply(rect.p1);
243   request->layer  = layer;
244
245   request->drawing_effect = transform.drawing_effect;
246   request->alpha = transform.alpha;
247
248   FillRectRequest* fillrectrequest = new(obst) FillRectRequest;
249   fillrectrequest->size = Vector(rect.get_width(), rect.get_height());
250   fillrectrequest->color = color;
251   fillrectrequest->color.alpha = color.alpha * transform.alpha;
252   fillrectrequest->radius = radius;
253   request->request_data = fillrectrequest;
254
255   requests->push_back(request); 
256 }
257
258 void
259 DrawingContext::draw_inverse_ellipse(const Vector& pos, const Vector& size, const Color& color, int layer)
260 {
261   DrawingRequest* request = new(obst) DrawingRequest();
262
263   request->target = target;
264   request->type   = INVERSEELLIPSE;
265   request->pos    = transform.apply(pos);
266   request->layer  = layer;
267
268   request->drawing_effect = transform.drawing_effect;
269   request->alpha = transform.alpha;
270
271   InverseEllipseRequest* ellipse = new(obst)InverseEllipseRequest;
272   
273   ellipse->color        = color;
274   ellipse->color.alpha  = color.alpha * transform.alpha;
275   ellipse->size         = size;
276   request->request_data = ellipse;
277
278   requests->push_back(request);     
279 }
280
281 void
282 DrawingContext::get_light(const Vector& position, Color* color)
283 {
284   if( ambient_color.red == 1.0f && ambient_color.green == 1.0f
285       && ambient_color.blue  == 1.0f ) {
286     *color = Color( 1.0f, 1.0f, 1.0f);
287     return;
288   }
289
290   DrawingRequest* request = new(obst) DrawingRequest();
291   request->target = target;
292   request->type = GETLIGHT;
293   request->pos = transform.apply(position);
294
295   //There is no light offscreen.
296   if(request->pos.x >= SCREEN_WIDTH || request->pos.y >= SCREEN_HEIGHT
297       || request->pos.x < 0 || request->pos.y < 0){
298     *color = Color( 0, 0, 0);
299     return;
300   }
301
302   request->layer = LAYER_GUI; //make sure all get_light requests are handled last.
303   GetLightRequest* getlightrequest = new(obst) GetLightRequest();
304   getlightrequest->color_ptr = color;
305   request->request_data = getlightrequest;
306   lightmap_requests.push_back(request);
307 }
308
309 void
310 DrawingContext::do_drawing()
311 {
312 #ifdef DEBUG
313   assert(transformstack.empty());
314   assert(target_stack.empty());
315 #endif
316   transformstack.clear();
317   target_stack.clear();
318
319   //Use Lightmap if ambient color is not white.
320   bool use_lightmap = ( ambient_color.red != 1.0f   || ambient_color.green != 1.0f ||
321                         ambient_color.blue  != 1.0f );
322
323   // PART1: create lightmap
324   if(use_lightmap) {
325     lightmap->start_draw(ambient_color);
326     handle_drawing_requests(lightmap_requests);
327     lightmap->end_draw();
328
329     DrawingRequest* request = new(obst) DrawingRequest();
330     request->target = NORMAL;
331     request->type = DRAW_LIGHTMAP;
332     request->layer = LAYER_HUD - 1;
333     drawing_requests.push_back(request);
334   }
335   lightmap_requests.clear();
336
337   handle_drawing_requests(drawing_requests);
338   drawing_requests.clear();
339   obstack_free(&obst, NULL);
340   obstack_init(&obst);
341
342   // if a screenshot was requested, take one
343   if (screenshot_requested) {
344     renderer->do_take_screenshot();
345     screenshot_requested = false;
346   }
347
348   renderer->flip();
349 }
350
351 class RequestPtrCompare
352   :  public std::binary_function<const DrawingRequest*,
353                                  const DrawingRequest*, 
354                                  bool>
355 {
356 public:
357   bool operator()(const DrawingRequest* r1, const DrawingRequest* r2) const
358   {
359     return *r1 < *r2;
360   }
361 };
362
363 void
364 DrawingContext::handle_drawing_requests(DrawingRequests& requests)
365 {
366   std::stable_sort(requests.begin(), requests.end(), RequestPtrCompare());
367
368   DrawingRequests::const_iterator i;
369   for(i = requests.begin(); i != requests.end(); ++i) {
370     const DrawingRequest& request = **i;
371
372     switch(request.target) {
373       case NORMAL:
374         switch(request.type) {
375           case SURFACE:
376             renderer->draw_surface(request);
377             break;
378           case SURFACE_PART:
379             renderer->draw_surface_part(request);
380             break;
381           case GRADIENT:
382             renderer->draw_gradient(request);
383             break;
384           case TEXT:
385             {
386               const TextRequest* textrequest = (TextRequest*) request.request_data;
387               textrequest->font->draw(renderer, textrequest->text, request.pos,
388                   textrequest->alignment, request.drawing_effect, request.alpha);
389             }
390             break;
391           case FILLRECT:
392             renderer->draw_filled_rect(request);
393             break;
394           case INVERSEELLIPSE:
395             renderer->draw_inverse_ellipse(request);
396             break;
397           case DRAW_LIGHTMAP:
398             lightmap->do_draw();
399             break;
400           case GETLIGHT:
401             lightmap->get_light(request);
402             break;
403         }
404         break;
405       case LIGHTMAP:
406         switch(request.type) {
407           case SURFACE:
408             lightmap->draw_surface(request);
409             break;
410           case SURFACE_PART:
411             lightmap->draw_surface_part(request);
412             break;
413           case GRADIENT:
414             lightmap->draw_gradient(request);
415             break;
416           case TEXT:
417             {
418               const TextRequest* textrequest = (TextRequest*) request.request_data;
419               textrequest->font->draw(renderer, textrequest->text, request.pos,
420                   textrequest->alignment, request.drawing_effect, request.alpha);
421             }
422             break;
423           case FILLRECT:
424             lightmap->draw_filled_rect(request);
425             break;
426           case INVERSEELLIPSE:
427             assert(!"InverseEllipse doesn't make sense on the lightmap");
428             break;
429           case DRAW_LIGHTMAP:
430             lightmap->do_draw();
431             break;
432           case GETLIGHT:
433             lightmap->get_light(request);
434             break;
435         }
436         break;
437     }
438   }
439 }
440
441 void
442 DrawingContext::push_transform()
443 {
444   transformstack.push_back(transform);
445 }
446
447 void
448 DrawingContext::pop_transform()
449 {
450   assert(!transformstack.empty());
451
452   transform = transformstack.back();
453   transformstack.pop_back();
454 }
455
456 void
457 DrawingContext::set_drawing_effect(DrawingEffect effect)
458 {
459   transform.drawing_effect = effect;
460 }
461
462 DrawingEffect
463 DrawingContext::get_drawing_effect() const
464 {
465   return transform.drawing_effect;
466 }
467
468 void
469 DrawingContext::set_alpha(float alpha)
470 {
471   transform.alpha = alpha;
472 }
473
474 float
475 DrawingContext::get_alpha() const
476 {
477   return transform.alpha;
478 }
479
480 void
481 DrawingContext::push_target()
482 {
483   target_stack.push_back(target);
484 }
485
486 void
487 DrawingContext::pop_target()
488 {
489   set_target(target_stack.back());
490   target_stack.pop_back();
491 }
492
493 void
494 DrawingContext::set_target(Target target)
495 {
496   this->target = target;
497   if(target == LIGHTMAP) {
498     requests = &lightmap_requests;
499   } else {
500     assert(target == NORMAL);
501     requests = &drawing_requests;
502   }
503 }
504
505 void
506 DrawingContext::set_ambient_color( Color new_color )
507 {
508   ambient_color = new_color;
509 }
510
511 void 
512 DrawingContext::take_screenshot()
513 {
514   screenshot_requested = true;
515 }
516