1 /****************************************************************************************
2  * Copyright (c) 2004 Enrico Ros <eros.kde@email.it>                                    *
3  *                                                                                      *
4  * This program is free software; you can redistribute it and/or modify it under        *
5  * the terms of the GNU General Public License as published by the Free Software        *
6  * Foundation; either version 2 of the License, or (at your option) any later           *
7  * version.                                                                             *
8  *                                                                                      *
9  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
10  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
11  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
12  *                                                                                      *
13  * You should have received a copy of the GNU General Public License along with         *
14  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
15  ****************************************************************************************/
16 
17 #include "BallsAnalyzer.h"
18 
19 #include <QStandardPaths>
20 
21 #include <QImage>
22 
23 #include <cmath>
24 #include <cstdlib>
25 #include <sys/time.h>
26 
27 
myfabsf(float f)28 inline float myfabsf( float f )
29 {
30     return f < 0.f ? -f : f;
31 }
32 
33 
34 class Ball
35 {
36 public:
Ball()37     Ball() : x( drand48() - drand48() ), y( 1 - 2.0 * drand48() ),
38         z( drand48() ), vx( 0.0 ), vy( 0.0 ), vz( 0.0 ),
39         mass( 0.01 + drand48() / 10.0 )
40         //,color( (float[3]) { 0.0, drand48()*0.5, 0.7 + drand48() * 0.3 } )
41     {
42         //this is because GCC < 3.3 can't compile the above line, we aren't sure why though
43         color[0] = 0.0; color[1] = drand48() * 0.5; color[2] = 0.7 + drand48() * 0.3;
44     };
45 
46     float x, y, z, vx, vy, vz, mass;
47     float color[3];
48 
updatePhysics(float dT)49     void updatePhysics( float dT )
50     {
51         x += vx * dT;                // position
52         y += vy * dT;                // position
53         z += vz * dT;                // position
54         if( y < -0.8 ) vy = myfabsf( vy );
55         if( y > 0.8 ) vy = -myfabsf( vy );
56         if( z < 0.1 ) vz = myfabsf( vz );
57         if( z > 0.9 ) vz = -myfabsf( vz );
58         vx += ( ( x > 0 ) ? 4.94 : -4.94 ) * dT;  // G-force
59         vx *= ( 1 - 2.9 * dT );          // air friction
60         vy *= ( 1 - 2.9 * dT );          // air friction
61         vz *= ( 1 - 2.9 * dT );          // air friction
62     }
63 };
64 
65 class Paddle
66 {
67 public:
Paddle(float xPos)68     Paddle( float xPos ) : onLeft( xPos < 0 ), mass( 1.0 ),
69         X( xPos ), x( xPos ), vx( 0.0 ) {};
70 
updatePhysics(float dT)71     void updatePhysics( float dT )
72     {
73         x += vx * dT;                // position
74         vx += ( 1300 * ( X - x ) / mass ) * dT;    // elasticity
75         vx *= ( 1 - 4.0 * dT );          // air friction
76     }
77 
renderGL()78     void renderGL()
79     {
80         glBegin( GL_TRIANGLE_STRIP );
81         glColor3f( 0.0f, 0.1f, 0.3f );
82         glVertex3f( x, -1.0f, 0.0 );
83         glVertex3f( x, 1.0f, 0.0 );
84         glColor3f( 0.1f, 0.2f, 0.6f );
85         glVertex3f( x, -1.0f, 1.0 );
86         glVertex3f( x, 1.0f, 1.0 );
87         glEnd();
88     }
89 
bounce(Ball * ball)90     void bounce( Ball * ball )
91     {
92         if( onLeft && ball->x < x )
93         {
94             ball->vx = vx * mass / ( mass + ball->mass ) + myfabsf( ball->vx );
95             ball->vy = ( drand48() - drand48() ) * 1.8;
96             ball->vz = ( drand48() - drand48() ) * 0.9;
97             ball->x = x;
98         }
99         else if( !onLeft && ball->x > x )
100         {
101             ball->vx = vx * mass / ( mass + ball->mass ) - myfabsf( ball->vx );
102             ball->vy = ( drand48() - drand48() ) * 1.8;
103             ball->vz = ( drand48() - drand48() ) * 0.9;
104             ball->x = x;
105         }
106     }
107 
impulse(float strength)108     void impulse( float strength )
109     {
110         if( ( onLeft && strength > vx ) || ( !onLeft && strength < vx ) )
111             vx += strength;
112     }
113 
114 private:
115     bool onLeft;
116     float mass, X, x, vx;
117 };
118 
119 
BallsAnalyzer(QWidget * parent)120 BallsAnalyzer::BallsAnalyzer( QWidget *parent ):
121     Analyzer::Base( parent )
122 {
123     setObjectName( "Balls" );
124 
125     m_ballTexture = bindTexture( QImage( QStandardPaths::locate( QStandardPaths::GenericDataLocation, "amarok/images/ball.png" ) ) );
126     m_gridTexture = bindTexture( QImage( QStandardPaths::locate( QStandardPaths::GenericDataLocation, "amarok/images/grid.png" ) ) );
127 
128     m_leftPaddle = new Paddle( -1.0 );
129     m_rightPaddle = new Paddle( 1.0 );
130     for( int i = 0; i < NUMBER_OF_BALLS; i++ )
131         m_balls.append( new Ball() );
132 
133     m_show.colorK = 0.0;
134     m_show.gridScrollK = 0.0;
135     m_show.gridEnergyK = 0.0;
136     m_show.camRot = 0.0;
137     m_show.camRoll = 0.0;
138     m_show.peakEnergy = 1.0;
139     m_frame.silence = true;
140     m_frame.energy = 0.0;
141     m_frame.dEnergy = 0.0;
142 }
143 
~BallsAnalyzer()144 BallsAnalyzer::~BallsAnalyzer()
145 {
146     deleteTexture( m_ballTexture );
147     deleteTexture( m_gridTexture );
148     delete m_leftPaddle;
149     delete m_rightPaddle;
150 
151     qDeleteAll( m_balls );
152 }
153 
initializeGL()154 void BallsAnalyzer::initializeGL()
155 {
156     // Set a smooth shade model
157     glShadeModel( GL_SMOOTH );
158 
159     // Disable depth test (all is drawn 'z-sorted')
160     glDisable( GL_DEPTH_TEST );
161 
162     // Set blending function (Alpha addition)
163     glBlendFunc( GL_SRC_ALPHA, GL_ONE );
164 
165     // Clear frame with a black background
166     glClearColor( 0.0f, 0.0f, 0.0f, 1.0f );
167 }
168 
resizeGL(int w,int h)169 void BallsAnalyzer::resizeGL( int w, int h )
170 {
171     // Setup screen. We're going to manually do the perspective projection
172     glViewport( 0, 0, ( GLint )w, ( GLint )h );
173     glMatrixMode( GL_PROJECTION );
174     glLoadIdentity();
175     glFrustum( -0.5f, 0.5f, -0.5f, 0.5f, 0.5f, 4.5f );
176 
177     // Get the aspect ratio of the screen to draw 'circular' particles
178     float ratio = ( float )w / ( float )h;
179     if( ratio >= 1.0 )
180     {
181         m_unitX = 0.34 / ratio;
182         m_unitY = 0.34;
183     }
184     else
185     {
186         m_unitX = 0.34;
187         m_unitY = 0.34 * ratio;
188     }
189 
190     // Get current timestamp.
191     timeval tv;
192     gettimeofday( &tv, NULL );
193     m_show.timeStamp = ( double )tv.tv_sec + ( double )tv.tv_usec / 1000000.0;
194 }
195 
analyze(const QVector<float> & s)196 void BallsAnalyzer::analyze( const QVector<float> &s )
197 {
198     // compute the dTime since the last call
199     timeval tv;
200     gettimeofday( &tv, NULL );
201     double currentTime = ( double )tv.tv_sec + ( double )tv.tv_usec / 1000000.0;
202     m_show.dT = currentTime - m_show.timeStamp;
203     m_show.timeStamp = currentTime;
204 
205     // compute energy integrating frame's spectrum
206     if( !s.empty() )
207     {
208         int bands = s.size();
209         float currentEnergy = 0,
210               maxValue = 0;
211         // integrate spectrum -> energy
212         for( int i = 0; i < bands; i++ )
213         {
214             float value = s[i];
215             currentEnergy += value;
216             if( value > maxValue )
217                 maxValue = value;
218         }
219         currentEnergy *= 100.0 / ( float )bands;
220         // emulate a peak detector: currentEnergy -> peakEnergy (3tau = 30 seconds)
221         m_show.peakEnergy = 1.0 + ( m_show.peakEnergy - 1.0 ) * exp( - m_show.dT / 10.0 );
222         if( currentEnergy > m_show.peakEnergy )
223             m_show.peakEnergy = currentEnergy;
224         // check for silence
225         m_frame.silence = currentEnergy < 0.001;
226         // normalize frame energy against peak energy and compute frame stats
227         currentEnergy /= m_show.peakEnergy;
228         m_frame.dEnergy = currentEnergy - m_frame.energy;
229         m_frame.energy = currentEnergy;
230     }
231     else
232         m_frame.silence = true;
233 
234     // limit max dT to 0.05 and update color and scroll constants
235     if( m_show.dT > 0.05 )
236         m_show.dT = 0.05;
237     m_show.colorK += m_show.dT * 0.4;
238     if( m_show.colorK > 3.0 )
239         m_show.colorK -= 3.0;
240     m_show.gridScrollK += 0.2 * m_show.peakEnergy * m_show.dT;
241 
242     // Roll camera up/down handling the beat
243     m_show.camRot += m_show.camRoll * m_show.dT;        // position
244     m_show.camRoll -= 400 * m_show.camRot * m_show.dT;    // elasticity
245     m_show.camRoll *= ( 1 - 2.0 * m_show.dT );      // friction
246     if( !m_frame.silence && m_frame.dEnergy > 0.4 )
247         m_show.camRoll += m_show.peakEnergy * 2.0;
248 
249     if( ( m_show.gridEnergyK > 0.05 ) || ( !m_frame.silence && m_frame.dEnergy < -0.3 ) )
250     {
251         m_show.gridEnergyK *= exp( -m_show.dT / 0.1 );
252         if( -m_frame.dEnergy > m_show.gridEnergyK )
253             m_show.gridEnergyK = -m_frame.dEnergy * 2.0;
254     }
255 
256     foreach( Ball * ball, m_balls )
257     {
258         ball->updatePhysics( m_show.dT );
259         if( ball->x < 0 )
260             m_leftPaddle->bounce( ball );
261         else
262             m_rightPaddle->bounce( ball );
263     }
264 
265     // Update physics of paddles
266     m_leftPaddle->updatePhysics( m_show.dT );
267     m_rightPaddle->updatePhysics( m_show.dT );
268     if( !m_frame.silence )
269     {
270         m_leftPaddle->impulse( m_frame.energy * 3.0 + m_frame.dEnergy * 6.0 );
271         m_rightPaddle->impulse( -m_frame.energy * 3.0 - m_frame.dEnergy * 6.0 );
272     }
273 }
274 
paintGL()275 void BallsAnalyzer::paintGL()
276 {
277     // Switch to MODEL matrix and clear screen
278     glMatrixMode( GL_MODELVIEW );
279     glLoadIdentity();
280     glClear( GL_COLOR_BUFFER_BIT );
281 
282     // Draw scrolling grid
283     float gridColor[4] = { 0.0, 1.0, 0.6, m_show.gridEnergyK };
284     drawScrollGrid( m_show.gridScrollK, gridColor );
285 
286     glRotatef( m_show.camRoll / 2.0, 1, 0, 0 );
287 
288     // Translate the drawing plane
289     glTranslatef( 0.0f, 0.0f, -1.8f );
290 
291     // Draw upper/lower planes and paddles
292     drawHFace( -1.0 );
293     drawHFace( 1.0 );
294     m_leftPaddle->renderGL();
295     m_rightPaddle->renderGL();
296 
297     // Draw Balls
298     if( m_ballTexture )
299     {
300         glEnable( GL_TEXTURE_2D );
301         glBindTexture( GL_TEXTURE_2D, m_ballTexture );
302     }
303     else
304         glDisable( GL_TEXTURE_2D );
305 
306     glEnable( GL_BLEND );
307 
308     foreach( Ball * ball, m_balls )
309     {
310         float color[3],
311               angle = m_show.colorK;
312         // Rotate the color based on 'angle' value [0,3)
313         if( angle < 1.0 )
314         {
315             color[ 0 ] = ball->color[ 0 ] * ( 1 - angle ) + ball->color[ 1 ] * angle;
316             color[ 1 ] = ball->color[ 1 ] * ( 1 - angle ) + ball->color[ 2 ] * angle;
317             color[ 2 ] = ball->color[ 2 ] * ( 1 - angle ) + ball->color[ 0 ] * angle;
318         }
319         else if( angle < 2.0 )
320         {
321             angle -= 1.0;
322             color[ 0 ] = ball->color[ 1 ] * ( 1 - angle ) + ball->color[ 2 ] * angle;
323             color[ 1 ] = ball->color[ 2 ] * ( 1 - angle ) + ball->color[ 0 ] * angle;
324             color[ 2 ] = ball->color[ 0 ] * ( 1 - angle ) + ball->color[ 1 ] * angle;
325         }
326         else
327         {
328             angle -= 2.0;
329             color[ 0 ] = ball->color[ 2 ] * ( 1 - angle ) + ball->color[ 0 ] * angle;
330             color[ 1 ] = ball->color[ 0 ] * ( 1 - angle ) + ball->color[ 1 ] * angle;
331             color[ 2 ] = ball->color[ 1 ] * ( 1 - angle ) + ball->color[ 2 ] * angle;
332         }
333         // Draw the dot and update its physics also checking at bounces
334         glColor3fv( color );
335         drawDot3s( ball->x, ball->y, ball->z, 1.0 );
336     }
337     glDisable( GL_BLEND );
338     glDisable( GL_TEXTURE_2D );
339 }
340 
drawDot3s(float x,float y,float z,float size)341 void BallsAnalyzer::drawDot3s( float x, float y, float z, float size )
342 {
343     // Circular XY dot drawing functions
344     float sizeX = size * m_unitX,
345           sizeY = size * m_unitY,
346           pXm = x - sizeX,
347           pXM = x + sizeX,
348           pYm = y - sizeY,
349           pYM = y + sizeY;
350     // Draw the Dot
351     glBegin( GL_QUADS );
352     glTexCoord2f( 0, 0 );    // Bottom Left
353     glVertex3f( pXm, pYm, z );
354     glTexCoord2f( 0, 1 );    // Top Left
355     glVertex3f( pXm, pYM, z );
356     glTexCoord2f( 1, 1 );    // Top Right
357     glVertex3f( pXM, pYM, z );
358     glTexCoord2f( 1, 0 );    // Bottom Right
359     glVertex3f( pXM, pYm, z );
360     glEnd();
361 
362     // Shadow XZ drawing functions
363     float sizeZ = size / 10.0,
364           pZm = z - sizeZ,
365           pZM = z + sizeZ,
366           currentColor[4];
367     glGetFloatv( GL_CURRENT_COLOR, currentColor );
368     float alpha = currentColor[3],
369           topSide = ( y + 1 ) / 4,
370           bottomSide = ( 1 - y ) / 4;
371     // Draw the top shadow
372     currentColor[3] = topSide * topSide * alpha;
373     glColor4fv( currentColor );
374     glBegin( GL_QUADS );
375     glTexCoord2f( 0, 0 );    // Bottom Left
376     glVertex3f( pXm, 1, pZm );
377     glTexCoord2f( 0, 1 );    // Top Left
378     glVertex3f( pXm, 1, pZM );
379     glTexCoord2f( 1, 1 );    // Top Right
380     glVertex3f( pXM, 1, pZM );
381     glTexCoord2f( 1, 0 );    // Bottom Right
382     glVertex3f( pXM, 1, pZm );
383     glEnd();
384     // Draw the bottom shadow
385     currentColor[3] = bottomSide * bottomSide * alpha;
386     glColor4fv( currentColor );
387     glBegin( GL_QUADS );
388     glTexCoord2f( 0, 0 );    // Bottom Left
389     glVertex3f( pXm, -1, pZm );
390     glTexCoord2f( 0, 1 );    // Top Left
391     glVertex3f( pXm, -1, pZM );
392     glTexCoord2f( 1, 1 );    // Top Right
393     glVertex3f( pXM, -1, pZM );
394     glTexCoord2f( 1, 0 );    // Bottom Right
395     glVertex3f( pXM, -1, pZm );
396     glEnd();
397 }
398 
drawHFace(float y)399 void BallsAnalyzer::drawHFace( float y )
400 {
401     glBegin( GL_TRIANGLE_STRIP );
402     glColor3f( 0.0f, 0.1f, 0.2f );
403     glVertex3f( -1.0f, y, 0.0 );
404     glVertex3f( 1.0f, y, 0.0 );
405     glColor3f( 0.1f, 0.6f, 0.5f );
406     glVertex3f( -1.0f, y, 2.0 );
407     glVertex3f( 1.0f, y, 2.0 );
408     glEnd();
409 }
410 
drawScrollGrid(float scroll,float color[4])411 void BallsAnalyzer::drawScrollGrid( float scroll, float color[4] )
412 {
413     if( !m_gridTexture )
414         return;
415     glMatrixMode( GL_TEXTURE );
416     glLoadIdentity();
417     glTranslatef( 0.0, -scroll, 0.0 );
418     glMatrixMode( GL_MODELVIEW );
419     float backColor[4] = { 1.0, 1.0, 1.0, 0.0 };
420     for( int i = 0; i < 3; i++ )
421         backColor[ i ] = color[ i ];
422     glEnable( GL_TEXTURE_2D );
423     glBindTexture( GL_TEXTURE_2D, m_gridTexture );
424     glEnable( GL_BLEND );
425     glBegin( GL_TRIANGLE_STRIP );
426     glColor4fv( color );    // top face
427     glTexCoord2f( 0.0f, 1.0f );
428     glVertex3f( -1.0f, 1.0f, -1.0f );
429     glTexCoord2f( 1.0f, 1.0f );
430     glVertex3f( 1.0f, 1.0f, -1.0f );
431     glColor4fv( backColor );    // central points
432     glTexCoord2f( 0.0f, 0.0f );
433     glVertex3f( -1.0f, 0.0f, -3.0f );
434     glTexCoord2f( 1.0f, 0.0f );
435     glVertex3f( 1.0f, 0.0f, -3.0f );
436     glColor4fv( color );    // bottom face
437     glTexCoord2f( 0.0f, 1.0f );
438     glVertex3f( -1.0f, -1.0f, -1.0f );
439     glTexCoord2f( 1.0f, 1.0f );
440     glVertex3f( 1.0f, -1.0f, -1.0f );
441     glEnd();
442     glDisable( GL_BLEND );
443     glDisable( GL_TEXTURE_2D );
444     glMatrixMode( GL_TEXTURE );
445     glLoadIdentity();
446     glMatrixMode( GL_MODELVIEW );
447 }
448