blob: 5f006a8a3a947a593f654fd458daf95a597b0565 [file] [log] [blame]
Camilla Berglund3249f812010-09-07 17:34:51 +02001/*****************************************************************************
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>
Camilla Berglund22134502012-06-05 23:55:10 +020033
34#define GLFW_INCLUDE_GLU
Camilla Berglundcfbafc52010-09-10 13:16:03 +020035#include <GL/glfw3.h>
Camilla Berglund3249f812010-09-07 17:34:51 +020036
37
38/*****************************************************************************
39 * Various declarations and macros
40 *****************************************************************************/
41
42/* Prototypes */
43void init( void );
44void display( void );
Camilla Berglund9af960e2013-01-05 21:13:28 +010045void reshape( GLFWwindow* window, int w, int h );
Camilla Berglund3249f812010-09-07 17:34:51 +020046void DrawBoingBall( void );
47void BounceBall( double dt );
48void DrawBoingBallBand( GLfloat long_lo, GLfloat long_hi );
49void DrawGrid( void );
50
51#define RADIUS 70.f
52#define STEP_LONGITUDE 22.5f /* 22.5 makes 8 bands like original Boing */
53#define STEP_LATITUDE 22.5f
54
55#define DIST_BALL (RADIUS * 2.f + RADIUS * 0.1f)
56
57#define VIEW_SCENE_DIST (DIST_BALL * 3.f + 200.f)/* distance from viewer to middle of boing area */
58#define GRID_SIZE (RADIUS * 4.5f) /* length (width) of grid */
59#define BOUNCE_HEIGHT (RADIUS * 2.1f)
60#define BOUNCE_WIDTH (RADIUS * 2.1f)
61
62#define SHADOW_OFFSET_X -20.f
63#define SHADOW_OFFSET_Y 10.f
64#define SHADOW_OFFSET_Z 0.f
65
66#define WALL_L_OFFSET 0.f
67#define WALL_R_OFFSET 5.f
68
69/* Animation speed (50.0 mimics the original GLUT demo speed) */
70#define ANIMATION_SPEED 50.f
71
72/* Maximum allowed delta time per physics iteration */
73#define MAX_DELTA_T 0.02f
74
75/* Draw ball, or its shadow */
76typedef enum { DRAW_BALL, DRAW_BALL_SHADOW } DRAW_BALL_ENUM;
77
78/* Vertex type */
79typedef struct {float x; float y; float z;} vertex_t;
80
81/* Global vars */
82GLfloat deg_rot_y = 0.f;
83GLfloat deg_rot_y_inc = 2.f;
84GLfloat ball_x = -RADIUS;
85GLfloat ball_y = -RADIUS;
86GLfloat ball_x_inc = 1.f;
87GLfloat ball_y_inc = 2.f;
88DRAW_BALL_ENUM drawBallHow;
89double t;
90double t_old = 0.f;
91double dt;
92
93/* Random number generator */
94#ifndef RAND_MAX
95 #define RAND_MAX 4095
96#endif
97
98/* PI */
99#ifndef M_PI
100 #define M_PI 3.1415926535897932384626433832795
101#endif
102
103
104/*****************************************************************************
105 * Truncate a degree.
106 *****************************************************************************/
107GLfloat TruncateDeg( GLfloat deg )
108{
109 if ( deg >= 360.f )
110 return (deg - 360.f);
111 else
112 return deg;
113}
114
115/*****************************************************************************
116 * Convert a degree (360-based) into a radian.
117 * 360' = 2 * PI
118 *****************************************************************************/
119double deg2rad( double deg )
120{
121 return deg / 360 * (2 * M_PI);
122}
123
124/*****************************************************************************
125 * 360' sin().
126 *****************************************************************************/
127double sin_deg( double deg )
128{
129 return sin( deg2rad( deg ) );
130}
131
132/*****************************************************************************
133 * 360' cos().
134 *****************************************************************************/
135double cos_deg( double deg )
136{
137 return cos( deg2rad( deg ) );
138}
139
140/*****************************************************************************
141 * Compute a cross product (for a normal vector).
142 *
143 * c = a x b
144 *****************************************************************************/
145void CrossProduct( vertex_t a, vertex_t b, vertex_t c, vertex_t *n )
146{
147 GLfloat u1, u2, u3;
148 GLfloat v1, v2, v3;
149
150 u1 = b.x - a.x;
151 u2 = b.y - a.y;
152 u3 = b.y - a.z;
153
154 v1 = c.x - a.x;
155 v2 = c.y - a.y;
156 v3 = c.z - a.z;
157
158 n->x = u2 * v3 - v2 * v3;
159 n->y = u3 * v1 - v3 * u1;
160 n->z = u1 * v2 - v1 * u2;
161}
162
163/*****************************************************************************
164 * Calculate the angle to be passed to gluPerspective() so that a scene
165 * is visible. This function originates from the OpenGL Red Book.
166 *
167 * Parms : size
168 * The size of the segment when the angle is intersected at "dist"
169 * (ie at the outermost edge of the angle of vision).
170 *
171 * dist
172 * Distance from viewpoint to scene.
173 *****************************************************************************/
174GLfloat PerspectiveAngle( GLfloat size,
175 GLfloat dist )
176{
177 GLfloat radTheta, degTheta;
178
179 radTheta = 2.f * (GLfloat) atan2( size / 2.f, dist );
180 degTheta = (180.f * radTheta) / (GLfloat) M_PI;
181 return degTheta;
182}
183
184
185
186#define BOING_DEBUG 0
187
188
189/*****************************************************************************
190 * init()
191 *****************************************************************************/
192void init( void )
193{
194 /*
195 * Clear background.
196 */
197 glClearColor( 0.55f, 0.55f, 0.55f, 0.f );
198
199 glShadeModel( GL_FLAT );
200}
201
202
203/*****************************************************************************
204 * display()
205 *****************************************************************************/
206void display(void)
207{
208 glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
209 glPushMatrix();
210
211 drawBallHow = DRAW_BALL_SHADOW;
212 DrawBoingBall();
213
214 DrawGrid();
215
216 drawBallHow = DRAW_BALL;
217 DrawBoingBall();
218
219 glPopMatrix();
220 glFlush();
221}
222
223
224/*****************************************************************************
225 * reshape()
226 *****************************************************************************/
Camilla Berglund9af960e2013-01-05 21:13:28 +0100227void reshape( GLFWwindow* window, int w, int h )
Camilla Berglund3249f812010-09-07 17:34:51 +0200228{
229 glViewport( 0, 0, (GLsizei)w, (GLsizei)h );
230
231 glMatrixMode( GL_PROJECTION );
232 glLoadIdentity();
233
234 gluPerspective( PerspectiveAngle( RADIUS * 2, 200 ),
235 (GLfloat)w / (GLfloat)h,
236 1.0,
237 VIEW_SCENE_DIST );
238
239 glMatrixMode( GL_MODELVIEW );
240 glLoadIdentity();
241
242 gluLookAt( 0.0, 0.0, VIEW_SCENE_DIST,/* eye */
243 0.0, 0.0, 0.0, /* center of vision */
244 0.0, -1.0, 0.0 ); /* up vector */
245}
246
247
248/*****************************************************************************
249 * Draw the Boing ball.
250 *
251 * The Boing ball is sphere in which each facet is a rectangle.
252 * Facet colors alternate between red and white.
253 * The ball is built by stacking latitudinal circles. Each circle is composed
254 * of a widely-separated set of points, so that each facet is noticably large.
255 *****************************************************************************/
256void DrawBoingBall( void )
257{
258 GLfloat lon_deg; /* degree of longitude */
259 double dt_total, dt2;
260
261 glPushMatrix();
262 glMatrixMode( GL_MODELVIEW );
263
264 /*
265 * Another relative Z translation to separate objects.
266 */
267 glTranslatef( 0.0, 0.0, DIST_BALL );
268
269 /* Update ball position and rotation (iterate if necessary) */
270 dt_total = dt;
271 while( dt_total > 0.0 )
272 {
273 dt2 = dt_total > MAX_DELTA_T ? MAX_DELTA_T : dt_total;
274 dt_total -= dt2;
275 BounceBall( dt2 );
276 deg_rot_y = TruncateDeg( deg_rot_y + deg_rot_y_inc*((float)dt2*ANIMATION_SPEED) );
277 }
278
279 /* Set ball position */
280 glTranslatef( ball_x, ball_y, 0.0 );
281
282 /*
283 * Offset the shadow.
284 */
285 if ( drawBallHow == DRAW_BALL_SHADOW )
286 {
287 glTranslatef( SHADOW_OFFSET_X,
288 SHADOW_OFFSET_Y,
289 SHADOW_OFFSET_Z );
290 }
291
292 /*
293 * Tilt the ball.
294 */
295 glRotatef( -20.0, 0.0, 0.0, 1.0 );
296
297 /*
298 * Continually rotate ball around Y axis.
299 */
300 glRotatef( deg_rot_y, 0.0, 1.0, 0.0 );
301
302 /*
303 * Set OpenGL state for Boing ball.
304 */
305 glCullFace( GL_FRONT );
306 glEnable( GL_CULL_FACE );
307 glEnable( GL_NORMALIZE );
308
309 /*
310 * Build a faceted latitude slice of the Boing ball,
311 * stepping same-sized vertical bands of the sphere.
312 */
313 for ( lon_deg = 0;
314 lon_deg < 180;
315 lon_deg += STEP_LONGITUDE )
316 {
317 /*
318 * Draw a latitude circle at this longitude.
319 */
320 DrawBoingBallBand( lon_deg,
321 lon_deg + STEP_LONGITUDE );
322 }
323
324 glPopMatrix();
325
326 return;
327}
328
329
330/*****************************************************************************
331 * Bounce the ball.
332 *****************************************************************************/
Lambert Clarac2729182012-08-12 13:24:21 +0200333void BounceBall( double delta_t )
Camilla Berglund3249f812010-09-07 17:34:51 +0200334{
335 GLfloat sign;
336 GLfloat deg;
337
338 /* Bounce on walls */
339 if ( ball_x > (BOUNCE_WIDTH/2 + WALL_R_OFFSET ) )
340 {
341 ball_x_inc = -0.5f - 0.75f * (GLfloat)rand() / (GLfloat)RAND_MAX;
342 deg_rot_y_inc = -deg_rot_y_inc;
343 }
344 if ( ball_x < -(BOUNCE_HEIGHT/2 + WALL_L_OFFSET) )
345 {
346 ball_x_inc = 0.5f + 0.75f * (GLfloat)rand() / (GLfloat)RAND_MAX;
347 deg_rot_y_inc = -deg_rot_y_inc;
348 }
349
350 /* Bounce on floor / roof */
351 if ( ball_y > BOUNCE_HEIGHT/2 )
352 {
353 ball_y_inc = -0.75f - 1.f * (GLfloat)rand() / (GLfloat)RAND_MAX;
354 }
355 if ( ball_y < -BOUNCE_HEIGHT/2*0.85 )
356 {
357 ball_y_inc = 0.75f + 1.f * (GLfloat)rand() / (GLfloat)RAND_MAX;
358 }
359
360 /* Update ball position */
Lambert Clarac2729182012-08-12 13:24:21 +0200361 ball_x += ball_x_inc * ((float)delta_t*ANIMATION_SPEED);
362 ball_y += ball_y_inc * ((float)delta_t*ANIMATION_SPEED);
Camilla Berglund3249f812010-09-07 17:34:51 +0200363
364 /*
365 * Simulate the effects of gravity on Y movement.
366 */
367 if ( ball_y_inc < 0 ) sign = -1.0; else sign = 1.0;
368
369 deg = (ball_y + BOUNCE_HEIGHT/2) * 90 / BOUNCE_HEIGHT;
370 if ( deg > 80 ) deg = 80;
371 if ( deg < 10 ) deg = 10;
372
373 ball_y_inc = sign * 4.f * (float) sin_deg( deg );
374}
375
376
377/*****************************************************************************
378 * Draw a faceted latitude band of the Boing ball.
379 *
380 * Parms: long_lo, long_hi
381 * Low and high longitudes of slice, resp.
382 *****************************************************************************/
383void DrawBoingBallBand( GLfloat long_lo,
384 GLfloat long_hi )
385{
386 vertex_t vert_ne; /* "ne" means south-east, so on */
387 vertex_t vert_nw;
388 vertex_t vert_sw;
389 vertex_t vert_se;
390 vertex_t vert_norm;
391 GLfloat lat_deg;
392 static int colorToggle = 0;
393
394 /*
395 * Iterate thru the points of a latitude circle.
396 * A latitude circle is a 2D set of X,Z points.
397 */
398 for ( lat_deg = 0;
399 lat_deg <= (360 - STEP_LATITUDE);
400 lat_deg += STEP_LATITUDE )
401 {
402 /*
403 * Color this polygon with red or white.
404 */
405 if ( colorToggle )
406 glColor3f( 0.8f, 0.1f, 0.1f );
407 else
408 glColor3f( 0.95f, 0.95f, 0.95f );
409#if 0
410 if ( lat_deg >= 180 )
411 if ( colorToggle )
412 glColor3f( 0.1f, 0.8f, 0.1f );
413 else
414 glColor3f( 0.5f, 0.5f, 0.95f );
415#endif
416 colorToggle = ! colorToggle;
417
418 /*
419 * Change color if drawing shadow.
420 */
421 if ( drawBallHow == DRAW_BALL_SHADOW )
422 glColor3f( 0.35f, 0.35f, 0.35f );
423
424 /*
425 * Assign each Y.
426 */
427 vert_ne.y = vert_nw.y = (float) cos_deg(long_hi) * RADIUS;
428 vert_sw.y = vert_se.y = (float) cos_deg(long_lo) * RADIUS;
429
430 /*
431 * Assign each X,Z with sin,cos values scaled by latitude radius indexed by longitude.
432 * Eg, long=0 and long=180 are at the poles, so zero scale is sin(longitude),
433 * while long=90 (sin(90)=1) is at equator.
434 */
435 vert_ne.x = (float) cos_deg( lat_deg ) * (RADIUS * (float) sin_deg( long_lo + STEP_LONGITUDE ));
436 vert_se.x = (float) cos_deg( lat_deg ) * (RADIUS * (float) sin_deg( long_lo ));
437 vert_nw.x = (float) cos_deg( lat_deg + STEP_LATITUDE ) * (RADIUS * (float) sin_deg( long_lo + STEP_LONGITUDE ));
438 vert_sw.x = (float) cos_deg( lat_deg + STEP_LATITUDE ) * (RADIUS * (float) sin_deg( long_lo ));
439
440 vert_ne.z = (float) sin_deg( lat_deg ) * (RADIUS * (float) sin_deg( long_lo + STEP_LONGITUDE ));
441 vert_se.z = (float) sin_deg( lat_deg ) * (RADIUS * (float) sin_deg( long_lo ));
442 vert_nw.z = (float) sin_deg( lat_deg + STEP_LATITUDE ) * (RADIUS * (float) sin_deg( long_lo + STEP_LONGITUDE ));
443 vert_sw.z = (float) sin_deg( lat_deg + STEP_LATITUDE ) * (RADIUS * (float) sin_deg( long_lo ));
444
445 /*
446 * Draw the facet.
447 */
448 glBegin( GL_POLYGON );
449
450 CrossProduct( vert_ne, vert_nw, vert_sw, &vert_norm );
451 glNormal3f( vert_norm.x, vert_norm.y, vert_norm.z );
452
453 glVertex3f( vert_ne.x, vert_ne.y, vert_ne.z );
454 glVertex3f( vert_nw.x, vert_nw.y, vert_nw.z );
455 glVertex3f( vert_sw.x, vert_sw.y, vert_sw.z );
456 glVertex3f( vert_se.x, vert_se.y, vert_se.z );
457
458 glEnd();
459
460#if BOING_DEBUG
461 printf( "----------------------------------------------------------- \n" );
462 printf( "lat = %f long_lo = %f long_hi = %f \n", lat_deg, long_lo, long_hi );
463 printf( "vert_ne x = %.8f y = %.8f z = %.8f \n", vert_ne.x, vert_ne.y, vert_ne.z );
464 printf( "vert_nw x = %.8f y = %.8f z = %.8f \n", vert_nw.x, vert_nw.y, vert_nw.z );
465 printf( "vert_se x = %.8f y = %.8f z = %.8f \n", vert_se.x, vert_se.y, vert_se.z );
466 printf( "vert_sw x = %.8f y = %.8f z = %.8f \n", vert_sw.x, vert_sw.y, vert_sw.z );
467#endif
468
469 }
470
471 /*
472 * Toggle color so that next band will opposite red/white colors than this one.
473 */
474 colorToggle = ! colorToggle;
475
476 /*
477 * This circular band is done.
478 */
479 return;
480}
481
482
483/*****************************************************************************
484 * Draw the purple grid of lines, behind the Boing ball.
485 * When the Workbench is dropped to the bottom, Boing shows 12 rows.
486 *****************************************************************************/
487void DrawGrid( void )
488{
489 int row, col;
490 const int rowTotal = 12; /* must be divisible by 2 */
491 const int colTotal = rowTotal; /* must be same as rowTotal */
492 const GLfloat widthLine = 2.0; /* should be divisible by 2 */
493 const GLfloat sizeCell = GRID_SIZE / rowTotal;
494 const GLfloat z_offset = -40.0;
495 GLfloat xl, xr;
496 GLfloat yt, yb;
497
498 glPushMatrix();
499 glDisable( GL_CULL_FACE );
500
501 /*
502 * Another relative Z translation to separate objects.
503 */
504 glTranslatef( 0.0, 0.0, DIST_BALL );
505
506 /*
507 * Draw vertical lines (as skinny 3D rectangles).
508 */
509 for ( col = 0; col <= colTotal; col++ )
510 {
511 /*
512 * Compute co-ords of line.
513 */
514 xl = -GRID_SIZE / 2 + col * sizeCell;
515 xr = xl + widthLine;
516
517 yt = GRID_SIZE / 2;
518 yb = -GRID_SIZE / 2 - widthLine;
519
520 glBegin( GL_POLYGON );
521
522 glColor3f( 0.6f, 0.1f, 0.6f ); /* purple */
523
524 glVertex3f( xr, yt, z_offset ); /* NE */
525 glVertex3f( xl, yt, z_offset ); /* NW */
526 glVertex3f( xl, yb, z_offset ); /* SW */
527 glVertex3f( xr, yb, z_offset ); /* SE */
528
529 glEnd();
530 }
531
532 /*
533 * Draw horizontal lines (as skinny 3D rectangles).
534 */
535 for ( row = 0; row <= rowTotal; row++ )
536 {
537 /*
538 * Compute co-ords of line.
539 */
540 yt = GRID_SIZE / 2 - row * sizeCell;
541 yb = yt - widthLine;
542
543 xl = -GRID_SIZE / 2;
544 xr = GRID_SIZE / 2 + widthLine;
545
546 glBegin( GL_POLYGON );
547
548 glColor3f( 0.6f, 0.1f, 0.6f ); /* purple */
549
550 glVertex3f( xr, yt, z_offset ); /* NE */
551 glVertex3f( xl, yt, z_offset ); /* NW */
552 glVertex3f( xl, yb, z_offset ); /* SW */
553 glVertex3f( xr, yb, z_offset ); /* SE */
554
555 glEnd();
556 }
557
558 glPopMatrix();
559
560 return;
561}
562
563
564/*======================================================================*
565 * main()
566 *======================================================================*/
567
568int main( void )
569{
Camilla Berglund9af960e2013-01-05 21:13:28 +0100570 GLFWwindow* window;
Camilla Berglund8ed66ea2012-08-07 12:14:58 +0200571 int width, height;
Camilla Berglund3249f812010-09-07 17:34:51 +0200572
573 /* Init GLFW */
Camilla Berglund0c3b1b52012-02-07 14:58:58 +0100574 if( !glfwInit() )
Camilla Berglund3249f812010-09-07 17:34:51 +0200575 exit( EXIT_FAILURE );
Camilla Berglund3249f812010-09-07 17:34:51 +0200576
Camilla Berglundaff30d02012-08-06 17:56:41 +0200577 glfwWindowHint(GLFW_DEPTH_BITS, 16);
Camilla Berglund950a3be2010-09-09 19:58:51 +0200578
Camilla Berglund1be16362012-09-27 21:37:36 +0200579 window = glfwCreateWindow( 400, 400, "Boing (classic Amiga demo)", NULL, NULL );
Camilla Berglund39fd7a52010-09-09 18:16:07 +0200580 if (!window)
Camilla Berglund3249f812010-09-07 17:34:51 +0200581 {
Camilla Berglund3249f812010-09-07 17:34:51 +0200582 glfwTerminate();
583 exit( EXIT_FAILURE );
584 }
585
Camilla Berglund18d71c22012-10-28 13:45:11 +0100586 glfwSetWindowSizeCallback(window, reshape);
587
Camilla Berglund2f095cc2012-08-10 15:29:45 +0200588 glfwMakeContextCurrent(window);
589 glfwSwapInterval( 1 );
590
Camilla Berglund8ed66ea2012-08-07 12:14:58 +0200591 glfwGetWindowSize(window, &width, &height);
592 reshape(window, width, height);
593
Camilla Berglundce288a82012-02-04 00:51:35 +0100594 glfwSetInputMode( window, GLFW_STICKY_KEYS, GL_TRUE );
Camilla Berglund3249f812010-09-07 17:34:51 +0200595 glfwSetTime( 0.0 );
596
597 init();
598
599 /* Main loop */
Camilla Berglund2410e2a2012-08-10 13:31:15 +0200600 for (;;)
Camilla Berglund3249f812010-09-07 17:34:51 +0200601 {
602 /* Timing */
603 t = glfwGetTime();
604 dt = t - t_old;
605 t_old = t;
606
607 /* Draw one frame */
608 display();
609
610 /* Swap buffers */
Camilla Berglund585a8402012-08-06 18:13:37 +0200611 glfwSwapBuffers(window);
Camilla Berglund39fd7a52010-09-09 18:16:07 +0200612 glfwPollEvents();
Camilla Berglund3249f812010-09-07 17:34:51 +0200613
614 /* Check if we are still running */
Camilla Berglund2972cdf2012-08-03 16:20:52 +0200615 if (glfwGetKey( window, GLFW_KEY_ESCAPE ))
Camilla Berglund2410e2a2012-08-10 13:31:15 +0200616 break;
Camilla Berglund21f41a22012-12-31 19:45:13 +0100617 if (glfwGetWindowParam(window, GLFW_SHOULD_CLOSE))
Camilla Berglund2410e2a2012-08-10 13:31:15 +0200618 break;
Camilla Berglund3249f812010-09-07 17:34:51 +0200619 }
Camilla Berglund3249f812010-09-07 17:34:51 +0200620
621 glfwTerminate();
622 exit( EXIT_SUCCESS );
623}
624