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