1 /*
2  * This file is released under the GNU General Public Licence
3  *
4  * authors:
5  * Richard Ashbury       <richard.asbury@btinternet.com>
6  * Jean-Christophe Hoelt <jeko@ios-software.com>
7  */
8 
9 /////////////////////////////////////////////////////////////////////////////
10 //
11 // Corona.cpp : Implementation of CCorona
12 //
13 /////////////////////////////////////////////////////////////////////////////
14 
15 #include "corona.h"
16 #include <cstdlib>
17 #include <cmath>
18 using namespace std;
19 
20 
21 /////////////////////////////////////////////////////////////////////////////
22 // Corona::Corona
23 // Constructor
24 
Corona()25 Corona::Corona()
26 {
27   m_clrForeground = 0x0000FF;
28   m_swirltime     = 0;
29   m_testing       = false;
30   m_silent        = false;
31   m_avg           = 1;
32   m_oldval        = 0;
33   m_pos           = 0;
34 
35   m_image         = 0;
36   m_real_image    = 0;
37   m_deltafield    = 0;
38   m_width         = -1;
39   m_height        = -1;
40   m_real_height   = -1;
41   nbParticules    = 1000;
42   m_reflArray     = 0;
43   m_waveloop      = 0.0;
44   m_nPreset       = PRESET_CORONA;
45 
46   m_particles = (Particle*)calloc (nbParticules, sizeof(Particle));
47   // Create particles in random positions
48   for (int i = nbParticules - 1; i >= 0; --i)
49   {
50     Particle *it = m_particles + i;
51     it->x = random(0, 1);
52     it->y = random(0, 1);
53     it->xvel = it->yvel = 0;
54   }
55   // Set up the background swirling effect
56   chooseRandomSwirl();
57 }
58 
59 /////////////////////////////////////////////////////////////////////////////
60 // Corona::~Corona
61 // Destructor
62 
~Corona()63 Corona::~Corona()
64 {
65   if (m_real_image) free(m_real_image);
66   if (m_deltafield) free(m_deltafield);
67 }
68 
random(double min,double max) const69 double Corona::random(double min, double max) const {
70   return rand() * (max - min) / RAND_MAX + min;
71 }
72 
setUpSurface(int width,int height)73 bool Corona::setUpSurface(int width, int height) {
74   // Delete any image that might have previously been allocated
75   if (m_real_image) free(m_real_image);
76   if (m_deltafield) free(m_deltafield);
77   if (m_reflArray)  free(m_reflArray);
78 
79   // Fill in the size details in the BitmapInfo structure
80   m_width  = width;
81   m_height = (height*4) / 5;
82   m_real_height = height;
83 
84   // Allocate the image data
85   m_real_image = (unsigned char *)calloc(1,width*height+1);
86   if (m_real_image == 0) return false;
87   m_image      = m_real_image + m_width * (m_real_height - m_height);
88   m_reflArray  = (int*)calloc(1,m_real_height - m_height + 1);
89 
90   // Allocate the delta-field memory, and initialise it
91   m_deltafield = (unsigned char**)malloc(m_width * m_height * sizeof(unsigned char*));
92 
93   for (int x = 0; x < m_width; ++x) {
94     for (int y = 0; y < m_height; ++y) {
95       setPointDelta(x, y);
96     }
97   }
98 
99   // Change the number of particles
100   int newsize = (int) (::sqrt(m_width * m_height) * 3.0);
101   if (newsize < 2000) newsize = 2000;
102   int oldsize = (int) nbParticules;
103   nbParticules = newsize;
104   m_particles = (Particle*)realloc (m_particles, sizeof(Particle) * newsize);
105   for (int i = oldsize; i < newsize; ++i) {
106     m_particles[i].x = random(0, 1);
107     m_particles[i].y = random(0, 1);
108     m_particles[i].xvel = m_particles[i].yvel = 0;
109   }
110 
111   return true;
112 }
113 
drawLine(int x0,int y0,int x1,int y1,unsigned char col)114 void Corona::drawLine(int x0, int y0, int x1, int y1, unsigned char col)
115 {
116   int incx = (x1 > x0) ? 1 : -1;
117   int incy = (y1 > y0) ? m_width : -m_width;
118   int dincx = 2 * abs(y1 - y0);
119   int dincy = 2 * abs(x1 - x0);
120   unsigned char* p = &(m_image[x0 + y0 * m_width]);
121   unsigned char* const end = &(m_image[m_width + (m_height - 1) * m_width]);
122   unsigned char* const start = m_image;
123   int n, d;	// n is the "pixel counter"
124 
125   // Always draw at least one pixel
126   if (start <= p && p < end) *p = col;
127 
128   if (abs(x1 - x0) > abs(y1 - y0)) {
129     d = x0 - x1;
130     for (n = abs(x1 - x0); n > 0; --n, p += incx) {
131       if (start <= p && p < end) *p = col;
132       d += dincx;
133       if (d > 0) {
134         p += incy;
135         d -= dincy;
136       }
137     }
138   }
139   else {
140     d = y0 - y1;
141     for (n = abs(y1 - y0); n > 0; --n, p += incy) {
142       if (start <= p && p < end) *p = col;
143       d += dincy;
144       if (d > 0) {
145         p += incx;
146         d -= dincx;
147       }
148     }
149   }
150 }
151 
152 
chooseRandomSwirl()153 void Corona::chooseRandomSwirl()
154 {
155   m_swirl.x = random(0.2, 0.8);
156   m_swirl.y = random(0.2, 0.8);
157   m_swirl.tightness = random(-0.01, 0.01);
158   m_swirl.pull = random(1.0, 1.04);
159 }
160 
setPointDelta(int x,int y)161 void Corona::setPointDelta(int x, int y)
162 {
163   double tx  = ((double) x / m_width)  - m_swirl.x;
164   double ty  = ((double) y / m_height) - m_swirl.y;
165   double d   = tx * tx + ty * ty;
166   double ds  = ::sqrt(d);
167   double ang = atan2(ty, tx) + m_swirl.tightness / (d + 0.01);
168   int dx = (int) ((ds * m_swirl.pull * cos(ang) - tx) * m_width) + rand()  % 5 - 2;
169   int dy = (int) ((ds * m_swirl.pull * sin(ang) - ty) * m_height) + rand() % 5 - 2;
170   if (x + dx < 0) dx = -dx - x;
171   if (x + dx >= m_width) dx = 2 * m_width - 2 * x - dx - 1;
172   if (y + dy < 0) dy = -dy - y;
173   if (y + dy >= m_height) dy = 2 * m_height - 2 * y - dy - 1;
174   m_deltafield[x + y * m_width] = &(m_image[x + dx + (y + dy) * m_width]);
175 }
176 
applyDeltaField(bool heavy)177 void Corona::applyDeltaField(bool heavy)
178 {
179   if (heavy) {
180     for (int y = 0; y < m_height; ++y) {
181       unsigned char *s = &(m_image[y * m_width]);
182       unsigned char **p = &(m_deltafield[y * m_width]);
183       for (int x = 0; x < m_width; ++x, ++s, ++p) {
184         *s = (*s + *(*p)) >> 1;
185         if (*s >= 2) *s -= 2;
186       }
187     }
188   }
189   else {
190     for (int y = 0; y < m_height; ++y) {
191       unsigned char *s = &(m_image[y * m_width]);
192       unsigned char **p = &(m_deltafield[y * m_width]);
193       for (int x = 0; x < m_width; ++x, ++s, ++p) {
194         *s = (*s + **p) >> 1;
195         if (*s >= 1) *s -= 1;
196       }
197     }
198   }
199 }
200 
getBeatVal(TimedLevel * tl)201 int Corona::getBeatVal(TimedLevel *tl)
202 {
203   int total = 0;
204   for (int i = 50; i < 250; ++i) {
205     int n = tl->frequency[0][i];
206     total += n;
207   }
208   total /= 3;
209 
210   m_avg = 0.9 * m_avg + 0.1 * total;
211   if (m_avg < 1000) m_avg = 1000;
212 
213   if (total > m_avg * 1.2 && tl->timeStamp - tl->lastbeat > 750000) {
214     m_avg = total;
215     tl->lastbeat = tl->timeStamp;
216     if (total > 2500) return 2500;
217     else return total;
218   }
219   else return 0;
220 }
221 
drawParticules()222 void Corona::drawParticules()
223 {
224   int p;
225   for (p = 0; p < nbParticules; ++p) {
226     Particle *it = m_particles + p;
227     int x = (int) (it->x * m_width);
228     int y = (int) (it->y * m_height);
229     int xv = (int) (it->xvel * m_width);
230     int yv = (int) (it->yvel * m_height);
231     drawLine(x, y, x - xv, y - yv, 255);
232   }
233 }
234 
drawParticulesWithShift()235 void Corona::drawParticulesWithShift()
236 {
237   int p;
238   for (p = 0; p < nbParticules; ++p) {
239     Particle *it = m_particles + p;
240     int x = (int) (it->x * m_width);
241     int y = (int) (it->y * m_height);
242     int xv = (int) (it->xvel * m_width);
243     int yv = (int) (it->yvel * m_height);
244     double l = (xv * xv + yv * yv);
245     if (l > 10.0 * 10.0) {
246       l = ::sqrt(l);
247       double dl = 10 / (l + 0.01);
248       xv = (int) (xv * dl);
249       yv = (int) (yv * dl);
250     }
251     drawLine(x, y, x - xv, y - yv, 255);
252   }
253 }
254 
getAvgParticlePos(double & x,double & y) const255 void Corona::getAvgParticlePos(double& x, double& y) const
256 {
257   x = y = 0;
258   for (int i = 0; i < 10; ++i) {
259     int r = rand() % nbParticules;
260     x += m_particles[r].x;
261     y += m_particles[r].y;
262   }
263   x /= 10;
264   y /= 10;
265 }
266 
267 #define REFL_MIN_WIDTH 3.0
268 #define REFL_INC_WIDTH 0.08
269 
genReflectedWaves(double loop)270 void Corona::genReflectedWaves(double loop)
271 {
272   double fdec   = 0.0;
273   double floop  = 0.0;
274   double fwidth = (m_real_height - m_height) * REFL_INC_WIDTH + REFL_MIN_WIDTH;
275   double REFL_MAX_WIDTH = fwidth;
276 
277   m_reflArray = new int[m_real_height - m_height + 1];
278 
279   for (int i = 0; i < (m_real_height - m_height); ++i)
280   {
281     double fincr = (3.1415 / 2.0) * (1.0 - (fwidth - REFL_MIN_WIDTH) / REFL_MAX_WIDTH);
282     floop  += fincr;
283 
284     fwidth -= REFL_INC_WIDTH;
285     fdec    = fwidth * sin(floop + loop);
286     m_reflArray[i] = (int)fdec;
287   }
288 }
289 
drawReflected()290 void Corona::drawReflected()
291 {
292   genReflectedWaves(m_waveloop);
293   int offsetDest  = (m_real_height - m_height - 1) * m_width;
294   int offsetSrc   = (m_real_height - m_height)     * m_width;
295 
296   for (int i = m_real_height - m_height; i--;)
297   {
298     int idec = m_reflArray[i];
299 
300     for (int x = m_width; x--;)
301     {
302       int out = m_real_image[(offsetSrc++) + idec];
303       m_real_image[offsetDest++] = out;
304     }
305 
306     offsetDest -= m_width * 2;
307     offsetSrc  += m_width;
308   }
309 }
310 
blurImage()311 void Corona::blurImage()
312 {
313   for (int y = 1; y < m_real_height - 1; ++y) {
314     m_real_image[y * m_width] = 0;
315     for (int x = 1; x < m_width - 1; ++x) {
316       int n = x + y * m_width;
317       int val = m_real_image[n + 1];
318       val += m_real_image[n - 1];
319       val += m_real_image[n - m_width];
320       val += m_real_image[n + m_width];
321       val >>= 2;
322       m_real_image[n] = val & 0xff;
323     }
324   }
325 }
326 
update(TimedLevel * pLevels)327 void Corona::update(TimedLevel *pLevels)
328 {
329   // Check for a beat
330   int beatval = getBeatVal(pLevels);
331   if (beatval > 1000)
332   {
333     int total = 0;
334     for (int i = 0; i < 512; ++i)
335       total += 2 * pLevels->frequency[0][i];
336     double currval = 1.0 - exp(-total / 40000.0);
337     m_oldval = (m_oldval + currval) / 2.0;
338 
339     double tx, ty;
340     getAvgParticlePos(tx, ty);
341     // If most of the particles are low down, use a launch
342     if (ty < 0.2 && rand() % 4 != 0) {
343       int p;
344       double bv = m_oldval * 5.0;
345       for (p = 0; p < nbParticules; ++p)
346       {
347         Particle *it = m_particles + p;
348         if (it->y < 0.1) {
349           double x = (it->x - tx) / bv;
350           it->yvel += 0.01 * bv * exp(-1000.0 * x * x);
351         }
352       }
353     }
354     else
355     {	// Otherwise use a swirl
356       tx += random(-0.1, 0.1);
357       ty += random(-0.1, 0.1);
358       double bv = 0.009 * m_oldval;
359       double bv2 = 0.0036 * m_oldval;
360       if (rand() % 2 == 0) bv = -bv;
361       m_movement.x = tx;
362       m_movement.y = ty;
363       m_movement.tightness = random(0.8 * bv, bv);
364       m_movement.pull = random(1 - bv2, 1 - 0.2 * bv2);
365       m_swirltime = 1;
366     }
367 
368     pLevels->lastbeat = pLevels->currentTimeMs;
369   }
370 
371   // Deal with the particles
372   int p;
373   for (p = 0; p < nbParticules; ++p)
374   {
375     Particle *it = m_particles + p;
376     // Apply gravity
377     it->yvel -= 0.0006;		// the gravity value
378 
379     // If there's an active swirl, swirl around it
380     if (m_swirltime > 0) {
381       double dx = it->x - m_movement.x;
382       double dy = it->y - m_movement.y;
383       double d = dx * dx + dy * dy;
384       double ds = ::sqrt(d);
385       double ang = atan2(dy, dx) + m_movement.tightness / (d + 0.01);
386       it->xvel += (ds * m_movement.pull * cos(ang) - dx);
387       it->yvel += (ds * m_movement.pull * sin(ang) - dy);
388     }
389 
390     // Gitter
391     it->xvel += random(-0.0002, 0.0002);
392     it->yvel += random(-0.0002, 0.0002);
393 
394     // Clamp the velocity
395     if (it->xvel < -0.1 ) it->xvel = -0.1;
396     if (it->xvel > 0.1 ) it->xvel = 0.1;
397     if (it->yvel < -0.1 ) it->yvel = -0.1;
398     if (it->yvel > 0.1 ) it->yvel = 0.1;
399 
400     // Randomly move the particle once in a while
401     if (rand() % (nbParticules / 5) == 0)
402     {
403       it->x = random(0, 1);
404       it->y = random(0, 1);
405       it->xvel = it->yvel = 0;
406     }
407 
408     // Move and bounce the particle
409     it->x += it->xvel;
410     it->y += it->yvel;
411     if (it->x < 0) { it->x = -it->x; it->xvel *= -0.25; it->yvel *= 0.25; }
412     if (it->y < 0) { it->y = -it->y; it->xvel *= 0.25; it->yvel *= -0.25; }
413     if (it->x > 1) { it->x = 2.0 - it->x; it->xvel *= -0.25; it->yvel *= 0.25; }
414     if (it->y > 1) { it->y = 2.0 - it->y; it->xvel *= 0.25; it->yvel = 0; }
415   }
416 
417   if (m_swirltime > 0) --m_swirltime;
418 
419   // Randomly change the delta field
420   if (rand() % 200 == 0) chooseRandomSwirl();
421 
422   // Animate the waves
423   m_waveloop += 0.6;
424 
425   // drawing.
426   if (m_image != 0)
427   {
428     // Draw the particles on the bitmap
429     drawParticules();
430 
431     // Apply the deltafield and update a few of its points
432     applyDeltaField((m_nPreset == PRESET_BLAZE) && m_width * m_height < 150000);
433 
434     int n = (m_width * m_height) / 100;
435     for (int i = 0; i < n; ++i)
436       setPointDelta(rand() % m_width, rand() % m_height);
437 
438     // If on the blaze preset, draw the particles again
439     if (m_nPreset == PRESET_BLAZE)
440       drawParticules();
441 
442     drawReflected();
443 
444     // Blur the bitmap
445     blurImage();
446 
447     // If on the blaze preset, draw the particles one last time
448     if (m_nPreset == PRESET_BLAZE)
449       drawParticulesWithShift();
450   }
451 }
452 
453