/*
**	$VER: fractal.c 1.0 (20.04.1997)
**
**	This is an example program for CyberGL
**
**      Written by Frank Gerberding
**
**	Copyright © 1996-1997 by phase5 digital products
**      All Rights reserved.
**
*/

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <clib/exec_protos.h>
#include <dos/dos.h>
#include <dos/dosasl.h>
#include <clib/dos_protos.h>
#include <string.h>
#define CYBERGLNAME             "cybergl.library"
#define CYBERGLVERSION          39L
#define SHARED                  /* we use the cybergl shared library */
#define GL_APICOMPATIBLE        /* we use stubs from cybergl.lib */
#include "cybergl.h"
#include "cybergl_protos.h"
#include "cybergl_display.h"
#ifdef SHARED
LONG __oslibversion   = 39L;
LONG __CGLlibversion = CYBERGLVERSION;
#include "cybergl_pragmas.h"
extern  struct  Library *CyberGLBase;
#endif


#define MAX_STEPS    8                  /* maximum number of genesis steps */
                                        /* 2^(2*MAX_STEPS)*6*sizeof(float) mem needed */
#define GRID_SIZE    (1 << MAX_STEPS)   /* fractal grid size */

#define WIDTH       0                   /* width parameter array index */
#define HEIGHT      1                   /* height parameter array index */
#define STEPS       2                   /* steps parameter array index */
#define MAX_HEIGHT  3                   /* max height parameter array index */
#define ROCK_REPEAT 4                   /* rock repeat parameter array index */
#define WAVE_REPEAT 5                   /* wave repeat parameter array index */
#define SIZE        6                   /* fractal size parameter array index */
#define SEA_LEVEL   7                   /* sea level parameter array index */
#define SEED        8                   /* random seed parameter array index */
#define FOG         9                   /* fog enable parameter array index */
#define TEXTURE    10                   /* texture enable parameter array index */


/* this sets the default values for all parameters: */
/* width:       initial window width */
/* height:      initial window height */
/* steps:       genesis step number (1..MAX_STEPS) */
/* maxHeight:   maximum fractal mountain height */
/* rockRepeat:  rock texture repeat count */
/* waveRepeat:  wave texture repeat count */
/* size:        fractal mountain x/z size */
/* seaLevel:    height of sea level */
/* seed:        random seed */
/* fog:         fog enable/disable */
/* texture:     texture enable/disable */
/*              width  height steps maxHeight rockRepeat waveRepeat size seaLevel seed fog texture */
int params [] = {320,   240,    6,     5,         3,         3,      20,    -1,   100,  0,    0};



typedef struct {
  GLfloat x;   /* point  x coordinate */
  GLfloat y;   /* point  y coordinate */
  GLfloat z;   /* point  z coordinate */
  GLfloat nx;  /* normal x coordinate */
  GLfloat ny;  /* normal y coordinate */
  GLfloat nz;  /* normal z coordinate */
} point;       /* grid point structure */



far point grid [GRID_SIZE + 1][GRID_SIZE + 1];   /* global fractal grid */




