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 }