/* Surface
 * (C) Copyright 1995 by Ashton Mason (amason@cs.uct.ac.za)
 *
 * Permission to use, modify, copy and distribute this source code for
 * any purpose and without fee is granted, provided that this copyright
 * notice appears in all copies and supporting documentation, and that
 * credit is given where due. This source code is provided "as is" with
 * no express or implied warranty.
 */


#include "headers.h"


	// constructor


shader::shader(void)
{
  intensity = NULL;

  numlights = 0;

  ka = 0.2;
  kd = 0.8;
  ks = 0.0;
}


	// constructor


shader::~shader(void)
{
  delete intensity;
}


	// precalculates all the vertex intensities


int shader::shade()
{
  float za, zb, zc, zd, ze, zf, zg;
  float normx, normy;
  float normlen;
  vector light, h;
  float factor, diffuse, specular;

  intensity = new unsigned char[field.gridsize * field.gridsize];
  if (!intensity)
  {
    error("not enough memory for gouraud shading");
  }

	// use bresenham to draw bar graph

  const int hashes = field.gridsize > HASHMARKS ? HASHMARKS : field.gridsize;

	// print percentages

  printf("0%%");
  for (int n = hashes - 6; n; n--)
  {
    printf(" ");
  }
  printf("100%%\n");

  const int twodx = field.gridsize * 2;
  const int twody = hashes * 2;
  int error = -field.gridsize;

  for (int y = 0; y < field.gridsize; y++)
  {
    error += twody;			// e = e + dy/dx
    if (error >= 0)
    {
      printf("#");
      fflush(stdout);
      error -= twodx;			// e = e - 1
    }

    for (int x = 0; x < field.gridsize; x++)
    {
      field.getNeighbours(x, y, za, zb, zc, zd, ze, zf, zg);

	// average of normals

      normx = (za - zb + zc + zc - ze - ze + zf - zg) / 6.0;
      normy = (zd - za + ze - zb + zg + zg - zd + zf - zc - za) / 6.0;

	// precalculate length of normal

      normlen = sqrt(normx * normx + normy * normy + 1.0);

      factor = 0.0;

	// for each light

      for (int lightno = numlights - 1; lightno >= 0; lightno--)
      {

	light = lights[lightno];

	// calculate diffuse reflection
	// normal has z component of 1, before normalization

	diffuse = (light.x * normx + light.y * normy + light.z)	/ normlen;

	if (diffuse > 0)
	  factor += kd * diffuse;

	// specular reflection

	h.x = light.x + eye.x - x;
	h.y = light.y + eye.y - y;
	h.z = light.z + eye.z - zd;

	specular = (h.x * normx + h.y * normy + h.z) / (h.length() * normlen);

	// phong exponent

	if (specular > 0)
	  factor += ks * pow(specular, phong);
      }

	// take average of all lights

      factor /= numlights;

	// add in the ambient part

      factor += ka;

	// store in array, biased to byte value

      intensity[y * field.gridsize + x] = (unsigned char)(SHADESCALE * factor);
    }
  }

  printf("\n");
  return 1;
}


	// returns the flat shaded intensity of the given point
	// assumes normal has been normalized


float shader::flat(vector &point, vector &normal)
{
  vector light, h;
  float diffuse, specular;

  float factor = 0.0;

	// for each light

  for (int lightno = numlights - 1; lightno >= 0; lightno--)
  {

    light = lights[lightno];

	// calculate diffuse reflection

    diffuse = dot(light, normal);

    if (diffuse > 0)
      factor += kd * diffuse;

	// specular reflection

    h = light + eye - point;

    specular = dot(h, normal) / h.length();

	// phong exponent

    if (specular > 0)
      factor += ks * pow(specular, phong);
  }

	// take average of all lights
	// add in the ambient part

  return (factor / numlights + ka);
}


	// returns the gouraud shaded intensity of the given point


float shader::gouraud(vector &point)
{
  int Ia, Ib, Iu;
  float Ip;

  int x = (int) point.x;
  int y = (int) point.y;
  float dx = point.x - x;
  float dy = point.y - y;

  int xplus = x + (x < field.clipsize);
  int yplus = y + (y < field.clipsize);

  if (dy > dx)				// upper triangle
  {
    Ia = intensity[y * field.gridsize + x];
    Ib = intensity[yplus * field.gridsize + xplus];
    Iu = intensity[yplus * field.gridsize + x];
    Ip = (Ia + (Iu - Ia) * dy + (Ib - Iu) * dx) / (float) SHADESCALE;
  }
  else					// lower triangle
  {
    Ia = intensity[yplus * field.gridsize + xplus];
    Ib = intensity[y * field.gridsize + x];
    Iu = intensity[y * field.gridsize + xplus];
    Ip = (Ia + (Iu - Ia) * (1.0 - dy) + (Ib - Iu) * (1.0 - dx)) / (float) SHADESCALE;
  }

  return Ip;
}


	// create a new light at a certain position


int shader::createLight(vector dirn)
{
  dirn.normalize();

  if (numlights < MAXLIGHTS)
  {
    lights[numlights++] = dirn;
    return 1;
  }

  return 0;
}


	// get the position of a light


vector shader::getLight(int n)
{
  return(lights[n]);
}


	// destroy all the lights


int shader::destroyLights()
{
  numlights = 0;
  return 1;
}