/************************************************************************/
/* texture minification routine. This function computes a texture of    */
/* half the original size using a simple box filter and returns a       */
/* pointer to a new allocated texture.                                  */
/* parameters:                                                          */
/* texture: pointer to original texture                                 */
/* size:    size of original texture                                    */
/* result:  new minified texture (or NULL for failure)                  */
/************************************************************************/
GLubyte *minifyTexture (GLubyte *texture, GLint size)
{
  GLubyte *newTexture = NULL;   /* pointer to minified texture */
  GLint    newSize;             /* minified texture size */
  GLint    x, y;                /* x and y texture coordinates */

  newSize = size / 2;
  if (newTexture = malloc (newSize * newSize * 3))
  {
    for (x = 0; x < newSize; x++)      /* for all rows */
    {
      for (y = 0; y < newSize; y++)    /* for all columns */
      {
        newTexture [(x + y * newSize) * 3 + 0] =
          (texture [(x + x +     (y + y)     * size) * 3 + 0] +
           texture [(x + x + 1 + (y + y)     * size) * 3 + 0] +
           texture [(x + x +     (y + y + 1) * size) * 3 + 0] +
           texture [(x + x + 1 + (y + y + 1) * size) * 3 + 0]) / 4; /* red pixel value */
        newTexture [(x + y * newSize) * 3 + 1] =
          (texture [(x + x +     (y + y)     * size) * 3 + 1] +
           texture [(x + x + 1 + (y + y)     * size) * 3 + 1] +
           texture [(x + x +     (y + y + 1) * size) * 3 + 1] +
           texture [(x + x + 1 + (y + y + 1) * size) * 3 + 1]) / 4; /* green pixel value */
        newTexture [(x + y * newSize) * 3 + 2] =
          (texture [(x + x +     (y + y)     * size) * 3 + 2] +
           texture [(x + x + 1 + (y + y)     * size) * 3 + 2] +
           texture [(x + x +     (y + y + 1) * size) * 3 + 2] +
           texture [(x + x + 1 + (y + y + 1) * size) * 3 + 2]) / 4; /* blue pixel value */
      }
    }
  }
  return newTexture;   /* return new texture */
}





/************************************************************************/
/* load a simple texture map from disk. The texture has to be organized */
/* as RGBRGBRGBRGB... and has to be of square size. The size has to be  */
/* a power of two. All mip mapping texture sizes are computed           */
/* automatically.                                                       */
/* parameters:                                                          */
/* name: full file name                                                 */
/* size: texture width/height in pixels, so file size is: size*size*3   */
/************************************************************************/
void loadTexMipMap (char *name, GLint size)
{
  FILE    *file;                 /* file pointer */
  GLubyte *texture    = NULL;    /* texture map pointer */
  GLubyte *newTexture = NULL;    /* new texture (for mip map sizes */
  int      level = 0;            /* actual mip mapping texture level */

  if (texture = malloc (size * size * 3))     /* alloc texture map */
  {
    if (file = fopen (name, "r"))             /* open texture file */
    {
      fread (texture, size * size * 3, 1, file);
      glTexImage2D (GL_TEXTURE_2D, level++, 3, size, size, 0, GL_RGB, GL_UNSIGNED_BYTE, texture);
      fclose (file);
      while (size > 1)                        /* mip map sizes remaining ? */
      {
        if (newTexture = minifyTexture (texture, size))   /* compute next mip map */
        {
          free (texture);                                 /* free old texture */
          texture = newTexture;
        }
        glTexImage2D (GL_TEXTURE_2D, level++, 3, size / 2, size / 2, 0, GL_RGB, GL_UNSIGNED_BYTE, texture);
        size /= 2;                                        /* next size is half original */
      }
    }
    free (texture);
  }
}





/************************************************************************/
/* compute a random height within -range..range.                        */
/* parameters:                                                          */
/* range:  range to compute a random value within                       */
/* result: random height within -range..range                           */
/************************************************************************/
GLfloat randomHeight (GLfloat range)
{
  return (GLfloat) (range * ((GLfloat) (2.0 * rand () / RAND_MAX) - 1.0));
}





/************************************************************************/
/* normalize a single vector.                                           */
/* parameters:                                                          */
/* point: pointer to point structure to normalize                       */
/************************************************************************/
void normalize (point *p)
{
  GLfloat length = p->nx * p->nx + p->ny * p->ny + p->nz * p->nz;

  if (length != 0.0)
  {
    length = 1.0 / sqrt (length);
    p->nx *= length;
    p->ny *= length;
    p->nz *= length;
  }
}





