Finished RLE thingy
[dosdemo] / src / grise.c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <math.h>
5 #include <assert.h>
6 #include "imago2.h"
7 #include "demo.h"
8 #include "screen.h"
9
10 /* APPROX. 170 FPS Minimum */
11
12 typedef struct {
13         unsigned int w, h;
14         unsigned char *scans;
15 } RLEBitmap;
16
17 static RLEBitmap createRLEBitmap(unsigned int w, unsigned int h);
18 static destroyRLEBitmap(RLEBitmap b);
19
20 #define BG_FILENAME "data/grise.png"
21 #define GROBJ_01_FILENAME "data/grobj_01.png"
22
23 #define BB_SIZE 512     /* Let's use a power of 2. Maybe we'll zoom/rotate the effect */
24
25 /* Every backBuffer scanline is guaranteed to have that many dummy pixels before and after */
26 #define PIXEL_PADDING 32
27
28 #define MIN_SCROLL PIXEL_PADDING
29 #define MAX_SCROLL (backgroundW - fb_width - MIN_SCROLL)
30
31 #define FAR_SCROLL_SPEED 50.0f
32 #define NEAR_SCROLL_SPEED 400.0f
33
34 #define HORIZON_HEIGHT 100
35 #define REFLECTION_HEIGHT (240 - HORIZON_HEIGHT)
36
37 #define NORMALMAP_SCANLINE 372
38
39 static int init(void);
40 static void destroy(void);
41 static void start(long trans_time);
42 static void stop(long trans_time);
43 static void draw(void);
44
45 static void convert32To16(unsigned int *src32, unsigned short *dst16, unsigned int pixelCount);
46 static void processNormal();
47 static void initScrollTables();
48 static void updateScrollTables(float dt);
49
50 static RLEBitmap rleEncode(unsigned char *pixels, unsigned int w, unsigned int h);
51
52 static unsigned short *background = 0;
53 static unsigned int backgroundW = 0;
54 static unsigned int backgroundH = 0;
55
56 static unsigned int lastFrameTime = 0;
57 static float lastFrameDuration = 0.0f;
58
59 static short *displacementMap;
60
61 static unsigned short *backBuffer;
62
63 static float scrollScaleTable[REFLECTION_HEIGHT];
64 static float scrollTable[REFLECTION_HEIGHT];
65 static int scrollTableRounded[REFLECTION_HEIGHT];
66 static int scrollModTable[REFLECTION_HEIGHT];
67 static float nearScrollAmount = 0.0f;
68
69 static RLEBitmap grobj;
70
71 static struct screen scr = {
72         "galaxyrise",
73         init,
74         destroy,
75         start,
76         stop,
77         draw
78 };
79
80 struct screen *grise_screen(void)
81 {
82         return &scr;
83 }
84
85
86 static int init(void)
87 {
88         unsigned char *tmpBitmap;
89         int tmpBitmapW, tmpBitmapH;
90
91         /* Allocate back buffer */
92         backBuffer = (unsigned short*) malloc(BB_SIZE * BB_SIZE * sizeof(unsigned short));
93
94         /* grise.png contains the background (horizon), baked reflection and normalmap for displacement */
95         if (!(background = img_load_pixels(BG_FILENAME, &backgroundW, &backgroundH, IMG_FMT_RGBA32))) {
96                 fprintf(stderr, "failed to load image " BG_FILENAME "\n");
97                 return -1;
98         }
99
100         /* Convert to 16bpp */
101         convert32To16((unsigned int*)background, background, backgroundW * NORMALMAP_SCANLINE); /* Normalmap will keep its 32 bit color */
102
103         /* Load reflected objects */
104         if (!(tmpBitmap = img_load_pixels(GROBJ_01_FILENAME, &tmpBitmapW, &tmpBitmapH, IMG_FMT_GREY8))) {
105                 fprintf(stderr, "failed to load image " GROBJ_01_FILENAME "\n");
106                 return -1;
107         }
108
109         grobj = rleEncode(tmpBitmap, tmpBitmapW, tmpBitmapH);
110
111         img_free_pixels(tmpBitmap);
112
113         initScrollTables();
114
115         processNormal();
116
117 #ifdef MIKE_PC
118         return 0xCAFE;
119 #else
120         return 0;
121 #endif
122 }
123
124 static void destroy(void)
125 {
126         free(backBuffer);
127         backBuffer = 0;
128
129         img_free_pixels(background);
130
131         destroyRLEBitmap(grobj);
132 }
133
134 static void start(long trans_time)
135 {
136         lastFrameTime = time_msec;
137 }
138
139 static void stop(long trans_time)
140 {
141 }
142
143 static void draw(void)
144 {       
145         int scroll = MIN_SCROLL + (MAX_SCROLL - MIN_SCROLL) * mouse_x / fb_width;
146         unsigned short *dst = backBuffer + PIXEL_PADDING;
147         unsigned short *src = background + scroll;
148         int scanline = 0;
149         int i = 0;
150         short *dispScanline;
151         int d;
152
153         lastFrameDuration = (time_msec - lastFrameTime) / 1000.0f;
154         lastFrameTime = time_msec;
155
156         /* First, render the horizon */
157         for (scanline = 0; scanline < HORIZON_HEIGHT; scanline++) {
158                 memcpy(dst, src, fb_width * 2);
159                 src += backgroundW;
160                 dst += BB_SIZE;
161         }
162         
163         /* Create scroll offsets for all scanlines of the normalmap */
164         updateScrollTables(lastFrameDuration);
165
166         /* Render the baked reflection one scanline below its place, so that 
167          * the displacement that follows will be done in a cache-friendly way
168          */
169         src -= PIXEL_PADDING; /* We want to also fill the PADDING pixels here */
170         dst = backBuffer + (HORIZON_HEIGHT + 1) * BB_SIZE;
171         for (scanline = 0; scanline < REFLECTION_HEIGHT; scanline++) {
172                 memcpy(dst, src, (fb_width + PIXEL_PADDING) * 2);
173                 src += backgroundW;
174                 dst += BB_SIZE;
175         }
176         
177         /* Perform displacement */
178         dst = backBuffer + HORIZON_HEIGHT * BB_SIZE + PIXEL_PADDING;
179         src = dst + BB_SIZE; /* The pixels to be displaced are 1 scanline below */
180         dispScanline = displacementMap;
181         for (scanline = 0; scanline < REFLECTION_HEIGHT; scanline++) {
182                 for (i = 0; i < fb_width; i++) {
183                         d = dispScanline[(i + scrollTableRounded[scanline]) % scrollModTable[scanline]];
184                         *dst++ = src[i + d];
185                 }
186                 src += backgroundW;
187                 dst += BB_SIZE - fb_width;
188                 dispScanline += backgroundW;
189         }
190
191         /* Blit effect to framebuffer */
192         src = backBuffer + PIXEL_PADDING;
193         dst = fb_pixels;
194         for (scanline = 0; scanline < fb_height; scanline++) {
195                 memcpy(dst, src, fb_width * 2);
196                 src += BB_SIZE;
197                 dst += fb_width;
198         }
199 }
200
201 /* src and dst can be the same */
202 static void convert32To16(unsigned int *src32, unsigned short *dst16, unsigned int pixelCount) {
203         unsigned int p;
204         while (pixelCount) {
205                 p = *src32++;
206                 *dst16++ =      ((p << 8) & 0xF800)             /* R */
207                         |               ((p >> 5) & 0x07E0)             /* G */
208                         |               ((p >> 19) & 0x001F);   /* B */
209                 pixelCount--;
210         }
211 }
212
213 /* Normal map preprocessing */
214 /* Scale normal with depth and unpack R component (horizontal component) */
215 static void processNormal() {
216         int scanline;
217         int i;
218         int x;
219         short maxDisplacement = 0;
220         short minDisplacement = 256;
221         unsigned short *dst;
222         short *dst2;
223         unsigned int *normalmap = (unsigned int*)background;
224         normalmap += NORMALMAP_SCANLINE * backgroundW;
225         dst = (unsigned short*)normalmap;
226         displacementMap = (short*)dst;
227         dst2 = displacementMap;
228
229         for (scanline = 0; scanline < REFLECTION_HEIGHT; scanline++) {
230                 scrollModTable[scanline] = (int) (backgroundW / scrollScaleTable[scanline] + 0.5f);
231                 for (i = 0; i < backgroundW; i++) {
232                         x = (int)(i * scrollScaleTable[scanline] + 0.5f);
233                         if (x < backgroundW) {
234                                 *dst = (unsigned short)(normalmap[x] >> 8) & 0xFF;
235                                 if ((short)*dst > maxDisplacement) maxDisplacement = (short)(*dst);
236                                 if ((short)*dst < minDisplacement) minDisplacement = (short)(*dst);
237                         } else {
238                                 *dst = 0;
239                         }
240                         dst++;
241                 }
242                 normalmap += backgroundW;
243         }
244
245         if (maxDisplacement == minDisplacement) {
246                 printf("Warning: grise normalmap fucked up\n");
247                 return;
248         }
249
250         /* Second pass - subtract half maximum displacement to displace in both directions */
251         for (scanline = 0; scanline < REFLECTION_HEIGHT; scanline++) {
252                 for (i = 0; i < backgroundW; i++) {
253                         /* Remember that MIN_SCROLL is the padding around the screen, so ti's the maximum displacement we can get (positive & negative) */
254                         *dst2 = 2 * MIN_SCROLL * (*dst2 - minDisplacement) / (maxDisplacement - minDisplacement) - MIN_SCROLL;
255                         *dst2 = (short)((float)*dst2 / scrollScaleTable[scanline] + 0.5f); /* Displacements must also scale with distance*/
256                         dst2++;
257                 }
258         }
259 }
260
261 static float distanceScale(int scanline) {
262         float farScale, t;
263         farScale = (float)NEAR_SCROLL_SPEED / (float)FAR_SCROLL_SPEED;
264         t = (float)scanline / ((float)REFLECTION_HEIGHT - 1);
265         return 1.0f / (1.0f / farScale + (1.0f - 1.0f / farScale) * t);
266 }
267
268 static void initScrollTables() {
269         int i = 0;
270         for (i = 0; i < REFLECTION_HEIGHT; i++) {
271                 scrollScaleTable[i] = distanceScale(i);
272                 scrollTable[i] = 0.0f;
273                 scrollTableRounded[i] = 0;
274         }
275 }
276
277
278 static void updateScrollTables(float dt) {
279         int i = 0;
280         
281         nearScrollAmount += dt * NEAR_SCROLL_SPEED;
282         nearScrollAmount = (float) fmod(nearScrollAmount, 512.0f);
283
284         for (i = 0; i < REFLECTION_HEIGHT; i++) {
285                 scrollTable[i] = nearScrollAmount / scrollScaleTable[i];
286                 scrollTableRounded[i] = (int)(scrollTable[i] + 0.5f) % scrollModTable[i];
287         }
288 }
289
290 /* -------------------------------------------------------------------------------------------------
291  *                                   RLE STUFF                                                                           
292  * -------------------------------------------------------------------------------------------------
293  */
294 /* Limit streak count per scanline so we can directly jump to specific scanline */
295 #define RLE_STREAKS_PER_SCANLINE 4
296 /* Every streak is encoded by 2 bytes: offset and count of black pixels in the streak */
297 #define RLE_BYTES_PER_SCANLINE RLE_STREAKS_PER_SCANLINE * 2
298
299 static RLEBitmap createRLEBitmap(unsigned int w, unsigned int h) {
300         RLEBitmap ret;
301         ret.w = w;
302         ret.h = h;
303
304         /* Add some padding at the end of the buffer, with the worst case for a scanline (w/2 streaks) */
305         ret.scans = (unsigned char*) calloc(h * RLE_BYTES_PER_SCANLINE + w, 1);
306
307         return ret;
308 }
309
310 static destroyRLEBitmap(RLEBitmap b) {
311         free(b.scans);
312 }
313
314 static RLEBitmap rleEncode(unsigned char *pixels, unsigned int w, unsigned int h) {
315         int scanline;
316         int i;
317         int penActive = 0;
318         int counter = 0;
319         int accum = 0;
320         RLEBitmap ret;
321         unsigned char *output;
322
323         /* https://www.youtube.com/watch?v=RKMR02o1I88&feature=youtu.be&t=55 */
324         ret = createRLEBitmap(w, h);
325
326         for (scanline = 0; scanline < h; scanline++) {
327                 output = ret.scans + scanline * RLE_BYTES_PER_SCANLINE;
328                 accum = 0;
329                 for (i = 0; i < w; i++) {
330                         if (*pixels++) {
331                                 if (penActive) {
332                                         if (counter >= PIXEL_PADDING) {
333                                                 *output++ = (unsigned char) counter;
334                                                 counter = 0;
335                                                 *output++ = (unsigned char)accum;
336                                         }
337                                         counter++;
338                                         accum++;
339                                 } else {
340                                         *output++ = (unsigned char)accum;
341                                         counter = 1;
342                                         accum++;
343                                         penActive = 1;
344                                 }
345                         } else {
346                                 if (penActive) {
347                                         *output++ = (unsigned char)counter;
348                                         counter = 1;
349                                         accum++;
350                                         penActive = 0;
351                                 } else {
352                                         counter++;
353                                         accum++;
354                                 }
355                         }
356                 }
357
358                 if (penActive) {
359                         *output++ = (unsigned char)counter;
360                 }
361                 penActive = 0;
362                 counter = 0;
363         }
364
365         return ret;
366 }