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