/************************************************************************/
/* calc vertex normals for all fractal mountain grid positions. This is */
/* done by computing all fractal patch normals and then averaging all   */
/* adjacent patch normals.                                              */
/* parameters:                                                          */
/* steps: number of fractal genesis steps                               */
/* size:  x/z size of fractal mountain                                  */
/************************************************************************/
void calcNormals (int steps, GLfloat size)
{
  int     gridStep = 1 << (MAX_STEPS - steps);  /* grid point index distance */
  int     x, z;                                 /* x/z index */
  GLfloat diff = size / (1 << steps);           /* x/z grid distance */
  GLfloat nx, ny, nz;                           /* actual normal coordinates */
  GLfloat y1, y2;                               /* actual y coordinates */

  for (x = 0; x < GRID_SIZE; x += gridStep)     /* for all rows */
  {
    for (z = 0; z < GRID_SIZE; z += gridStep)   /* for all columns */
    {
      grid [x][z].nx = 0.0;   /* init normal x coordinate */
      grid [x][z].ny = 0.0;   /* init normal y coordinate */
      grid [x][z].nz = 0.0;   /* init normal z coordinate */
    }
  }
  for (x = 0; x < GRID_SIZE; x += gridStep)    /* for all rows */
  {
    for (z = 0; z < GRID_SIZE; z += gridStep)  /* for all columns */
    {
      y1 = grid [x + gridStep][z].y - grid[x][z].y;   /* edge vector y1 */
      y2 = grid [x][z + gridStep].y - grid[x][z].y;   /* edge vector y2 */

      nx = y2   * 0.0  - diff * y1;                   /* normal computation */
      ny = diff * diff - 0.0  * 0.0;                  /* using edge vector */
      nz = 0.0  * y1   - y2   * diff;                 /* cross product */

      grid [x           ][z           ].nx += nx;     /* add actual normal */
      grid [x           ][z           ].ny += ny;
      grid [x           ][z           ].nz += nz;

      grid [x           ][z + gridStep].nx += nx;     /* add actual normal */
      grid [x           ][z + gridStep].ny += ny;
      grid [x           ][z + gridStep].nz += nz;

      grid [x + gridStep][z           ].nx += nx;     /* add actual normal */
      grid [x + gridStep][z           ].ny += ny;
      grid [x + gridStep][z           ].nz += nz;

      grid [x + gridStep][z + gridStep].nx += nx;     /* add actual normal */
      grid [x + gridStep][z + gridStep].ny += ny;
      grid [x + gridStep][z + gridStep].nz += nz;
    }
  }
  for (x = 0; x <= GRID_SIZE; x += gridStep)    /* for all rows */
  {
    for (z = 0; z <= GRID_SIZE; z += gridStep)  /* for all columns */
    {
      normalize (&(grid [x][z]));               /* normalize all vectors */
    }
  }
}





