// graphac.c
//
// David Simpson
// http://www.ugcs.caltech.edu/~dsimpson

// GraphAC takes a map file generated by mapac and produces a pretty picture.
// More specifically, for each data point in the map file, the z data is used
// to calculate a normal vector to that point, a dot product is performed between
// the normalized normal vector and the normalized light vector, and this value
// is then noramlized, scaled by LIGHTCORRECTION, and has AMBIENTLIGHT added to
// it to determine a final lighting scalar.  This lighting scalar is then applied
// to the base color for the topography type for that data point which generates
// a single colored pixel.  A basic knowledge of lighting is assumed, so please
// don't ask me questions about the math.
//
// The pixels are all output into a raw graphics file.  I use Paint Shop Pro to
// read these raw data files.  If you use Paint Shop Pro to view the pictures
// (and subsequently save them in some more user-friendly format), open them
// as the RAW type, and use the following options:
//    WIDTH: 2041
//    HEIGHT: 2041
//    Color Channels: Three Channel (RGB)
//    File Structure:
//        Header size: 0
//        Interleaved (RGB RGB...)
//        Order: RGB
// If you wish that graphac output a more user-friendly file format, go right
// ahead.
//
// See mapac.c if you want to create a map file from your cell.dat.  This is not
// done here.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#define uchar  unsigned char
#define ushort unsigned short
#define uint   unsigned int
#define ulong  unsigned long

#define LANDSIZE 2041

// The following constants change how the lighting works.  It is easy to wash out
// the bright whites of the snow, so be careful.

// Incresing COLORCORRECTION makes the base color more prominant.
#define COLORCORRECTION  70.0

// Increasing LIGHTCORRECTION increases the contrast between steep and flat slopes.
#define LIGHTCORRECTION   2.25

// Increasing AMBIENTLIGHT makes everyting brighter.
#define AMBIENTLIGHT     64.0

typedef struct {
  ushort type;
  uchar  z;
  uchar  used;
} landData;

// This vector reprsents a light coming from the northwest corner of the map.
// Pretend the sun is on the horizon at the northwest corner.
double lightVector[3] = {
  -1.0, -1.0, 0.0
};

// These color values deserve the most comment in this file.  I edited my cell.dat
// to create strips of each land type.  A road passes over the end of each strip.
// I then took screenshots of each of the strips.  There were four strips in each
// screenshot.  I then cut out a piece of each of the strips and made a seperate
// image of each one.  Using the histogram feature in Paint Shop Pro, I found the
// average red, green, and blue values for each land type.  These are the numbers
// found below.  The fourth number in each group below is the average luminance
// of a control patch in each screenshot, in this case the road.  The control is
// needed since the screenshots were taken at slightly different times of the day
// and thus the general brightness of the scene is different.
//
// The last entry is the color for the roads.
uchar landColor[33][4] = {
  {84, 67, 37, 110},
  {56, 66, 21, 110},
  {147, 154, 167, 110},
  {51, 69, 10, 110},
  {71, 37, 7, 113},
  {54, 34, 23, 113},
  {39, 35, 43, 113},
  {89, 65, 34, 113},
  {57, 41, 9, 113},
  {44, 77, 2, 113},
  {144, 99, 50, 113},
  {132, 132, 97, 113},
  {138, 93, 53, 114},
  {111, 68, 41, 114},
  {75, 85, 59, 114},
  {208, 219, 233, 114},
  {62, 108, 131, 130},
  {20, 79, 56, 130},
  {31, 80, 100, 130},
  {44, 76, 94, 130},
  {43, 59, 83, 130},
  {34, 47, 6, 130},
  {62, 108, 131, 130},
  {30, 38, 26, 130},
  {100, 79, 43, 130},
  {45, 33, 33, 130},
  {72, 72, 70, 130},
  {197, 227, 242, 130},
  {100, 79, 43, 130},
  {100, 79, 43, 130},
  {100, 79, 43, 130},
  {100, 79, 43, 130},
  {138, 130, 112, 130}
};

