blob: 8348df66c3421cc6b3cf432b7174b2e474d25b0e [file] [log] [blame]
Camilla Berglund3249f812010-09-07 17:34:51 +02001/*****************************************************************************
2 * Title: GLBoing
3 * Desc: Tribute to Amiga Boing.
4 * Author: Jim Brooks <gfx@jimbrooks.org>
5 * Original Amiga authors were R.J. Mical and Dale Luck.
6 * GLFW conversion by Marcus Geelnard
7 * Notes: - 360' = 2*PI [radian]
8 *
9 * - Distances between objects are created by doing a relative
10 * Z translations.
11 *
12 * - Although OpenGL enticingly supports alpha-blending,
13 * the shadow of the original Boing didn't affect the color
14 * of the grid.
15 *
16 * - [Marcus] Changed timing scheme from interval driven to frame-
17 * time based animation steps (which results in much smoother
18 * movement)
19 *
20 * History of Amiga Boing:
21 *
22 * Boing was demonstrated on the prototype Amiga (codenamed "Lorraine") in
23 * 1985. According to legend, it was written ad-hoc in one night by
24 * R. J. Mical and Dale Luck. Because the bouncing ball animation was so fast
25 * and smooth, attendees did not believe the Amiga prototype was really doing
26 * the rendering. Suspecting a trick, they began looking around the booth for
27 * a hidden computer or VCR.
28 *****************************************************************************/
29
30#include <stdio.h>
31#include <stdlib.h>
32#include <math.h>
Camilla Berglund22134502012-06-05 23:55:10 +020033
34#define GLFW_INCLUDE_GLU
Camilla Berglundcfbafc52010-09-10 13:16:03 +020035#include <GL/glfw3.h>
Camilla Berglund3249f812010-09-07 17:34:51 +020036
37
38/*****************************************************************************
39 * Various declarations and macros
40 *****************************************************************************/
41
42/* Prototypes */
43void init( void );
44void display( void );
Camilla Berglund0c4b6772010-09-09 19:01:32 +020045void reshape( GLFWwindow window, int w, int h );
Camilla Berglund3249f812010-09-07 17:34:51 +020046void DrawBoingBall( void );
47void BounceBall( double dt );
48void DrawBoingBallBand( GLfloat long_lo, GLfloat long_hi );
49void DrawGrid( void );
50
51#define RADIUS 70.f
52#define STEP_LONGITUDE 22.5f /* 22.5 makes 8 bands like original Boing */
53#define STEP_LATITUDE 22.5f
54
55#define DIST_BALL (RADIUS * 2.f + RADIUS * 0.1f)
56
57#define VIEW_SCENE_DIST (DIST_BALL * 3.f + 200.f)/* distance from viewer to middle of boing area */
58#define GRID_SIZE (RADIUS * 4.5f) /* length (width) of grid */
59#define BOUNCE_HEIGHT (RADIUS * 2.1f)
60#define BOUNCE_WIDTH (RADIUS * 2.1f)
61
62#define SHADOW_OFFSET_X -20.f
63#define SHADOW_OFFSET_Y 10.f
64#define SHADOW_OFFSET_Z 0.f
65
66#define WALL_L_OFFSET 0.f
67#define WALL_R_OFFSET 5.f
68
69/* Animation speed (50.0 mimics the original GLUT demo speed) */
70#define ANIMATION_SPEED 50.f
71
72/* Maximum allowed delta time per physics iteration */
73#define MAX_DELTA_T 0.02f
74
75/* Draw ball, or its shadow */
76typedef enum { DRAW_BALL, DRAW_BALL_SHADOW } DRAW_BALL_ENUM;
77
78/* Vertex type */
79typedef struct {float x; float y; float z;} vertex_t;
80
81/* Global vars */
82GLfloat deg_rot_y = 0.f;
83GLfloat deg_rot_y_inc = 2.f;
84GLfloat ball_x = -RADIUS;
85GLfloat ball_y = -RADIUS;
86GLfloat ball_x_inc = 1.f;
87GLfloat ball_y_inc = 2.f;
88DRAW_BALL_ENUM drawBallHow;
89double t;
90double t_old = 0.f;
91double dt;
Camilla Berglund2972cdf2012-08-03 16:20:52 +020092static GLboolean running = GL_TRUE;
Camilla Berglund3249f812010-09-07 17:34:51 +020093
94/* Random number generator */
95#ifndef RAND_MAX
96 #define RAND_MAX 4095
97#endif
98
99/* PI */
100#ifndef M_PI
101 #define M_PI 3.1415926535897932384626433832795
102#endif
103
104
105/*****************************************************************************
106 * Truncate a degree.
107 *****************************************************************************/
108GLfloat TruncateDeg( GLfloat deg )
109{
110 if ( deg >= 360.f )
111 return (deg - 360.f);
112 else
113 return deg;
114}
115
116/*****************************************************************************
117 * Convert a degree (360-based) into a radian.
118 * 360' = 2 * PI
119 *****************************************************************************/
120double deg2rad( double deg )
121{
122 return deg / 360 * (2 * M_PI);
123}
124
125/*****************************************************************************
126 * 360' sin().
127 *****************************************************************************/
128double sin_deg( double deg )
129{
130 return sin( deg2rad( deg ) );
131}
132
133/*****************************************************************************
134 * 360' cos().
135 *****************************************************************************/
136double cos_deg( double deg )
137{
138 return cos( deg2rad( deg ) );
139}
140
141/*****************************************************************************
142 * Compute a cross product (for a normal vector).
143 *
144 * c = a x b
145 *****************************************************************************/
146void CrossProduct( vertex_t a, vertex_t b, vertex_t c, vertex_t *n )
147{
148 GLfloat u1, u2, u3;
149 GLfloat v1, v2, v3;
150
151 u1 = b.x - a.x;
152 u2 = b.y - a.y;
153 u3 = b.y - a.z;
154
155 v1 = c.x - a.x;
156 v2 = c.y - a.y;
157 v3 = c.z - a.z;
158
159 n->x = u2 * v3 - v2 * v3;
160 n->y = u3 * v1 - v3 * u1;
161 n->z = u1 * v2 - v1 * u2;
162}
163
164/*****************************************************************************
165 * Calculate the angle to be passed to gluPerspective() so that a scene
166 * is visible. This function originates from the OpenGL Red Book.
167 *
168 * Parms : size
169 * The size of the segment when the angle is intersected at "dist"
170 * (ie at the outermost edge of the angle of vision).
171 *
172 * dist
173 * Distance from viewpoint to scene.
174 *****************************************************************************/
175GLfloat PerspectiveAngle( GLfloat size,
176 GLfloat dist )
177{
178 GLfloat radTheta, degTheta;
179
180 radTheta = 2.f * (GLfloat) atan2( size / 2.f, dist );
181 degTheta = (180.f * radTheta) / (GLfloat) M_PI;
182 return degTheta;
183}
184
185
186
187#define BOING_DEBUG 0
188
189
190/*****************************************************************************
191 * init()
192 *****************************************************************************/
193void init( void )
194{
195 /*
196 * Clear background.
197 */
198 glClearColor( 0.55f, 0.55f, 0.55f, 0.f );
199
200 glShadeModel( GL_FLAT );
201}
202
203
204/*****************************************************************************
205 * display()
206 *****************************************************************************/
207void display(void)
208{
209 glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
210 glPushMatrix();
211
212 drawBallHow = DRAW_BALL_SHADOW;
213 DrawBoingBall();
214
215 DrawGrid();
216
217 drawBallHow = DRAW_BALL;
218 DrawBoingBall();
219
220 glPopMatrix();
221 glFlush();
222}
223
224
225/*****************************************************************************
226 * reshape()
227 *****************************************************************************/
Camilla Berglund0c4b6772010-09-09 19:01:32 +0200228void reshape( GLFWwindow window, int w, int h )
Camilla Berglund3249f812010-09-07 17:34:51 +0200229{
230 glViewport( 0, 0, (GLsizei)w, (GLsizei)h );
231
232 glMatrixMode( GL_PROJECTION );
233 glLoadIdentity();
234
235 gluPerspective( PerspectiveAngle( RADIUS * 2, 200 ),
236 (GLfloat)w / (GLfloat)h,
237 1.0,
238 VIEW_SCENE_DIST );
239
240 glMatrixMode( GL_MODELVIEW );
241 glLoadIdentity();
242
243 gluLookAt( 0.0, 0.0, VIEW_SCENE_DIST,/* eye */
244 0.0, 0.0, 0.0, /* center of vision */
245 0.0, -1.0, 0.0 ); /* up vector */
246}
247
248
249/*****************************************************************************
Camilla Berglund2972cdf2012-08-03 16:20:52 +0200250 * Window close callback
251 *****************************************************************************/
Camilla Berglund6b462252012-08-03 16:28:17 +0200252static int window_close_callback(GLFWwindow window)
Camilla Berglund2972cdf2012-08-03 16:20:52 +0200253{
254 running = GL_FALSE;
255 return GL_TRUE;
256}
257
258
259/*****************************************************************************
Camilla Berglund3249f812010-09-07 17:34:51 +0200260 * Draw the Boing ball.
261 *
262 * The Boing ball is sphere in which each facet is a rectangle.
263 * Facet colors alternate between red and white.
264 * The ball is built by stacking latitudinal circles. Each circle is composed
265 * of a widely-separated set of points, so that each facet is noticably large.
266 *****************************************************************************/
267void DrawBoingBall( void )
268{
269 GLfloat lon_deg; /* degree of longitude */
270 double dt_total, dt2;
271
272 glPushMatrix();
273 glMatrixMode( GL_MODELVIEW );
274
275 /*
276 * Another relative Z translation to separate objects.
277 */
278 glTranslatef( 0.0, 0.0, DIST_BALL );
279
280 /* Update ball position and rotation (iterate if necessary) */
281 dt_total = dt;
282 while( dt_total > 0.0 )
283 {
284 dt2 = dt_total > MAX_DELTA_T ? MAX_DELTA_T : dt_total;
285 dt_total -= dt2;
286 BounceBall( dt2 );
287 deg_rot_y = TruncateDeg( deg_rot_y + deg_rot_y_inc*((float)dt2*ANIMATION_SPEED) );
288 }
289
290 /* Set ball position */
291 glTranslatef( ball_x, ball_y, 0.0 );
292
293 /*
294 * Offset the shadow.
295 */
296 if ( drawBallHow == DRAW_BALL_SHADOW )
297 {
298 glTranslatef( SHADOW_OFFSET_X,
299 SHADOW_OFFSET_Y,
300 SHADOW_OFFSET_Z );
301 }
302
303 /*
304 * Tilt the ball.
305 */
306 glRotatef( -20.0, 0.0, 0.0, 1.0 );
307
308 /*
309 * Continually rotate ball around Y axis.
310 */
311 glRotatef( deg_rot_y, 0.0, 1.0, 0.0 );
312
313 /*
314 * Set OpenGL state for Boing ball.
315 */
316 glCullFace( GL_FRONT );
317 glEnable( GL_CULL_FACE );
318 glEnable( GL_NORMALIZE );
319
320 /*
321 * Build a faceted latitude slice of the Boing ball,
322 * stepping same-sized vertical bands of the sphere.
323 */
324 for ( lon_deg = 0;
325 lon_deg < 180;
326 lon_deg += STEP_LONGITUDE )
327 {
328 /*
329 * Draw a latitude circle at this longitude.
330 */
331 DrawBoingBallBand( lon_deg,
332 lon_deg + STEP_LONGITUDE );
333 }
334
335 glPopMatrix();
336
337 return;
338}
339
340
341/*****************************************************************************
342 * Bounce the ball.
343 *****************************************************************************/
344void BounceBall( double dt )
345{
346 GLfloat sign;
347 GLfloat deg;
348
349 /* Bounce on walls */
350 if ( ball_x > (BOUNCE_WIDTH/2 + WALL_R_OFFSET ) )
351 {
352 ball_x_inc = -0.5f - 0.75f * (GLfloat)rand() / (GLfloat)RAND_MAX;
353 deg_rot_y_inc = -deg_rot_y_inc;
354 }
355 if ( ball_x < -(BOUNCE_HEIGHT/2 + WALL_L_OFFSET) )
356 {
357 ball_x_inc = 0.5f + 0.75f * (GLfloat)rand() / (GLfloat)RAND_MAX;
358 deg_rot_y_inc = -deg_rot_y_inc;
359 }
360
361 /* Bounce on floor / roof */
362 if ( ball_y > BOUNCE_HEIGHT/2 )
363 {
364 ball_y_inc = -0.75f - 1.f * (GLfloat)rand() / (GLfloat)RAND_MAX;
365 }
366 if ( ball_y < -BOUNCE_HEIGHT/2*0.85 )
367 {
368 ball_y_inc = 0.75f + 1.f * (GLfloat)rand() / (GLfloat)RAND_MAX;
369 }
370
371 /* Update ball position */
372 ball_x += ball_x_inc * ((float)dt*ANIMATION_SPEED);
373 ball_y += ball_y_inc * ((float)dt*ANIMATION_SPEED);
374
375 /*
376 * Simulate the effects of gravity on Y movement.
377 */
378 if ( ball_y_inc < 0 ) sign = -1.0; else sign = 1.0;
379
380 deg = (ball_y + BOUNCE_HEIGHT/2) * 90 / BOUNCE_HEIGHT;
381 if ( deg > 80 ) deg = 80;
382 if ( deg < 10 ) deg = 10;
383
384 ball_y_inc = sign * 4.f * (float) sin_deg( deg );
385}
386
387
388/*****************************************************************************
389 * Draw a faceted latitude band of the Boing ball.
390 *
391 * Parms: long_lo, long_hi
392 * Low and high longitudes of slice, resp.
393 *****************************************************************************/
394void DrawBoingBallBand( GLfloat long_lo,
395 GLfloat long_hi )
396{
397 vertex_t vert_ne; /* "ne" means south-east, so on */
398 vertex_t vert_nw;
399 vertex_t vert_sw;
400 vertex_t vert_se;
401 vertex_t vert_norm;
402 GLfloat lat_deg;
403 static int colorToggle = 0;
404
405 /*
406 * Iterate thru the points of a latitude circle.
407 * A latitude circle is a 2D set of X,Z points.
408 */
409 for ( lat_deg = 0;
410 lat_deg <= (360 - STEP_LATITUDE);
411 lat_deg += STEP_LATITUDE )
412 {
413 /*
414 * Color this polygon with red or white.
415 */
416 if ( colorToggle )
417 glColor3f( 0.8f, 0.1f, 0.1f );
418 else
419 glColor3f( 0.95f, 0.95f, 0.95f );
420#if 0
421 if ( lat_deg >= 180 )
422 if ( colorToggle )
423 glColor3f( 0.1f, 0.8f, 0.1f );
424 else
425 glColor3f( 0.5f, 0.5f, 0.95f );
426#endif
427 colorToggle = ! colorToggle;
428
429 /*
430 * Change color if drawing shadow.
431 */
432 if ( drawBallHow == DRAW_BALL_SHADOW )
433 glColor3f( 0.35f, 0.35f, 0.35f );
434
435 /*
436 * Assign each Y.
437 */
438 vert_ne.y = vert_nw.y = (float) cos_deg(long_hi) * RADIUS;
439 vert_sw.y = vert_se.y = (float) cos_deg(long_lo) * RADIUS;
440
441 /*
442 * Assign each X,Z with sin,cos values scaled by latitude radius indexed by longitude.
443 * Eg, long=0 and long=180 are at the poles, so zero scale is sin(longitude),
444 * while long=90 (sin(90)=1) is at equator.
445 */
446 vert_ne.x = (float) cos_deg( lat_deg ) * (RADIUS * (float) sin_deg( long_lo + STEP_LONGITUDE ));
447 vert_se.x = (float) cos_deg( lat_deg ) * (RADIUS * (float) sin_deg( long_lo ));
448 vert_nw.x = (float) cos_deg( lat_deg + STEP_LATITUDE ) * (RADIUS * (float) sin_deg( long_lo + STEP_LONGITUDE ));
449 vert_sw.x = (float) cos_deg( lat_deg + STEP_LATITUDE ) * (RADIUS * (float) sin_deg( long_lo ));
450
451 vert_ne.z = (float) sin_deg( lat_deg ) * (RADIUS * (float) sin_deg( long_lo + STEP_LONGITUDE ));
452 vert_se.z = (float) sin_deg( lat_deg ) * (RADIUS * (float) sin_deg( long_lo ));
453 vert_nw.z = (float) sin_deg( lat_deg + STEP_LATITUDE ) * (RADIUS * (float) sin_deg( long_lo + STEP_LONGITUDE ));
454 vert_sw.z = (float) sin_deg( lat_deg + STEP_LATITUDE ) * (RADIUS * (float) sin_deg( long_lo ));
455
456 /*
457 * Draw the facet.
458 */
459 glBegin( GL_POLYGON );
460
461 CrossProduct( vert_ne, vert_nw, vert_sw, &vert_norm );
462 glNormal3f( vert_norm.x, vert_norm.y, vert_norm.z );
463
464 glVertex3f( vert_ne.x, vert_ne.y, vert_ne.z );
465 glVertex3f( vert_nw.x, vert_nw.y, vert_nw.z );
466 glVertex3f( vert_sw.x, vert_sw.y, vert_sw.z );
467 glVertex3f( vert_se.x, vert_se.y, vert_se.z );
468
469 glEnd();
470
471#if BOING_DEBUG
472 printf( "----------------------------------------------------------- \n" );
473 printf( "lat = %f long_lo = %f long_hi = %f \n", lat_deg, long_lo, long_hi );
474 printf( "vert_ne x = %.8f y = %.8f z = %.8f \n", vert_ne.x, vert_ne.y, vert_ne.z );
475 printf( "vert_nw x = %.8f y = %.8f z = %.8f \n", vert_nw.x, vert_nw.y, vert_nw.z );
476 printf( "vert_se x = %.8f y = %.8f z = %.8f \n", vert_se.x, vert_se.y, vert_se.z );
477 printf( "vert_sw x = %.8f y = %.8f z = %.8f \n", vert_sw.x, vert_sw.y, vert_sw.z );
478#endif
479
480 }
481
482 /*
483 * Toggle color so that next band will opposite red/white colors than this one.
484 */
485 colorToggle = ! colorToggle;
486
487 /*
488 * This circular band is done.
489 */
490 return;
491}
492
493
494/*****************************************************************************
495 * Draw the purple grid of lines, behind the Boing ball.
496 * When the Workbench is dropped to the bottom, Boing shows 12 rows.
497 *****************************************************************************/
498void DrawGrid( void )
499{
500 int row, col;
501 const int rowTotal = 12; /* must be divisible by 2 */
502 const int colTotal = rowTotal; /* must be same as rowTotal */
503 const GLfloat widthLine = 2.0; /* should be divisible by 2 */
504 const GLfloat sizeCell = GRID_SIZE / rowTotal;
505 const GLfloat z_offset = -40.0;
506 GLfloat xl, xr;
507 GLfloat yt, yb;
508
509 glPushMatrix();
510 glDisable( GL_CULL_FACE );
511
512 /*
513 * Another relative Z translation to separate objects.
514 */
515 glTranslatef( 0.0, 0.0, DIST_BALL );
516
517 /*
518 * Draw vertical lines (as skinny 3D rectangles).
519 */
520 for ( col = 0; col <= colTotal; col++ )
521 {
522 /*
523 * Compute co-ords of line.
524 */
525 xl = -GRID_SIZE / 2 + col * sizeCell;
526 xr = xl + widthLine;
527
528 yt = GRID_SIZE / 2;
529 yb = -GRID_SIZE / 2 - widthLine;
530
531 glBegin( GL_POLYGON );
532
533 glColor3f( 0.6f, 0.1f, 0.6f ); /* purple */
534
535 glVertex3f( xr, yt, z_offset ); /* NE */
536 glVertex3f( xl, yt, z_offset ); /* NW */
537 glVertex3f( xl, yb, z_offset ); /* SW */
538 glVertex3f( xr, yb, z_offset ); /* SE */
539
540 glEnd();
541 }
542
543 /*
544 * Draw horizontal lines (as skinny 3D rectangles).
545 */
546 for ( row = 0; row <= rowTotal; row++ )
547 {
548 /*
549 * Compute co-ords of line.
550 */
551 yt = GRID_SIZE / 2 - row * sizeCell;
552 yb = yt - widthLine;
553
554 xl = -GRID_SIZE / 2;
555 xr = GRID_SIZE / 2 + widthLine;
556
557 glBegin( GL_POLYGON );
558
559 glColor3f( 0.6f, 0.1f, 0.6f ); /* purple */
560
561 glVertex3f( xr, yt, z_offset ); /* NE */
562 glVertex3f( xl, yt, z_offset ); /* NW */
563 glVertex3f( xl, yb, z_offset ); /* SW */
564 glVertex3f( xr, yb, z_offset ); /* SE */
565
566 glEnd();
567 }
568
569 glPopMatrix();
570
571 return;
572}
573
574
575/*======================================================================*
576 * main()
577 *======================================================================*/
578
579int main( void )
580{
Camilla Berglund39a966d2010-09-14 02:22:52 +0200581 GLFWwindow window;
Camilla Berglund8ed66ea2012-08-07 12:14:58 +0200582 int width, height;
Camilla Berglund3249f812010-09-07 17:34:51 +0200583
584 /* Init GLFW */
Camilla Berglund0c3b1b52012-02-07 14:58:58 +0100585 if( !glfwInit() )
Camilla Berglund3249f812010-09-07 17:34:51 +0200586 {
587 fprintf( stderr, "Failed to initialize GLFW\n" );
588 exit( EXIT_FAILURE );
589 }
590
Camilla Berglund8ed66ea2012-08-07 12:14:58 +0200591 glfwSetWindowCloseCallback( window_close_callback );
592 glfwSetWindowSizeCallback( reshape );
593
Camilla Berglundaff30d02012-08-06 17:56:41 +0200594 glfwWindowHint(GLFW_DEPTH_BITS, 16);
Camilla Berglund950a3be2010-09-09 19:58:51 +0200595
Camilla Berglundaff30d02012-08-06 17:56:41 +0200596 window = glfwCreateWindow( 400, 400, GLFW_WINDOWED, "Boing (classic Amiga demo)", NULL );
Camilla Berglund39fd7a52010-09-09 18:16:07 +0200597 if (!window)
Camilla Berglund3249f812010-09-07 17:34:51 +0200598 {
599 fprintf( stderr, "Failed to open GLFW window\n" );
600 glfwTerminate();
601 exit( EXIT_FAILURE );
602 }
603
Camilla Berglund8ed66ea2012-08-07 12:14:58 +0200604 glfwGetWindowSize(window, &width, &height);
605 reshape(window, width, height);
606
Camilla Berglundce288a82012-02-04 00:51:35 +0100607 glfwSetInputMode( window, GLFW_STICKY_KEYS, GL_TRUE );
Camilla Berglund3249f812010-09-07 17:34:51 +0200608 glfwSwapInterval( 1 );
609 glfwSetTime( 0.0 );
610
611 init();
612
613 /* Main loop */
614 do
615 {
616 /* Timing */
617 t = glfwGetTime();
618 dt = t - t_old;
619 t_old = t;
620
621 /* Draw one frame */
622 display();
623
624 /* Swap buffers */
Camilla Berglund585a8402012-08-06 18:13:37 +0200625 glfwSwapBuffers(window);
Camilla Berglund39fd7a52010-09-09 18:16:07 +0200626 glfwPollEvents();
Camilla Berglund3249f812010-09-07 17:34:51 +0200627
628 /* Check if we are still running */
Camilla Berglund2972cdf2012-08-03 16:20:52 +0200629 if (glfwGetKey( window, GLFW_KEY_ESCAPE ))
630 running = GL_FALSE;
Camilla Berglund3249f812010-09-07 17:34:51 +0200631 }
632 while( running );
633
634 glfwTerminate();
635 exit( EXIT_SUCCESS );
636}
637