/************************************************************************/
/* fractal mountain genesis. This is done by the popular fractal        */
/* mountain generation algorithm.                                       */
/* parameters:                                                          */
/* seed:      random seed                                               */
/* steps:     number of generation steps (1..MAX_STEPS)                 */
/* maxHeight: maximum mountain height                                   */
/* size:      mountain x/z size                                         */
/************************************************************************/
void genesis (int seed, int steps, GLfloat maxHeight, GLfloat size)
{
  int x, z;       /* x/z index */
  int gridStep;   /* actual grid index distance */
  int step;       /* actual generation step */

  srand (seed);
  grid [        0][        0].y = randomHeight (maxHeight);  /* corner height */
  grid [GRID_SIZE][        0].y = randomHeight (maxHeight);  /* corner height */
  grid [        0][GRID_SIZE].y = randomHeight (maxHeight);  /* corner height */
  grid [GRID_SIZE][GRID_SIZE].y = randomHeight (maxHeight);  /* corner height */

  gridStep = GRID_SIZE / 2;          /* init grid step */

  for (step = 1; step <= steps; step++)   /* for all generation steps */
  {
    maxHeight /= 2.0;                     /* new max height */

    for (x = gridStep / 2; x < GRID_SIZE; x += gridStep)   /* for all rows */
    {
      int xl = x - gridStep / 2;                    /* x neighbour index left */
      int xr = x + gridStep / 2;                    /* x neighbour index right */

      for (z = gridStep / 2; z < GRID_SIZE; z += gridStep)  /* for all columns */
      {
        int zl = z - gridStep / 2;                  /* z neighbour index left */
        int zr = z + gridStep / 2;                  /* z neighbour index right */

        grid [x][z].y = (grid [xl][zl].y +
                         grid [xl][zr].y +
                         grid [xr][zl].y +
                         grid [xr][zr].y) / 4.0 + randomHeight (maxHeight);
        grid [xl][z].y = (grid [xl][zl].y +
                          grid [xl][zr].y) / 2.0 + randomHeight (maxHeight);
        grid [x][zl].y = (grid [xl][zl].y +
                          grid [xr][zl].y) / 2.0 + randomHeight (maxHeight);
      }
      grid [x][GRID_SIZE].y = (grid [xl][GRID_SIZE].y +
                               grid [xr][GRID_SIZE].y) / 2.0 + randomHeight (maxHeight);

    }
    for (z = gridStep / 2; z < GRID_SIZE; z += gridStep)    /* right border */
    {
      grid [GRID_SIZE][z].y = (grid [GRID_SIZE][z - gridStep / 2].y +
                               grid [GRID_SIZE][z + gridStep / 2].y) / 2.0 + randomHeight (maxHeight);
    }
    gridStep /= 2;             /* next grid index distance */
  }
  {
    GLfloat offset = - size / 2.0;           /* offset for x/z coordinate computation */
    GLfloat factor = size / GRID_SIZE;       /* factor for x/z coordinate computation */

    gridStep = 1 << (MAX_STEPS - steps);     /* grid index step size */

    for (x = 0; x <= GRID_SIZE; x += gridStep)   /* for all rows */
    {
      for (z = 0; z <= GRID_SIZE; z += gridStep) /* for all columns */
      {
        grid [x][z].x = factor * x + offset;     /* x coordinate computation */
        grid [x][z].z = factor * z + offset;     /* z coordinate computation */
      }
    }
  }
  calcNormals (steps, size);                     /* calc all vertex normals */
}





/************************************************************************/
/* draw the fractal mountain. This is done by drawing several adjacent  */
/* triangle strips.                                                     */
/* parameters:                                                          */
/* step:      number of generation steps performed before               */
/* texRepeat: rock texture repeat count                                 */
/************************************************************************/
void drawFractal (int step, GLfloat texRepeat)
{
  GLint    x, z;                            /* x/z index */
  GLint    gridStep;                        /* grid index distance */
  GLfloat  rock [] = {1.0, 0.8, 0.6, 1.0};  /* rock color */
  GLdouble texScale;                        /* texture coordinate factor */

  gridStep = 1 << (MAX_STEPS - step);
  texScale = texRepeat / GRID_SIZE;

  glMaterialfv  (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, rock);
  
  if (params [TEXTURE])
  {
    loadTexMipMap ("rock", 256);
  }
  for (z = 0; z < GRID_SIZE; z += gridStep)   /* for all rows */
  {
    glBegin (GL_TRIANGLE_STRIP);                   /* draw a triangle strip */
      for (x = 0; x <= GRID_SIZE; x += gridStep)   /* for all columns */
      {
        glNormal3f   (grid [x][z].nx, grid [x][z].ny, grid [x][z].nz);
        glTexCoord2d (x * texScale, z * texScale);
        glVertex3f   (grid [x][z].x,  grid [x][z].y,  grid [x][z].z);

        glNormal3f   (grid [x][z + gridStep].nx, grid [x][z + gridStep].ny, grid [x][z + gridStep].nz);
        glTexCoord2d (x * texScale, (z + gridStep) * texScale);
        glVertex3f   (grid [x][z + gridStep].x,  grid [x][z + gridStep].y,  grid [x][z + gridStep].z);
      }
    glEnd ();
  }
}





