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