9226cc6c03fba1a155169ad2095e76a78427af1a
[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 *rleCreate(unsigned int w, unsigned int h);
18 static void rleDestroy(RLEBitmap *b);
19 static void rleBlit(unsigned short *dst, int dstW, int dstH, int dstStride, 
20         RLEBitmap *bitmap, int blitX, int blitY);
21 static void rleBlitScale(unsigned short *dst, int dstW, int dstH, int dstStride,
22         RLEBitmap *bitmap, int blitX, int blitY, float scaleX, float scaleY);
23 static void rleBlitScaleInv(unsigned short *dst, int dstW, int dstH, int dstStride,
24         RLEBitmap *bitmap, int blitX, int blitY, float scaleX, float scaleY);
25 static RLEBitmap *rleEncode(RLEBitmap *b, unsigned char *pixels, unsigned int w, unsigned int h);
26
27 static void updatePropeller(float t);
28
29 #define BG_FILENAME "data/grise.png"
30 #define GROBJ_01_FILENAME "data/grobj_01.png"
31
32 #define BB_SIZE 512     /* Let's use a power of 2. Maybe we'll zoom/rotate the effect */
33
34 /* Every backBuffer scanline is guaranteed to have that many dummy pixels before and after */
35 #define PIXEL_PADDING 32
36
37 /* Make sure this is less than PIXEL_PADDING*/
38 #define MAX_DISPLACEMENT 16
39
40 #define MIN_SCROLL PIXEL_PADDING
41 #define MAX_SCROLL (backgroundW - fb_width - MIN_SCROLL)
42
43 #define FAR_SCROLL_SPEED 15.0f
44 #define NEAR_SCROLL_SPEED 120.0f
45
46 #define HORIZON_HEIGHT 100
47 #define REFLECTION_HEIGHT (240 - HORIZON_HEIGHT)
48
49 #define NORMALMAP_SCANLINE 372
50
51 static int init(void);
52 static void destroy(void);
53 static void start(long trans_time);
54 static void stop(long trans_time);
55 static void draw(void);
56
57 static void convert32To16(unsigned int *src32, unsigned short *dst16, unsigned int pixelCount);
58 static void processNormal();
59 static void initScrollTables();
60 static void updateScrollTables(float dt);
61
62
63
64 static unsigned short *background = 0;
65 static unsigned int backgroundW = 0;
66 static unsigned int backgroundH = 0;
67
68 static unsigned int lastFrameTime = 0;
69 static float lastFrameDuration = 0.0f;
70
71 static short *displacementMap;
72
73 static unsigned short *backBuffer;
74
75 static float scrollScaleTable[REFLECTION_HEIGHT];
76 static float scrollTable[REFLECTION_HEIGHT];
77 static int scrollTableRounded[REFLECTION_HEIGHT];
78 static int scrollModTable[REFLECTION_HEIGHT];
79 static float nearScrollAmount = 0.0f;
80
81 static char miniFXBuffer[1024];
82
83 static RLEBitmap *grobj = 0;
84 static RLEBitmap *rlePropeller = 0;
85
86 static struct screen scr = {
87         "galaxyrise",
88         init,
89         destroy,
90         start,
91         stop,
92         draw
93 };
94
95 struct screen *grise_screen(void)
96 {
97         return &scr;
98 }
99
100
101 static int init(void)
102 {
103         unsigned char *tmpBitmap;
104         int tmpBitmapW, tmpBitmapH;
105
106         /* Allocate back buffer */
107         backBuffer = (unsigned short*) calloc(BB_SIZE * BB_SIZE, sizeof(unsigned short));
108
109         /* grise.png contains the background (horizon), baked reflection and normalmap for displacement */
110         if (!(background = img_load_pixels(BG_FILENAME, &backgroundW, &backgroundH, IMG_FMT_RGBA32))) {
111                 fprintf(stderr, "failed to load image " BG_FILENAME "\n");
112                 return -1;
113         }
114
115         /* Convert to 16bpp */
116         convert32To16((unsigned int*)background, background, backgroundW * NORMALMAP_SCANLINE); /* Normalmap will keep its 32 bit color */
117
118         /* Load reflected objects */
119         if (!(tmpBitmap = img_load_pixels(GROBJ_01_FILENAME, &tmpBitmapW, &tmpBitmapH, IMG_FMT_GREY8))) {
120                 fprintf(stderr, "failed to load image " GROBJ_01_FILENAME "\n");
121                 return -1;
122         }
123
124         grobj = rleEncode(0, tmpBitmap, tmpBitmapW, tmpBitmapH);
125
126         img_free_pixels(tmpBitmap);
127
128         initScrollTables();
129
130         processNormal();
131
132 #ifdef MIKE_PC
133         return 0xCAFE;
134 #else
135         return 0;
136 #endif
137 }
138
139 static void destroy(void)
140 {
141         free(backBuffer);
142         backBuffer = 0;
143
144         img_free_pixels(background);
145
146         rleDestroy(grobj);
147 }
148
149 static void start(long trans_time)
150 {
151         lastFrameTime = time_msec;
152 }
153
154 static void stop(long trans_time)
155 {
156 }
157
158
159
160
161 static void draw(void)
162 {       
163         int scroll = MIN_SCROLL + (MAX_SCROLL - MIN_SCROLL) * mouse_x / fb_width;
164         unsigned short *dst = backBuffer + PIXEL_PADDING;
165         unsigned short *src = background + scroll;
166         int scanline = 0;
167         int i = 0;
168         short *dispScanline;
169         int d;
170
171         lastFrameDuration = (time_msec - lastFrameTime) / 1000.0f;
172         lastFrameTime = time_msec;
173
174         /* Update mini-effects here */
175         updatePropeller(4.0f * time_msec / 1000.0f);
176
177         /* First, render the horizon */
178         for (scanline = 0; scanline < HORIZON_HEIGHT; scanline++) {
179                 memcpy(dst, src, fb_width * 2);
180                 src += backgroundW;
181                 dst += BB_SIZE;
182         }
183         
184         /* Create scroll offsets for all scanlines of the normalmap */
185         updateScrollTables(lastFrameDuration);
186
187         /* Render the baked reflection one scanline below its place, so that 
188          * the displacement that follows will be done in a cache-friendly way
189          */
190         src -= PIXEL_PADDING; /* We want to also fill the PADDING pixels here */
191         dst = backBuffer + (HORIZON_HEIGHT + 1) * BB_SIZE;
192         for (scanline = 0; scanline < REFLECTION_HEIGHT; scanline++) {
193                 memcpy(dst, src, (fb_width + PIXEL_PADDING) * 2);
194                 src += backgroundW;
195                 dst += BB_SIZE;
196         }
197
198         /* Blit reflections first, to be  displaced */
199         for (i = 0; i < 5; i++) rleBlitScaleInv(backBuffer + PIXEL_PADDING, fb_width, fb_height, BB_SIZE, rlePropeller, 134 + (i-3) * 60, 200, 1.0f, 1.8f);
200
201         /* Perform displacement */
202         dst = backBuffer + HORIZON_HEIGHT * BB_SIZE + PIXEL_PADDING;
203         src = dst + BB_SIZE; /* The pixels to be displaced are 1 scanline below */
204         dispScanline = displacementMap;
205         for (scanline = 0; scanline < REFLECTION_HEIGHT; scanline++) {
206                 for (i = 0; i < fb_width; i++) {
207                         d = dispScanline[(i + scrollTableRounded[scanline]) % scrollModTable[scanline]];
208                         *dst++ = src[i + d];
209                 }
210                 src += backgroundW;
211                 dst += BB_SIZE - fb_width;
212                 dispScanline += backgroundW;
213         }
214
215         /* Then after displacement, blit the objects */
216         for (i = 0; i < 5; i++) rleBlit(backBuffer + PIXEL_PADDING, fb_width, fb_height, BB_SIZE, rlePropeller, 134 + (i-3) * 60, 100);
217         
218         /* Blit effect to framebuffer */
219         src = backBuffer + PIXEL_PADDING;
220         dst = vmem_back;
221         for (scanline = 0; scanline < fb_height; scanline++) {
222                 memcpy(dst, src, fb_width * 2);
223                 src += BB_SIZE; 
224                 dst += fb_width;
225         }
226
227         swap_buffers(0);
228 }
229
230 /* src and dst can be the same */
231 static void convert32To16(unsigned int *src32, unsigned short *dst16, unsigned int pixelCount) {
232         unsigned int p;
233         while (pixelCount) {
234                 p = *src32++;
235                 *dst16++ =      ((p << 8) & 0xF800)             /* R */
236                         |               ((p >> 5) & 0x07E0)             /* G */
237                         |               ((p >> 19) & 0x001F);   /* B */
238                 pixelCount--;
239         }
240 }
241
242 /* Normal map preprocessing */
243 /* Scale normal with depth and unpack R component (horizontal component) */
244 static void processNormal() {
245         int scanline;
246         int i;
247         int x;
248         short maxDisplacement = 0;
249         short minDisplacement = 256;
250         unsigned short *dst;
251         short *dst2;
252         unsigned int *normalmap = (unsigned int*)background;
253         normalmap += NORMALMAP_SCANLINE * backgroundW;
254         dst = (unsigned short*)normalmap;
255         displacementMap = (short*)dst;
256         dst2 = displacementMap;
257
258         for (scanline = 0; scanline < REFLECTION_HEIGHT; scanline++) {
259                 scrollModTable[scanline] = (int) (backgroundW / scrollScaleTable[scanline] + 0.5f);
260                 for (i = 0; i < backgroundW; i++) {
261                         x = (int)(i * scrollScaleTable[scanline] + 0.5f);
262                         if (x < backgroundW) {
263                                 *dst = (unsigned short)(normalmap[x] >> 8) & 0xFF;
264                                 if ((short)*dst > maxDisplacement) maxDisplacement = (short)(*dst);
265                                 if ((short)*dst < minDisplacement) minDisplacement = (short)(*dst);
266                         } else {
267                                 *dst = 0;
268                         }
269                         dst++;
270                 }
271                 normalmap += backgroundW;
272         }
273
274         if (maxDisplacement == minDisplacement) {
275                 printf("Warning: grise normalmap fucked up\n");
276                 return;
277         }
278
279         /* Second pass - subtract half maximum displacement to displace in both directions */
280         for (scanline = 0; scanline < REFLECTION_HEIGHT; scanline++) {
281                 for (i = 0; i < backgroundW; i++) {
282                         /* Remember that MIN_SCROLL is the padding around the screen, so ti's the maximum displacement we can get (positive & negative) */
283                         *dst2 = 2 * MAX_DISPLACEMENT * (*dst2 - minDisplacement) / (maxDisplacement - minDisplacement) - MAX_DISPLACEMENT;
284                         *dst2 = (short)((float)*dst2 / scrollScaleTable[scanline] + 0.5f); /* Displacements must also scale with distance*/
285                         dst2++;
286                 }
287         }
288 }
289
290 static float distanceScale(int scanline) {
291         float farScale, t;
292         farScale = (float)NEAR_SCROLL_SPEED / (float)FAR_SCROLL_SPEED;
293         t = (float)scanline / ((float)REFLECTION_HEIGHT - 1);
294         return 1.0f / (1.0f / farScale + (1.0f - 1.0f / farScale) * t);
295 }
296
297 static void initScrollTables() {
298         int i = 0;
299         for (i = 0; i < REFLECTION_HEIGHT; i++) {
300                 scrollScaleTable[i] = distanceScale(i);
301                 scrollTable[i] = 0.0f;
302                 scrollTableRounded[i] = 0;
303         }
304 }
305
306
307 static void updateScrollTables(float dt) {
308         int i = 0;
309         
310         nearScrollAmount += dt * NEAR_SCROLL_SPEED;
311         nearScrollAmount = (float) fmod(nearScrollAmount, 512.0f);
312
313         for (i = 0; i < REFLECTION_HEIGHT; i++) {
314                 scrollTable[i] = nearScrollAmount / scrollScaleTable[i];
315                 scrollTableRounded[i] = (int)(scrollTable[i] + 0.5f) % scrollModTable[i];
316         }
317 }
318
319 /* -------------------------------------------------------------------------------------------------
320  *                                   RLE STUFF                                                                           
321  * -------------------------------------------------------------------------------------------------
322  */
323 /* Limit streak count per scanline so we can directly jump to specific scanline */
324 #define RLE_STREAKS_PER_SCANLINE 4
325 /* Every streak is encoded by 2 bytes: offset and count of black pixels in the streak */
326 #define RLE_BYTES_PER_SCANLINE RLE_STREAKS_PER_SCANLINE * 2
327 #define RLE_FILL_COLOR 0
328 #define RLE_FILL_COLOR_32 ((RLE_FILL_COLOR << 16) | RLE_FILL_COLOR)
329
330 #define RLE_FIXED_BITS 16
331
332 static int rleByteCount(int w, int h) {
333         return h * RLE_BYTES_PER_SCANLINE + w;
334 }
335
336 static RLEBitmap *rleCreate(unsigned int w, unsigned int h) {
337         RLEBitmap *ret = (RLEBitmap*)malloc(sizeof(RLEBitmap));
338         ret->w = w;
339         ret->h = h;
340
341         /* Add some padding at the end of the buffer, with the worst case for a scanline (w/2 streaks) */
342         ret->scans = (unsigned char*) calloc(rleByteCount(w, h), 1);
343
344         return ret;
345 }
346
347 static void rleDestroy(RLEBitmap *b) {
348         if (!b) return;
349         free(b->scans);
350         free(b);
351 }
352
353 static RLEBitmap *rleEncode(RLEBitmap *b, unsigned char *pixels, unsigned int w, unsigned int h) {
354         int scanline;
355         int i;
356         int penActive = 0;
357         int counter = 0;
358         int accum = 0;
359         unsigned char *output;
360
361         /* https://www.youtube.com/watch?v=RKMR02o1I88&feature=youtu.be&t=55 */
362         if (!b) b = rleCreate(w, h);
363         else memset(b->scans, 0, rleByteCount(b->w, b->h)); /* The following code assumes cleared array */
364
365         for (scanline = 0; scanline < h; scanline++) {
366                 output = b->scans + scanline * RLE_BYTES_PER_SCANLINE;
367                 accum = 0;
368                 for (i = 0; i < w; i++) {
369                         if (*pixels++) {
370                                 if (penActive) {
371                                         if (counter >= PIXEL_PADDING) {
372                                                 *output++ = (unsigned char) counter;
373                                                 counter = 0;
374                                                 *output++ = (unsigned char)accum;
375                                         }
376                                         counter++;
377                                         accum++;
378                                 } else {
379                                         *output++ = (unsigned char)accum;
380                                         counter = 1;
381                                         accum++;
382                                         penActive = 1;
383                                 }
384                         } else {
385                                 if (penActive) {
386                                         *output++ = (unsigned char)counter;
387                                         counter = 1;
388                                         accum++;
389                                         penActive = 0;
390                                 } else {
391                                         counter++;
392                                         accum++;
393                                 }
394                         }
395                 }
396
397                 if (penActive) {
398                         *output++ = (unsigned char)counter;
399                 }
400                 penActive = 0;
401                 counter = 0;
402         }
403
404         return b;
405 }
406
407 static void rleDistributeStreaks(RLEBitmap *bitmap) {
408         int scanline, halfW = bitmap->w >> 1;
409         unsigned char *ptr, tmp;
410         
411         ptr = bitmap->scans;
412         for (scanline = 0; scanline < bitmap->h; scanline++) {
413                 if (ptr[0] >= halfW) {
414                         tmp = ptr[0];
415                         ptr[0] = ptr[6];
416                         ptr[6] = tmp;
417                         tmp = ptr[1];
418                         ptr[1] = ptr[7];
419                         ptr[7] = tmp;
420                 }
421
422                 ptr += 8;
423         }
424 }
425
426 static void rleBlit(unsigned short *dst, int dstW, int dstH, int dstStride,
427         RLEBitmap *bitmap, int blitX, int blitY) 
428 {
429         int scanline = 0;
430         int streakPos = 0;
431         int streakLength = 0;
432         int streak = 0;
433         unsigned char *input = bitmap->scans;
434         unsigned short *output;
435         unsigned int *output32;
436
437         dst += blitX + blitY * dstStride;
438
439         for (scanline = blitY; scanline < blitY + bitmap->h; scanline++) {
440                 if (scanline < 0 || scanline >= dstH) continue;
441                 for (streak = 0; streak < RLE_STREAKS_PER_SCANLINE; streak++) {
442                         streakPos = *input++;
443                         streakLength = *input++;
444
445                         if ((streakPos + blitX) <= 0) continue;
446
447                         output = dst + streakPos;
448
449                         /* Check if we need to write the first pixel as 16bit */
450                         if (streakLength % 2) {
451                                 *output++ = RLE_FILL_COLOR;
452                         }
453
454                         /* Then, write 2 pixels at a time */
455                         streakLength >>= 1;
456                         output32 = (unsigned int*) output;
457                         while (streakLength--) {
458                                 *output32++ = RLE_FILL_COLOR_32;
459                         }
460                 }
461
462                 dst += dstStride;
463         }
464 }
465
466 static void interpolateScan(unsigned char *output, unsigned char *a, unsigned char *b, float t) {
467         static int div = 1 << 23;
468         int ti, i;
469
470         t += 1.0f;
471         ti = (*((unsigned int*)&t)) & 0x7FFFFF;
472         
473         for (i = 0; i < RLE_BYTES_PER_SCANLINE; i++) {
474                 if (*a == 0) {
475                         *output++ = *b++;
476                         a++;
477                 } else {
478                         if (*b == 0) {
479                                 *output++ = *a++;
480                                 b++;
481                         } else {
482                                 *output++ = ((*b++ * ti) + (*a++ * (div - ti))) >> 23;
483                         }
484                 }
485         }
486 }
487
488 static void rleBlitScale(unsigned short *dst, int dstW, int dstH, int dstStride,
489         RLEBitmap *bitmap, int blitX, int blitY, float scaleX, float scaleY)
490 {
491         int scanline = 0;
492         int streakPos = 0;
493         int streakLength = 0;
494         int streak = 0;
495         unsigned short *output;
496         unsigned int *output32;
497         unsigned char *input;
498         int scanlineCounter = 0;
499         static unsigned char scan[512];
500
501         int blitW = (int)(bitmap->w * scaleX + 0.5f);
502         int blitH = (int)(bitmap->h * scaleY + 0.5f);
503
504         /* From this point on, scaleY will be inverted */
505         scaleY = 1.0f / scaleY;
506
507         int scaleXFixed = (int)(scaleX * (float)(1 << RLE_FIXED_BITS) + 0.5f);
508         
509         dst += blitX + blitY * dstStride;
510
511         for (scanline = blitY; scanline < blitY + blitH; scanline++) {
512                 float normalScan = scanlineCounter * scaleY; /* ScaleY  is inverted */
513                 unsigned char *scan0 = bitmap->scans + RLE_BYTES_PER_SCANLINE * (int)normalScan;
514                 unsigned char *scan1 = scan0 + RLE_BYTES_PER_SCANLINE;
515                 normalScan -= (int)normalScan;
516                 interpolateScan(scan, scan0, scan1, normalScan);
517                 input = scan;
518                 scanlineCounter++;
519
520                 if (scanline < 0 || scanline >= dstH) continue;
521                 for (streak = 0; streak < RLE_STREAKS_PER_SCANLINE; streak++) {
522                         streakPos = (*input++ * scaleXFixed) >> RLE_FIXED_BITS;
523                         streakLength = (*input++ * scaleXFixed) >> RLE_FIXED_BITS;
524
525                         if ((streakPos + blitX) <= 0) continue;
526
527                         output = dst + streakPos;
528
529                         /* Check if we need to write the first pixel as 16bit */
530                         if (streakLength % 2) {
531                                 *output++ = RLE_FILL_COLOR;
532                         }
533
534                         /* Then, write 2 pixels at a time */
535                         streakLength >>= 1;
536                         output32 = (unsigned int*)output;
537                         while (streakLength--) {
538                                 *output32++ = RLE_FILL_COLOR_32;
539                         }
540                 }
541
542                 dst += dstStride;
543         }
544 }
545
546
547
548 static void rleBlitScaleInv(unsigned short *dst, int dstW, int dstH, int dstStride,
549         RLEBitmap *bitmap, int blitX, int blitY, float scaleX, float scaleY)
550 {
551         int scanline = 0;
552         int streakPos = 0;
553         int streakLength = 0;
554         int streak = 0;
555         unsigned short *output;
556         unsigned int *output32;
557         unsigned char *input;
558         int scanlineCounter = 0;
559         static unsigned char scan[512];
560
561         int blitW = (int)(bitmap->w * scaleX + 0.5f);
562         int blitH = (int)(bitmap->h * scaleY + 0.5f);
563
564         /* From this point on, scaleY will be inverted */
565         scaleY = 1.0f / scaleY;
566
567         int scaleXFixed = (int)(scaleX * (float)(1 << RLE_FIXED_BITS) + 0.5f);
568
569         dst += blitX + blitY * dstStride;
570
571         for (scanline = blitY; scanline > blitY - blitH; scanline--) {
572                 float normalScan = scanlineCounter * scaleY; /* ScaleY is inverted */
573                 unsigned char *scan0 = bitmap->scans + RLE_BYTES_PER_SCANLINE * (int)normalScan;
574                 unsigned char *scan1 = scan0 + RLE_BYTES_PER_SCANLINE;
575                 normalScan -= (int)normalScan;
576                 interpolateScan(scan, scan0, scan1, normalScan);
577                 input = scan;
578                 scanlineCounter++;
579
580                 if (scanline < 0 || scanline >= dstH) continue;
581                 for (streak = 0; streak < RLE_STREAKS_PER_SCANLINE; streak++) {
582                         streakPos = (*input++ * scaleXFixed) >> RLE_FIXED_BITS;
583                         streakLength = (*input++ * scaleXFixed) >> RLE_FIXED_BITS;
584
585                         if ((streakPos + blitX) <= 0) continue;
586
587                         output = dst + streakPos;
588
589                         /* Check if we need to write the first pixel as 16bit */
590                         if (streakLength % 2) {
591                                 *output++ = RLE_FILL_COLOR;
592                         }
593
594                         /* Then, write 2 pixels at a time */
595                         streakLength >>= 1;
596                         output32 = (unsigned int*)output;
597                         while (streakLength--) {
598                                 *output32++ = RLE_FILL_COLOR_32;
599                         }
600                 }
601
602                 dst -= dstStride;
603         }
604 }
605
606 /* -------------------------------------------------------------------------------------------------
607 *                                   PROPELLER STUFF
608 * -------------------------------------------------------------------------------------------------
609 */
610
611 #define PROPELLER_CIRCLE_RADIUS 18
612 #define PROPELLER_CIRCLE_RADIUS_SQ (PROPELLER_CIRCLE_RADIUS * PROPELLER_CIRCLE_RADIUS)
613
614 static struct {
615         int circleX[3];
616         int circleY[3];
617 } propellerState;
618
619 static void updatePropeller(float t) {
620         int i, j;
621         int cx, cy, count = 0;
622         char *dst;
623         float x = 0.0f;
624         float y = 18.0f;
625         float nx, ny;
626         float cost, sint;
627         static float sin120 = 0.86602540378f;
628         static float cos120 = -0.5f;
629
630         /* Rotate */
631         sint = sin(t);
632         cost = cos(t);
633         nx = x * cost - y * sint;
634         ny = y * cost + x * sint;
635         x = nx;
636         y = ny;
637         propellerState.circleX[0] = (int)(x + 0.5f) + 16;
638         propellerState.circleY[0] = (int)(y + 0.5f) + 16;
639
640         /* Rotate by 120 degrees, for the second circle */
641         nx = x * cos120 - y * sin120;
642         ny = y * cos120 + x * sin120;
643         x = nx;
644         y = ny;
645         propellerState.circleX[1] = (int)(x + 0.5f) + 16;
646         propellerState.circleY[1] = (int)(y + 0.5f) + 16;
647
648         /* 3rd circle */
649         nx = x * cos120 - y * sin120;
650         ny = y * cos120 + x * sin120;
651         x = nx;
652         y = ny;
653         propellerState.circleX[2] = (int)(x + 0.5f) + 16;
654         propellerState.circleY[2] = (int)(y + 0.5f) + 16;
655
656         /* Write effect to the mini fx buffer*/
657         dst = miniFXBuffer;
658         for (j = 0; j < 32; j++) {
659                 for (i = 0; i < 32; i++) {
660                         count = 0;
661
662                         /* First circle */
663                         cx = propellerState.circleX[0] - i;
664                         cy = propellerState.circleY[0] - j;
665                         if (cx*cx + cy*cy < PROPELLER_CIRCLE_RADIUS_SQ) count++;
666
667                         /* 2nd circle */
668                         cx = propellerState.circleX[1] - i;
669                         cy = propellerState.circleY[1] - j;
670                         if (cx*cx + cy*cy < PROPELLER_CIRCLE_RADIUS_SQ) count++;
671
672                         /* 3rd circle */
673                         cx = propellerState.circleX[2] - i;
674                         cy = propellerState.circleY[2] - j;
675                         if (cx*cx + cy*cy < PROPELLER_CIRCLE_RADIUS_SQ) count++;
676
677                         *dst++ = count >= 2;
678                 }
679         }
680
681         /* Then, encode to rle */
682         rlePropeller = rleEncode(rlePropeller, miniFXBuffer, 32, 32);
683
684         /* Distribute the produced streaks so that they don't produce garbage when interpolated */
685         rleDistributeStreaks(rlePropeller);
686 }