/************************************************************************/
/* draw thesea level as one big polygon.                                */
/* parameters:                                                          */
/* level:     sea level                                                 */
/* size:      size of fractal mountain in x/z direction                 */
/* texRepeat: sea texture repeat count                                  */
/************************************************************************/
void drawSea (GLfloat level, GLfloat size, GLfloat texRepeat)
{
  GLfloat  water [] = {0.2, 0.5, 1.0, 1.0};   /* water colr */

  glMaterialfv  (GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, water);

  if (params [TEXTURE])
  {
    loadTexMipMap ("waves", 256);
  }
  glBegin (GL_POLYGON);              /* draw one big polygon */
    glNormal3d   (0.0, 1.0, 0.0);

    glTexCoord2f (0.0F, 0.0F);
    glVertex3d   (-size / 2, level,  size / 2);

    glTexCoord2f (texRepeat, 0.0F);
    glVertex3d   ( size / 2, level,  size / 2);

    glTexCoord2f (texRepeat, texRepeat);
    glVertex3d   ( size / 2, level, -size / 2);

    glTexCoord2f (0.0F, texRepeat);
    glVertex3d   (-size / 2, level, -size / 2);
  glEnd ();
}





/************************************************************************/
/* handle all window events for the given window.                       */
/* parameters:                                                          */
/* win: pointer to CyberGL window                                       */
/************************************************************************/
void handle_window_events (void *win)
{
  struct IntuiMessage *msg;        /* intuition message */
  int                  done = 0;   /* all done ? */
  struct        Window *window;    /* Intuition window pointer */

  window = getWindow (win);        /* get Intuition window */

  while (!done)                    /* whilenot done */
  {
    Wait (1L << window->UserPort->mp_SigBit);
    while ((!done) && (msg = (struct IntuiMessage *) GetMsg (window->UserPort)))
    {
      switch (msg->Class)
      {
        case IDCMP_CLOSEWINDOW:
          done = 1;                 /* all done */
          break;
        case IDCMP_NEWSIZE:
          resizeGLWindow   (win, window->GZZWidth, window->GZZHeight);
          glClear          (GL_COLOR_BUFFER_BIT);
          drawFractal      (params [STEPS], (GLfloat) params [ROCK_REPEAT]);
          drawSea          ((GLfloat) params [SEA_LEVEL], (GLfloat) params [SIZE],
                            (GLfloat) params [WAVE_REPEAT]);
          break;
      }
      ReplyMsg ((struct Message *) msg);
    }
  }
}




/************************************************************************/
/* set up a directional light source with direction xyz and color rgb.  */
/* parameters:                                                          */
/* x:     x coordinate of light direction                               */
/* y:     y coordinate of light direction                               */
/* z:     z coordinate of light direction                               */
/* r:     red color component of light color                            */
/* g:     green color component of light color                          */
/* b:     blue color component of light color                           */
/* light: symbolic light name (GL_LIGHT0..GL_LIGHT7)                    */
/************************************************************************/
void dirLight (GLdouble x, GLdouble y, GLdouble z,
               GLdouble r, GLdouble g, GLdouble b, GLenum light)
{
  GLfloat pos   [4];    /* virtual light position */
  GLfloat color [4];    /* light color */

  pos   [0] = (GLfloat) x;
  pos   [1] = (GLfloat) y;
  pos   [2] = (GLfloat) z;
  pos   [3] = (GLfloat) 0.0;
  color [0] = (GLfloat) r;
  color [1] = (GLfloat) g;
  color [2] = (GLfloat) b;
  color [3] = (GLfloat) 0.0;

  glEnable  (GL_LIGHTING);                /* enable lighting */
  glEnable  (light);                      /* enable light */
  glLightfv (light, GL_POSITION, pos);    /* set position */
  glLightfv (light, GL_DIFFUSE,  color);  /* set diffuse color */
  glLightfv (light, GL_SPECULAR, color);  /* set specular color */
}





