88e288b0086aa26a6ca2a7d9d4bbc90cad16a287
[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, Color color)
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   request->color = color;
166
167   TextRequest* textrequest = new(obst) TextRequest();
168   textrequest->font = font;
169   textrequest->text = text;
170   textrequest->alignment = alignment;
171   request->request_data = textrequest;
172
173   requests->push_back(request);
174 }
175
176 void
177 DrawingContext::draw_center_text(const Font* font, const std::string& text,
178     const Vector& position, int layer, Color color)
179 {
180   draw_text(font, text, Vector(position.x + SCREEN_WIDTH/2, position.y),
181       ALIGN_CENTER, layer, color);
182 }
183
184 void
185 DrawingContext::draw_gradient(const Color& top, const Color& bottom, int layer)
186 {
187   DrawingRequest* request = new(obst) DrawingRequest();
188
189   request->target = target;
190   request->type = GRADIENT;
191   request->pos = Vector(0,0);
192   request->layer = layer;
193
194   request->drawing_effect = transform.drawing_effect;
195   request->alpha = transform.alpha;
196
197   GradientRequest* gradientrequest = new(obst) GradientRequest();
198   gradientrequest->top = top;
199   gradientrequest->bottom = bottom;
200   request->request_data = gradientrequest;
201
202   requests->push_back(request);
203 }
204
205 void
206 DrawingContext::draw_filled_rect(const Vector& topleft, const Vector& size,
207                                  const Color& color, int layer)
208 {
209   DrawingRequest* request = new(obst) DrawingRequest();
210
211   request->target = target;
212   request->type = FILLRECT;
213   request->pos = transform.apply(topleft);
214   request->layer = layer;
215
216   request->drawing_effect = transform.drawing_effect;
217   request->alpha = transform.alpha;
218
219   FillRectRequest* fillrectrequest = new(obst) FillRectRequest();
220   fillrectrequest->size = size;
221   fillrectrequest->color = color;
222   fillrectrequest->color.alpha = color.alpha * transform.alpha;
223   fillrectrequest->radius = 0.0f;
224   request->request_data = fillrectrequest;
225
226   requests->push_back(request);
227 }
228
229 void
230 DrawingContext::draw_filled_rect(const Rect& rect, const Color& color,
231                                  int layer)
232 {
233   draw_filled_rect(rect, color, 0.0f, layer);
234 }
235
236 void
237 DrawingContext::draw_filled_rect(const Rect& rect, const Color& color, float radius, int layer)
238 {
239   DrawingRequest* request = new(obst) DrawingRequest();
240
241   request->target = target;
242   request->type   = FILLRECT;
243   request->pos    = transform.apply(rect.p1);
244   request->layer  = layer;
245
246   request->drawing_effect = transform.drawing_effect;
247   request->alpha = transform.alpha;
248
249   FillRectRequest* fillrectrequest = new(obst) FillRectRequest;
250   fillrectrequest->size = Vector(rect.get_width(), rect.get_height());
251   fillrectrequest->color = color;
252   fillrectrequest->color.alpha = color.alpha * transform.alpha;
253   fillrectrequest->radius = radius;
254   request->request_data = fillrectrequest;
255
256   requests->push_back(request); 
257 }
258
259 void
260 DrawingContext::draw_inverse_ellipse(const Vector& pos, const Vector& size, const Color& color, int layer)
261 {
262   DrawingRequest* request = new(obst) DrawingRequest();
263
264   request->target = target;
265   request->type   = INVERSEELLIPSE;
266   request->pos    = transform.apply(pos);
267   request->layer  = layer;
268
269   request->drawing_effect = transform.drawing_effect;
270   request->alpha = transform.alpha;
271
272   InverseEllipseRequest* ellipse = new(obst)InverseEllipseRequest;
273   
274   ellipse->color        = color;
275   ellipse->color.alpha  = color.alpha * transform.alpha;
276   ellipse->size         = size;
277   request->request_data = ellipse;
278
279   requests->push_back(request);     
280 }
281
282 void
283 DrawingContext::get_light(const Vector& position, Color* color)
284 {
285   if( ambient_color.red == 1.0f && ambient_color.green == 1.0f
286       && ambient_color.blue  == 1.0f ) {
287     *color = Color( 1.0f, 1.0f, 1.0f);
288     return;
289   }
290
291   DrawingRequest* request = new(obst) DrawingRequest();
292   request->target = target;
293   request->type = GETLIGHT;
294   request->pos = transform.apply(position);
295
296   //There is no light offscreen.
297   if(request->pos.x >= SCREEN_WIDTH || request->pos.y >= SCREEN_HEIGHT
298       || request->pos.x < 0 || request->pos.y < 0){
299     *color = Color( 0, 0, 0);
300     return;
301   }
302
303   request->layer = LAYER_GUI; //make sure all get_light requests are handled last.
304   GetLightRequest* getlightrequest = new(obst) GetLightRequest();
305   getlightrequest->color_ptr = color;
306   request->request_data = getlightrequest;
307   lightmap_requests.push_back(request);
308 }
309
310 void
311 DrawingContext::do_drawing()
312 {
313 #ifdef DEBUG
314   assert(transformstack.empty());
315   assert(target_stack.empty());
316 #endif
317   transformstack.clear();
318   target_stack.clear();
319
320   //Use Lightmap if ambient color is not white.
321   bool use_lightmap = ( ambient_color.red != 1.0f   || ambient_color.green != 1.0f ||
322                         ambient_color.blue  != 1.0f );
323
324   // PART1: create lightmap
325   if(use_lightmap) {
326     lightmap->start_draw(ambient_color);
327     handle_drawing_requests(lightmap_requests);
328     lightmap->end_draw();
329
330     DrawingRequest* request = new(obst) DrawingRequest();
331     request->target = NORMAL;
332     request->type = DRAW_LIGHTMAP;
333     request->layer = LAYER_HUD - 1;
334     drawing_requests.push_back(request);
335   }
336   lightmap_requests.clear();
337
338   handle_drawing_requests(drawing_requests);
339   drawing_requests.clear();
340   obstack_free(&obst, NULL);
341   obstack_init(&obst);
342
343   // if a screenshot was requested, take one
344   if (screenshot_requested) {
345     renderer->do_take_screenshot();
346     screenshot_requested = false;
347   }
348
349   renderer->flip();
350 }
351
352 class RequestPtrCompare
353   :  public std::binary_function<const DrawingRequest*,
354                                  const DrawingRequest*, 
355                                  bool>
356 {
357 public:
358   bool operator()(const DrawingRequest* r1, const DrawingRequest* r2) const
359   {
360     return *r1 < *r2;
361   }
362 };
363
364 void
365 DrawingContext::handle_drawing_requests(DrawingRequests& requests)
366 {
367   std::stable_sort(requests.begin(), requests.end(), RequestPtrCompare());
368
369   DrawingRequests::const_iterator i;
370   for(i = requests.begin(); i != requests.end(); ++i) {
371     const DrawingRequest& request = **i;
372
373     switch(request.target) {
374       case NORMAL:
375         switch(request.type) {
376           case SURFACE:
377             renderer->draw_surface(request);
378             break;
379           case SURFACE_PART:
380             renderer->draw_surface_part(request);
381             break;
382           case GRADIENT:
383             renderer->draw_gradient(request);
384             break;
385           case TEXT:
386             {
387               const TextRequest* textrequest = (TextRequest*) request.request_data;
388               textrequest->font->draw(renderer, textrequest->text, request.pos,
389                   textrequest->alignment, request.drawing_effect, request.color, request.alpha);
390             }
391             break;
392           case FILLRECT:
393             renderer->draw_filled_rect(request);
394             break;
395           case INVERSEELLIPSE:
396             renderer->draw_inverse_ellipse(request);
397             break;
398           case DRAW_LIGHTMAP:
399             lightmap->do_draw();
400             break;
401           case GETLIGHT:
402             lightmap->get_light(request);
403             break;
404         }
405         break;
406       case LIGHTMAP:
407         switch(request.type) {
408           case SURFACE:
409             lightmap->draw_surface(request);
410             break;
411           case SURFACE_PART:
412             lightmap->draw_surface_part(request);
413             break;
414           case GRADIENT:
415             lightmap->draw_gradient(request);
416             break;
417           case TEXT:
418             {
419               const TextRequest* textrequest = (TextRequest*) request.request_data;
420               textrequest->font->draw(renderer, textrequest->text, request.pos,
421                   textrequest->alignment, request.drawing_effect, request.color, request.alpha);
422             }
423             break;
424           case FILLRECT:
425             lightmap->draw_filled_rect(request);
426             break;
427           case INVERSEELLIPSE:
428             assert(!"InverseEllipse doesn't make sense on the lightmap");
429             break;
430           case DRAW_LIGHTMAP:
431             lightmap->do_draw();
432             break;
433           case GETLIGHT:
434             lightmap->get_light(request);
435             break;
436         }
437         break;
438     }
439   }
440 }
441
442 void
443 DrawingContext::push_transform()
444 {
445   transformstack.push_back(transform);
446 }
447
448 void
449 DrawingContext::pop_transform()
450 {
451   assert(!transformstack.empty());
452
453   transform = transformstack.back();
454   transformstack.pop_back();
455 }
456
457 void
458 DrawingContext::set_drawing_effect(DrawingEffect effect)
459 {
460   transform.drawing_effect = effect;
461 }
462
463 DrawingEffect
464 DrawingContext::get_drawing_effect() const
465 {
466   return transform.drawing_effect;
467 }
468
469 void
470 DrawingContext::set_alpha(float alpha)
471 {
472   transform.alpha = alpha;
473 }
474
475 float
476 DrawingContext::get_alpha() const
477 {
478   return transform.alpha;
479 }
480
481 void
482 DrawingContext::push_target()
483 {
484   target_stack.push_back(target);
485 }
486
487 void
488 DrawingContext::pop_target()
489 {
490   set_target(target_stack.back());
491   target_stack.pop_back();
492 }
493
494 void
495 DrawingContext::set_target(Target target)
496 {
497   this->target = target;
498   if(target == LIGHTMAP) {
499     requests = &lightmap_requests;
500   } else {
501     assert(target == NORMAL);
502     requests = &drawing_requests;
503   }
504 }
505
506 void
507 DrawingContext::set_ambient_color( Color new_color )
508 {
509   ambient_color = new_color;
510 }
511
512 void 
513 DrawingContext::take_screenshot()
514 {
515   screenshot_requested = true;
516 }
517