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