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