| /***************************************************************************** |
| * Title: GLBoing |
| * Desc: Tribute to Amiga Boing. |
| * Author: Jim Brooks <gfx@jimbrooks.org> |
| * Original Amiga authors were R.J. Mical and Dale Luck. |
| * GLFW conversion by Marcus Geelnard |
| * Notes: - 360' = 2*PI [radian] |
| * |
| * - Distances between objects are created by doing a relative |
| * Z translations. |
| * |
| * - Although OpenGL enticingly supports alpha-blending, |
| * the shadow of the original Boing didn't affect the color |
| * of the grid. |
| * |
| * - [Marcus] Changed timing scheme from interval driven to frame- |
| * time based animation steps (which results in much smoother |
| * movement) |
| * |
| * History of Amiga Boing: |
| * |
| * Boing was demonstrated on the prototype Amiga (codenamed "Lorraine") in |
| * 1985. According to legend, it was written ad-hoc in one night by |
| * R. J. Mical and Dale Luck. Because the bouncing ball animation was so fast |
| * and smooth, attendees did not believe the Amiga prototype was really doing |
| * the rendering. Suspecting a trick, they began looking around the booth for |
| * a hidden computer or VCR. |
| *****************************************************************************/ |
| |
| #if defined(_MSC_VER) |
| // Make MS math.h define M_PI |
| #define _USE_MATH_DEFINES |
| #endif |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <math.h> |
| |
| #include <glad/glad.h> |
| #include <GLFW/glfw3.h> |
| |
| #include <linmath.h> |
| |
| |
| /***************************************************************************** |
| * Various declarations and macros |
| *****************************************************************************/ |
| |
| /* Prototypes */ |
| void init( void ); |
| void display( void ); |
| void reshape( GLFWwindow* window, int w, int h ); |
| void key_callback( GLFWwindow* window, int key, int scancode, int action, int mods ); |
| void mouse_button_callback( GLFWwindow* window, int button, int action, int mods ); |
| void cursor_position_callback( GLFWwindow* window, double x, double y ); |
| void DrawBoingBall( void ); |
| void BounceBall( double dt ); |
| void DrawBoingBallBand( GLfloat long_lo, GLfloat long_hi ); |
| void DrawGrid( void ); |
| |
| #define RADIUS 70.f |
| #define STEP_LONGITUDE 22.5f /* 22.5 makes 8 bands like original Boing */ |
| #define STEP_LATITUDE 22.5f |
| |
| #define DIST_BALL (RADIUS * 2.f + RADIUS * 0.1f) |
| |
| #define VIEW_SCENE_DIST (DIST_BALL * 3.f + 200.f)/* distance from viewer to middle of boing area */ |
| #define GRID_SIZE (RADIUS * 4.5f) /* length (width) of grid */ |
| #define BOUNCE_HEIGHT (RADIUS * 2.1f) |
| #define BOUNCE_WIDTH (RADIUS * 2.1f) |
| |
| #define SHADOW_OFFSET_X -20.f |
| #define SHADOW_OFFSET_Y 10.f |
| #define SHADOW_OFFSET_Z 0.f |
| |
| #define WALL_L_OFFSET 0.f |
| #define WALL_R_OFFSET 5.f |
| |
| /* Animation speed (50.0 mimics the original GLUT demo speed) */ |
| #define ANIMATION_SPEED 50.f |
| |
| /* Maximum allowed delta time per physics iteration */ |
| #define MAX_DELTA_T 0.02f |
| |
| /* Draw ball, or its shadow */ |
| typedef enum { DRAW_BALL, DRAW_BALL_SHADOW } DRAW_BALL_ENUM; |
| |
| /* Vertex type */ |
| typedef struct {float x; float y; float z;} vertex_t; |
| |
| /* Global vars */ |
| int windowed_xpos, windowed_ypos, windowed_width, windowed_height; |
| int width, height; |
| GLfloat deg_rot_y = 0.f; |
| GLfloat deg_rot_y_inc = 2.f; |
| int override_pos = GLFW_FALSE; |
| GLfloat cursor_x = 0.f; |
| GLfloat cursor_y = 0.f; |
| GLfloat ball_x = -RADIUS; |
| GLfloat ball_y = -RADIUS; |
| GLfloat ball_x_inc = 1.f; |
| GLfloat ball_y_inc = 2.f; |
| DRAW_BALL_ENUM drawBallHow; |
| double t; |
| double t_old = 0.f; |
| double dt; |
| |
| /* Random number generator */ |
| #ifndef RAND_MAX |
| #define RAND_MAX 4095 |
| #endif |
| |
| |
| /***************************************************************************** |
| * Truncate a degree. |
| *****************************************************************************/ |
| GLfloat TruncateDeg( GLfloat deg ) |
| { |
| if ( deg >= 360.f ) |
| return (deg - 360.f); |
| else |
| return deg; |
| } |
| |
| /***************************************************************************** |
| * Convert a degree (360-based) into a radian. |
| * 360' = 2 * PI |
| *****************************************************************************/ |
| double deg2rad( double deg ) |
| { |
| return deg / 360 * (2 * M_PI); |
| } |
| |
| /***************************************************************************** |
| * 360' sin(). |
| *****************************************************************************/ |
| double sin_deg( double deg ) |
| { |
| return sin( deg2rad( deg ) ); |
| } |
| |
| /***************************************************************************** |
| * 360' cos(). |
| *****************************************************************************/ |
| double cos_deg( double deg ) |
| { |
| return cos( deg2rad( deg ) ); |
| } |
| |
| /***************************************************************************** |
| * Compute a cross product (for a normal vector). |
| * |
| * c = a x b |
| *****************************************************************************/ |
| void CrossProduct( vertex_t a, vertex_t b, vertex_t c, vertex_t *n ) |
| { |
| GLfloat u1, u2, u3; |
| GLfloat v1, v2, v3; |
| |
| u1 = b.x - a.x; |
| u2 = b.y - a.y; |
| u3 = b.y - a.z; |
| |
| v1 = c.x - a.x; |
| v2 = c.y - a.y; |
| v3 = c.z - a.z; |
| |
| n->x = u2 * v3 - v2 * u3; |
| n->y = u3 * v1 - v3 * u1; |
| n->z = u1 * v2 - v1 * u2; |
| } |
| |
| |
| #define BOING_DEBUG 0 |
| |
| |
| /***************************************************************************** |
| * init() |
| *****************************************************************************/ |
| void init( void ) |
| { |
| /* |
| * Clear background. |
| */ |
| glClearColor( 0.55f, 0.55f, 0.55f, 0.f ); |
| |
| glShadeModel( GL_FLAT ); |
| } |
| |
| |
| /***************************************************************************** |
| * display() |
| *****************************************************************************/ |
| void display(void) |
| { |
| glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); |
| glPushMatrix(); |
| |
| drawBallHow = DRAW_BALL_SHADOW; |
| DrawBoingBall(); |
| |
| DrawGrid(); |
| |
| drawBallHow = DRAW_BALL; |
| DrawBoingBall(); |
| |
| glPopMatrix(); |
| glFlush(); |
| } |
| |
| |
| /***************************************************************************** |
| * reshape() |
| *****************************************************************************/ |
| void reshape( GLFWwindow* window, int w, int h ) |
| { |
| mat4x4 projection, view; |
| |
| glViewport( 0, 0, (GLsizei)w, (GLsizei)h ); |
| |
| glMatrixMode( GL_PROJECTION ); |
| mat4x4_perspective( projection, |
| 2.f * (float) atan2( RADIUS, 200.f ), |
| (float)w / (float)h, |
| 1.f, VIEW_SCENE_DIST ); |
| glLoadMatrixf((const GLfloat*) projection); |
| |
| glMatrixMode( GL_MODELVIEW ); |
| { |
| vec3 eye = { 0.f, 0.f, VIEW_SCENE_DIST }; |
| vec3 center = { 0.f, 0.f, 0.f }; |
| vec3 up = { 0.f, -1.f, 0.f }; |
| mat4x4_look_at( view, eye, center, up ); |
| } |
| glLoadMatrixf((const GLfloat*) view); |
| } |
| |
| void key_callback( GLFWwindow* window, int key, int scancode, int action, int mods ) |
| { |
| if (action != GLFW_PRESS) |
| return; |
| |
| if (key == GLFW_KEY_ESCAPE && mods == 0) |
| glfwSetWindowShouldClose(window, GLFW_TRUE); |
| if ((key == GLFW_KEY_ENTER && mods == GLFW_MOD_ALT) || |
| (key == GLFW_KEY_F11 && mods == GLFW_MOD_ALT)) |
| { |
| if (glfwGetWindowMonitor(window)) |
| { |
| glfwSetWindowMonitor(window, NULL, |
| windowed_xpos, windowed_ypos, |
| windowed_width, windowed_height, 0); |
| } |
| else |
| { |
| GLFWmonitor* monitor = glfwGetPrimaryMonitor(); |
| if (monitor) |
| { |
| const GLFWvidmode* mode = glfwGetVideoMode(monitor); |
| glfwGetWindowPos(window, &windowed_xpos, &windowed_ypos); |
| glfwGetWindowSize(window, &windowed_width, &windowed_height); |
| glfwSetWindowMonitor(window, monitor, 0, 0, mode->width, mode->height, mode->refreshRate); |
| } |
| } |
| } |
| } |
| |
| static void set_ball_pos ( GLfloat x, GLfloat y ) |
| { |
| ball_x = (width / 2) - x; |
| ball_y = y - (height / 2); |
| } |
| |
| void mouse_button_callback( GLFWwindow* window, int button, int action, int mods ) |
| { |
| if (button != GLFW_MOUSE_BUTTON_LEFT) |
| return; |
| |
| if (action == GLFW_PRESS) |
| { |
| override_pos = GLFW_TRUE; |
| set_ball_pos(cursor_x, cursor_y); |
| } |
| else |
| { |
| override_pos = GLFW_FALSE; |
| } |
| } |
| |
| void cursor_position_callback( GLFWwindow* window, double x, double y ) |
| { |
| cursor_x = (float) x; |
| cursor_y = (float) y; |
| |
| if ( override_pos ) |
| set_ball_pos(cursor_x, cursor_y); |
| } |
| |
| /***************************************************************************** |
| * Draw the Boing ball. |
| * |
| * The Boing ball is sphere in which each facet is a rectangle. |
| * Facet colors alternate between red and white. |
| * The ball is built by stacking latitudinal circles. Each circle is composed |
| * of a widely-separated set of points, so that each facet is noticeably large. |
| *****************************************************************************/ |
| void DrawBoingBall( void ) |
| { |
| GLfloat lon_deg; /* degree of longitude */ |
| double dt_total, dt2; |
| |
| glPushMatrix(); |
| glMatrixMode( GL_MODELVIEW ); |
| |
| /* |
| * Another relative Z translation to separate objects. |
| */ |
| glTranslatef( 0.0, 0.0, DIST_BALL ); |
| |
| /* Update ball position and rotation (iterate if necessary) */ |
| dt_total = dt; |
| while( dt_total > 0.0 ) |
| { |
| dt2 = dt_total > MAX_DELTA_T ? MAX_DELTA_T : dt_total; |
| dt_total -= dt2; |
| BounceBall( dt2 ); |
| deg_rot_y = TruncateDeg( deg_rot_y + deg_rot_y_inc*((float)dt2*ANIMATION_SPEED) ); |
| } |
| |
| /* Set ball position */ |
| glTranslatef( ball_x, ball_y, 0.0 ); |
| |
| /* |
| * Offset the shadow. |
| */ |
| if ( drawBallHow == DRAW_BALL_SHADOW ) |
| { |
| glTranslatef( SHADOW_OFFSET_X, |
| SHADOW_OFFSET_Y, |
| SHADOW_OFFSET_Z ); |
| } |
| |
| /* |
| * Tilt the ball. |
| */ |
| glRotatef( -20.0, 0.0, 0.0, 1.0 ); |
| |
| /* |
| * Continually rotate ball around Y axis. |
| */ |
| glRotatef( deg_rot_y, 0.0, 1.0, 0.0 ); |
| |
| /* |
| * Set OpenGL state for Boing ball. |
| */ |
| glCullFace( GL_FRONT ); |
| glEnable( GL_CULL_FACE ); |
| glEnable( GL_NORMALIZE ); |
| |
| /* |
| * Build a faceted latitude slice of the Boing ball, |
| * stepping same-sized vertical bands of the sphere. |
| */ |
| for ( lon_deg = 0; |
| lon_deg < 180; |
| lon_deg += STEP_LONGITUDE ) |
| { |
| /* |
| * Draw a latitude circle at this longitude. |
| */ |
| DrawBoingBallBand( lon_deg, |
| lon_deg + STEP_LONGITUDE ); |
| } |
| |
| glPopMatrix(); |
| |
| return; |
| } |
| |
| |
| /***************************************************************************** |
| * Bounce the ball. |
| *****************************************************************************/ |
| void BounceBall( double delta_t ) |
| { |
| GLfloat sign; |
| GLfloat deg; |
| |
| if ( override_pos ) |
| return; |
| |
| /* Bounce on walls */ |
| if ( ball_x > (BOUNCE_WIDTH/2 + WALL_R_OFFSET ) ) |
| { |
| ball_x_inc = -0.5f - 0.75f * (GLfloat)rand() / (GLfloat)RAND_MAX; |
| deg_rot_y_inc = -deg_rot_y_inc; |
| } |
| if ( ball_x < -(BOUNCE_HEIGHT/2 + WALL_L_OFFSET) ) |
| { |
| ball_x_inc = 0.5f + 0.75f * (GLfloat)rand() / (GLfloat)RAND_MAX; |
| deg_rot_y_inc = -deg_rot_y_inc; |
| } |
| |
| /* Bounce on floor / roof */ |
| if ( ball_y > BOUNCE_HEIGHT/2 ) |
| { |
| ball_y_inc = -0.75f - 1.f * (GLfloat)rand() / (GLfloat)RAND_MAX; |
| } |
| if ( ball_y < -BOUNCE_HEIGHT/2*0.85 ) |
| { |
| ball_y_inc = 0.75f + 1.f * (GLfloat)rand() / (GLfloat)RAND_MAX; |
| } |
| |
| /* Update ball position */ |
| ball_x += ball_x_inc * ((float)delta_t*ANIMATION_SPEED); |
| ball_y += ball_y_inc * ((float)delta_t*ANIMATION_SPEED); |
| |
| /* |
| * Simulate the effects of gravity on Y movement. |
| */ |
| if ( ball_y_inc < 0 ) sign = -1.0; else sign = 1.0; |
| |
| deg = (ball_y + BOUNCE_HEIGHT/2) * 90 / BOUNCE_HEIGHT; |
| if ( deg > 80 ) deg = 80; |
| if ( deg < 10 ) deg = 10; |
| |
| ball_y_inc = sign * 4.f * (float) sin_deg( deg ); |
| } |
| |
| |
| /***************************************************************************** |
| * Draw a faceted latitude band of the Boing ball. |
| * |
| * Parms: long_lo, long_hi |
| * Low and high longitudes of slice, resp. |
| *****************************************************************************/ |
| void DrawBoingBallBand( GLfloat long_lo, |
| GLfloat long_hi ) |
| { |
| vertex_t vert_ne; /* "ne" means south-east, so on */ |
| vertex_t vert_nw; |
| vertex_t vert_sw; |
| vertex_t vert_se; |
| vertex_t vert_norm; |
| GLfloat lat_deg; |
| static int colorToggle = 0; |
| |
| /* |
| * Iterate through the points of a latitude circle. |
| * A latitude circle is a 2D set of X,Z points. |
| */ |
| for ( lat_deg = 0; |
| lat_deg <= (360 - STEP_LATITUDE); |
| lat_deg += STEP_LATITUDE ) |
| { |
| /* |
| * Color this polygon with red or white. |
| */ |
| if ( colorToggle ) |
| glColor3f( 0.8f, 0.1f, 0.1f ); |
| else |
| glColor3f( 0.95f, 0.95f, 0.95f ); |
| #if 0 |
| if ( lat_deg >= 180 ) |
| if ( colorToggle ) |
| glColor3f( 0.1f, 0.8f, 0.1f ); |
| else |
| glColor3f( 0.5f, 0.5f, 0.95f ); |
| #endif |
| colorToggle = ! colorToggle; |
| |
| /* |
| * Change color if drawing shadow. |
| */ |
| if ( drawBallHow == DRAW_BALL_SHADOW ) |
| glColor3f( 0.35f, 0.35f, 0.35f ); |
| |
| /* |
| * Assign each Y. |
| */ |
| vert_ne.y = vert_nw.y = (float) cos_deg(long_hi) * RADIUS; |
| vert_sw.y = vert_se.y = (float) cos_deg(long_lo) * RADIUS; |
| |
| /* |
| * Assign each X,Z with sin,cos values scaled by latitude radius indexed by longitude. |
| * Eg, long=0 and long=180 are at the poles, so zero scale is sin(longitude), |
| * while long=90 (sin(90)=1) is at equator. |
| */ |
| vert_ne.x = (float) cos_deg( lat_deg ) * (RADIUS * (float) sin_deg( long_lo + STEP_LONGITUDE )); |
| vert_se.x = (float) cos_deg( lat_deg ) * (RADIUS * (float) sin_deg( long_lo )); |
| vert_nw.x = (float) cos_deg( lat_deg + STEP_LATITUDE ) * (RADIUS * (float) sin_deg( long_lo + STEP_LONGITUDE )); |
| vert_sw.x = (float) cos_deg( lat_deg + STEP_LATITUDE ) * (RADIUS * (float) sin_deg( long_lo )); |
| |
| vert_ne.z = (float) sin_deg( lat_deg ) * (RADIUS * (float) sin_deg( long_lo + STEP_LONGITUDE )); |
| vert_se.z = (float) sin_deg( lat_deg ) * (RADIUS * (float) sin_deg( long_lo )); |
| vert_nw.z = (float) sin_deg( lat_deg + STEP_LATITUDE ) * (RADIUS * (float) sin_deg( long_lo + STEP_LONGITUDE )); |
| vert_sw.z = (float) sin_deg( lat_deg + STEP_LATITUDE ) * (RADIUS * (float) sin_deg( long_lo )); |
| |
| /* |
| * Draw the facet. |
| */ |
| glBegin( GL_POLYGON ); |
| |
| CrossProduct( vert_ne, vert_nw, vert_sw, &vert_norm ); |
| glNormal3f( vert_norm.x, vert_norm.y, vert_norm.z ); |
| |
| glVertex3f( vert_ne.x, vert_ne.y, vert_ne.z ); |
| glVertex3f( vert_nw.x, vert_nw.y, vert_nw.z ); |
| glVertex3f( vert_sw.x, vert_sw.y, vert_sw.z ); |
| glVertex3f( vert_se.x, vert_se.y, vert_se.z ); |
| |
| glEnd(); |
| |
| #if BOING_DEBUG |
| printf( "----------------------------------------------------------- \n" ); |
| printf( "lat = %f long_lo = %f long_hi = %f \n", lat_deg, long_lo, long_hi ); |
| printf( "vert_ne x = %.8f y = %.8f z = %.8f \n", vert_ne.x, vert_ne.y, vert_ne.z ); |
| printf( "vert_nw x = %.8f y = %.8f z = %.8f \n", vert_nw.x, vert_nw.y, vert_nw.z ); |
| printf( "vert_se x = %.8f y = %.8f z = %.8f \n", vert_se.x, vert_se.y, vert_se.z ); |
| printf( "vert_sw x = %.8f y = %.8f z = %.8f \n", vert_sw.x, vert_sw.y, vert_sw.z ); |
| #endif |
| |
| } |
| |
| /* |
| * Toggle color so that next band will opposite red/white colors than this one. |
| */ |
| colorToggle = ! colorToggle; |
| |
| /* |
| * This circular band is done. |
| */ |
| return; |
| } |
| |
| |
| /***************************************************************************** |
| * Draw the purple grid of lines, behind the Boing ball. |
| * When the Workbench is dropped to the bottom, Boing shows 12 rows. |
| *****************************************************************************/ |
| void DrawGrid( void ) |
| { |
| int row, col; |
| const int rowTotal = 12; /* must be divisible by 2 */ |
| const int colTotal = rowTotal; /* must be same as rowTotal */ |
| const GLfloat widthLine = 2.0; /* should be divisible by 2 */ |
| const GLfloat sizeCell = GRID_SIZE / rowTotal; |
| const GLfloat z_offset = -40.0; |
| GLfloat xl, xr; |
| GLfloat yt, yb; |
| |
| glPushMatrix(); |
| glDisable( GL_CULL_FACE ); |
| |
| /* |
| * Another relative Z translation to separate objects. |
| */ |
| glTranslatef( 0.0, 0.0, DIST_BALL ); |
| |
| /* |
| * Draw vertical lines (as skinny 3D rectangles). |
| */ |
| for ( col = 0; col <= colTotal; col++ ) |
| { |
| /* |
| * Compute co-ords of line. |
| */ |
| xl = -GRID_SIZE / 2 + col * sizeCell; |
| xr = xl + widthLine; |
| |
| yt = GRID_SIZE / 2; |
| yb = -GRID_SIZE / 2 - widthLine; |
| |
| glBegin( GL_POLYGON ); |
| |
| glColor3f( 0.6f, 0.1f, 0.6f ); /* purple */ |
| |
| glVertex3f( xr, yt, z_offset ); /* NE */ |
| glVertex3f( xl, yt, z_offset ); /* NW */ |
| glVertex3f( xl, yb, z_offset ); /* SW */ |
| glVertex3f( xr, yb, z_offset ); /* SE */ |
| |
| glEnd(); |
| } |
| |
| /* |
| * Draw horizontal lines (as skinny 3D rectangles). |
| */ |
| for ( row = 0; row <= rowTotal; row++ ) |
| { |
| /* |
| * Compute co-ords of line. |
| */ |
| yt = GRID_SIZE / 2 - row * sizeCell; |
| yb = yt - widthLine; |
| |
| xl = -GRID_SIZE / 2; |
| xr = GRID_SIZE / 2 + widthLine; |
| |
| glBegin( GL_POLYGON ); |
| |
| glColor3f( 0.6f, 0.1f, 0.6f ); /* purple */ |
| |
| glVertex3f( xr, yt, z_offset ); /* NE */ |
| glVertex3f( xl, yt, z_offset ); /* NW */ |
| glVertex3f( xl, yb, z_offset ); /* SW */ |
| glVertex3f( xr, yb, z_offset ); /* SE */ |
| |
| glEnd(); |
| } |
| |
| glPopMatrix(); |
| |
| return; |
| } |
| |
| |
| /*======================================================================* |
| * main() |
| *======================================================================*/ |
| |
| int main( void ) |
| { |
| GLFWwindow* window; |
| |
| /* Init GLFW */ |
| if( !glfwInit() ) |
| exit( EXIT_FAILURE ); |
| |
| window = glfwCreateWindow( 400, 400, "Boing (classic Amiga demo)", NULL, NULL ); |
| if (!window) |
| { |
| glfwTerminate(); |
| exit( EXIT_FAILURE ); |
| } |
| |
| glfwSetWindowAspectRatio(window, 1, 1); |
| |
| glfwSetFramebufferSizeCallback(window, reshape); |
| glfwSetKeyCallback(window, key_callback); |
| glfwSetMouseButtonCallback(window, mouse_button_callback); |
| glfwSetCursorPosCallback(window, cursor_position_callback); |
| |
| glfwMakeContextCurrent(window); |
| gladLoadGLLoader((GLADloadproc) glfwGetProcAddress); |
| glfwSwapInterval( 1 ); |
| |
| glfwGetFramebufferSize(window, &width, &height); |
| reshape(window, width, height); |
| |
| glfwSetTime( 0.0 ); |
| |
| init(); |
| |
| /* Main loop */ |
| for (;;) |
| { |
| /* Timing */ |
| t = glfwGetTime(); |
| dt = t - t_old; |
| t_old = t; |
| |
| /* Draw one frame */ |
| display(); |
| |
| /* Swap buffers */ |
| glfwSwapBuffers(window); |
| glfwPollEvents(); |
| |
| /* Check if we are still running */ |
| if (glfwWindowShouldClose(window)) |
| break; |
| } |
| |
| glfwTerminate(); |
| exit( EXIT_SUCCESS ); |
| } |
| |