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