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