1 /*****************************************************************************
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>
33 
34 #define GLFW_INCLUDE_GLU
35 #include <GLFW/glfw3.h>
36 
37 
38 /*****************************************************************************
39  * Various declarations and macros
40  *****************************************************************************/
41 
42 /* Prototypes */
43 void init( void );
44 void display( void );
45 void reshape( GLFWwindow* window, int w, int h );
46 void key_callback( GLFWwindow* window, int key, int scancode, int action, int mods );
47 void DrawBoingBall( void );
48 void BounceBall( double dt );
49 void DrawBoingBallBand( GLfloat long_lo, GLfloat long_hi );
50 void DrawGrid( void );
51 
52 #define RADIUS           70.f
53 #define STEP_LONGITUDE   22.5f                   /* 22.5 makes 8 bands like original Boing */
54 #define STEP_LATITUDE    22.5f
55 
56 #define DIST_BALL       (RADIUS * 2.f + RADIUS * 0.1f)
57 
58 #define VIEW_SCENE_DIST (DIST_BALL * 3.f + 200.f)/* distance from viewer to middle of boing area */
59 #define GRID_SIZE       (RADIUS * 4.5f)          /* length (width) of grid */
60 #define BOUNCE_HEIGHT   (RADIUS * 2.1f)
61 #define BOUNCE_WIDTH    (RADIUS * 2.1f)
62 
63 #define SHADOW_OFFSET_X -20.f
64 #define SHADOW_OFFSET_Y  10.f
65 #define SHADOW_OFFSET_Z   0.f
66 
67 #define WALL_L_OFFSET   0.f
68 #define WALL_R_OFFSET   5.f
69 
70 /* Animation speed (50.0 mimics the original GLUT demo speed) */
71 #define ANIMATION_SPEED 50.f
72 
73 /* Maximum allowed delta time per physics iteration */
74 #define MAX_DELTA_T 0.02f
75 
76 /* Draw ball, or its shadow */
77 typedef enum { DRAW_BALL, DRAW_BALL_SHADOW } DRAW_BALL_ENUM;
78 
79 /* Vertex type */
80 typedef struct {float x; float y; float z;} vertex_t;
81 
82 /* Global vars */
83 GLfloat deg_rot_y       = 0.f;
84 GLfloat deg_rot_y_inc   = 2.f;
85 GLfloat ball_x          = -RADIUS;
86 GLfloat ball_y          = -RADIUS;
87 GLfloat ball_x_inc      = 1.f;
88 GLfloat ball_y_inc      = 2.f;
89 DRAW_BALL_ENUM drawBallHow;
90 double  t;
91 double  t_old = 0.f;
92 double  dt;
93 
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  *****************************************************************************/
TruncateDeg(GLfloat deg)108 GLfloat 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  *****************************************************************************/
deg2rad(double deg)120 double deg2rad( double deg )
121 {
122    return deg / 360 * (2 * M_PI);
123 }
124 
125 /*****************************************************************************
126  * 360' sin().
127  *****************************************************************************/
sin_deg(double deg)128 double sin_deg( double deg )
129 {
130    return sin( deg2rad( deg ) );
131 }
132 
133 /*****************************************************************************
134  * 360' cos().
135  *****************************************************************************/
cos_deg(double deg)136 double 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  *****************************************************************************/
CrossProduct(vertex_t a,vertex_t b,vertex_t c,vertex_t * n)146 void 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  *****************************************************************************/
PerspectiveAngle(GLfloat size,GLfloat dist)175 GLfloat 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  *****************************************************************************/
init(void)193 void 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  *****************************************************************************/
display(void)207 void 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  *****************************************************************************/
reshape(GLFWwindow * window,int w,int h)228 void reshape( GLFWwindow* window, int w, int h )
229 {
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 
key_callback(GLFWwindow * window,int key,int scancode,int action,int mods)248 void key_callback( GLFWwindow* window, int key, int scancode, int action, int mods )
249 {
250     if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
251         glfwSetWindowShouldClose(window, GL_TRUE);
252 }
253 
254 /*****************************************************************************
255  * Draw the Boing ball.
256  *
257  * The Boing ball is sphere in which each facet is a rectangle.
258  * Facet colors alternate between red and white.
259  * The ball is built by stacking latitudinal circles.  Each circle is composed
260  * of a widely-separated set of points, so that each facet is noticably large.
261  *****************************************************************************/
DrawBoingBall(void)262 void DrawBoingBall( void )
263 {
264    GLfloat lon_deg;     /* degree of longitude */
265    double dt_total, dt2;
266 
267    glPushMatrix();
268    glMatrixMode( GL_MODELVIEW );
269 
270   /*
271    * Another relative Z translation to separate objects.
272    */
273    glTranslatef( 0.0, 0.0, DIST_BALL );
274 
275    /* Update ball position and rotation (iterate if necessary) */
276    dt_total = dt;
277    while( dt_total > 0.0 )
278    {
279        dt2 = dt_total > MAX_DELTA_T ? MAX_DELTA_T : dt_total;
280        dt_total -= dt2;
281        BounceBall( dt2 );
282        deg_rot_y = TruncateDeg( deg_rot_y + deg_rot_y_inc*((float)dt2*ANIMATION_SPEED) );
283    }
284 
285    /* Set ball position */
286    glTranslatef( ball_x, ball_y, 0.0 );
287 
288   /*
289    * Offset the shadow.
290    */
291    if ( drawBallHow == DRAW_BALL_SHADOW )
292    {
293       glTranslatef( SHADOW_OFFSET_X,
294                     SHADOW_OFFSET_Y,
295                     SHADOW_OFFSET_Z );
296    }
297 
298   /*
299    * Tilt the ball.
300    */
301    glRotatef( -20.0, 0.0, 0.0, 1.0 );
302 
303   /*
304    * Continually rotate ball around Y axis.
305    */
306    glRotatef( deg_rot_y, 0.0, 1.0, 0.0 );
307 
308   /*
309    * Set OpenGL state for Boing ball.
310    */
311    glCullFace( GL_FRONT );
312    glEnable( GL_CULL_FACE );
313    glEnable( GL_NORMALIZE );
314 
315   /*
316    * Build a faceted latitude slice of the Boing ball,
317    * stepping same-sized vertical bands of the sphere.
318    */
319    for ( lon_deg = 0;
320          lon_deg < 180;
321          lon_deg += STEP_LONGITUDE )
322    {
323      /*
324       * Draw a latitude circle at this longitude.
325       */
326       DrawBoingBallBand( lon_deg,
327                          lon_deg + STEP_LONGITUDE );
328    }
329 
330    glPopMatrix();
331 
332    return;
333 }
334 
335 
336 /*****************************************************************************
337  * Bounce the ball.
338  *****************************************************************************/
BounceBall(double delta_t)339 void BounceBall( double delta_t )
340 {
341    GLfloat sign;
342    GLfloat deg;
343 
344    /* Bounce on walls */
345    if ( ball_x >  (BOUNCE_WIDTH/2 + WALL_R_OFFSET ) )
346    {
347       ball_x_inc = -0.5f - 0.75f * (GLfloat)rand() / (GLfloat)RAND_MAX;
348       deg_rot_y_inc = -deg_rot_y_inc;
349    }
350    if ( ball_x < -(BOUNCE_HEIGHT/2 + WALL_L_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 
356    /* Bounce on floor / roof */
357    if ( ball_y >  BOUNCE_HEIGHT/2      )
358    {
359       ball_y_inc = -0.75f - 1.f * (GLfloat)rand() / (GLfloat)RAND_MAX;
360    }
361    if ( ball_y < -BOUNCE_HEIGHT/2*0.85 )
362    {
363       ball_y_inc =  0.75f + 1.f * (GLfloat)rand() / (GLfloat)RAND_MAX;
364    }
365 
366    /* Update ball position */
367    ball_x += ball_x_inc * ((float)delta_t*ANIMATION_SPEED);
368    ball_y += ball_y_inc * ((float)delta_t*ANIMATION_SPEED);
369 
370   /*
371    * Simulate the effects of gravity on Y movement.
372    */
373    if ( ball_y_inc < 0 ) sign = -1.0; else sign = 1.0;
374 
375    deg = (ball_y + BOUNCE_HEIGHT/2) * 90 / BOUNCE_HEIGHT;
376    if ( deg > 80 ) deg = 80;
377    if ( deg < 10 ) deg = 10;
378 
379    ball_y_inc = sign * 4.f * (float) sin_deg( deg );
380 }
381 
382 
383 /*****************************************************************************
384  * Draw a faceted latitude band of the Boing ball.
385  *
386  * Parms:   long_lo, long_hi
387  *          Low and high longitudes of slice, resp.
388  *****************************************************************************/
DrawBoingBallBand(GLfloat long_lo,GLfloat long_hi)389 void DrawBoingBallBand( GLfloat long_lo,
390                         GLfloat long_hi )
391 {
392    vertex_t vert_ne;            /* "ne" means south-east, so on */
393    vertex_t vert_nw;
394    vertex_t vert_sw;
395    vertex_t vert_se;
396    vertex_t vert_norm;
397    GLfloat  lat_deg;
398    static int colorToggle = 0;
399 
400   /*
401    * Iterate thru the points of a latitude circle.
402    * A latitude circle is a 2D set of X,Z points.
403    */
404    for ( lat_deg = 0;
405          lat_deg <= (360 - STEP_LATITUDE);
406          lat_deg += STEP_LATITUDE )
407    {
408      /*
409       * Color this polygon with red or white.
410       */
411       if ( colorToggle )
412          glColor3f( 0.8f, 0.1f, 0.1f );
413       else
414          glColor3f( 0.95f, 0.95f, 0.95f );
415 #if 0
416       if ( lat_deg >= 180 )
417          if ( colorToggle )
418             glColor3f( 0.1f, 0.8f, 0.1f );
419          else
420             glColor3f( 0.5f, 0.5f, 0.95f );
421 #endif
422       colorToggle = ! colorToggle;
423 
424      /*
425       * Change color if drawing shadow.
426       */
427       if ( drawBallHow == DRAW_BALL_SHADOW )
428          glColor3f( 0.35f, 0.35f, 0.35f );
429 
430      /*
431       * Assign each Y.
432       */
433       vert_ne.y = vert_nw.y = (float) cos_deg(long_hi) * RADIUS;
434       vert_sw.y = vert_se.y = (float) cos_deg(long_lo) * RADIUS;
435 
436      /*
437       * Assign each X,Z with sin,cos values scaled by latitude radius indexed by longitude.
438       * Eg, long=0 and long=180 are at the poles, so zero scale is sin(longitude),
439       * while long=90 (sin(90)=1) is at equator.
440       */
441       vert_ne.x = (float) cos_deg( lat_deg                 ) * (RADIUS * (float) sin_deg( long_lo + STEP_LONGITUDE ));
442       vert_se.x = (float) cos_deg( lat_deg                 ) * (RADIUS * (float) sin_deg( long_lo                  ));
443       vert_nw.x = (float) cos_deg( lat_deg + STEP_LATITUDE ) * (RADIUS * (float) sin_deg( long_lo + STEP_LONGITUDE ));
444       vert_sw.x = (float) cos_deg( lat_deg + STEP_LATITUDE ) * (RADIUS * (float) sin_deg( long_lo                  ));
445 
446       vert_ne.z = (float) sin_deg( lat_deg                 ) * (RADIUS * (float) sin_deg( long_lo + STEP_LONGITUDE ));
447       vert_se.z = (float) sin_deg( lat_deg                 ) * (RADIUS * (float) sin_deg( long_lo                  ));
448       vert_nw.z = (float) sin_deg( lat_deg + STEP_LATITUDE ) * (RADIUS * (float) sin_deg( long_lo + STEP_LONGITUDE ));
449       vert_sw.z = (float) sin_deg( lat_deg + STEP_LATITUDE ) * (RADIUS * (float) sin_deg( long_lo                  ));
450 
451      /*
452       * Draw the facet.
453       */
454       glBegin( GL_POLYGON );
455 
456       CrossProduct( vert_ne, vert_nw, vert_sw, &vert_norm );
457       glNormal3f( vert_norm.x, vert_norm.y, vert_norm.z );
458 
459       glVertex3f( vert_ne.x, vert_ne.y, vert_ne.z );
460       glVertex3f( vert_nw.x, vert_nw.y, vert_nw.z );
461       glVertex3f( vert_sw.x, vert_sw.y, vert_sw.z );
462       glVertex3f( vert_se.x, vert_se.y, vert_se.z );
463 
464       glEnd();
465 
466 #if BOING_DEBUG
467       printf( "----------------------------------------------------------- \n" );
468       printf( "lat = %f  long_lo = %f  long_hi = %f \n", lat_deg, long_lo, long_hi );
469       printf( "vert_ne  x = %.8f  y = %.8f  z = %.8f \n", vert_ne.x, vert_ne.y, vert_ne.z );
470       printf( "vert_nw  x = %.8f  y = %.8f  z = %.8f \n", vert_nw.x, vert_nw.y, vert_nw.z );
471       printf( "vert_se  x = %.8f  y = %.8f  z = %.8f \n", vert_se.x, vert_se.y, vert_se.z );
472       printf( "vert_sw  x = %.8f  y = %.8f  z = %.8f \n", vert_sw.x, vert_sw.y, vert_sw.z );
473 #endif
474 
475    }
476 
477   /*
478    * Toggle color so that next band will opposite red/white colors than this one.
479    */
480    colorToggle = ! colorToggle;
481 
482   /*
483    * This circular band is done.
484    */
485    return;
486 }
487 
488 
489 /*****************************************************************************
490  * Draw the purple grid of lines, behind the Boing ball.
491  * When the Workbench is dropped to the bottom, Boing shows 12 rows.
492  *****************************************************************************/
DrawGrid(void)493 void DrawGrid( void )
494 {
495    int              row, col;
496    const int        rowTotal    = 12;                   /* must be divisible by 2 */
497    const int        colTotal    = rowTotal;             /* must be same as rowTotal */
498    const GLfloat    widthLine   = 2.0;                  /* should be divisible by 2 */
499    const GLfloat    sizeCell    = GRID_SIZE / rowTotal;
500    const GLfloat    z_offset    = -40.0;
501    GLfloat          xl, xr;
502    GLfloat          yt, yb;
503 
504    glPushMatrix();
505    glDisable( GL_CULL_FACE );
506 
507   /*
508    * Another relative Z translation to separate objects.
509    */
510    glTranslatef( 0.0, 0.0, DIST_BALL );
511 
512   /*
513    * Draw vertical lines (as skinny 3D rectangles).
514    */
515    for ( col = 0; col <= colTotal; col++ )
516    {
517      /*
518       * Compute co-ords of line.
519       */
520       xl = -GRID_SIZE / 2 + col * sizeCell;
521       xr = xl + widthLine;
522 
523       yt =  GRID_SIZE / 2;
524       yb = -GRID_SIZE / 2 - widthLine;
525 
526       glBegin( GL_POLYGON );
527 
528       glColor3f( 0.6f, 0.1f, 0.6f );               /* purple */
529 
530       glVertex3f( xr, yt, z_offset );       /* NE */
531       glVertex3f( xl, yt, z_offset );       /* NW */
532       glVertex3f( xl, yb, z_offset );       /* SW */
533       glVertex3f( xr, yb, z_offset );       /* SE */
534 
535       glEnd();
536    }
537 
538   /*
539    * Draw horizontal lines (as skinny 3D rectangles).
540    */
541    for ( row = 0; row <= rowTotal; row++ )
542    {
543      /*
544       * Compute co-ords of line.
545       */
546       yt = GRID_SIZE / 2 - row * sizeCell;
547       yb = yt - widthLine;
548 
549       xl = -GRID_SIZE / 2;
550       xr =  GRID_SIZE / 2 + widthLine;
551 
552       glBegin( GL_POLYGON );
553 
554       glColor3f( 0.6f, 0.1f, 0.6f );               /* purple */
555 
556       glVertex3f( xr, yt, z_offset );       /* NE */
557       glVertex3f( xl, yt, z_offset );       /* NW */
558       glVertex3f( xl, yb, z_offset );       /* SW */
559       glVertex3f( xr, yb, z_offset );       /* SE */
560 
561       glEnd();
562    }
563 
564    glPopMatrix();
565 
566    return;
567 }
568 
569 
570 /*======================================================================*
571  * main()
572  *======================================================================*/
573 
main(void)574 int main( void )
575 {
576    GLFWwindow* window;
577    int width, height;
578 
579    /* Init GLFW */
580    if( !glfwInit() )
581       exit( EXIT_FAILURE );
582 
583    glfwWindowHint(GLFW_DEPTH_BITS, 16);
584 
585    window = glfwCreateWindow( 400, 400, "Boing (classic Amiga demo)", NULL, NULL );
586    if (!window)
587    {
588        glfwTerminate();
589        exit( EXIT_FAILURE );
590    }
591 
592    glfwSetFramebufferSizeCallback(window, reshape);
593    glfwSetKeyCallback(window, key_callback);
594 
595    glfwMakeContextCurrent(window);
596    glfwSwapInterval( 1 );
597 
598    glfwGetFramebufferSize(window, &width, &height);
599    reshape(window, width, height);
600 
601    glfwSetTime( 0.0 );
602 
603    init();
604 
605    /* Main loop */
606    for (;;)
607    {
608        /* Timing */
609        t = glfwGetTime();
610        dt = t - t_old;
611        t_old = t;
612 
613        /* Draw one frame */
614        display();
615 
616        /* Swap buffers */
617        glfwSwapBuffers(window);
618        glfwPollEvents();
619 
620        /* Check if we are still running */
621        if (glfwWindowShouldClose(window))
622            break;
623    }
624 
625    glfwTerminate();
626    exit( EXIT_SUCCESS );
627 }
628 
629