040189cddd99172cc7694f974d791b98e4901031
[supertux.git] / src / video / sdl_lightmap.cpp
1 //  $Id: sdl_lightmap.cpp 5063 2007-05-27 11:32:00Z matzeb $
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 "glutil.hpp"
31 #include "sdl_lightmap.hpp"
32 #include "sdl_texture.hpp"
33 #include "sdl_surface_data.hpp"
34 #include "drawing_context.hpp"
35 #include "drawing_request.hpp"
36 #include "renderer.hpp"
37 #include "surface.hpp"
38 #include "font.hpp"
39 #include "main.hpp"
40 #include "gameconfig.hpp"
41 #include "texture.hpp"
42 #include "texture_manager.hpp"
43 #include "obstack/obstackpp.hpp"
44
45 namespace SDL
46 {
47   Lightmap::Lightmap()
48   {
49     screen = SDL_GetVideoSurface();
50
51     float xfactor = (float) config->screenwidth / SCREEN_WIDTH;
52     float yfactor = (float) config->screenheight / SCREEN_HEIGHT;
53     if(xfactor < yfactor)
54     {
55       numerator = config->screenwidth;
56       denominator = SCREEN_WIDTH;
57     }
58     else
59     {
60       numerator = config->screenheight;
61       denominator = SCREEN_HEIGHT;
62     }
63
64     LIGHTMAP_DIV = 8 * numerator / denominator;
65
66     width = screen->w / LIGHTMAP_DIV;
67     height = screen->h / LIGHTMAP_DIV;
68
69     red_channel = (Uint8 *)malloc(width * height * sizeof(Uint8));
70     green_channel = (Uint8 *)malloc(width * height * sizeof(Uint8));
71     blue_channel = (Uint8 *)malloc(width * height * sizeof(Uint8));
72   }
73
74   Lightmap::~Lightmap()
75   {
76     free(red_channel);
77     free(green_channel);
78     free(blue_channel);
79   }
80
81   void
82   Lightmap::start_draw(const Color &ambient_color)
83   {
84     memset(red_channel, (Uint8) (ambient_color.red * 255), width * height * sizeof(Uint8));
85     memset(green_channel, (Uint8) (ambient_color.green * 255), width * height * sizeof(Uint8));
86     memset(blue_channel, (Uint8) (ambient_color.blue * 255), width * height * sizeof(Uint8));
87   }
88
89   void
90   Lightmap::end_draw()
91   {
92   }
93
94 //#define BILINEAR
95
96 #ifdef BILINEAR
97   namespace
98   {
99     void merge(Uint8 color[3], Uint8 color0[3], Uint8 color1[3], int rem, int total)
100     {
101       color[0] = (color0[0] * (total - rem) + color1[0] * rem) / total;
102       color[1] = (color0[1] * (total - rem) + color1[1] * rem) / total;
103       color[2] = (color0[2] * (total - rem) + color1[2] * rem) / total;
104     }
105   }
106 #endif
107
108   void
109   Lightmap::do_draw()
110   {
111     // FIXME: This is really slow
112     if(LIGHTMAP_DIV == 1)
113     {
114       int bpp = screen->format->BytesPerPixel;
115       Uint8 *pixel = (Uint8 *) screen->pixels;
116       int loc = 0;
117       for(int y = 0;y < height;y++) {
118         for(int x = 0;x < width;x++, pixel += bpp, loc++) {
119           if(red_channel[loc] == 0xff && green_channel[loc] == 0xff && blue_channel[loc] == 0xff)
120           {
121             continue;
122           }
123           Uint32 mapped = 0;
124           switch(bpp) {
125             case 1:
126               mapped = *pixel;
127               break;
128             case 2:
129               mapped = *(Uint16 *)pixel;
130               break;
131             case 3:
132 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
133               mapped |= pixel[0] << 16;
134               mapped |= pixel[1] << 8;
135               mapped |= pixel[2] << 0;
136 #else
137               mapped |= pixel[0] << 0;
138               mapped |= pixel[1] << 8;
139               mapped |= pixel[2] << 16;
140 #endif
141               break;
142             case 4:
143               mapped = *(Uint32 *)pixel;
144               break;
145           }
146           Uint8 red, green, blue, alpha;
147           SDL_GetRGBA(mapped, screen->format, &red, &green, &blue, &alpha);
148           red = (red * red_channel[loc]) >> 8;
149           green = (green * green_channel[loc]) >> 8;
150           blue = (blue * blue_channel[loc]) >> 8;
151           mapped = SDL_MapRGBA(screen->format, red, green, blue, alpha);
152           switch(bpp) {
153             case 1:
154               *pixel = mapped;
155               break;
156             case 2:
157               *(Uint16 *)pixel = mapped;
158               break;
159             case 3:
160 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
161               pixel[0] = (mapped >> 16) & 0xff;
162               pixel[1] = (mapped >> 8) & 0xff;
163               pixel[2] = (mapped >> 0) & 0xff;
164 #else
165               pixel[0] = (mapped >> 0) & 0xff;
166               pixel[1] = (mapped >> 8) & 0xff;
167               pixel[2] = (mapped >> 16) & 0xff;
168 #endif
169               break;
170             case 4:
171               *(Uint32 *)pixel = mapped;
172               break;
173           }
174         }
175         pixel += screen->pitch - width * bpp;
176       }
177     }
178     else
179     {
180       int bpp = screen->format->BytesPerPixel;
181       Uint8 *div_pixel = (Uint8 *) screen->pixels;
182       int loc = 0;
183       for(int y = 0;y < height;y++) {
184         for(int x = 0;x < width;x++, div_pixel += bpp * LIGHTMAP_DIV, loc++) {
185           if(red_channel[loc] == 0xff && green_channel[loc] == 0xff && blue_channel[loc] == 0xff)
186           {
187             continue;
188           }
189           Uint8 *pixel = div_pixel;
190           for(int div_y = 0;div_y < LIGHTMAP_DIV;div_y++) {
191             for(int div_x = 0;div_x < LIGHTMAP_DIV;pixel += bpp, div_x++) {
192               Uint32 mapped = 0;
193               switch(bpp) {
194                 case 1:
195                   mapped = *pixel;
196                   break;
197                 case 2:
198                   mapped = *(Uint16 *)pixel;
199                   break;
200                 case 3:
201 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
202                   mapped |= pixel[0] << 16;
203                   mapped |= pixel[1] << 8;
204                   mapped |= pixel[2] << 0;
205 #else
206                   mapped |= pixel[0] << 0;
207                   mapped |= pixel[1] << 8;
208                   mapped |= pixel[2] << 16;
209 #endif
210                   break;
211                 case 4:
212                   mapped = *(Uint32 *)pixel;
213                   break;
214               }
215               Uint8 red, green, blue, alpha;
216               SDL_GetRGBA(mapped, screen->format, &red, &green, &blue, &alpha);
217
218 #ifdef BILINEAR
219               int xinc = (x + 1 != width ? 1 : 0);
220               int yinc = (y + 1 != height ? width : 0);
221               Uint8 color00[3], color01[3], color10[3], color11[3];
222               {
223                 color00[0] = red_channel[loc];
224                 color00[1] = green_channel[loc];
225                 color00[2] = blue_channel[loc];
226               }
227               {
228                 color01[0] = red_channel[loc + xinc];
229                 color01[1] = green_channel[loc + xinc];
230                 color01[2] = blue_channel[loc + xinc];
231               }
232               {
233                 color10[0] = red_channel[loc + yinc];
234                 color10[1] = green_channel[loc + yinc];
235                 color10[2] = blue_channel[loc + yinc];
236               }
237               {
238                 color11[0] = red_channel[loc + yinc + xinc];
239                 color11[1] = green_channel[loc + yinc + xinc];
240                 color11[2] = blue_channel[loc + yinc + xinc];
241               }
242               Uint8 color0[3], color1[3], color[3];
243               merge(color0, color00, color01, div_x, LIGHTMAP_DIV);
244               merge(color1, color10, color11, div_x, LIGHTMAP_DIV);
245               merge(color, color0, color1, div_y, LIGHTMAP_DIV);
246               red = (red * color[0]) >> 8;
247               green = (green * color[1]) >> 8;
248               blue = (blue * color[2]) >> 8;
249 #else
250               red = (red * red_channel[loc]) >> 8;
251               green = (green * green_channel[loc]) >> 8;
252               blue = (blue * blue_channel[loc]) >> 8;
253 #endif
254
255               mapped = SDL_MapRGBA(screen->format, red, green, blue, alpha);
256               switch(bpp) {
257                 case 1:
258                   *pixel = mapped;
259                   break;
260                 case 2:
261                   *(Uint16 *)pixel = mapped;
262                   break;
263                 case 3:
264 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
265                   pixel[0] = (mapped >> 16) & 0xff;
266                   pixel[1] = (mapped >> 8) & 0xff;
267                   pixel[2] = (mapped >> 0) & 0xff;
268 #else
269                   pixel[0] = (mapped >> 0) & 0xff;
270                   pixel[1] = (mapped >> 8) & 0xff;
271                   pixel[2] = (mapped >> 16) & 0xff;
272 #endif
273                   break;
274                 case 4:
275                   *(Uint32 *)pixel = mapped;
276                   break;
277               }
278             }
279             pixel += screen->pitch - LIGHTMAP_DIV * bpp;
280           }
281         }
282         div_pixel += (screen->pitch - width * bpp) * LIGHTMAP_DIV;
283       }
284     }
285   }
286
287   void Lightmap::light_blit(SDL_Surface *src, SDL_Rect *src_rect, int dstx, int dsty)
288   {
289     dstx /= LIGHTMAP_DIV;
290     dsty /= LIGHTMAP_DIV;
291     int srcx = src_rect->x / LIGHTMAP_DIV;
292     int srcy = src_rect->y / LIGHTMAP_DIV;
293     int blit_width = src_rect->w / LIGHTMAP_DIV;
294     int blit_height = src_rect->h / LIGHTMAP_DIV;
295     int bpp = src->format->BytesPerPixel;
296     Uint8 *pixel = (Uint8 *) src->pixels + srcy * src->pitch + srcx * bpp;
297     int loc = dsty * width + dstx;
298     for(int y = 0;y < blit_height;y++) {
299       for(int x = 0;x < blit_width;x++, pixel += bpp * LIGHTMAP_DIV, loc++) {
300         if(x + dstx < 0 || y + dsty < 0 || x + dstx >= width || y + dsty >= height)
301         {
302           continue;
303         }
304         if(red_channel[loc] == 0xff && green_channel[loc] == 0xff && blue_channel[loc] == 0xff)
305         {
306           continue;
307         }
308
309         Uint32 mapped = 0;
310         switch(bpp) {
311           case 1:
312             mapped = *pixel;
313             break;
314           case 2:
315             mapped = *(Uint16 *)pixel;
316             break;
317           case 3:
318 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
319             mapped |= pixel[0] << 16;
320             mapped |= pixel[1] << 8;
321             mapped |= pixel[2] << 0;
322 #else
323             mapped |= pixel[0] << 0;
324             mapped |= pixel[1] << 8;
325             mapped |= pixel[2] << 16;
326 #endif
327             break;
328           case 4:
329             mapped = *(Uint32 *)pixel;
330             break;
331         }
332         Uint8 red, green, blue, alpha;
333         SDL_GetRGBA(mapped, src->format, &red, &green, &blue, &alpha);
334
335         if(red != 0)
336         {
337           int redsum = red_channel[loc] + (red * alpha >> 8);
338           red_channel[loc] = redsum & ~0xff ? 0xff : redsum;
339         }
340         if(green != 0)
341         {
342           int greensum = green_channel[loc] + (green * alpha >> 8);
343           green_channel[loc] = greensum & ~0xff ? 0xff : greensum;
344         }
345         if(blue != 0)
346         {
347           int bluesum = blue_channel[loc] + (blue * alpha >> 8);
348           blue_channel[loc] = bluesum & ~0xff ? 0xff : bluesum;
349         }
350       }
351       pixel += (src->pitch - blit_width * bpp) * LIGHTMAP_DIV;
352       loc += width - blit_width;
353     }
354   }
355
356   /*void Lightmap::light_blit(SDL_Surface *src, SDL_Rect *src_rect, int dstx, int dsty)
357   {
358     int bpp = src->format->BytesPerPixel;
359       Uint8 *pixel = (Uint8 *) src->pixels + src_rect->y * src->pitch + src_rect->x * bpp;
360     int loc = dsty * width + dstx;
361     for(int y = 0;y < src_rect->h;y++) {
362       for(int x = 0;x < src_rect->w;x++, pixel += bpp, loc++) {
363         if(x + dstx < 0 || y + dsty < 0 || x + dstx >= width || y + dsty >= height)
364         {
365           continue;
366         }
367         if(red_channel[loc] == 0xff && green_channel[loc] == 0xff && blue_channel[loc] == 0xff)
368         {
369           continue;
370         }
371
372         Uint32 mapped = 0;
373         switch(bpp) {
374           case 1:
375             mapped = *pixel;
376             break;
377           case 2:
378             mapped = *(Uint16 *)pixel;
379             break;
380           case 3:
381 #if SDL_BYTEORDER == SDL_BIG_ENDIAN
382             mapped |= pixel[0] << 16;
383             mapped |= pixel[1] << 8;
384             mapped |= pixel[2] << 0;
385 #else
386             mapped |= pixel[0] << 0;
387             mapped |= pixel[1] << 8;
388             mapped |= pixel[2] << 16;
389 #endif
390             break;
391           case 4:
392             mapped = *(Uint32 *)pixel;
393             break;
394         }
395         Uint8 red, green, blue, alpha;
396         SDL_GetRGBA(mapped, src->format, &red, &green, &blue, &alpha);
397
398         if(red != 0)
399         {
400           int redsum = red_channel[loc] + (red * alpha >> 8);
401           red_channel[loc] = redsum & ~0xff ? 0xff : redsum;
402         }
403         if(green != 0)
404         {
405           int greensum = green_channel[loc] + (green * alpha >> 8);
406           green_channel[loc] = greensum & ~0xff ? 0xff : greensum;
407         }
408         if(blue != 0)
409         {
410           int bluesum = blue_channel[loc] + (blue * alpha >> 8);
411           blue_channel[loc] = bluesum & ~0xff ? 0xff : bluesum;
412         }
413       }
414       pixel += src->pitch - src_rect->w * bpp;
415       loc += width - src_rect->w;
416     }
417   }*/
418
419   void
420   Lightmap::draw_surface(const DrawingRequest& request)
421   {
422     if((request.color.red == 0.0 && request.color.green == 0.0 && request.color.blue == 0.0) || request.color.alpha == 0.0 || request.alpha == 0.0)
423     {
424       return;
425     }
426     //FIXME: support parameters request.alpha, request.angle, request.blend
427  
428     const Surface* surface = (const Surface*) request.request_data;
429     SDL::Texture *sdltexture = dynamic_cast<SDL::Texture *>(surface->get_texture());
430     SDL::SurfaceData *surface_data = reinterpret_cast<SDL::SurfaceData *>(surface->get_surface_data());
431
432     DrawingEffect effect = request.drawing_effect;
433     if (surface->get_flipx()) effect = HORIZONTAL_FLIP;
434
435     SDL_Surface *transform = sdltexture->get_transform(request.color, effect);
436
437     // get and check SDL_Surface
438     if (transform == 0) {
439       std::cerr << "Warning: Tried to draw NULL surface, skipped draw" << std::endl;
440       return;
441     }   
442
443     SDL_Rect *src_rect = surface_data->get_src_rect(effect);
444     int dstx = (int) request.pos.x * numerator / denominator;
445     int dsty = (int) request.pos.y * numerator / denominator;
446     light_blit(transform, src_rect, dstx, dsty);
447   }
448
449   void
450   Lightmap::draw_surface_part(const DrawingRequest& request)
451   {
452     const SurfacePartRequest* surfacepartrequest
453       = (SurfacePartRequest*) request.request_data;
454
455     const Surface* surface = surfacepartrequest->surface;
456     SDL::Texture *sdltexture = dynamic_cast<SDL::Texture *>(surface->get_texture());
457
458     DrawingEffect effect = request.drawing_effect;
459     if (surface->get_flipx()) effect = HORIZONTAL_FLIP;
460
461     SDL_Surface *transform = sdltexture->get_transform(Color(1.0, 1.0, 1.0), effect);
462
463     // get and check SDL_Surface
464     if (transform == 0) {
465       std::cerr << "Warning: Tried to draw NULL surface, skipped draw" << std::endl;
466       return;
467     }   
468
469     int ox, oy;
470     if (effect == HORIZONTAL_FLIP)
471     {
472       ox = sdltexture->get_texture_width() - surface->get_x() - (int) surfacepartrequest->size.x;
473     }
474     else
475     {
476       ox = surface->get_x();
477     }
478     if (effect == VERTICAL_FLIP)
479     {
480       oy = sdltexture->get_texture_height() - surface->get_y() - (int) surfacepartrequest->size.y;
481     }
482     else
483     {
484       oy = surface->get_y();
485     }
486
487     SDL_Rect src_rect;
488     src_rect.x = (ox + (int) surfacepartrequest->source.x) * numerator / denominator;
489     src_rect.y = (oy + (int) surfacepartrequest->source.y) * numerator / denominator;
490     src_rect.w = (int) surfacepartrequest->size.x * numerator / denominator;
491     src_rect.h = (int) surfacepartrequest->size.y * numerator / denominator;
492     int dstx = (int) request.pos.x * numerator / denominator;
493     int dsty = (int) request.pos.y * numerator / denominator;
494     light_blit(transform, &src_rect, dstx, dsty);
495   }
496
497   void
498   Lightmap::draw_gradient(const DrawingRequest& request)
499   {
500     const GradientRequest* gradientrequest 
501       = (GradientRequest*) request.request_data;
502     const Color& top = gradientrequest->top;
503     const Color& bottom = gradientrequest->bottom;
504
505     int loc = 0;
506     for(int y = 0;y < height;++y)
507     {
508       Uint8 red = (Uint8)((((float)(top.red-bottom.red)/(0-height)) * y + top.red) * 255);
509       Uint8 green = (Uint8)((((float)(top.green-bottom.green)/(0-height)) * y + top.green) * 255);
510       Uint8 blue = (Uint8)((((float)(top.blue-bottom.blue)/(0-height)) * y + top.blue) * 255);
511       Uint8 alpha = (Uint8)((((float)(top.alpha-bottom.alpha)/(0-height)) * y + top.alpha) * 255);
512       for(int x = 0;x < width;x++, loc++) {
513         if(red != 0)
514         {
515           int redsum = red_channel[loc] + (red * alpha >> 8);
516           red_channel[loc] = redsum & ~0xff ? 0xff : redsum;
517         }
518         if(green != 0)
519         {
520           int greensum = green_channel[loc] + (green * alpha >> 8);
521           green_channel[loc] = greensum & ~0xff ? 0xff : greensum;
522         }
523         if(blue != 0)
524         {
525           int bluesum = blue_channel[loc] + (blue * alpha >> 8);
526           blue_channel[loc] = bluesum & ~0xff ? 0xff : bluesum;
527         }
528       }
529     }
530   }
531
532   void
533   Lightmap::draw_text(const DrawingRequest& /*request*/)
534   {
535     //const TextRequest* textrequest = (TextRequest*) request.request_data;
536
537     //textrequest->font->draw(textrequest->text, request.pos,
538     //    textrequest->alignment, request.drawing_effect, request.alpha);
539   }
540
541   void
542   Lightmap::draw_filled_rect(const DrawingRequest& request)
543   {
544     const FillRectRequest* fillrectrequest
545       = (FillRectRequest*) request.request_data;
546
547     int rect_x = (int) (request.pos.x * width / SCREEN_WIDTH);
548     int rect_y = (int) (request.pos.y * height / SCREEN_HEIGHT);
549     int rect_w = (int) (fillrectrequest->size.x * width / SCREEN_WIDTH);
550     int rect_h = (int) (fillrectrequest->size.y * height / SCREEN_HEIGHT);
551     Uint8 red = (Uint8) (fillrectrequest->color.red * fillrectrequest->color.alpha * 255);
552     Uint8 green = (Uint8) (fillrectrequest->color.green * fillrectrequest->color.alpha * 255);
553     Uint8 blue = (Uint8) (fillrectrequest->color.blue * fillrectrequest->color.alpha * 255);
554     if(red == 0 && green == 0 && blue == 0)
555     {
556       return;
557     }
558     for(int y = rect_y;y < rect_y + rect_h;y++) {
559       for(int x = rect_x;x < rect_x + rect_w;x++) {
560         int loc = y * width + x;
561         if(red != 0)
562         {
563           int redsum = red_channel[loc] + red;
564           red_channel[loc] = redsum & ~0xff ? 0xff : redsum;
565         }
566         if(green != 0)
567         {
568           int greensum = green_channel[loc] + green;
569           green_channel[loc] = greensum & ~0xff ? 0xff : greensum;
570         }
571         if(blue != 0)
572         {
573           int bluesum = blue_channel[loc] + blue;
574           blue_channel[loc] = bluesum & ~0xff ? 0xff : bluesum;
575         }
576       }
577     }
578   }
579
580   void
581   Lightmap::get_light(const DrawingRequest& request) const
582   {
583     const GetLightRequest* getlightrequest 
584       = (GetLightRequest*) request.request_data;
585
586     int x = (int) (request.pos.x * width / SCREEN_WIDTH);
587     int y = (int) (request.pos.y * height / SCREEN_HEIGHT);
588     int loc = y * width + x;
589     *(getlightrequest->color_ptr) = Color(((float)red_channel[loc])/255, ((float)green_channel[loc])/255, ((float)blue_channel[loc])/255);
590   }
591 }