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 */
52 void init( void );
53 void display( void );
54 void reshape( GLFWwindow* window, int w, int h );
55 void key_callback( GLFWwindow* window, int key, int scancode, int action, int mods );
56 void mouse_button_callback( GLFWwindow* window, int button, int action, int mods );
57 void cursor_position_callback( GLFWwindow* window, double x, double y );
58 void DrawBoingBall( void );
59 void BounceBall( double dt );
60 void DrawBoingBallBand( GLfloat long_lo, GLfloat long_hi );
61 void 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 */
88 typedef enum { DRAW_BALL, DRAW_BALL_SHADOW } DRAW_BALL_ENUM;
89
90 /* Vertex type */
91 typedef struct {float x; float y; float z;} vertex_t;
92
93 /* Global vars */
94 int windowed_xpos, windowed_ypos, windowed_width, windowed_height;
95 int width, height;
96 GLfloat deg_rot_y = 0.f;
97 GLfloat deg_rot_y_inc = 2.f;
98 int override_pos = GLFW_FALSE;
99 GLfloat cursor_x = 0.f;
100 GLfloat cursor_y = 0.f;
101 GLfloat ball_x = -RADIUS;
102 GLfloat ball_y = -RADIUS;
103 GLfloat ball_x_inc = 1.f;
104 GLfloat ball_y_inc = 2.f;
105 DRAW_BALL_ENUM drawBallHow;
106 double t;
107 double t_old = 0.f;
108 double 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 *****************************************************************************/
TruncateDeg( GLfloat deg )119 GLfloat 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 *****************************************************************************/
deg2rad( double deg )131 double deg2rad( double deg )
132 {
133 return deg / 360 * (2 * M_PI);
134 }
135
136 /*****************************************************************************
137 * 360' sin().
138 *****************************************************************************/
sin_deg( double deg )139 double sin_deg( double deg )
140 {
141 return sin( deg2rad( deg ) );
142 }
143
144 /*****************************************************************************
145 * 360' cos().
146 *****************************************************************************/
cos_deg( double deg )147 double 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 *****************************************************************************/
CrossProduct( vertex_t a, vertex_t b, vertex_t c, vertex_t *n )157 void 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 *****************************************************************************/
init( void )182 void 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 *****************************************************************************/
display(void)196 void 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 *****************************************************************************/
reshape( GLFWwindow* window, int w, int h )217 void 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
key_callback( GLFWwindow* window, int key, int scancode, int action, int mods )240 void 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
set_ball_pos( GLfloat x, GLfloat y )270 static void set_ball_pos ( GLfloat x, GLfloat y )
271 {
272 ball_x = (width / 2) - x;
273 ball_y = y - (height / 2);
274 }
275
mouse_button_callback( GLFWwindow* window, int button, int action, int mods )276 void 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
cursor_position_callback( GLFWwindow* window, double x, double y )292 void 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 *****************************************************************************/
DrawBoingBall( void )309 void 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 *****************************************************************************/
BounceBall( double delta_t )386 void 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 *****************************************************************************/
DrawBoingBallBand( GLfloat long_lo, GLfloat long_hi )439 void 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 *****************************************************************************/
DrawGrid( void )543 void 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
main( void )624 int 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