File indexing completed on 2025-01-05 04:25:41

0001 /****************************************************************************************
0002  * Copyright (c) 2004 Enrico Ros <eros.kde@email.it>                                    *
0003  *                                                                                      *
0004  * This program is free software; you can redistribute it and/or modify it under        *
0005  * the terms of the GNU General Public License as published by the Free Software        *
0006  * Foundation; either version 2 of the License, or (at your option) any later           *
0007  * version.                                                                             *
0008  *                                                                                      *
0009  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
0010  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
0011  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
0012  *                                                                                      *
0013  * You should have received a copy of the GNU General Public License along with         *
0014  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
0015  ****************************************************************************************/
0016 
0017 #include "BallsAnalyzer.h"
0018 
0019 #include <QStandardPaths>
0020 
0021 #include <QImage>
0022 
0023 #include <cmath>
0024 #include <cstdlib>
0025 #include <sys/time.h>
0026 
0027 
0028 inline float myfabsf( float f )
0029 {
0030     return f < 0.f ? -f : f;
0031 }
0032 
0033 
0034 class Ball
0035 {
0036 public:
0037     Ball() : x( drand48() - drand48() ), y( 1 - 2.0 * drand48() ),
0038         z( drand48() ), vx( 0.0 ), vy( 0.0 ), vz( 0.0 ),
0039         mass( 0.01 + drand48() / 10.0 )
0040         //,color( (float[3]) { 0.0, drand48()*0.5, 0.7 + drand48() * 0.3 } )
0041     {
0042         //this is because GCC < 3.3 can't compile the above line, we aren't sure why though
0043         color[0] = 0.0; color[1] = drand48() * 0.5; color[2] = 0.7 + drand48() * 0.3;
0044     };
0045 
0046     float x, y, z, vx, vy, vz, mass;
0047     float color[3];
0048 
0049     void updatePhysics( float dT )
0050     {
0051         x += vx * dT;                // position
0052         y += vy * dT;                // position
0053         z += vz * dT;                // position
0054         if( y < -0.8 ) vy = myfabsf( vy );
0055         if( y > 0.8 ) vy = -myfabsf( vy );
0056         if( z < 0.1 ) vz = myfabsf( vz );
0057         if( z > 0.9 ) vz = -myfabsf( vz );
0058         vx += ( ( x > 0 ) ? 4.94 : -4.94 ) * dT;  // G-force
0059         vx *= ( 1 - 2.9 * dT );          // air friction
0060         vy *= ( 1 - 2.9 * dT );          // air friction
0061         vz *= ( 1 - 2.9 * dT );          // air friction
0062     }
0063 };
0064 
0065 class Paddle
0066 {
0067 public:
0068     Paddle( float xPos ) : onLeft( xPos < 0 ), mass( 1.0 ),
0069         X( xPos ), x( xPos ), vx( 0.0 ) {};
0070 
0071     void updatePhysics( float dT )
0072     {
0073         x += vx * dT;                // position
0074         vx += ( 1300 * ( X - x ) / mass ) * dT;    // elasticity
0075         vx *= ( 1 - 4.0 * dT );          // air friction
0076     }
0077 
0078     void renderGL()
0079     {
0080         glBegin( GL_TRIANGLE_STRIP );
0081         glColor3f( 0.0f, 0.1f, 0.3f );
0082         glVertex3f( x, -1.0f, 0.0 );
0083         glVertex3f( x, 1.0f, 0.0 );
0084         glColor3f( 0.1f, 0.2f, 0.6f );
0085         glVertex3f( x, -1.0f, 1.0 );
0086         glVertex3f( x, 1.0f, 1.0 );
0087         glEnd();
0088     }
0089 
0090     void bounce( Ball * ball )
0091     {
0092         if( onLeft && ball->x < x )
0093         {
0094             ball->vx = vx * mass / ( mass + ball->mass ) + myfabsf( ball->vx );
0095             ball->vy = ( drand48() - drand48() ) * 1.8;
0096             ball->vz = ( drand48() - drand48() ) * 0.9;
0097             ball->x = x;
0098         }
0099         else if( !onLeft && ball->x > x )
0100         {
0101             ball->vx = vx * mass / ( mass + ball->mass ) - myfabsf( ball->vx );
0102             ball->vy = ( drand48() - drand48() ) * 1.8;
0103             ball->vz = ( drand48() - drand48() ) * 0.9;
0104             ball->x = x;
0105         }
0106     }
0107 
0108     void impulse( float strength )
0109     {
0110         if( ( onLeft && strength > vx ) || ( !onLeft && strength < vx ) )
0111             vx += strength;
0112     }
0113 
0114 private:
0115     bool onLeft;
0116     float mass, X, x, vx;
0117 };
0118 
0119 
0120 BallsAnalyzer::BallsAnalyzer( QWidget *parent ):
0121     Analyzer::Base( parent )
0122 {
0123     setObjectName( "Balls" );
0124 
0125     m_ballTexture = bindTexture( QImage( QStandardPaths::locate( QStandardPaths::GenericDataLocation, "amarok/images/ball.png" ) ) );
0126     m_gridTexture = bindTexture( QImage( QStandardPaths::locate( QStandardPaths::GenericDataLocation, "amarok/images/grid.png" ) ) );
0127 
0128     m_leftPaddle = new Paddle( -1.0 );
0129     m_rightPaddle = new Paddle( 1.0 );
0130     for( int i = 0; i < NUMBER_OF_BALLS; i++ )
0131         m_balls.append( new Ball() );
0132 
0133     m_show.colorK = 0.0;
0134     m_show.gridScrollK = 0.0;
0135     m_show.gridEnergyK = 0.0;
0136     m_show.camRot = 0.0;
0137     m_show.camRoll = 0.0;
0138     m_show.peakEnergy = 1.0;
0139     m_frame.silence = true;
0140     m_frame.energy = 0.0;
0141     m_frame.dEnergy = 0.0;
0142 }
0143 
0144 BallsAnalyzer::~BallsAnalyzer()
0145 {
0146     deleteTexture( m_ballTexture );
0147     deleteTexture( m_gridTexture );
0148     delete m_leftPaddle;
0149     delete m_rightPaddle;
0150 
0151     qDeleteAll( m_balls );
0152 }
0153 
0154 void BallsAnalyzer::initializeGL()
0155 {
0156     // Set a smooth shade model
0157     glShadeModel( GL_SMOOTH );
0158 
0159     // Disable depth test (all is drawn 'z-sorted')
0160     glDisable( GL_DEPTH_TEST );
0161 
0162     // Set blending function (Alpha addition)
0163     glBlendFunc( GL_SRC_ALPHA, GL_ONE );
0164 
0165     // Clear frame with a black background
0166     glClearColor( 0.0f, 0.0f, 0.0f, 1.0f );
0167 }
0168 
0169 void BallsAnalyzer::resizeGL( int w, int h )
0170 {
0171     // Setup screen. We're going to manually do the perspective projection
0172     glViewport( 0, 0, ( GLint )w, ( GLint )h );
0173     glMatrixMode( GL_PROJECTION );
0174     glLoadIdentity();
0175     glFrustum( -0.5f, 0.5f, -0.5f, 0.5f, 0.5f, 4.5f );
0176 
0177     // Get the aspect ratio of the screen to draw 'circular' particles
0178     float ratio = ( float )w / ( float )h;
0179     if( ratio >= 1.0 )
0180     {
0181         m_unitX = 0.34 / ratio;
0182         m_unitY = 0.34;
0183     }
0184     else
0185     {
0186         m_unitX = 0.34;
0187         m_unitY = 0.34 * ratio;
0188     }
0189 
0190     // Get current timestamp.
0191     timeval tv;
0192     gettimeofday( &tv, NULL );
0193     m_show.timeStamp = ( double )tv.tv_sec + ( double )tv.tv_usec / 1000000.0;
0194 }
0195 
0196 void BallsAnalyzer::analyze( const QVector<float> &s )
0197 {
0198     // compute the dTime since the last call
0199     timeval tv;
0200     gettimeofday( &tv, NULL );
0201     double currentTime = ( double )tv.tv_sec + ( double )tv.tv_usec / 1000000.0;
0202     m_show.dT = currentTime - m_show.timeStamp;
0203     m_show.timeStamp = currentTime;
0204 
0205     // compute energy integrating frame's spectrum
0206     if( !s.empty() )
0207     {
0208         int bands = s.size();
0209         float currentEnergy = 0,
0210               maxValue = 0;
0211         // integrate spectrum -> energy
0212         for( int i = 0; i < bands; i++ )
0213         {
0214             float value = s[i];
0215             currentEnergy += value;
0216             if( value > maxValue )
0217                 maxValue = value;
0218         }
0219         currentEnergy *= 100.0 / ( float )bands;
0220         // emulate a peak detector: currentEnergy -> peakEnergy (3tau = 30 seconds)
0221         m_show.peakEnergy = 1.0 + ( m_show.peakEnergy - 1.0 ) * exp( - m_show.dT / 10.0 );
0222         if( currentEnergy > m_show.peakEnergy )
0223             m_show.peakEnergy = currentEnergy;
0224         // check for silence
0225         m_frame.silence = currentEnergy < 0.001;
0226         // normalize frame energy against peak energy and compute frame stats
0227         currentEnergy /= m_show.peakEnergy;
0228         m_frame.dEnergy = currentEnergy - m_frame.energy;
0229         m_frame.energy = currentEnergy;
0230     }
0231     else
0232         m_frame.silence = true;
0233 
0234     // limit max dT to 0.05 and update color and scroll constants
0235     if( m_show.dT > 0.05 )
0236         m_show.dT = 0.05;
0237     m_show.colorK += m_show.dT * 0.4;
0238     if( m_show.colorK > 3.0 )
0239         m_show.colorK -= 3.0;
0240     m_show.gridScrollK += 0.2 * m_show.peakEnergy * m_show.dT;
0241 
0242     // Roll camera up/down handling the beat
0243     m_show.camRot += m_show.camRoll * m_show.dT;        // position
0244     m_show.camRoll -= 400 * m_show.camRot * m_show.dT;    // elasticity
0245     m_show.camRoll *= ( 1 - 2.0 * m_show.dT );      // friction
0246     if( !m_frame.silence && m_frame.dEnergy > 0.4 )
0247         m_show.camRoll += m_show.peakEnergy * 2.0;
0248 
0249     if( ( m_show.gridEnergyK > 0.05 ) || ( !m_frame.silence && m_frame.dEnergy < -0.3 ) )
0250     {
0251         m_show.gridEnergyK *= exp( -m_show.dT / 0.1 );
0252         if( -m_frame.dEnergy > m_show.gridEnergyK )
0253             m_show.gridEnergyK = -m_frame.dEnergy * 2.0;
0254     }
0255 
0256     foreach( Ball * ball, m_balls )
0257     {
0258         ball->updatePhysics( m_show.dT );
0259         if( ball->x < 0 )
0260             m_leftPaddle->bounce( ball );
0261         else
0262             m_rightPaddle->bounce( ball );
0263     }
0264 
0265     // Update physics of paddles
0266     m_leftPaddle->updatePhysics( m_show.dT );
0267     m_rightPaddle->updatePhysics( m_show.dT );
0268     if( !m_frame.silence )
0269     {
0270         m_leftPaddle->impulse( m_frame.energy * 3.0 + m_frame.dEnergy * 6.0 );
0271         m_rightPaddle->impulse( -m_frame.energy * 3.0 - m_frame.dEnergy * 6.0 );
0272     }
0273 }
0274 
0275 void BallsAnalyzer::paintGL()
0276 {
0277     // Switch to MODEL matrix and clear screen
0278     glMatrixMode( GL_MODELVIEW );
0279     glLoadIdentity();
0280     glClear( GL_COLOR_BUFFER_BIT );
0281 
0282     // Draw scrolling grid
0283     float gridColor[4] = { 0.0, 1.0, 0.6, m_show.gridEnergyK };
0284     drawScrollGrid( m_show.gridScrollK, gridColor );
0285 
0286     glRotatef( m_show.camRoll / 2.0, 1, 0, 0 );
0287 
0288     // Translate the drawing plane
0289     glTranslatef( 0.0f, 0.0f, -1.8f );
0290 
0291     // Draw upper/lower planes and paddles
0292     drawHFace( -1.0 );
0293     drawHFace( 1.0 );
0294     m_leftPaddle->renderGL();
0295     m_rightPaddle->renderGL();
0296 
0297     // Draw Balls
0298     if( m_ballTexture )
0299     {
0300         glEnable( GL_TEXTURE_2D );
0301         glBindTexture( GL_TEXTURE_2D, m_ballTexture );
0302     }
0303     else
0304         glDisable( GL_TEXTURE_2D );
0305 
0306     glEnable( GL_BLEND );
0307 
0308     foreach( Ball * ball, m_balls )
0309     {
0310         float color[3],
0311               angle = m_show.colorK;
0312         // Rotate the color based on 'angle' value [0,3)
0313         if( angle < 1.0 )
0314         {
0315             color[ 0 ] = ball->color[ 0 ] * ( 1 - angle ) + ball->color[ 1 ] * angle;
0316             color[ 1 ] = ball->color[ 1 ] * ( 1 - angle ) + ball->color[ 2 ] * angle;
0317             color[ 2 ] = ball->color[ 2 ] * ( 1 - angle ) + ball->color[ 0 ] * angle;
0318         }
0319         else if( angle < 2.0 )
0320         {
0321             angle -= 1.0;
0322             color[ 0 ] = ball->color[ 1 ] * ( 1 - angle ) + ball->color[ 2 ] * angle;
0323             color[ 1 ] = ball->color[ 2 ] * ( 1 - angle ) + ball->color[ 0 ] * angle;
0324             color[ 2 ] = ball->color[ 0 ] * ( 1 - angle ) + ball->color[ 1 ] * angle;
0325         }
0326         else
0327         {
0328             angle -= 2.0;
0329             color[ 0 ] = ball->color[ 2 ] * ( 1 - angle ) + ball->color[ 0 ] * angle;
0330             color[ 1 ] = ball->color[ 0 ] * ( 1 - angle ) + ball->color[ 1 ] * angle;
0331             color[ 2 ] = ball->color[ 1 ] * ( 1 - angle ) + ball->color[ 2 ] * angle;
0332         }
0333         // Draw the dot and update its physics also checking at bounces
0334         glColor3fv( color );
0335         drawDot3s( ball->x, ball->y, ball->z, 1.0 );
0336     }
0337     glDisable( GL_BLEND );
0338     glDisable( GL_TEXTURE_2D );
0339 }
0340 
0341 void BallsAnalyzer::drawDot3s( float x, float y, float z, float size )
0342 {
0343     // Circular XY dot drawing functions
0344     float sizeX = size * m_unitX,
0345           sizeY = size * m_unitY,
0346           pXm = x - sizeX,
0347           pXM = x + sizeX,
0348           pYm = y - sizeY,
0349           pYM = y + sizeY;
0350     // Draw the Dot
0351     glBegin( GL_QUADS );
0352     glTexCoord2f( 0, 0 );    // Bottom Left
0353     glVertex3f( pXm, pYm, z );
0354     glTexCoord2f( 0, 1 );    // Top Left
0355     glVertex3f( pXm, pYM, z );
0356     glTexCoord2f( 1, 1 );    // Top Right
0357     glVertex3f( pXM, pYM, z );
0358     glTexCoord2f( 1, 0 );    // Bottom Right
0359     glVertex3f( pXM, pYm, z );
0360     glEnd();
0361 
0362     // Shadow XZ drawing functions
0363     float sizeZ = size / 10.0,
0364           pZm = z - sizeZ,
0365           pZM = z + sizeZ,
0366           currentColor[4];
0367     glGetFloatv( GL_CURRENT_COLOR, currentColor );
0368     float alpha = currentColor[3],
0369           topSide = ( y + 1 ) / 4,
0370           bottomSide = ( 1 - y ) / 4;
0371     // Draw the top shadow
0372     currentColor[3] = topSide * topSide * alpha;
0373     glColor4fv( currentColor );
0374     glBegin( GL_QUADS );
0375     glTexCoord2f( 0, 0 );    // Bottom Left
0376     glVertex3f( pXm, 1, pZm );
0377     glTexCoord2f( 0, 1 );    // Top Left
0378     glVertex3f( pXm, 1, pZM );
0379     glTexCoord2f( 1, 1 );    // Top Right
0380     glVertex3f( pXM, 1, pZM );
0381     glTexCoord2f( 1, 0 );    // Bottom Right
0382     glVertex3f( pXM, 1, pZm );
0383     glEnd();
0384     // Draw the bottom shadow
0385     currentColor[3] = bottomSide * bottomSide * alpha;
0386     glColor4fv( currentColor );
0387     glBegin( GL_QUADS );
0388     glTexCoord2f( 0, 0 );    // Bottom Left
0389     glVertex3f( pXm, -1, pZm );
0390     glTexCoord2f( 0, 1 );    // Top Left
0391     glVertex3f( pXm, -1, pZM );
0392     glTexCoord2f( 1, 1 );    // Top Right
0393     glVertex3f( pXM, -1, pZM );
0394     glTexCoord2f( 1, 0 );    // Bottom Right
0395     glVertex3f( pXM, -1, pZm );
0396     glEnd();
0397 }
0398 
0399 void BallsAnalyzer::drawHFace( float y )
0400 {
0401     glBegin( GL_TRIANGLE_STRIP );
0402     glColor3f( 0.0f, 0.1f, 0.2f );
0403     glVertex3f( -1.0f, y, 0.0 );
0404     glVertex3f( 1.0f, y, 0.0 );
0405     glColor3f( 0.1f, 0.6f, 0.5f );
0406     glVertex3f( -1.0f, y, 2.0 );
0407     glVertex3f( 1.0f, y, 2.0 );
0408     glEnd();
0409 }
0410 
0411 void BallsAnalyzer::drawScrollGrid( float scroll, float color[4] )
0412 {
0413     if( !m_gridTexture )
0414         return;
0415     glMatrixMode( GL_TEXTURE );
0416     glLoadIdentity();
0417     glTranslatef( 0.0, -scroll, 0.0 );
0418     glMatrixMode( GL_MODELVIEW );
0419     float backColor[4] = { 1.0, 1.0, 1.0, 0.0 };
0420     for( int i = 0; i < 3; i++ )
0421         backColor[ i ] = color[ i ];
0422     glEnable( GL_TEXTURE_2D );
0423     glBindTexture( GL_TEXTURE_2D, m_gridTexture );
0424     glEnable( GL_BLEND );
0425     glBegin( GL_TRIANGLE_STRIP );
0426     glColor4fv( color );    // top face
0427     glTexCoord2f( 0.0f, 1.0f );
0428     glVertex3f( -1.0f, 1.0f, -1.0f );
0429     glTexCoord2f( 1.0f, 1.0f );
0430     glVertex3f( 1.0f, 1.0f, -1.0f );
0431     glColor4fv( backColor );    // central points
0432     glTexCoord2f( 0.0f, 0.0f );
0433     glVertex3f( -1.0f, 0.0f, -3.0f );
0434     glTexCoord2f( 1.0f, 0.0f );
0435     glVertex3f( 1.0f, 0.0f, -3.0f );
0436     glColor4fv( color );    // bottom face
0437     glTexCoord2f( 0.0f, 1.0f );
0438     glVertex3f( -1.0f, -1.0f, -1.0f );
0439     glTexCoord2f( 1.0f, 1.0f );
0440     glVertex3f( 1.0f, -1.0f, -1.0f );
0441     glEnd();
0442     glDisable( GL_BLEND );
0443     glDisable( GL_TEXTURE_2D );
0444     glMatrixMode( GL_TEXTURE );
0445     glLoadIdentity();
0446     glMatrixMode( GL_MODELVIEW );
0447 }