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