fixing C compatibility problems
[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         int scaleXFixed;
500         static unsigned char scan[512];
501
502         int blitW = (int)(bitmap->w * scaleX + 0.5f);
503         int blitH = (int)(bitmap->h * scaleY + 0.5f);
504
505         /* From this point on, scaleY will be inverted */
506         scaleY = 1.0f / scaleY;
507
508         scaleXFixed = (int)(scaleX * (float)(1 << RLE_FIXED_BITS) + 0.5f);
509
510         dst += blitX + blitY * dstStride;
511
512         for (scanline = blitY; scanline < blitY + blitH; scanline++) {
513                 float normalScan = scanlineCounter * scaleY; /* ScaleY  is inverted */
514                 unsigned char *scan0 = bitmap->scans + RLE_BYTES_PER_SCANLINE * (int)normalScan;
515                 unsigned char *scan1 = scan0 + RLE_BYTES_PER_SCANLINE;
516                 normalScan -= (int)normalScan;
517                 interpolateScan(scan, scan0, scan1, normalScan);
518                 input = scan;
519                 scanlineCounter++;
520
521                 if (scanline < 0 || scanline >= dstH) continue;
522                 for (streak = 0; streak < RLE_STREAKS_PER_SCANLINE; streak++) {
523                         streakPos = (*input++ * scaleXFixed) >> RLE_FIXED_BITS;
524                         streakLength = (*input++ * scaleXFixed) >> RLE_FIXED_BITS;
525
526                         if ((streakPos + blitX) <= 0) continue;
527
528                         output = dst + streakPos;
529
530                         /* Check if we need to write the first pixel as 16bit */
531                         if (streakLength % 2) {
532                                 *output++ = RLE_FILL_COLOR;
533                         }
534
535                         /* Then, write 2 pixels at a time */
536                         streakLength >>= 1;
537                         output32 = (unsigned int*)output;
538                         while (streakLength--) {
539                                 *output32++ = RLE_FILL_COLOR_32;
540                         }
541                 }
542
543                 dst += dstStride;
544         }
545 }
546
547
548
549 static void rleBlitScaleInv(unsigned short *dst, int dstW, int dstH, int dstStride,
550         RLEBitmap *bitmap, int blitX, int blitY, float scaleX, float scaleY)
551 {
552         int scanline = 0;
553         int streakPos = 0;
554         int streakLength = 0;
555         int streak = 0;
556         unsigned short *output;
557         unsigned int *output32;
558         unsigned char *input;
559         int scanlineCounter = 0;
560         int scaleXFixed;
561         static unsigned char scan[512];
562
563         int blitW = (int)(bitmap->w * scaleX + 0.5f);
564         int blitH = (int)(bitmap->h * scaleY + 0.5f);
565
566         /* From this point on, scaleY will be inverted */
567         scaleY = 1.0f / scaleY;
568
569         scaleXFixed = (int)(scaleX * (float)(1 << RLE_FIXED_BITS) + 0.5f);
570
571         dst += blitX + blitY * dstStride;
572
573         for (scanline = blitY; scanline > blitY - blitH; scanline--) {
574                 float normalScan = scanlineCounter * scaleY; /* ScaleY is inverted */
575                 unsigned char *scan0 = bitmap->scans + RLE_BYTES_PER_SCANLINE * (int)normalScan;
576                 unsigned char *scan1 = scan0 + RLE_BYTES_PER_SCANLINE;
577                 normalScan -= (int)normalScan;
578                 interpolateScan(scan, scan0, scan1, normalScan);
579                 input = scan;
580                 scanlineCounter++;
581
582                 if (scanline < 0 || scanline >= dstH) continue;
583                 for (streak = 0; streak < RLE_STREAKS_PER_SCANLINE; streak++) {
584                         streakPos = (*input++ * scaleXFixed) >> RLE_FIXED_BITS;
585                         streakLength = (*input++ * scaleXFixed) >> RLE_FIXED_BITS;
586
587                         if ((streakPos + blitX) <= 0) continue;
588
589                         output = dst + streakPos;
590
591                         /* Check if we need to write the first pixel as 16bit */
592                         if (streakLength % 2) {
593                                 *output++ = RLE_FILL_COLOR;
594                         }
595
596                         /* Then, write 2 pixels at a time */
597                         streakLength >>= 1;
598                         output32 = (unsigned int*)output;
599                         while (streakLength--) {
600                                 *output32++ = RLE_FILL_COLOR_32;
601                         }
602                 }
603
604                 dst -= dstStride;
605         }
606 }
607
608 /* -------------------------------------------------------------------------------------------------
609 *                                   PROPELLER STUFF
610 * -------------------------------------------------------------------------------------------------
611 */
612
613 #define PROPELLER_CIRCLE_RADIUS 18
614 #define PROPELLER_CIRCLE_RADIUS_SQ (PROPELLER_CIRCLE_RADIUS * PROPELLER_CIRCLE_RADIUS)
615
616 static struct {
617         int circleX[3];
618         int circleY[3];
619 } propellerState;
620
621 static void updatePropeller(float t) {
622         int i, j;
623         int cx, cy, count = 0;
624         char *dst;
625         float x = 0.0f;
626         float y = 18.0f;
627         float nx, ny;
628         float cost, sint;
629         static float sin120 = 0.86602540378f;
630         static float cos120 = -0.5f;
631
632         /* Rotate */
633         sint = sin(t);
634         cost = cos(t);
635         nx = x * cost - y * sint;
636         ny = y * cost + x * sint;
637         x = nx;
638         y = ny;
639         propellerState.circleX[0] = (int)(x + 0.5f) + 16;
640         propellerState.circleY[0] = (int)(y + 0.5f) + 16;
641
642         /* Rotate by 120 degrees, for the second circle */
643         nx = x * cos120 - y * sin120;
644         ny = y * cos120 + x * sin120;
645         x = nx;
646         y = ny;
647         propellerState.circleX[1] = (int)(x + 0.5f) + 16;
648         propellerState.circleY[1] = (int)(y + 0.5f) + 16;
649
650         /* 3rd circle */
651         nx = x * cos120 - y * sin120;
652         ny = y * cos120 + x * sin120;
653         x = nx;
654         y = ny;
655         propellerState.circleX[2] = (int)(x + 0.5f) + 16;
656         propellerState.circleY[2] = (int)(y + 0.5f) + 16;
657
658         /* Write effect to the mini fx buffer*/
659         dst = miniFXBuffer;
660         for (j = 0; j < 32; j++) {
661                 for (i = 0; i < 32; i++) {
662                         count = 0;
663
664                         /* First circle */
665                         cx = propellerState.circleX[0] - i;
666                         cy = propellerState.circleY[0] - j;
667                         if (cx*cx + cy*cy < PROPELLER_CIRCLE_RADIUS_SQ) count++;
668
669                         /* 2nd circle */
670                         cx = propellerState.circleX[1] - i;
671                         cy = propellerState.circleY[1] - j;
672                         if (cx*cx + cy*cy < PROPELLER_CIRCLE_RADIUS_SQ) count++;
673
674                         /* 3rd circle */
675                         cx = propellerState.circleX[2] - i;
676                         cy = propellerState.circleY[2] - j;
677                         if (cx*cx + cy*cy < PROPELLER_CIRCLE_RADIUS_SQ) count++;
678
679                         *dst++ = count >= 2;
680                 }
681         }
682
683         /* Then, encode to rle */
684         rlePropeller = rleEncode(rlePropeller, miniFXBuffer, 32, 32);
685
686         /* Distribute the produced streaks so that they don't produce garbage when interpolated */
687         rleDistributeStreaks(rlePropeller);
688 }