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