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