- Use obstacks for memory allocation for lispfiles and DrawingRequests,
[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 <GL/gl.h>
27
28 #include "drawing_context.hpp"
29 #include "surface.hpp"
30 #include "font.hpp"
31 #include "main.hpp"
32 #include "gameconfig.hpp"
33 #include "glutil.hpp"
34 #include "texture.hpp"
35 #include "texture_manager.hpp"
36 #include "obstack/obstackpp.hpp"
37 #define LIGHTMAP_DIV 5
38
39 enum RequestType
40 {
41   SURFACE, SURFACE_PART, TEXT, GRADIENT, FILLRECT, LIGHTMAPREQUEST, GETLIGHT
42 };
43
44 struct SurfacePartRequest
45 {
46   const Surface* surface;
47   Vector source, size;
48 };
49
50 struct TextRequest
51 {
52   const Font* font;
53   std::string text;
54   FontAlignment alignment;
55 };
56
57 struct GradientRequest
58 {
59   Color top, bottom;
60   Vector size;
61 };
62
63 struct FillRectRequest
64 {
65   Color color;
66   Vector size;
67 };
68
69 struct DrawingRequest
70 {
71   RequestType type;
72   Vector pos;
73
74   int layer;
75   DrawingEffect drawing_effect;
76   float alpha;
77   Blend blend;
78   float angle;
79   Color color;
80
81   void* request_data;
82
83   DrawingRequest()
84     : angle(0.0f),
85       color(1.0f, 1.0f, 1.0f, 1.0f)
86   {}
87
88   bool operator<(const DrawingRequest& other) const
89   {
90     return layer < other.layer;
91   }
92 };
93
94 struct GetLightRequest
95 {
96   Color* color_ptr;
97 };
98
99 static inline int next_po2(int val)
100 {
101   int result = 1;
102   while(result < val)
103     result *= 2;
104
105   return result;
106 }
107
108 DrawingContext::DrawingContext()
109   : ambient_color(1.0f, 1.0f, 1.0f, 1.0f), target(NORMAL)
110 {
111   screen = SDL_GetVideoSurface();
112
113   lightmap_width = screen->w / LIGHTMAP_DIV;
114   lightmap_height = screen->h / LIGHTMAP_DIV;
115   unsigned int width = next_po2(lightmap_width);
116   unsigned int height = next_po2(lightmap_height);
117
118   lightmap = new Texture(width, height, GL_RGB);
119
120   lightmap_uv_right = static_cast<float>(lightmap_width) / static_cast<float>(width);
121   lightmap_uv_bottom = static_cast<float>(lightmap_height) / static_cast<float>(height);
122   texture_manager->register_texture(lightmap);
123
124   requests = &drawing_requests;
125
126   obstack_init(&obst);
127 }
128
129 DrawingContext::~DrawingContext()
130 {
131   obstack_free(&obst, NULL);
132
133   texture_manager->remove_texture(lightmap);
134   delete lightmap;
135 }
136
137 void
138 DrawingContext::draw_surface(const Surface* surface, const Vector& position,
139                              float angle, const Color& color, const Blend& blend,
140                              int layer)
141 {
142   assert(surface != 0);
143
144   DrawingRequest* request = new(obst) DrawingRequest();
145
146   request->type = SURFACE;
147   request->pos = transform.apply(position);
148
149   if(request->pos.x >= SCREEN_WIDTH || request->pos.y >= SCREEN_HEIGHT
150       || request->pos.x + surface->get_width() < 0
151       || request->pos.y + surface->get_height() < 0)
152     return;
153
154   request->layer = layer;
155   request->drawing_effect = transform.drawing_effect;
156   request->alpha = transform.alpha;
157   request->angle = angle;
158   request->color = color;
159   request->blend = blend;
160
161   request->request_data = const_cast<Surface*> (surface);
162
163   requests->push_back(request);
164 }
165
166 void
167 DrawingContext::draw_surface(const Surface* surface, const Vector& position,
168     int layer)
169 {
170   draw_surface(surface, position, 0.0f, Color(1.0f, 1.0f, 1.0f), Blend(), layer);
171 }
172
173 void
174 DrawingContext::draw_surface_part(const Surface* surface, const Vector& source,
175     const Vector& size, const Vector& dest, int layer)
176 {
177   assert(surface != 0);
178
179   DrawingRequest* request = new(obst) DrawingRequest();
180
181   request->type = SURFACE_PART;
182   request->pos = transform.apply(dest);
183   request->layer = layer;
184   request->drawing_effect = transform.drawing_effect;
185   request->alpha = transform.alpha;
186
187   SurfacePartRequest* surfacepartrequest = new(obst) SurfacePartRequest();
188   surfacepartrequest->size = size;
189   surfacepartrequest->source = source;
190   surfacepartrequest->surface = surface;
191
192   // clip on screen borders
193   if(request->pos.x < 0) {
194     surfacepartrequest->size.x += request->pos.x;
195     if(surfacepartrequest->size.x <= 0)
196       return;
197     surfacepartrequest->source.x -= request->pos.x;
198     request->pos.x = 0;
199   }
200   if(request->pos.y < 0) {
201     surfacepartrequest->size.y += request->pos.y;
202     if(surfacepartrequest->size.y <= 0)
203       return;
204     surfacepartrequest->source.y -= request->pos.y;
205     request->pos.y = 0;
206   }
207   request->request_data = surfacepartrequest;
208
209   requests->push_back(request);
210 }
211
212 void
213 DrawingContext::draw_text(const Font* font, const std::string& text,
214     const Vector& position, FontAlignment alignment, int layer)
215 {
216   DrawingRequest* request = new(obst) DrawingRequest();
217
218   request->type = TEXT;
219   request->pos = transform.apply(position);
220   request->layer = layer;
221   request->drawing_effect = transform.drawing_effect;
222   request->alpha = transform.alpha;
223
224   TextRequest* textrequest = new(obst) TextRequest();
225   textrequest->font = font;
226   textrequest->text = text;
227   textrequest->alignment = alignment;
228   request->request_data = textrequest;
229
230   requests->push_back(request);
231 }
232
233 void
234 DrawingContext::draw_center_text(const Font* font, const std::string& text,
235     const Vector& position, int layer)
236 {
237   draw_text(font, text, Vector(position.x + SCREEN_WIDTH/2, position.y),
238       ALIGN_CENTER, layer);
239 }
240
241 void
242 DrawingContext::draw_gradient(const Color& top, const Color& bottom, int layer)
243 {
244   DrawingRequest* request = new(obst) DrawingRequest();
245
246   request->type = GRADIENT;
247   request->pos = Vector(0,0);
248   request->layer = layer;
249
250   request->drawing_effect = transform.drawing_effect;
251   request->alpha = transform.alpha;
252
253   GradientRequest* gradientrequest = new(obst) GradientRequest();
254   gradientrequest->top = top;
255   gradientrequest->bottom = bottom;
256   request->request_data = gradientrequest;
257
258   requests->push_back(request);
259 }
260
261 void
262 DrawingContext::draw_filled_rect(const Vector& topleft, const Vector& size,
263                                  const Color& color, int layer)
264 {
265   DrawingRequest* request = new(obst) DrawingRequest();
266
267   request->type = FILLRECT;
268   request->pos = transform.apply(topleft);
269   request->layer = layer;
270
271   request->drawing_effect = transform.drawing_effect;
272   request->alpha = transform.alpha;
273
274   FillRectRequest* fillrectrequest = new(obst) FillRectRequest();
275   fillrectrequest->size = size;
276   fillrectrequest->color = color;
277   fillrectrequest->color.alpha = color.alpha * transform.alpha;
278   request->request_data = fillrectrequest;
279
280   requests->push_back(request);
281 }
282
283 void
284 DrawingContext::draw_filled_rect(const Rect& rect, const Color& color,
285                                  int layer)
286 {
287   DrawingRequest* request = new(obst) DrawingRequest();
288
289   request->type = FILLRECT;
290   request->pos = transform.apply(rect.p1);
291   request->layer = layer;
292
293   request->drawing_effect = transform.drawing_effect;
294   request->alpha = transform.alpha;
295
296   FillRectRequest* fillrectrequest = new(obst) FillRectRequest;
297   fillrectrequest->size = Vector(rect.get_width(), rect.get_height());
298   fillrectrequest->color = color;
299   fillrectrequest->color.alpha = color.alpha * transform.alpha;
300   request->request_data = fillrectrequest;
301
302   requests->push_back(request);
303 }
304
305 void
306 DrawingContext::get_light(const Vector& position, Color* color)
307 {
308   if( ambient_color.red == 1.0f && ambient_color.green == 1.0f
309       && ambient_color.blue  == 1.0f ) {
310     *color = Color( 1.0f, 1.0f, 1.0f);
311     return;
312   }
313
314   DrawingRequest* request = new(obst) DrawingRequest();
315   request->type = GETLIGHT;
316   request->pos = transform.apply(position);
317
318   //There is no light offscreen.
319   if(request->pos.x >= SCREEN_WIDTH || request->pos.y >= SCREEN_HEIGHT
320       || request->pos.x < 0 || request->pos.y < 0){
321     *color = Color( 0, 0, 0);
322     return;
323   }
324
325   request->layer = LAYER_GUI; //make sure all get_light requests are handled last.
326   GetLightRequest* getlightrequest = new(obst) GetLightRequest();
327   getlightrequest->color_ptr = color;
328   request->request_data = getlightrequest;
329   lightmap_requests.push_back(request);
330 }
331
332 void
333 DrawingContext::get_light(const DrawingRequest& request) const
334 {
335   const GetLightRequest* getlightrequest 
336     = (GetLightRequest*) request.request_data;
337
338   float pixels[3];
339   for( int i = 0; i<3; i++)
340     pixels[i] = 0.0f; //set to black
341
342   float posX = request.pos.x * lightmap_width / SCREEN_WIDTH;
343   float posY = screen->h - request.pos.y * lightmap_height / SCREEN_HEIGHT;
344   glReadPixels((GLint) posX, (GLint) posY , 1, 1, GL_RGB, GL_FLOAT, pixels);
345     *(getlightrequest->color_ptr) = Color( pixels[0], pixels[1], pixels[2]);
346   //printf("get_light %f/%f =>%f/%f r%f g%f b%f\n", request.pos.x, request.pos.y, posX, posY, pixels[0], pixels[1], pixels[2]);
347 }
348
349 void
350 DrawingContext::draw_surface_part(const DrawingRequest& request) const
351 {
352   const SurfacePartRequest* surfacepartrequest
353     = (SurfacePartRequest*) request.request_data;
354
355   surfacepartrequest->surface->draw_part(
356       surfacepartrequest->source.x, surfacepartrequest->source.y,
357       request.pos.x, request.pos.y,
358       surfacepartrequest->size.x, surfacepartrequest->size.y,
359       request.alpha, request.drawing_effect);
360 }
361
362 void
363 DrawingContext::draw_gradient(const DrawingRequest& request) const
364 {
365   const GradientRequest* gradientrequest 
366     = (GradientRequest*) request.request_data;
367   const Color& top = gradientrequest->top;
368   const Color& bottom = gradientrequest->bottom;
369
370   glDisable(GL_TEXTURE_2D);
371   glBegin(GL_QUADS);
372   glColor4f(top.red, top.green, top.blue, top.alpha);
373   glVertex2f(0, 0);
374   glVertex2f(SCREEN_WIDTH, 0);
375   glColor4f(bottom.red, bottom.green, bottom.blue, bottom.alpha);
376   glVertex2f(SCREEN_WIDTH, SCREEN_HEIGHT);
377   glVertex2f(0, SCREEN_HEIGHT);
378   glEnd();
379   glEnable(GL_TEXTURE_2D);
380 }
381
382 void
383 DrawingContext::draw_text(const DrawingRequest& request) const
384 {
385   const TextRequest* textrequest = (TextRequest*) request.request_data;
386
387   textrequest->font->draw(textrequest->text, request.pos,
388       textrequest->alignment, request.drawing_effect, request.alpha);
389 }
390
391 void
392 DrawingContext::draw_filled_rect(const DrawingRequest& request) const
393 {
394   const FillRectRequest* fillrectrequest
395     = (FillRectRequest*) request.request_data;
396
397   float x = request.pos.x;
398   float y = request.pos.y;
399   float w = fillrectrequest->size.x;
400   float h = fillrectrequest->size.y;
401
402   glDisable(GL_TEXTURE_2D);
403   glColor4f(fillrectrequest->color.red, fillrectrequest->color.green,
404             fillrectrequest->color.blue, fillrectrequest->color.alpha);
405
406   glBegin(GL_QUADS);
407   glVertex2f(x, y);
408   glVertex2f(x+w, y);
409   glVertex2f(x+w, y+h);
410   glVertex2f(x, y+h);
411   glEnd();
412   glEnable(GL_TEXTURE_2D);
413 }
414
415 void
416 DrawingContext::draw_lightmap(const DrawingRequest& request) const
417 {
418   const Texture* texture = reinterpret_cast<Texture*> (request.request_data);
419
420   // multiple the lightmap with the framebuffer
421   glBlendFunc(GL_DST_COLOR, GL_ZERO);
422
423   glBindTexture(GL_TEXTURE_2D, texture->get_handle());
424   glBegin(GL_QUADS);
425
426   glTexCoord2f(0, lightmap_uv_bottom);
427   glVertex2f(0, 0);
428
429   glTexCoord2f(lightmap_uv_right, lightmap_uv_bottom);
430   glVertex2f(SCREEN_WIDTH, 0);
431
432   glTexCoord2f(lightmap_uv_right, 0);
433   glVertex2f(SCREEN_WIDTH, SCREEN_HEIGHT);
434
435   glTexCoord2f(0, 0);
436   glVertex2f(0, SCREEN_HEIGHT);
437
438   glEnd();
439
440   glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
441 }
442
443 void
444 DrawingContext::do_drawing()
445 {
446 #ifdef DEBUG
447   assert(transformstack.empty());
448   assert(target_stack.empty());
449 #endif
450   transformstack.clear();
451   target_stack.clear();
452
453   //Use Lightmap if ambient color is not white.
454   bool use_lightmap = ( ambient_color.red != 1.0f   || ambient_color.green != 1.0f ||
455                         ambient_color.blue  != 1.0f );
456
457   // PART1: create lightmap
458   if(use_lightmap) {
459     glViewport(0, screen->h - lightmap_height, lightmap_width, lightmap_height);
460     glMatrixMode(GL_PROJECTION);
461     glLoadIdentity();
462     glOrtho(0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, -1.0, 1.0);
463     glMatrixMode(GL_MODELVIEW);
464     glLoadIdentity();
465
466     glClearColor( ambient_color.red, ambient_color.green, ambient_color.blue, 1 );
467     glClear(GL_COLOR_BUFFER_BIT);
468     handle_drawing_requests(lightmap_requests);
469     lightmap_requests.clear();
470
471     glDisable(GL_BLEND);
472     glBindTexture(GL_TEXTURE_2D, lightmap->get_handle());
473     glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, screen->h - lightmap_height, lightmap_width, lightmap_height);
474
475     glViewport(0, 0, screen->w, screen->h);
476     glMatrixMode(GL_PROJECTION);
477     glLoadIdentity();
478     glOrtho(0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, -1.0, 1.0);
479     glMatrixMode(GL_MODELVIEW);
480     glLoadIdentity();
481     glEnable(GL_BLEND);
482
483     // add a lightmap drawing request into the queue
484     DrawingRequest* request = new(obst) DrawingRequest();
485     request->type = LIGHTMAPREQUEST;
486     request->layer = LAYER_HUD - 1;
487     request->request_data = lightmap;
488     requests->push_back(request);
489   }
490
491   //glClear(GL_COLOR_BUFFER_BIT);
492   handle_drawing_requests(drawing_requests);
493   drawing_requests.clear();
494   obstack_free(&obst, NULL);
495   obstack_init(&obst);
496   assert_gl("drawing");
497
498   SDL_GL_SwapBuffers();
499 }
500
501 class RequestPtrCompare
502   :  public std::binary_function<const DrawingRequest*,
503                                  const DrawingRequest*, 
504                                  bool>
505 {
506 public:
507   bool operator()(const DrawingRequest* r1, const DrawingRequest* r2) const
508   {
509     return *r1 < *r2;
510   }
511 };
512
513 void
514 DrawingContext::handle_drawing_requests(DrawingRequests& requests) const
515 {
516   std::stable_sort(requests.begin(), requests.end(), RequestPtrCompare());
517
518   DrawingRequests::const_iterator i;
519   for(i = requests.begin(); i != requests.end(); ++i) {
520     const DrawingRequest& request = **i;
521
522     switch(request.type) {
523       case SURFACE:
524       {
525         const Surface* surface = (const Surface*) request.request_data;
526         if (request.angle == 0.0f &&
527             request.color.red == 1.0f && request.color.green == 1.0f  &&
528             request.color.blue == 1.0f &&  request.color.alpha == 1.0f) {
529           surface->draw(request.pos.x, request.pos.y, request.alpha,
530               request.drawing_effect);
531         } else {
532           surface->draw(request.pos.x, request.pos.y,
533               request.alpha, request.angle, request.color,
534               request.blend, request.drawing_effect);
535         }
536         break;
537       }
538       case SURFACE_PART:
539         draw_surface_part(request);
540         break;
541       case GRADIENT:
542         draw_gradient(request);
543         break;
544       case TEXT:
545         draw_text(request);
546         break;
547       case FILLRECT:
548         draw_filled_rect(request);
549         break;
550       case LIGHTMAPREQUEST:
551         draw_lightmap(request);
552         break;
553       case GETLIGHT:
554         get_light(request);
555         break;
556     }
557   }
558 }
559
560 void
561 DrawingContext::push_transform()
562 {
563   transformstack.push_back(transform);
564 }
565
566 void
567 DrawingContext::pop_transform()
568 {
569   assert(!transformstack.empty());
570
571   transform = transformstack.back();
572   transformstack.pop_back();
573 }
574
575 void
576 DrawingContext::set_drawing_effect(DrawingEffect effect)
577 {
578   transform.drawing_effect = effect;
579 }
580
581 DrawingEffect
582 DrawingContext::get_drawing_effect() const
583 {
584   return transform.drawing_effect;
585 }
586
587 void
588 DrawingContext::set_alpha(float alpha)
589 {
590   transform.alpha = alpha;
591 }
592
593 float
594 DrawingContext::get_alpha() const
595 {
596   return transform.alpha;
597 }
598
599 void
600 DrawingContext::push_target()
601 {
602   target_stack.push_back(target);
603 }
604
605 void
606 DrawingContext::pop_target()
607 {
608   set_target(target_stack.back());
609   target_stack.pop_back();
610 }
611
612 void
613 DrawingContext::set_target(Target target)
614 {
615   this->target = target;
616   if(target == LIGHTMAP) {
617     requests = &lightmap_requests;
618   } else {
619     assert(target == NORMAL);
620     requests = &drawing_requests;
621   }
622 }
623
624 void
625 DrawingContext::set_ambient_color( Color new_color )
626 {
627   ambient_color = new_color;
628 }