landData land[LANDSIZE][LANDSIZE];
uchar    topo[LANDSIZE][LANDSIZE][3];

void PrintUsage()
{
  printf("usgae:\n");
  printf("graphac <MAP FILE> <RAW GRAPHICS FILE\n");
}

int main(int argc, char *argv[])
{
  FILE   *mapFile, *topoFile;
  int    x, y;
  int    i;
  ushort type;
  double color, light;
  double v[3];
  
  if (argc != 3) {
    printf("ERROR: Incorrect number of arguments!\n");
    PrintUsage();
    return -1;
  }

  // Read map file
  mapFile = fopen(argv[1], "rb");
  if (mapFile == NULL) {
    printf("ERROR: File %s could not be opened!\n", argv[1]);
    return -1;
  }
  fread(land, sizeof(landData), LANDSIZE * LANDSIZE, mapFile);
  fclose(mapFile);

  topoFile = fopen(argv[2], "wb");
  if (topoFile == NULL) {
    printf("ERROR: File %s could not be opened!\n", argv[2]);
    return -1;
  }

  for (y = 0; y < LANDSIZE; y++) {
    for (x = 0; x < LANDSIZE; x++) {
      if (land[y][x].used) {
        // Calculate normal by using surrounding z values, if they exist
        v[0] = 0.0;
        v[1] = 0.0;
        v[2] = 0.0;
        if ((x < LANDSIZE - 1) && (y < LANDSIZE - 1)) {
          if (land[y][x + 1].used && land[y + 1][x].used) {
            v[0] -= land[y][x + 1].z - land[y][x].z;
            v[1] -= land[y + 1][x].z - land[y][x].z;
            v[2] += 12.0;
          }
        }
        if ((x > 0) && (y < LANDSIZE - 1)) {
          if (land[y][x - 1].used && land[y + 1][x].used) {
            v[0] += land[y][x - 1].z - land[y][x].z;
            v[1] -= land[y + 1][x].z - land[y][x].z;
            v[2] += 12.0;
          }
        }
        if ((x > 0) && (y > 0)) {
          if (land[y][x - 1].used && land[y - 1][x].used) {
            v[0] += land[y][x - 1].z - land[y][x].z;
            v[1] += land[y - 1][x].z - land[y][x].z;
            v[2] += 12.0;
          }
        }
        if ((x < LANDSIZE - 1) && (y > 0)) {
          if (land[y][x + 1].used && land[y - 1][x].used) {
            v[0] -= land[y][x + 1].z - land[y][x].z;
            v[1] += land[y - 1][x].z - land[y][x].z;
            v[2] += 12.0;
          }
        }

        // Check for road bit(s)
        if ((land[y][x].type & 0x0003) != 0)
          type = 32;
        else
          type = (land[y][x].type & 0x00FF) >> 2;

        // Calculate lighting scalar
        light = (((lightVector[0] * v[0] + lightVector[1] * v[1] + lightVector[2] * v[2]) /
            sqrt((lightVector[0] * lightVector[0] + lightVector[1] * lightVector[1] + lightVector[2] * lightVector[2]) *
            (v[0] * v[0] + v[1] * v[1] + v[2] * v[2]))) * 128.0 + 128.0) * LIGHTCORRECTION + AMBIENTLIGHT;

        // Apply lighting scalar to base colors
        for (i = 0; i < 3; i++) {
          color = (landColor[type][i] * COLORCORRECTION / landColor[type][3]) * light / 256.0;
          if (color > 255.0)
            topo[y][x][i] = 255;
          else if (color < 0.0)
            topo[y][x][i] = 0;
          else
            topo[y][x][i] = (uchar)color;
        }
      }
      else {
        // If data is not present for a point on the map, the resultant pixel is green
        topo[y][x][0] = 0;
        topo[y][x][1] = 0xFF;
        topo[y][x][2] = 0;
      }
    }
  }

  // Write raw picture data
  fwrite(topo, sizeof(uchar), LANDSIZE * LANDSIZE * 3, topoFile);
  fclose(topoFile);

  return 0;
}
