xref: /third_party/glfw/examples/boing.c (revision b877906b)
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#if defined(_MSC_VER)
31 // Make MS math.h define M_PI
32 #define _USE_MATH_DEFINES
33#endif
34
35#include <stdio.h>
36#include <stdlib.h>
37#include <math.h>
38
39#define GLAD_GL_IMPLEMENTATION
40#include <glad/gl.h>
41#define GLFW_INCLUDE_NONE
42#include <GLFW/glfw3.h>
43
44#include <linmath.h>
45
46
47/*****************************************************************************
48 * Various declarations and macros
49 *****************************************************************************/
50
51/* Prototypes */
52void init( void );
53void display( void );
54void reshape( GLFWwindow* window, int w, int h );
55void key_callback( GLFWwindow* window, int key, int scancode, int action, int mods );
56void mouse_button_callback( GLFWwindow* window, int button, int action, int mods );
57void cursor_position_callback( GLFWwindow* window, double x, double y );
58void DrawBoingBall( void );
59void BounceBall( double dt );
60void DrawBoingBallBand( GLfloat long_lo, GLfloat long_hi );
61void DrawGrid( void );
62
63#define RADIUS           70.f
64#define STEP_LONGITUDE   22.5f                   /* 22.5 makes 8 bands like original Boing */
65#define STEP_LATITUDE    22.5f
66
67#define DIST_BALL       (RADIUS * 2.f + RADIUS * 0.1f)
68
69#define VIEW_SCENE_DIST (DIST_BALL * 3.f + 200.f)/* distance from viewer to middle of boing area */
70#define GRID_SIZE       (RADIUS * 4.5f)          /* length (width) of grid */
71#define BOUNCE_HEIGHT   (RADIUS * 2.1f)
72#define BOUNCE_WIDTH    (RADIUS * 2.1f)
73
74#define SHADOW_OFFSET_X -20.f
75#define SHADOW_OFFSET_Y  10.f
76#define SHADOW_OFFSET_Z   0.f
77
78#define WALL_L_OFFSET   0.f
79#define WALL_R_OFFSET   5.f
80
81/* Animation speed (50.0 mimics the original GLUT demo speed) */
82#define ANIMATION_SPEED 50.f
83
84/* Maximum allowed delta time per physics iteration */
85#define MAX_DELTA_T 0.02f
86
87/* Draw ball, or its shadow */
88typedef enum { DRAW_BALL, DRAW_BALL_SHADOW } DRAW_BALL_ENUM;
89
90/* Vertex type */
91typedef struct {float x; float y; float z;} vertex_t;
92
93/* Global vars */
94int windowed_xpos, windowed_ypos, windowed_width, windowed_height;
95int width, height;
96GLfloat deg_rot_y       = 0.f;
97GLfloat deg_rot_y_inc   = 2.f;
98int override_pos        = GLFW_FALSE;
99GLfloat cursor_x        = 0.f;
100GLfloat cursor_y        = 0.f;
101GLfloat ball_x          = -RADIUS;
102GLfloat ball_y          = -RADIUS;
103GLfloat ball_x_inc      = 1.f;
104GLfloat ball_y_inc      = 2.f;
105DRAW_BALL_ENUM drawBallHow;
106double  t;
107double  t_old = 0.f;
108double  dt;
109
110/* Random number generator */
111#ifndef RAND_MAX
112 #define RAND_MAX 4095
113#endif
114
115
116/*****************************************************************************
117 * Truncate a degree.
118 *****************************************************************************/
119GLfloat TruncateDeg( GLfloat deg )
120{
121   if ( deg >= 360.f )
122      return (deg - 360.f);
123   else
124      return deg;
125}
126
127/*****************************************************************************
128 * Convert a degree (360-based) into a radian.
129 * 360' = 2 * PI
130 *****************************************************************************/
131double deg2rad( double deg )
132{
133   return deg / 360 * (2 * M_PI);
134}
135
136/*****************************************************************************
137 * 360' sin().
138 *****************************************************************************/
139double sin_deg( double deg )
140{
141   return sin( deg2rad( deg ) );
142}
143
144/*****************************************************************************
145 * 360' cos().
146 *****************************************************************************/
147double cos_deg( double deg )
148{
149   return cos( deg2rad( deg ) );
150}
151
152/*****************************************************************************
153 * Compute a cross product (for a normal vector).
154 *
155 * c = a x b
156 *****************************************************************************/
157void CrossProduct( vertex_t a, vertex_t b, vertex_t c, vertex_t *n )
158{
159   GLfloat u1, u2, u3;
160   GLfloat v1, v2, v3;
161
162   u1 = b.x - a.x;
163   u2 = b.y - a.y;
164   u3 = b.y - a.z;
165
166   v1 = c.x - a.x;
167   v2 = c.y - a.y;
168   v3 = c.z - a.z;
169
170   n->x = u2 * v3 - v2 * u3;
171   n->y = u3 * v1 - v3 * u1;
172   n->z = u1 * v2 - v1 * u2;
173}
174
175
176#define BOING_DEBUG 0
177
178
179/*****************************************************************************
180 * init()
181 *****************************************************************************/
182void init( void )
183{
184   /*
185    * Clear background.
186    */
187   glClearColor( 0.55f, 0.55f, 0.55f, 0.f );
188
189   glShadeModel( GL_FLAT );
190}
191
192
193/*****************************************************************************
194 * display()
195 *****************************************************************************/
196void display(void)
197{
198   glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
199   glPushMatrix();
200
201   drawBallHow = DRAW_BALL_SHADOW;
202   DrawBoingBall();
203
204   DrawGrid();
205
206   drawBallHow = DRAW_BALL;
207   DrawBoingBall();
208
209   glPopMatrix();
210   glFlush();
211}
212
213
214/*****************************************************************************
215 * reshape()
216 *****************************************************************************/
217void reshape( GLFWwindow* window, int w, int h )
218{
219   mat4x4 projection, view;
220
221   glViewport( 0, 0, (GLsizei)w, (GLsizei)h );
222
223   glMatrixMode( GL_PROJECTION );
224   mat4x4_perspective( projection,
225                       2.f * (float) atan2( RADIUS, 200.f ),
226                       (float)w / (float)h,
227                       1.f, VIEW_SCENE_DIST );
228   glLoadMatrixf((const GLfloat*) projection);
229
230   glMatrixMode( GL_MODELVIEW );
231   {
232      vec3 eye = { 0.f, 0.f, VIEW_SCENE_DIST };
233      vec3 center = { 0.f, 0.f, 0.f };
234      vec3 up = { 0.f, -1.f, 0.f };
235      mat4x4_look_at( view, eye, center, up );
236   }
237   glLoadMatrixf((const GLfloat*) view);
238}
239
240void key_callback( GLFWwindow* window, int key, int scancode, int action, int mods )
241{
242    if (action != GLFW_PRESS)
243        return;
244
245    if (key == GLFW_KEY_ESCAPE && mods == 0)
246        glfwSetWindowShouldClose(window, GLFW_TRUE);
247    if ((key == GLFW_KEY_ENTER && mods == GLFW_MOD_ALT) ||
248        (key == GLFW_KEY_F11 && mods == GLFW_MOD_ALT))
249    {
250        if (glfwGetWindowMonitor(window))
251        {
252            glfwSetWindowMonitor(window, NULL,
253                                 windowed_xpos, windowed_ypos,
254                                 windowed_width, windowed_height, 0);
255        }
256        else
257        {
258            GLFWmonitor* monitor = glfwGetPrimaryMonitor();
259            if (monitor)
260            {
261                const GLFWvidmode* mode = glfwGetVideoMode(monitor);
262                glfwGetWindowPos(window, &windowed_xpos, &windowed_ypos);
263                glfwGetWindowSize(window, &windowed_width, &windowed_height);
264                glfwSetWindowMonitor(window, monitor, 0, 0, mode->width, mode->height, mode->refreshRate);
265            }
266        }
267    }
268}
269
270static void set_ball_pos ( GLfloat x, GLfloat y )
271{
272   ball_x = (width / 2) - x;
273   ball_y = y - (height / 2);
274}
275
276void mouse_button_callback( GLFWwindow* window, int button, int action, int mods )
277{
278   if (button != GLFW_MOUSE_BUTTON_LEFT)
279      return;
280
281   if (action == GLFW_PRESS)
282   {
283      override_pos = GLFW_TRUE;
284      set_ball_pos(cursor_x, cursor_y);
285   }
286   else
287   {
288      override_pos = GLFW_FALSE;
289   }
290}
291
292void cursor_position_callback( GLFWwindow* window, double x, double y )
293{
294   cursor_x = (float) x;
295   cursor_y = (float) y;
296
297   if ( override_pos )
298      set_ball_pos(cursor_x, cursor_y);
299}
300
301/*****************************************************************************
302 * Draw the Boing ball.
303 *
304 * The Boing ball is sphere in which each facet is a rectangle.
305 * Facet colors alternate between red and white.
306 * The ball is built by stacking latitudinal circles.  Each circle is composed
307 * of a widely-separated set of points, so that each facet is noticeably large.
308 *****************************************************************************/
309void DrawBoingBall( void )
310{
311   GLfloat lon_deg;     /* degree of longitude */
312   double dt_total, dt2;
313
314   glPushMatrix();
315   glMatrixMode( GL_MODELVIEW );
316
317  /*
318   * Another relative Z translation to separate objects.
319   */
320   glTranslatef( 0.0, 0.0, DIST_BALL );
321
322   /* Update ball position and rotation (iterate if necessary) */
323   dt_total = dt;
324   while( dt_total > 0.0 )
325   {
326       dt2 = dt_total > MAX_DELTA_T ? MAX_DELTA_T : dt_total;
327       dt_total -= dt2;
328       BounceBall( dt2 );
329       deg_rot_y = TruncateDeg( deg_rot_y + deg_rot_y_inc*((float)dt2*ANIMATION_SPEED) );
330   }
331
332   /* Set ball position */
333   glTranslatef( ball_x, ball_y, 0.0 );
334
335  /*
336   * Offset the shadow.
337   */
338   if ( drawBallHow == DRAW_BALL_SHADOW )
339   {
340      glTranslatef( SHADOW_OFFSET_X,
341                    SHADOW_OFFSET_Y,
342                    SHADOW_OFFSET_Z );
343   }
344
345  /*
346   * Tilt the ball.
347   */
348   glRotatef( -20.0, 0.0, 0.0, 1.0 );
349
350  /*
351   * Continually rotate ball around Y axis.
352   */
353   glRotatef( deg_rot_y, 0.0, 1.0, 0.0 );
354
355  /*
356   * Set OpenGL state for Boing ball.
357   */
358   glCullFace( GL_FRONT );
359   glEnable( GL_CULL_FACE );
360   glEnable( GL_NORMALIZE );
361
362  /*
363   * Build a faceted latitude slice of the Boing ball,
364   * stepping same-sized vertical bands of the sphere.
365   */
366   for ( lon_deg = 0;
367         lon_deg < 180;
368         lon_deg += STEP_LONGITUDE )
369   {
370     /*
371      * Draw a latitude circle at this longitude.
372      */
373      DrawBoingBallBand( lon_deg,
374                         lon_deg + STEP_LONGITUDE );
375   }
376
377   glPopMatrix();
378
379   return;
380}
381
382
383/*****************************************************************************
384 * Bounce the ball.
385 *****************************************************************************/
386void BounceBall( double delta_t )
387{
388   GLfloat sign;
389   GLfloat deg;
390
391   if ( override_pos )
392     return;
393
394   /* Bounce on walls */
395   if ( ball_x >  (BOUNCE_WIDTH/2 + WALL_R_OFFSET ) )
396   {
397      ball_x_inc = -0.5f - 0.75f * (GLfloat)rand() / (GLfloat)RAND_MAX;
398      deg_rot_y_inc = -deg_rot_y_inc;
399   }
400   if ( ball_x < -(BOUNCE_HEIGHT/2 + WALL_L_OFFSET) )
401   {
402      ball_x_inc =  0.5f + 0.75f * (GLfloat)rand() / (GLfloat)RAND_MAX;
403      deg_rot_y_inc = -deg_rot_y_inc;
404   }
405
406   /* Bounce on floor / roof */
407   if ( ball_y >  BOUNCE_HEIGHT/2      )
408   {
409      ball_y_inc = -0.75f - 1.f * (GLfloat)rand() / (GLfloat)RAND_MAX;
410   }
411   if ( ball_y < -BOUNCE_HEIGHT/2*0.85 )
412   {
413      ball_y_inc =  0.75f + 1.f * (GLfloat)rand() / (GLfloat)RAND_MAX;
414   }
415
416   /* Update ball position */
417   ball_x += ball_x_inc * ((float)delta_t*ANIMATION_SPEED);
418   ball_y += ball_y_inc * ((float)delta_t*ANIMATION_SPEED);
419
420  /*
421   * Simulate the effects of gravity on Y movement.
422   */
423   if ( ball_y_inc < 0 ) sign = -1.0; else sign = 1.0;
424
425   deg = (ball_y + BOUNCE_HEIGHT/2) * 90 / BOUNCE_HEIGHT;
426   if ( deg > 80 ) deg = 80;
427   if ( deg < 10 ) deg = 10;
428
429   ball_y_inc = sign * 4.f * (float) sin_deg( deg );
430}
431
432
433/*****************************************************************************
434 * Draw a faceted latitude band of the Boing ball.
435 *
436 * Parms:   long_lo, long_hi
437 *          Low and high longitudes of slice, resp.
438 *****************************************************************************/
439void DrawBoingBallBand( GLfloat long_lo,
440                        GLfloat long_hi )
441{
442   vertex_t vert_ne;            /* "ne" means south-east, so on */
443   vertex_t vert_nw;
444   vertex_t vert_sw;
445   vertex_t vert_se;
446   vertex_t vert_norm;
447   GLfloat  lat_deg;
448   static int colorToggle = 0;
449
450  /*
451   * Iterate through the points of a latitude circle.
452   * A latitude circle is a 2D set of X,Z points.
453   */
454   for ( lat_deg = 0;
455         lat_deg <= (360 - STEP_LATITUDE);
456         lat_deg += STEP_LATITUDE )
457   {
458     /*
459      * Color this polygon with red or white.
460      */
461      if ( colorToggle )
462         glColor3f( 0.8f, 0.1f, 0.1f );
463      else
464         glColor3f( 0.95f, 0.95f, 0.95f );
465#if 0
466      if ( lat_deg >= 180 )
467         if ( colorToggle )
468            glColor3f( 0.1f, 0.8f, 0.1f );
469         else
470            glColor3f( 0.5f, 0.5f, 0.95f );
471#endif
472      colorToggle = ! colorToggle;
473
474     /*
475      * Change color if drawing shadow.
476      */
477      if ( drawBallHow == DRAW_BALL_SHADOW )
478         glColor3f( 0.35f, 0.35f, 0.35f );
479
480     /*
481      * Assign each Y.
482      */
483      vert_ne.y = vert_nw.y = (float) cos_deg(long_hi) * RADIUS;
484      vert_sw.y = vert_se.y = (float) cos_deg(long_lo) * RADIUS;
485
486     /*
487      * Assign each X,Z with sin,cos values scaled by latitude radius indexed by longitude.
488      * Eg, long=0 and long=180 are at the poles, so zero scale is sin(longitude),
489      * while long=90 (sin(90)=1) is at equator.
490      */
491      vert_ne.x = (float) cos_deg( lat_deg                 ) * (RADIUS * (float) sin_deg( long_lo + STEP_LONGITUDE ));
492      vert_se.x = (float) cos_deg( lat_deg                 ) * (RADIUS * (float) sin_deg( long_lo                  ));
493      vert_nw.x = (float) cos_deg( lat_deg + STEP_LATITUDE ) * (RADIUS * (float) sin_deg( long_lo + STEP_LONGITUDE ));
494      vert_sw.x = (float) cos_deg( lat_deg + STEP_LATITUDE ) * (RADIUS * (float) sin_deg( long_lo                  ));
495
496      vert_ne.z = (float) sin_deg( lat_deg                 ) * (RADIUS * (float) sin_deg( long_lo + STEP_LONGITUDE ));
497      vert_se.z = (float) sin_deg( lat_deg                 ) * (RADIUS * (float) sin_deg( long_lo                  ));
498      vert_nw.z = (float) sin_deg( lat_deg + STEP_LATITUDE ) * (RADIUS * (float) sin_deg( long_lo + STEP_LONGITUDE ));
499      vert_sw.z = (float) sin_deg( lat_deg + STEP_LATITUDE ) * (RADIUS * (float) sin_deg( long_lo                  ));
500
501     /*
502      * Draw the facet.
503      */
504      glBegin( GL_POLYGON );
505
506      CrossProduct( vert_ne, vert_nw, vert_sw, &vert_norm );
507      glNormal3f( vert_norm.x, vert_norm.y, vert_norm.z );
508
509      glVertex3f( vert_ne.x, vert_ne.y, vert_ne.z );
510      glVertex3f( vert_nw.x, vert_nw.y, vert_nw.z );
511      glVertex3f( vert_sw.x, vert_sw.y, vert_sw.z );
512      glVertex3f( vert_se.x, vert_se.y, vert_se.z );
513
514      glEnd();
515
516#if BOING_DEBUG
517      printf( "----------------------------------------------------------- \n" );
518      printf( "lat = %f  long_lo = %f  long_hi = %f \n", lat_deg, long_lo, long_hi );
519      printf( "vert_ne  x = %.8f  y = %.8f  z = %.8f \n", vert_ne.x, vert_ne.y, vert_ne.z );
520      printf( "vert_nw  x = %.8f  y = %.8f  z = %.8f \n", vert_nw.x, vert_nw.y, vert_nw.z );
521      printf( "vert_se  x = %.8f  y = %.8f  z = %.8f \n", vert_se.x, vert_se.y, vert_se.z );
522      printf( "vert_sw  x = %.8f  y = %.8f  z = %.8f \n", vert_sw.x, vert_sw.y, vert_sw.z );
523#endif
524
525   }
526
527  /*
528   * Toggle color so that next band will opposite red/white colors than this one.
529   */
530   colorToggle = ! colorToggle;
531
532  /*
533   * This circular band is done.
534   */
535   return;
536}
537
538
539/*****************************************************************************
540 * Draw the purple grid of lines, behind the Boing ball.
541 * When the Workbench is dropped to the bottom, Boing shows 12 rows.
542 *****************************************************************************/
543void DrawGrid( void )
544{
545   int              row, col;
546   const int        rowTotal    = 12;                   /* must be divisible by 2 */
547   const int        colTotal    = rowTotal;             /* must be same as rowTotal */
548   const GLfloat    widthLine   = 2.0;                  /* should be divisible by 2 */
549   const GLfloat    sizeCell    = GRID_SIZE / rowTotal;
550   const GLfloat    z_offset    = -40.0;
551   GLfloat          xl, xr;
552   GLfloat          yt, yb;
553
554   glPushMatrix();
555   glDisable( GL_CULL_FACE );
556
557  /*
558   * Another relative Z translation to separate objects.
559   */
560   glTranslatef( 0.0, 0.0, DIST_BALL );
561
562  /*
563   * Draw vertical lines (as skinny 3D rectangles).
564   */
565   for ( col = 0; col <= colTotal; col++ )
566   {
567     /*
568      * Compute co-ords of line.
569      */
570      xl = -GRID_SIZE / 2 + col * sizeCell;
571      xr = xl + widthLine;
572
573      yt =  GRID_SIZE / 2;
574      yb = -GRID_SIZE / 2 - widthLine;
575
576      glBegin( GL_POLYGON );
577
578      glColor3f( 0.6f, 0.1f, 0.6f );               /* purple */
579
580      glVertex3f( xr, yt, z_offset );       /* NE */
581      glVertex3f( xl, yt, z_offset );       /* NW */
582      glVertex3f( xl, yb, z_offset );       /* SW */
583      glVertex3f( xr, yb, z_offset );       /* SE */
584
585      glEnd();
586   }
587
588  /*
589   * Draw horizontal lines (as skinny 3D rectangles).
590   */
591   for ( row = 0; row <= rowTotal; row++ )
592   {
593     /*
594      * Compute co-ords of line.
595      */
596      yt = GRID_SIZE / 2 - row * sizeCell;
597      yb = yt - widthLine;
598
599      xl = -GRID_SIZE / 2;
600      xr =  GRID_SIZE / 2 + widthLine;
601
602      glBegin( GL_POLYGON );
603
604      glColor3f( 0.6f, 0.1f, 0.6f );               /* purple */
605
606      glVertex3f( xr, yt, z_offset );       /* NE */
607      glVertex3f( xl, yt, z_offset );       /* NW */
608      glVertex3f( xl, yb, z_offset );       /* SW */
609      glVertex3f( xr, yb, z_offset );       /* SE */
610
611      glEnd();
612   }
613
614   glPopMatrix();
615
616   return;
617}
618
619
620/*======================================================================*
621 * main()
622 *======================================================================*/
623
624int main( void )
625{
626   GLFWwindow* window;
627
628   /* Init GLFW */
629   if( !glfwInit() )
630      exit( EXIT_FAILURE );
631
632   window = glfwCreateWindow( 400, 400, "Boing (classic Amiga demo)", NULL, NULL );
633   if (!window)
634   {
635       glfwTerminate();
636       exit( EXIT_FAILURE );
637   }
638
639   glfwSetWindowAspectRatio(window, 1, 1);
640
641   glfwSetFramebufferSizeCallback(window, reshape);
642   glfwSetKeyCallback(window, key_callback);
643   glfwSetMouseButtonCallback(window, mouse_button_callback);
644   glfwSetCursorPosCallback(window, cursor_position_callback);
645
646   glfwMakeContextCurrent(window);
647   gladLoadGL(glfwGetProcAddress);
648   glfwSwapInterval( 1 );
649
650   glfwGetFramebufferSize(window, &width, &height);
651   reshape(window, width, height);
652
653   glfwSetTime( 0.0 );
654
655   init();
656
657   /* Main loop */
658   for (;;)
659   {
660       /* Timing */
661       t = glfwGetTime();
662       dt = t - t_old;
663       t_old = t;
664
665       /* Draw one frame */
666       display();
667
668       /* Swap buffers */
669       glfwSwapBuffers(window);
670       glfwPollEvents();
671
672       /* Check if we are still running */
673       if (glfwWindowShouldClose(window))
674           break;
675   }
676
677   glfwTerminate();
678   exit( EXIT_SUCCESS );
679}
680
681