Added DrawingContext::get_cliprect()
[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 "video/drawing_context.hpp"
18
19 #include <algorithm>
20 #include <config.h>
21
22 #include "math/sizef.hpp"
23 #include "supertux/gameconfig.hpp"
24 #include "supertux/globals.hpp"
25 #include "util/obstackpp.hpp"
26 #include "video/drawing_request.hpp"
27 #include "video/lightmap.hpp"
28 #include "video/renderer.hpp"
29 #include "video/surface.hpp"
30 #include "video/texture.hpp"
31 #include "video/texture_manager.hpp"
32 #include "video/video_systems.hpp"
33
34 DrawingContext::DrawingContext() :
35   renderer(0), 
36   lightmap(0),
37   transformstack(),
38   transform(),
39   blend_stack(),
40   blend_mode(),
41   drawing_requests(),
42   lightmap_requests(),
43   requests(),
44   ambient_color(1.0f, 1.0f, 1.0f, 1.0f),
45   target(NORMAL),
46   target_stack(),
47   obst(),
48   screenshot_requested(false)
49 {
50   requests = &drawing_requests;
51   obstack_init(&obst);
52 }
53
54 DrawingContext::~DrawingContext()
55 {
56   delete renderer;
57   delete lightmap;
58
59   obstack_free(&obst, NULL);
60 }
61
62 void
63 DrawingContext::init_renderer()
64 {
65   delete renderer;
66   delete lightmap;
67
68   renderer = VideoSystem::new_renderer();
69   lightmap = VideoSystem::new_lightmap();
70 }
71
72 void
73 DrawingContext::draw_surface(SurfacePtr surface, const Vector& position,
74                              float angle, const Color& color, const Blend& blend,
75                              int layer)
76 {
77   assert(surface != 0);
78
79   DrawingRequest* request = new(obst) DrawingRequest();
80
81   request->target = target;
82   request->type = SURFACE;
83   request->pos = transform.apply(position);
84
85   if(request->pos.x >= SCREEN_WIDTH || request->pos.y >= SCREEN_HEIGHT
86      || request->pos.x + surface->get_width() < 0
87      || request->pos.y + surface->get_height() < 0)
88     return;
89
90   request->layer = layer;
91   request->drawing_effect = transform.drawing_effect;
92   request->alpha = transform.alpha;
93   request->angle = angle;
94   request->color = color;
95   request->blend = blend;
96
97   request->request_data = surface.get();
98
99   requests->push_back(request);
100 }
101
102 void
103 DrawingContext::draw_surface(SurfacePtr surface, const Vector& position,
104                              int layer)
105 {
106   draw_surface(surface, position, 0.0f, Color(1.0f, 1.0f, 1.0f), Blend(), layer);
107 }
108
109 void
110 DrawingContext::draw_surface_part(SurfacePtr surface, const Vector& source,
111                                   const Vector& size, const Vector& dest, int layer)
112 {
113   assert(surface != 0);
114
115   DrawingRequest* request = new(obst) DrawingRequest();
116
117   request->target = target;
118   request->type = SURFACE_PART;
119   request->pos = transform.apply(dest);
120   request->layer = layer;
121   request->drawing_effect = transform.drawing_effect;
122   request->alpha = transform.alpha;
123
124   SurfacePartRequest* surfacepartrequest = new(obst) SurfacePartRequest();
125   surfacepartrequest->size = size;
126   surfacepartrequest->source = source;
127   surfacepartrequest->surface = surface.get();
128
129   // clip on screen borders
130   if(request->pos.x < 0) {
131     surfacepartrequest->size.x += request->pos.x;
132     if(surfacepartrequest->size.x <= 0)
133       return;
134     surfacepartrequest->source.x -= request->pos.x;
135     request->pos.x = 0;
136   }
137   if(request->pos.y < 0) {
138     surfacepartrequest->size.y += request->pos.y;
139     if(surfacepartrequest->size.y <= 0)
140       return;
141     surfacepartrequest->source.y -= request->pos.y;
142     request->pos.y = 0;
143   }
144   request->request_data = surfacepartrequest;
145
146   requests->push_back(request);
147 }
148
149 void
150 DrawingContext::draw_text(FontPtr font, const std::string& text,
151                           const Vector& position, FontAlignment alignment, int layer, Color color)
152 {
153   DrawingRequest* request = new(obst) DrawingRequest();
154
155   request->target = target;
156   request->type = TEXT;
157   request->pos = transform.apply(position);
158   request->layer = layer;
159   request->drawing_effect = transform.drawing_effect;
160   request->alpha = transform.alpha;
161   request->color = color;
162
163   TextRequest* textrequest = new(obst) TextRequest();
164   textrequest->font = font.get();
165   textrequest->text = text;
166   textrequest->alignment = alignment;
167   request->request_data = textrequest;
168
169   requests->push_back(request);
170 }
171
172 void
173 DrawingContext::draw_center_text(FontPtr font, const std::string& text,
174                                  const Vector& position, int layer, Color color)
175 {
176   draw_text(font, text, Vector(position.x + SCREEN_WIDTH/2, position.y),
177             ALIGN_CENTER, layer, color);
178 }
179
180 void
181 DrawingContext::draw_gradient(const Color& top, const Color& bottom, int layer)
182 {
183   DrawingRequest* request = new(obst) DrawingRequest();
184
185   request->target = target;
186   request->type = GRADIENT;
187   request->pos = Vector(0,0);
188   request->layer = layer;
189
190   request->drawing_effect = transform.drawing_effect;
191   request->alpha = transform.alpha;
192
193   GradientRequest* gradientrequest = new(obst) GradientRequest();
194   gradientrequest->top = top;
195   gradientrequest->bottom = bottom;
196   request->request_data = gradientrequest;
197
198   requests->push_back(request);
199 }
200
201 void
202 DrawingContext::draw_filled_rect(const Vector& topleft, const Vector& size,
203                                  const Color& color, int layer)
204 {
205   DrawingRequest* request = new(obst) DrawingRequest();
206
207   request->target = target;
208   request->type = FILLRECT;
209   request->pos = transform.apply(topleft);
210   request->layer = layer;
211
212   request->drawing_effect = transform.drawing_effect;
213   request->alpha = transform.alpha;
214
215   FillRectRequest* fillrectrequest = new(obst) FillRectRequest();
216   fillrectrequest->size = size;
217   fillrectrequest->color = color;
218   fillrectrequest->color.alpha = color.alpha * transform.alpha;
219   fillrectrequest->radius = 0.0f;
220   request->request_data = fillrectrequest;
221
222   requests->push_back(request);
223 }
224
225 void
226 DrawingContext::draw_filled_rect(const Rectf& rect, const Color& color,
227                                  int layer)
228 {
229   draw_filled_rect(rect, color, 0.0f, layer);
230 }
231
232 void
233 DrawingContext::draw_filled_rect(const Rectf& rect, const Color& color, float radius, int layer)
234 {
235   DrawingRequest* request = new(obst) DrawingRequest();
236
237   request->target = target;
238   request->type   = FILLRECT;
239   request->pos    = transform.apply(rect.p1);
240   request->layer  = layer;
241
242   request->drawing_effect = transform.drawing_effect;
243   request->alpha = transform.alpha;
244
245   FillRectRequest* fillrectrequest = new(obst) FillRectRequest;
246   fillrectrequest->size = Vector(rect.get_width(), rect.get_height());
247   fillrectrequest->color = color;
248   fillrectrequest->color.alpha = color.alpha * transform.alpha;
249   fillrectrequest->radius = radius;
250   request->request_data = fillrectrequest;
251
252   requests->push_back(request); 
253 }
254
255 void
256 DrawingContext::draw_inverse_ellipse(const Vector& pos, const Vector& size, const Color& color, int layer)
257 {
258   DrawingRequest* request = new(obst) DrawingRequest();
259
260   request->target = target;
261   request->type   = INVERSEELLIPSE;
262   request->pos    = transform.apply(pos);
263   request->layer  = layer;
264
265   request->drawing_effect = transform.drawing_effect;
266   request->alpha = transform.alpha;
267
268   InverseEllipseRequest* ellipse = new(obst)InverseEllipseRequest;
269   
270   ellipse->color        = color;
271   ellipse->color.alpha  = color.alpha * transform.alpha;
272   ellipse->size         = size;
273   request->request_data = ellipse;
274
275   requests->push_back(request);     
276 }
277
278 Rectf
279 DrawingContext::get_cliprect() const
280 {
281   return Rectf(get_translation().x, get_translation().y,
282                get_translation().x + SCREEN_WIDTH, 
283                get_translation().y + SCREEN_HEIGHT);
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 */