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