070cffbb0d4ae35e39cf132d06689c9bbc842e83
[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/globals.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   assert(transformstack.empty());
318   assert(target_stack.empty());
319   transformstack.clear();
320   target_stack.clear();
321
322   //Use Lightmap if ambient color is not white.
323   bool use_lightmap = ( ambient_color.red != 1.0f   || ambient_color.green != 1.0f ||
324                         ambient_color.blue  != 1.0f );
325
326   // PART1: create lightmap
327   if(use_lightmap) {
328     lightmap->start_draw(ambient_color);
329     handle_drawing_requests(lightmap_requests);
330     lightmap->end_draw();
331
332     DrawingRequest* request = new(obst) DrawingRequest();
333     request->target = NORMAL;
334     request->type = DRAW_LIGHTMAP;
335     request->layer = LAYER_HUD - 1;
336     drawing_requests.push_back(request);
337   }
338   lightmap_requests.clear();
339
340   handle_drawing_requests(drawing_requests);
341   drawing_requests.clear();
342   obstack_free(&obst, NULL);
343   obstack_init(&obst);
344
345   // if a screenshot was requested, take one
346   if (screenshot_requested) {
347     renderer->do_take_screenshot();
348     screenshot_requested = false;
349   }
350
351   renderer->flip();
352 }
353
354 class RequestPtrCompare
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.color, 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.color, 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
517 /* EOF */