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