void main (void)
{
  void              *window;                          /* CyberGL window */
  GLfloat            fog [] = {1.0, 1.0, 1.0, 1.0};   /* fog color */
  struct RDArgs     *args;
  char               template [] = "W=WIDTH/K/N,H=HEIGHT/K/N,S=STEPS/K/N,M=MAXHEIGHT/K/N,ROCK/K/N,WAVE/K/N,SIZE/K/N,L=SEALEVEL/K/N,R=RANDOM/K/N,F=FOG/K/S,T=TEXTURE/K/S";
  long              *arglist [11] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL};
  int                count;

  if (args = ReadArgs (template, (long *) arglist, NULL))
  {
    for (count = 0; count < 9; count++)
    {
      if (arglist [count])
      {
        params [count] = *(arglist [count]);
      }
    }
    params [FOG]     = (int) arglist [FOG];
    params [TEXTURE] = (int) arglist [TEXTURE];
    FreeArgs (args);
  }
  window  = openGLWindowTags (params [WIDTH], params [HEIGHT],
    GLWA_Title, "Fractal",
    GLWA_IDCMP, IDCMP_CLOSEWINDOW | IDCMP_NEWSIZE,
    GLWA_CloseGadget, TRUE,
    GLWA_DepthGadget, TRUE,
    GLWA_DragBar,     TRUE,
    GLWA_Activate,    TRUE,
    GLWA_RGBAMode,    GL_TRUE,
    GLWA_SizeGadget,  TRUE,
    GLWA_MinWidth,     320,
    GLWA_MinHeight,    240,
    GLWA_MaxWidth,    1280,
    GLWA_MaxHeight,   1024,
  TAG_DONE);

  if (window)
  {
    glEnable         (GL_DITHER);              /* enable dithering */
    glEnable         (GL_CULL_FACE);           /* enable back face culling */
    glShadeModel     (GL_SMOOTH);              /* enable gouraud shading */
    glEnable         (GL_DEPTH_TEST);          /* enable depth testing */
    glLightModeli    (GL_LIGHT_MODEL_LOCAL_VIEWER, GL_FALSE);
    glMatrixMode     (GL_PROJECTION);
    glPerspective    (45.0, 1.333, 0.5, (GLdouble) params [SIZE] + 2.5);    /* projection */
    glMatrixMode     (GL_MODELVIEW);
    glLookAt         (0.0, 10.0, 10.0,   0.0, 0.0, 0.0,   0.0, 1.0, 0.0);   /* camera position */
    glTexParameteri  (GL_TEXTURE_2D,  GL_TEXTURE_MIN_FILTER, GL_LINEAR);    /* minification filter */
    glTexParameteri  (GL_TEXTURE_2D,  GL_TEXTURE_MAG_FILTER, GL_LINEAR);    /* magnification filter */
    glTexParameteri  (GL_TEXTURE_2D,  GL_TEXTURE_WRAP_S, GL_REPEAT);        /* texture repeating */
    glTexParameteri  (GL_TEXTURE_2D,  GL_TEXTURE_WRAP_T, GL_REPEAT);        /* texture repeating */
    glTexEnvi        (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);    /* modulate color */
    glFogi           (GL_FOG_MODE,    GL_LINEAR);                           /* linear fog mode */
    glFogf           (GL_FOG_START,   (GLfloat)  0.0);                      /* fog minimum distance */
    glFogf           (GL_FOG_END,     (GLfloat) 20.0);                      /* fog maximum distance */
    glFogfv          (GL_FOG_COLOR,   fog);                                 /* fog color */

    if (params [FOG])
    {
      glClearColor (fog [0], fog [1], fog [2], fog [3]);
      glEnable     (GL_FOG);     /* enable fog */
    }
    else
    {
      glDisable (GL_FOG);        /* enable fog */
    }
    if (params [TEXTURE])
    {
      glEnable  (GL_TEXTURE_2D); /* enable texturing */
    }
    else
    {
      glDisable (GL_TEXTURE_2D); /* disable texturing */
    }
    glClear          (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    dirLight         (1.0, 1.0, 1.0,   1.0, 1.0, 1.0,  GL_LIGHT0);          /* setup light */

    genesis          (params [SEED], params [STEPS], (GLfloat) params [MAX_HEIGHT], (GLfloat) params [SIZE]);
    drawFractal      (params [STEPS], (GLfloat) params [ROCK_REPEAT]);
    drawSea          ((GLfloat) params [SEA_LEVEL], (GLfloat) params [SIZE], (GLfloat) params [WAVE_REPEAT]);

    handle_window_events (window);

    closeGLWindow (window);
  }
}
