/****************************************************************************
*                   lighting.c
*
*  This module calculates lighting properties like ambient, diffuse, specular,
*  reflection, refraction, etc.
*
*  from Persistence of Vision Raytracer
*  Copyright 1993 Persistence of Vision Team
*---------------------------------------------------------------------------
*  NOTICE: This source code file is provided so that users may experiment
*  with enhancements to POV-Ray and to port the software to platforms other
*  than those supported by the POV-Ray Team.  There are strict rules under
*  which you are permitted to use this file.  The rules are in the file
*  named POVLEGAL.DOC which should be distributed with this file. If
*  POVLEGAL.DOC is not available or for more info please contact the POV-Ray
*  Team Coordinator by leaving a message in CompuServe's Graphics Developer's
*  Forum.  The latest version of POV-Ray may be found there as well.
*
* This program is based on the popular DKB raytracer version 2.12.
* DKBTrace was originally written by David K. Buck.
* DKBTrace Ver 2.0-2.12 were written by David K. Buck & Aaron A. Collins.
*
*****************************************************************************/

#include "frame.h"
#include "vector.h"
#include "povproto.h"

extern int Trace_Level;
extern FRAME Frame;
extern OBJECT *Root_Object;
extern unsigned int Options;
extern int Use_Slabs;
extern unsigned long Quality_Flags;
extern int Shadow_Test_Flag;
extern long Shadow_Ray_Tests, Shadow_Rays_Succeeded, Shadow_Cache_Hits;
extern long Reflected_Rays_Traced, Refracted_Rays_Traced;
extern long Transmitted_Rays_Traced;

extern short *hashTable;
extern unsigned short crctab[256];
#define rand3d(a,b) crctab[(int)(hashTable[(int)(hashTable[(int)((a)&0xfff)]^(b))&0xfff])&0xff]

#define COORDINATE_LIMIT 1.0e17
#define BLACK_LEVEL 0.003

/* "Small_Tolerance" is just too tight for higher order polynomial equations.
   this value should probably be a variable of some sort, but for now just
   use a reasonably small value.  If people render real small objects real
   close to each other then there may be some shading problems.  Otherwise
   having SHADOW_TOLERANCE as large as this won't affect images. */
#define SHADOW_TOLERANCE 1.0e-3

static void do_light PARAMS((LIGHT_SOURCE *Light_Source,
DBL *Light_Source_Depth, RAY *Light_Source_Ray, VECTOR *IPoint,
COLOUR *Light_Colour));
static int do_blocking PARAMS((INTERSECTION *Local_Intersection,
COLOUR *Light_Colour, ISTACK *Local_Stack));
static void do_phong PARAMS((FINISH *Finish, RAY *Light_Source_Ray,
VECTOR *Eye, VECTOR *Layer_Normal, COLOUR *Colour, COLOUR *Light_Colour,
COLOUR *Layer_Pigment_Colour));
static void do_specular PARAMS((FINISH *Finish, RAY *Light_Source_Ray,
VECTOR *REye, VECTOR *Layer_Normal, COLOUR *Colour, COLOUR *Light_Colour,
COLOUR *Layer_Pigment_Colour));
static void do_diffuse PARAMS((FINISH *Finish, RAY *Light_Source_Ray,
VECTOR *Layer_Normal, COLOUR *Colour, COLOUR *Light_Colour,
COLOUR *Layer_Pigment_Colour, DBL Attenuation));
static void Block_Area_Light PARAMS((LIGHT_SOURCE *Light_Source,
DBL Light_Source_Depth, RAY *Light_Source_Ray_Ptr, VECTOR *IPoint,
COLOUR *Light_Colour, int u1, int v1, int u2, int v2, int Level));
static void Block_Point_Light PARAMS((LIGHT_SOURCE *Light_Source,
DBL Light_Source_Depth, RAY *Light_Source_Ray_Ptr, COLOUR *Light_Colour));

static void Block_Point_Light (Light_Source, Light_Source_Depth, Light_Source_Ray_Ptr, Light_Colour)
LIGHT_SOURCE *Light_Source;
DBL Light_Source_Depth;
RAY *Light_Source_Ray_Ptr;
COLOUR *Light_Colour;
  {
  OBJECT *Blocking_Object;
  int Quit_Looking, Not_Found_Shadow, Cache_Me;
  INTERSECTION *Local_Intersection, Bounded_Intersection;
  ISTACK *Local_Stack;
  RAY Local_Ray;
  RAY Light_Source_Ray;

  Light_Source_Ray = *Light_Source_Ray_Ptr;

  Local_Stack = open_istack ();
  Quit_Looking = FALSE;

  /* Test the cached object first */
  /* Made changes so that semi-transparent objects never get cached */
  if (Light_Source->Shadow_Cached_Object != NULL) 
    {
    Shadow_Ray_Tests++;

    if (Ray_In_Bounds (&Light_Source_Ray, Light_Source->Shadow_Cached_Object->Bound))
      {
      if (All_Intersections (Light_Source->Shadow_Cached_Object, &Light_Source_Ray, Local_Stack))
        while ((Local_Intersection=pop_entry(Local_Stack)) != NULL)
          {
          if ((!Local_Intersection->Object->No_Shadow_Flag) && 
            (Local_Intersection->Depth < Light_Source_Depth-Small_Tolerance) && 
            (Local_Intersection->Depth > SHADOW_TOLERANCE))
            if (do_blocking(Local_Intersection, Light_Colour, Local_Stack))
              {
              Quit_Looking = TRUE;
              Shadow_Cache_Hits++;
              break;
              }
          }
      }
    }

  if (Quit_Looking) 
    {
    close_istack (Local_Stack);
    return;
    }

  Not_Found_Shadow = TRUE;   
  Cache_Me = FALSE;
  if (!Use_Slabs)
    {
    for (Blocking_Object = Frame.Objects;
    Blocking_Object != NULL;
    Blocking_Object = Blocking_Object->Sibling)
      {
      if (Blocking_Object == Light_Source->Shadow_Cached_Object)
        continue;

      Shadow_Ray_Tests++;

      if (!Ray_In_Bounds (&Light_Source_Ray, Blocking_Object->Bound))
        continue;

      if (!All_Intersections (Blocking_Object, &Light_Source_Ray, Local_Stack))
        continue;

      while ((Local_Intersection = pop_entry(Local_Stack)) != NULL)
        if ((!Local_Intersection->Object->No_Shadow_Flag) && 
          (Local_Intersection->Depth < Light_Source_Depth-Small_Tolerance) && 
          (Local_Intersection->Depth > SHADOW_TOLERANCE))
          {
          if (do_blocking(Local_Intersection, Light_Colour, Local_Stack))
            {
            Cache_Me = Not_Found_Shadow;
            Quit_Looking = TRUE;
            break; /* from while */
            }
          Not_Found_Shadow = FALSE;
          }
      if (Quit_Looking)
        break; /* from for */
      }
    }
  else   /* Use bounding slabs to look for shadows */
    {
    Local_Ray = Light_Source_Ray;
    while (!Quit_Looking) 
      {
      Shadow_Ray_Tests++;
      Local_Ray.Quadric_Constants_Cached = 0;
      Bounded_Intersection.Depth = Light_Source_Depth;
      if (Bounds_Intersect(Root_Object, &Local_Ray, &Bounded_Intersection,
        &Blocking_Object)) 
        {
        if (Bounded_Intersection.Depth > Light_Source_Depth) 
          break; /* Intersection was beyond the light */

        if (!(Bounded_Intersection.Object->No_Shadow_Flag))
          if (Blocking_Object != Light_Source->Shadow_Cached_Object)
            {
            Shadow_Rays_Succeeded++;
            Filter_Shadow_Ray(&Bounded_Intersection, Light_Colour);
            if ((fabs(Light_Colour->Red) < BLACK_LEVEL) &&
              (fabs(Light_Colour->Green) < BLACK_LEVEL) &&
              (fabs(Light_Colour->Blue) < BLACK_LEVEL))
              {
              Cache_Me = Not_Found_Shadow;
              Quit_Looking = TRUE;
              break; /* from while */
              }
            }
        /* Move the ray to the point of intersection, plus some */
        Light_Source_Depth -= Bounded_Intersection.Depth;
        Local_Ray.Initial = Bounded_Intersection.IPoint;
        Not_Found_Shadow = FALSE;
        }
      else /* No intersections in the direction of the ray */
        {
        break;
        }
      } /*endwhile*/
    } /*endelse*/
  if (Cache_Me) 
    Light_Source->Shadow_Cached_Object = Blocking_Object;
  close_istack (Local_Stack);
  }


static void Block_Area_Light (Light_Source, Light_Source_Depth,
Light_Source_Ray_Ptr,IPoint, Light_Colour, u1, v1, u2, v2, Level)
LIGHT_SOURCE *Light_Source;
DBL Light_Source_Depth;
RAY *Light_Source_Ray_Ptr;
VECTOR *IPoint;
COLOUR *Light_Colour;
int u1, v1, u2, v2, Level;
  {
  COLOUR Sample_Colour[4], Dummy_Colour;
  VECTOR Center_Save, NewAxis1, NewAxis2;
  int i, j, u, v, New_u1, New_v1, New_u2, New_v2;

  DBL Jitter_u, Jitter_v, ScaleFactor;
  RAY Light_Source_Ray;

  Light_Source_Ray = *Light_Source_Ray_Ptr;

  /* First call, initialize */
  if (u1 == 0 && v1 == 0 && u2 == 0 && v2 == 0) 
    {
    /* Flag uncalculated points with a negative value for Red */
    for (i = 0; i < Light_Source->Area_Size1; i++) 
      {
      for (j = 0; j < Light_Source->Area_Size2; j++)
        Light_Source->Light_Grid[i][j].Red = -1.0;
      }

    u1 = 0;
    v1 = 0;
    u2 = Light_Source->Area_Size1 - 1;
    v2 = Light_Source->Area_Size2 - 1;
    }

  /* Save the light source center since we'll be fiddling with it */
  Center_Save=Light_Source->Center;

  /* Sample the four corners of the region */
  for (i = 0; i < 4; i++) 
    {
    switch (i) 
    {
    case 0: 
      u = u1; v = v1; break;
    case 1: 
      u = u2; v = v1; break;
    case 2: 
      u = u1; v = v2; break;
    case 3: 
      u = u2; v = v2; break;
    }

    if (Light_Source -> Light_Grid[u][v].Red >= 0.0)
      /* We've already calculated this point, reuse it */
      Sample_Colour[i]=Light_Source->Light_Grid[u][v];
    else 
      {
      Jitter_u = (DBL)u;
      Jitter_v = (DBL)v;

      if (Light_Source -> Jitter) 
        {
        Jitter_u += (rand() % 4096)/4096.0 - 0.5;
        Jitter_v += (rand() % 4096)/4096.0 - 0.5;
        /*
  Not sure if    jx = IPoint->x + 100*u;
  this works    jy = IPoint->y + 100*v;
  yet
        Jitter_u += ((rand3d(jx, jy) & 0x7FFF)/32768.0) - 0.5;
        Jitter_v += ((rand3d(jx+10, jy+10) & 0x7FFF)/32768.0) - 0.5;
*/
        }

      if (Light_Source -> Area_Size1 > 1) 
        {
        ScaleFactor = Jitter_u/(DBL)(Light_Source->Area_Size1 - 1) - 0.5;
        VScale (NewAxis1, Light_Source->Axis1, ScaleFactor)
          }
      else
        Make_Vector (&NewAxis1, 0.0, 0.0, 0.0);

      if (Light_Source -> Area_Size2 > 1) 
        {
        ScaleFactor = Jitter_v/(DBL)(Light_Source->Area_Size2 - 1) - 0.5;
        VScale (NewAxis2, Light_Source->Axis2, ScaleFactor)
        }
      else
        Make_Vector (&NewAxis2, 0.0, 0.0, 0.0);

      Light_Source->Center=Center_Save;
      VAdd  (Light_Source->Center, Light_Source->Center, NewAxis1);
      VAdd  (Light_Source->Center, Light_Source->Center, NewAxis2);

      /* Recalculate the light source ray but not the colour */
      do_light (Light_Source, &Light_Source_Depth, &Light_Source_Ray,
        IPoint, &Dummy_Colour);

      Sample_Colour[i]=*Light_Colour;

      Block_Point_Light (Light_Source, Light_Source_Depth,
        &Light_Source_Ray, &Sample_Colour[i]);

      Light_Source->Light_Grid[u][v]=Sample_Colour[i];
      }
    }

  Light_Source->Center=Center_Save;

  if ( (u2 - u1 > 1 || v2 - v1 > 1) && (Level < Light_Source -> Adaptive_Level ||
    Colour_Distance (&Sample_Colour[0], &Sample_Colour[1]) > 0.1 ||
    Colour_Distance (&Sample_Colour[1], &Sample_Colour[3]) > 0.1 ||
    Colour_Distance (&Sample_Colour[3], &Sample_Colour[2]) > 0.1 ||
    Colour_Distance (&Sample_Colour[2], &Sample_Colour[0]) > 0.1) )
    {
    for (i = 0; i < 4; i++) 
      {
      switch (i) 
      {
      case 0: 
        New_u1 = u1;
        New_v1 = v1;
        New_u2 = (int)floor ((u1 + u2)/2.0);
        New_v2 = (int)floor ((v1 + v2)/2.0);
        break;

      case 1: 
        New_u1 = (int)ceil  ((u1 + u2)/2.0);
        New_v1 = v1;
        New_u2 = u2;
        New_v2 = (int)floor ((v1 + v2)/2.0);
        break;

      case 2: 
        New_u1 = u1;
        New_v1 = (int)ceil  ((v1 + v2)/2.0);
        New_u2 = (int)floor ((u1 + u2)/2.0);
        New_v2 = v2;
        break;

      case 3: 
        New_u1 = (int)ceil ((u1 + u2)/2.0);
        New_v1 = (int)ceil ((v1 + v2)/2.0);
        New_u2 = u2;
        New_v2 = v2;
        break;
      }

      /* Recalculate the light source ray but not the colour */
      do_light (Light_Source, &Light_Source_Depth, &Light_Source_Ray,
        IPoint, &Dummy_Colour);

      Sample_Colour[i]=*Light_Colour;

      Block_Area_Light (Light_Source, Light_Source_Depth,
        &Light_Source_Ray, IPoint,
        &Sample_Colour[i],
        New_u1, New_v1, New_u2, New_v2, Level+1);
      }
    }

  /* Add up the light contributions */
  Make_Colour (Light_Colour, 0.0, 0.0, 0.0);

  for (i = 0; i < 4; i++) 
    {
    Scale_Colour (&Sample_Colour[i], &Sample_Colour[i], 0.25);
    Add_Colour (Light_Colour, Light_Colour, &Sample_Colour[i]);
    }
  }

static void do_light(Light_Source, Light_Source_Depth, Light_Source_Ray, IPoint, Light_Colour)
LIGHT_SOURCE *Light_Source;
DBL *Light_Source_Depth;
RAY *Light_Source_Ray;
VECTOR *IPoint;
COLOUR *Light_Colour;
  {
  DBL Attenuation = 1.0;

  /* Get the light source colour. */
  *Light_Colour = Light_Source->Colour;

  Light_Source_Ray->Initial = *IPoint;
  Light_Source_Ray->Quadric_Constants_Cached = FALSE;

  VSub (Light_Source_Ray->Direction,
    Light_Source->Center,
    *IPoint);

  VLength (*Light_Source_Depth, Light_Source_Ray->Direction);

  VScale (Light_Source_Ray->Direction, Light_Source_Ray->Direction,
    1.0/(*Light_Source_Depth));

  Attenuation = Attenuate_Light(Light_Source, Light_Source_Ray);

  /* Now scale the color by the attenuation */
  Light_Colour->Red   *= Attenuation;
  Light_Colour->Green *= Attenuation;
  Light_Colour->Blue  *= Attenuation;


  return;
  }

static int do_blocking(Local_Intersection, Light_Colour, Local_Stack)
INTERSECTION *Local_Intersection;
COLOUR *Light_Colour;
ISTACK *Local_Stack;
  {
  Shadow_Rays_Succeeded++;

  Filter_Shadow_Ray (Local_Intersection, Light_Colour);

  if ((fabs(Light_Colour->Red) < BLACK_LEVEL) && 
    (fabs(Light_Colour->Green) < BLACK_LEVEL) && 
    (fabs(Light_Colour->Blue) < BLACK_LEVEL)) 
    {
    while ((Local_Intersection = pop_entry(Local_Stack)) != NULL)
      {
      }
    return(TRUE);
    }
  return(FALSE);
  }

static void do_phong(Finish, Light_Source_Ray, Eye, Layer_Normal, Colour, Light_Colour, Layer_Pigment_Colour)
FINISH *Finish;
RAY *Light_Source_Ray;
VECTOR *Layer_Normal, *Eye;
COLOUR *Colour, *Light_Colour, *Layer_Pigment_Colour;
  {
  DBL Cos_Angle_Of_Incidence, Normal_Length, Intensity;
  VECTOR Local_Normal, Normal_Projection, Reflect_Direction;

  VDot(Cos_Angle_Of_Incidence, *Eye, *Layer_Normal);

  if (Cos_Angle_Of_Incidence < 0.0)
    {
    Local_Normal = *Layer_Normal;
    Cos_Angle_Of_Incidence = -Cos_Angle_Of_Incidence;
    }
  else
    {
    VScale (Local_Normal, *Layer_Normal, -1.0);
    }

  VScale (Normal_Projection, Local_Normal, Cos_Angle_Of_Incidence);
  VScale (Normal_Projection, Normal_Projection, 2.0);
  VAdd (Reflect_Direction, *Eye, Normal_Projection);

  VDot (Cos_Angle_Of_Incidence, Reflect_Direction, Light_Source_Ray->Direction);
  VLength (Normal_Length, Light_Source_Ray->Direction);

  if (Normal_Length == 0.0)
    Cos_Angle_Of_Incidence = 0.0;
  else 
    Cos_Angle_Of_Incidence /= Normal_Length;

  if (Cos_Angle_Of_Incidence < 0.0)
    Cos_Angle_Of_Incidence = 0;

  if (Finish->Phong_Size != 1.0)
    Intensity = pow(Cos_Angle_Of_Incidence, Finish->Phong_Size);
  else
    Intensity = Cos_Angle_Of_Incidence;

  Intensity *= Finish->Phong;

  if (Finish->Metallic_Flag) 
    {
    Colour->Red+=Intensity*(Layer_Pigment_Colour->Red)*(Light_Colour->Red);    
    Colour->Green+=Intensity*(Layer_Pigment_Colour->Green)*(Light_Colour->Green);
    Colour->Blue+=Intensity*(Layer_Pigment_Colour->Blue)*(Light_Colour->Blue);  
    }
  else 
    {
    Colour->Red+=Intensity*(Light_Colour->Red);
    Colour->Green+=Intensity*(Light_Colour->Green);
    Colour->Blue+=Intensity*(Light_Colour->Blue);  
    }
  }

static void do_specular(Finish, Light_Source_Ray, REye, Layer_Normal, Colour, Light_Colour, Layer_Pigment_Colour)
FINISH *Finish;
RAY *Light_Source_Ray;
VECTOR *Layer_Normal, *REye;
COLOUR *Colour, *Light_Colour, *Layer_Pigment_Colour;
  {
  DBL Cos_Angle_Of_Incidence, Normal_Length, Intensity, Halfway_Length;
  VECTOR Halfway;

  VHalf (Halfway, *REye, Light_Source_Ray->Direction);
  VLength (Normal_Length, *Layer_Normal);
  VLength (Halfway_Length, Halfway);
  VDot (Cos_Angle_Of_Incidence, Halfway, *Layer_Normal);

  if (Normal_Length == 0.0 || Halfway_Length == 0.0)
    Cos_Angle_Of_Incidence = 0.0;
  else
    Cos_Angle_Of_Incidence /= (Normal_Length * Halfway_Length);

  if (Cos_Angle_Of_Incidence < 0.0)
    Cos_Angle_Of_Incidence = 0.0;


  if (Finish->Roughness != 1.0)
    Intensity = pow(Cos_Angle_Of_Incidence, Finish->Roughness);
  else
    Intensity = Cos_Angle_Of_Incidence;

  Intensity *= Finish->Specular;
  if (Finish->Metallic_Flag) 
    {
    Colour->Red+=Intensity*(Layer_Pigment_Colour->Red)*(Light_Colour->Red);    
    Colour->Green+=Intensity*(Layer_Pigment_Colour->Green)*(Light_Colour->Green);
    Colour->Blue+=Intensity*(Layer_Pigment_Colour->Blue)*(Light_Colour->Blue);  
    }
  else 
    {
    Colour->Red+=Intensity*(Light_Colour->Red);
    Colour->Green+=Intensity*(Light_Colour->Green);
    Colour->Blue+=Intensity*(Light_Colour->Blue);
    }
  }

static void do_diffuse(Finish, Light_Source_Ray, Layer_Normal, Colour, Light_Colour, Layer_Pigment_Colour, Attenuation)
FINISH *Finish;
RAY *Light_Source_Ray;
VECTOR *Layer_Normal;
COLOUR *Colour, *Light_Colour, *Layer_Pigment_Colour;
DBL Attenuation;
  {
  DBL Cos_Angle_Of_Incidence, Intensity;

  VDot (Cos_Angle_Of_Incidence, *Layer_Normal, Light_Source_Ray->Direction);
  if (Cos_Angle_Of_Incidence < 0.0)
    Cos_Angle_Of_Incidence = -Cos_Angle_Of_Incidence;

  if (Finish->Brilliance != 1.0)
    Intensity = pow(Cos_Angle_Of_Incidence, Finish->Brilliance);
  else
    Intensity = Cos_Angle_Of_Incidence;

  Intensity *= Finish->Diffuse * Attenuation;

  if (Finish->Crand > 0.0)
    Intensity -= ((rand()&0x7FFF)/(DBL) 0x7FFF) * Finish->Crand;

  Colour->Red += Intensity * (Layer_Pigment_Colour->Red) * (Light_Colour->Red);
  Colour->Green += Intensity * (Layer_Pigment_Colour->Green) * (Light_Colour->Green);
  Colour->Blue += Intensity * (Layer_Pigment_Colour->Blue) * (Light_Colour->Blue);
  return;
  }

/* Given a 3d point and a pigment, accumulate colour from that layer */
/* Formerly called "Colour_At" */
void Add_Pigment (Colour, Pigment, IPoint)
COLOUR *Colour;
PIGMENT *Pigment;
VECTOR *IPoint;
  {
  register DBL x, y, z;
  VECTOR TPoint,PTurbulence;

  if (Pigment->Trans != NULL) 
    MInvTransPoint (&TPoint, IPoint, Pigment->Trans);
  else 
    TPoint = *IPoint;

  x = TPoint.x;
  y = TPoint.y;
  z = TPoint.z;
  if(Pigment->Type != WOOD_PIGMENT &&
    Pigment->Type != MARBLE_PIGMENT &&
    Pigment->Type != NO_PIGMENT &&
    Pigment->Type != COLOUR_PIGMENT &&
    /*      Pigment->Type != SPOTTED_PIGMENT && */ /*maybe?*/
    Pigment->Flags & HAS_TURB)
    {
    DTurbulence (&PTurbulence, x, y, z,
      Pigment->omega,Pigment->lambda,Pigment->Octaves);
    x += PTurbulence.x * Pigment->Turbulence.x;
    y += PTurbulence.y * Pigment->Turbulence.y;
    z += PTurbulence.z * Pigment->Turbulence.z;
    }


  if (x > COORDINATE_LIMIT)
    x = COORDINATE_LIMIT;
  else
    if (x < -COORDINATE_LIMIT)
      x = -COORDINATE_LIMIT;

  if (y > COORDINATE_LIMIT)
    y = COORDINATE_LIMIT;
  else
    if (y < -COORDINATE_LIMIT)
      y = -COORDINATE_LIMIT;

  if (z > COORDINATE_LIMIT)
    z = COORDINATE_LIMIT;
  else
    if (z < -COORDINATE_LIMIT)
      z = -COORDINATE_LIMIT;

  switch (Pigment->Type) 
  {
  case NO_PIGMENT:
    /* No colouring pigment has been specified - make it black. */
    Make_Colour (Colour, 0.0, 0.0, 0.0);
    Colour -> Filter  = 0.0;
    break;

  case COLOUR_PIGMENT:
    Colour -> Red += Pigment->Colour1->Red;
    Colour -> Green += Pigment->Colour1->Green;
    Colour -> Blue += Pigment->Colour1->Blue;
    Colour -> Filter += Pigment->Colour1->Filter;
    break;

  case BOZO_PIGMENT: 
    bozo (x, y, z, Pigment, Colour);
    break;

  case MARBLE_PIGMENT:
    marble (x, y, z, Pigment, Colour);
    break;

  case WOOD_PIGMENT:
    wood (x, y, z, Pigment, Colour);
    break;

  case CHECKER_PIGMENT:
    checker (x, y, z, Pigment, Colour);
    break;

  case SPOTTED_PIGMENT:
    spotted (x, y, z, Pigment, Colour);
    break;

  case AGATE_PIGMENT:
    agate (x, y, z, Pigment, Colour);
    break;

  case GRANITE_PIGMENT:
    granite (x, y, z, Pigment, Colour);
    break;

  case GRADIENT_PIGMENT:
    gradient (x, y, z, Pigment, Colour);
    break;

  case HEXAGON_PIGMENT:
    hexagon (x, y, z, Pigment, Colour);
    break;

  case RADIAL_PIGMENT:
    radial (x, y, z, Pigment, Colour);
    break;

  case MANDEL_PIGMENT:
    mandel (x, y, z, Pigment, Colour);
    break;

  case IMAGE_MAP_PIGMENT:
    image_map (x, y, z, Pigment, Colour);
    break;

  case ONION_PIGMENT:
    onion (x, y, z, Pigment, Colour);
    break;

  case LEOPARD_PIGMENT:
    leopard (x, y, z, Pigment, Colour);
    break;

  case PAINTED1_PIGMENT:
    painted1 (x, y, z, Pigment, Colour);
    break;

  case PAINTED2_PIGMENT:
    painted2 (x, y, z, Pigment, Colour);
    break;

  case PAINTED3_PIGMENT:
    painted3 (x, y, z, Pigment, Colour);
    break;
  }
  }


void Perturb_Normal(Layer_Normal, Tnormal, IPoint)
VECTOR *Layer_Normal, *IPoint;
TNORMAL *Tnormal;
  {
  register DBL x, y, z;
  VECTOR TPoint,NTurbulence;

  if (Tnormal->Trans != NULL) 
    MInvTransPoint (&TPoint, IPoint, Tnormal->Trans);
  else 
    TPoint = *IPoint;

  x = TPoint.x;
  y = TPoint.y;
  z = TPoint.z;

  if(Tnormal->Flags && HAS_TURB)
    {
    DTurbulence (&NTurbulence, x, y, z,
      Tnormal->omega,Tnormal->lambda,Tnormal->Octaves);
    x += NTurbulence.x * Tnormal->Turbulence.x;
    y += NTurbulence.y * Tnormal->Turbulence.y;
    z += NTurbulence.z * Tnormal->Turbulence.z;
    }


  switch (Tnormal->Type) 
  {

  case WAVES: 
    waves (x, y, z, Tnormal, Layer_Normal);
    break;

  case RIPPLES: 
    ripples (x, y, z, Tnormal, Layer_Normal);
    break;

  case WRINKLES: 
    wrinkles (x, y, z, Tnormal, Layer_Normal);
    break;

  case BUMPS: 
    bumps (x, y, z, Tnormal, Layer_Normal);
    break;

  case DENTS: 
    dents (x, y, z, Tnormal, Layer_Normal);
    break; 

  case BUMPY1: 
    bumpy1 (x, y, z, Tnormal, Layer_Normal);
    break;

  case BUMPY2: 
    bumpy2 (x, y, z, Tnormal, Layer_Normal);
    break;

  case BUMPY3: 
    bumpy3 (x, y, z, Tnormal, Layer_Normal);
    break;

  case BUMP_MAP: 
    bump_map (x, y, z, Tnormal, Layer_Normal);
    break;
  }
  return;
  }

void Diffuse (Finish, IPoint, Eye, Layer_Normal, Layer_Pigment_Colour, Colour, Attenuation)
FINISH *Finish;
VECTOR *IPoint, *Layer_Normal;
COLOUR *Layer_Pigment_Colour;
COLOUR *Colour;
RAY    *Eye;
DBL    Attenuation;
  {
  DBL Light_Source_Depth, Cos_Shadow_Angle;
  RAY Light_Source_Ray;
  LIGHT_SOURCE *Light_Source;
  VECTOR REye;
  COLOUR Light_Colour;

  if ((Finish->Diffuse == 0.0) && (Finish->Specular == 0.0) && (Finish->Phong == 0.0))
    return;

  if (Finish->Specular != 0.0)
    {
    REye.x = -Eye->Direction.x;
    REye.y = -Eye->Direction.y;
    REye.z = -Eye->Direction.z;
    }

  for (Light_Source = Frame.Light_Sources ; 
  Light_Source != NULL;
  Light_Source = Light_Source->Next_Light_Source)
    {
    /* Get a colour and a ray */

    do_light(Light_Source,       &Light_Source_Depth, 
      &Light_Source_Ray,  IPoint,
      &Light_Colour);

    /* Don't calculate spotlights when outside of the light's cone */
    if (fabs(Light_Colour.Red) < BLACK_LEVEL && 
      fabs(Light_Colour.Green) < BLACK_LEVEL && 
      fabs(Light_Colour.Blue) < BLACK_LEVEL)
      continue;

    /* See if light on far side of surface from camera. */
    VDot(Cos_Shadow_Angle,*Layer_Normal,Light_Source_Ray.Direction);

    if (Cos_Shadow_Angle < 0.0)
      continue;

    /* If light source was not blocked by any intervening object, then
      calculate it's contribution to the object's overall illumination */

    Shadow_Test_Flag = TRUE;
    if (Quality_Flags & Q_SHADOW)
      {
      if ((Light_Source->Area_Light) && (Quality_Flags & Q_AREA_LIGHT))
        Block_Area_Light (Light_Source, Light_Source_Depth,
          &Light_Source_Ray, IPoint,
          &Light_Colour, 0, 0, 0, 0, 0);
      else
        Block_Point_Light (Light_Source, Light_Source_Depth,
          &Light_Source_Ray, &Light_Colour);
      }
    Shadow_Test_Flag = FALSE;

    if (fabs(Light_Colour.Red)  > BLACK_LEVEL || 
      fabs(Light_Colour.Green) > BLACK_LEVEL || 
      fabs(Light_Colour.Blue) > BLACK_LEVEL) 
      {
      if (Finish->Phong > 0.0) 
        do_phong(Finish,&Light_Source_Ray,&Eye->Direction,Layer_Normal,Colour,&Light_Colour, Layer_Pigment_Colour);

      if (Finish->Specular > 0.0) 
        do_specular(Finish,&Light_Source_Ray,&REye,Layer_Normal,Colour,&Light_Colour, Layer_Pigment_Colour);

      if (Finish->Diffuse > 0.0) 
        do_diffuse(Finish,&Light_Source_Ray,Layer_Normal,Colour,&Light_Colour,Layer_Pigment_Colour, Attenuation);
      }
    }
  return;
  }

void Reflect (Reflection, IPoint, Ray, Layer_Normal, Colour)
DBL Reflection;
VECTOR *IPoint;
RAY *Ray;
VECTOR *Layer_Normal;
COLOUR *Colour;
  {
  RAY New_Ray;
  COLOUR Temp_Colour;
  VECTOR Local_Normal;
  VECTOR Normal_Projection;
  VECTOR Surface_Offset;
  register DBL Normal_Component;

  if (Reflection != 0.0)
    {
    Reflected_Rays_Traced++;
    VDot (Normal_Component, Ray -> Direction, *Layer_Normal);
    if (Normal_Component < 0.0) 
      {
      Local_Normal = *Layer_Normal;
      Normal_Component *= -1.0;
      }
    else
      VScale (Local_Normal, *Layer_Normal, -1.0);

    VScale (Normal_Projection, Local_Normal, Normal_Component);
    VScale (Normal_Projection, Normal_Projection, 2.0);
    VAdd (New_Ray.Direction, Ray -> Direction, Normal_Projection);
    New_Ray.Initial = *IPoint;

    /* ARE 08/25/91 */

    VScale(Surface_Offset, New_Ray.Direction, 2.0 * Small_Tolerance); 
    VAdd(New_Ray.Initial, New_Ray.Initial, Surface_Offset);           

    Copy_Ray_Containers (&New_Ray, Ray);
    Trace_Level++;
    Make_Colour (&Temp_Colour, 0.0, 0.0, 0.0);
    New_Ray.Quadric_Constants_Cached = FALSE;
    Trace (&New_Ray, &Temp_Colour);
    Trace_Level--;

    Colour -> Red   += Temp_Colour.Red   * Reflection;
    Colour -> Green += Temp_Colour.Green * Reflection;
    Colour -> Blue  += Temp_Colour.Blue  * Reflection;

    }
  }

void Refract (Texture, IPoint, Ray, Top_Normal, Colour)
TEXTURE *Texture;
VECTOR *IPoint;
RAY *Ray;
VECTOR *Top_Normal;
COLOUR *Colour;
  {
  RAY New_Ray;
  COLOUR Temp_Colour;
  VECTOR Local_Normal;
  VECTOR Ray_Direction;
  register DBL Normal_Component, Temp_IOR;
  DBL temp, ior;
  /*   int inside; */

  if (Top_Normal == NULL) 
    {
    New_Ray.Initial = *IPoint;
    New_Ray.Direction = Ray->Direction;

    Copy_Ray_Containers (&New_Ray, Ray);
    Trace_Level++;
    Transmitted_Rays_Traced++;
    Make_Colour (&Temp_Colour, 0.0, 0.0, 0.0);
    New_Ray.Quadric_Constants_Cached = FALSE;
    Trace (&New_Ray, &Temp_Colour);
    Trace_Level--;
    (Colour -> Red) += Temp_Colour.Red;
    (Colour -> Green) += Temp_Colour.Green;
    (Colour -> Blue) += Temp_Colour.Blue;
    }
  else 
    {
    Refracted_Rays_Traced++;
    VDot (Normal_Component, Ray -> Direction, *Top_Normal);
    if (Normal_Component <= 0.0)
      {
      Local_Normal.x = Top_Normal -> x;
      Local_Normal.y = Top_Normal -> y;
      Local_Normal.z = Top_Normal -> z;
      Normal_Component *= -1.0;
      /*     inside = FALSE;*/
      }
    else
      {
      VScale (Local_Normal, *Top_Normal, -1.0);
      /*     inside = TRUE;*/
      }


    Copy_Ray_Containers (&New_Ray, Ray);

    if (Ray -> Containing_Index == -1)
      {
      /* The ray is entering from the atmosphere */
      Ray_Enter (&New_Ray, Texture);
      ior = (Frame.Atmosphere_IOR)/(Texture->Finish->Index_Of_Refraction);
      }
else
  {
  /* The ray is currently inside an object */
  if (New_Ray.Containing_Textures [New_Ray.Containing_Index] == Texture) 
    /*         if (inside) */
    {
    /* The ray is leaving the current object */
    Ray_Exit (&New_Ray);
    if (New_Ray.Containing_Index == -1)
      /* The ray is leaving into the atmosphere */
    Temp_IOR = Frame.Atmosphere_IOR;
    else
      /* The ray is leaving into another object */
      Temp_IOR = New_Ray.Containing_IORs [New_Ray.Containing_Index];

    ior =  (Texture->Finish->Index_Of_Refraction)/Temp_IOR;
    }
    else
      {
      /* The ray is entering a new object */
      Temp_IOR = New_Ray.Containing_IORs [New_Ray.Containing_Index];
      Ray_Enter (&New_Ray, Texture);

      ior =  Temp_IOR / (Texture->Finish->Index_Of_Refraction);
      }
    }

  temp = 1.0 + ior * ior * (Normal_Component * Normal_Component - 1.0);
  if (temp < 0.0) 
      {
    Reflect ((1.0 - Texture->Finish->Reflection), IPoint, 
    Ray, Top_Normal, Colour);
    return;
    }

  temp = ior*Normal_Component - sqrt(temp);
  VScale (Local_Normal, Local_Normal, temp);
  VScale (Ray_Direction, Ray->Direction, ior);
  VAdd (New_Ray.Direction, Local_Normal, Ray_Direction);
  VNormalize (New_Ray.Direction, New_Ray.Direction);

  New_Ray.Initial = *IPoint;
  Trace_Level++;
  Make_Colour (&Temp_Colour, 0.0, 0.0, 0.0);
  New_Ray.Quadric_Constants_Cached = FALSE;

  Trace (&New_Ray, &Temp_Colour);
  Trace_Level--;

  (Colour -> Red) += (Temp_Colour.Red)
    * (Texture -> Finish->Refraction);
  (Colour -> Green) += (Temp_Colour.Green)
    * (Texture -> Finish->Refraction);
  (Colour -> Blue) += (Temp_Colour.Blue)
    * (Texture -> Finish->Refraction);
  }
}

void Fog (Distance, Fog_Colour, Fog_Distance, Colour)
DBL Distance, Fog_Distance;
COLOUR *Fog_Colour, *Colour;
  {
  DBL Fog_Factor, Fog_Factor_Inverse;

  Fog_Factor = exp(-1.0 * Distance/Fog_Distance);
  Fog_Factor_Inverse = 1.0 - Fog_Factor;
  Colour->Red = Colour->Red*Fog_Factor + Fog_Colour->Red*Fog_Factor_Inverse;
  Colour->Green = Colour->Green*Fog_Factor + Fog_Colour->Green*Fog_Factor_Inverse;
  Colour->Blue = Colour->Blue*Fog_Factor + Fog_Colour->Blue*Fog_Factor_Inverse;
  }

void Compute_Reflected_Colour (Ray, Finish, Ray_Intersection, Layer_Pigment_Colour, Filter_Colour, Colour, Layer_Normal)
RAY *Ray;
FINISH *Finish;
INTERSECTION *Ray_Intersection;
COLOUR *Layer_Pigment_Colour;
COLOUR *Filter_Colour;
COLOUR *Colour;
VECTOR *Layer_Normal;
  {
  DBL Attenuation, Ambient;
  COLOUR Emitted_Colour;

  /* This variable keeps track of how much colour comes from the surface
      of the object and how much is transmited through. */
  Make_Colour (&Emitted_Colour, 0.0, 0.0, 0.0);

  if (Quality_Flags & Q_FULL_AMBIENT) 
    {
    Layer_Pigment_Colour->Filter = 0.0;

    Colour->Red   += Layer_Pigment_Colour->Red * Filter_Colour->Filter;
    Colour->Green += Layer_Pigment_Colour->Green * Filter_Colour->Filter;
    Colour->Blue  += Layer_Pigment_Colour->Blue * Filter_Colour->Filter;
    return;
    }

  Attenuation = Filter_Colour->Filter * (1.0 - Layer_Pigment_Colour->Filter);

  if ((Ambient = Finish->Ambient*Attenuation) != 0.0)
    {
    Emitted_Colour.Red += Layer_Pigment_Colour->Red * Ambient;
    Emitted_Colour.Green += Layer_Pigment_Colour->Green * Ambient;
    Emitted_Colour.Blue += Layer_Pigment_Colour->Blue * Ambient;
    }

  Diffuse (Finish, &Ray_Intersection ->IPoint, Ray,
    Layer_Normal, Layer_Pigment_Colour, &Emitted_Colour, Attenuation);

  Colour->Red   += Emitted_Colour.Red;
  Colour->Green += Emitted_Colour.Green;
  Colour->Blue  += Emitted_Colour.Blue;

  if (Quality_Flags & Q_REFLECT)
    Reflect (Finish->Reflection, &Ray_Intersection -> IPoint, Ray,
      Layer_Normal, Colour); 
  }

/* Given an intersection point, a ray, & a shadow flag, add that point's
  color to the given colour and return it. */

void Determine_Apparent_Colour (Ray_Intersection, Colour, Ray)
INTERSECTION *Ray_Intersection;
COLOUR *Colour;
RAY *Ray;
  {
  COLOUR Layer_Pigment_Colour, Refracted_Colour, Filter_Colour;
  TEXTURE *Layer, *Texture;
  FINISH *Finish;
  VECTOR Layer_Normal, Raw_Normal, Top_Normal;
  DBL Normal_Direction;
  int layer_number;

#define QColour Texture->Pigment->Quick_Colour

  Normal (&Raw_Normal, Ray_Intersection->Object, &Ray_Intersection->IPoint);
  /* Now, we perform the lighting calculations. */

  /* We assume here that Post_Process has propagated all parent
   textures to the object itself and that everything has some texture.
   Redirrect to the proper texture if its a material or checker texture */

  for (Texture = Ray_Intersection->Object->Texture;
  Texture->Type != PNF_TEXTURE;)
    switch (Texture->Type)
    {
    case TILE_TEXTURE:
      Texture = tiles_texture(&Ray_Intersection->IPoint,((TILES *)Texture));
      break;
    case MAT_TEXTURE:
      Texture = material_map(&Ray_Intersection->IPoint,((MATERIAL *)Texture));
      break;
    default:
      fprintf(stderr, "Bad texture type: %d\n", Texture->Type);
      close_all(); 
      exit(1);
    };

  Make_ColourA (&Filter_Colour, 1.0, 1.0, 1.0, 1.0);
  for (layer_number=1 , Layer = Texture;
  (Layer != NULL) && (fabs(Filter_Colour.Filter) > BLACK_LEVEL);
  layer_number++, Layer = Layer->Next_Layer)
    {
    Make_Colour (&Layer_Pigment_Colour, 0.0, 0.0, 0.0);
    if (Quality_Flags & Q_QUICKC)
      Layer_Pigment_Colour = QColour;
    else
      Add_Pigment (&Layer_Pigment_Colour, Layer->Pigment, &Ray_Intersection->IPoint);

    Layer_Normal = Raw_Normal;
    if ((Quality_Flags & Q_NORMAL) && (Texture->Tnormal != NULL))
      Perturb_Normal (&Layer_Normal, Texture->Tnormal,
        &Ray_Intersection->IPoint);

    /* If the surface normal points away, flip its direction. */
    VDot (Normal_Direction, Layer_Normal, Ray->Direction);
    if (Normal_Direction > 0.0) 
      {
      VScaleEq (Layer_Normal, -1.0);
      }
    if (layer_number == 1)
      Top_Normal = Layer_Normal;

    Compute_Reflected_Colour (Ray,
      Layer->Finish,
      Ray_Intersection,
      &Layer_Pigment_Colour,
      &Filter_Colour,
      Colour, &Layer_Normal);

    Filter_Colour.Red   *= Layer_Pigment_Colour.Red;
    Filter_Colour.Green *= Layer_Pigment_Colour.Green;
    Filter_Colour.Blue  *= Layer_Pigment_Colour.Blue;
    Filter_Colour.Filter *= Layer_Pigment_Colour.Filter;
    }

  Finish = Texture->Finish;

  if ((fabs(Filter_Colour.Filter) > BLACK_LEVEL) && (Quality_Flags & Q_REFRACT))
    {
    Make_Colour (&Refracted_Colour, 0.0, 0.0, 0.0);

    if (Finish->Refraction > 0.0)
      Refract (Texture, &Ray_Intersection -> IPoint, Ray,
        &Top_Normal, &Refracted_Colour);
    else
      Refract (Texture, &Ray_Intersection->IPoint, Ray,
        NULL, &Refracted_Colour);

    Colour->Red += Filter_Colour.Red * Refracted_Colour.Red * Filter_Colour.Filter;
    Colour->Green += Filter_Colour.Green * Refracted_Colour.Green * Filter_Colour.Filter;
    Colour->Blue += Filter_Colour.Blue * Refracted_Colour.Blue * Filter_Colour.Filter;
    }

  if (Frame.Fog_Distance != 0.0)
    Fog (Ray_Intersection->Depth, &Frame.Fog_Colour, Frame.Fog_Distance,
      Colour);
  }

void Filter_Shadow_Ray (Ray_Intersection, Colour)
INTERSECTION *Ray_Intersection;
COLOUR *Colour;
  {
  COLOUR Layer_Pigment_Colour, Filter_Colour;
  TEXTURE *Layer, *Texture;
  FINISH *Finish;
  int layer_number;

#define QColour Texture->Pigment->Quick_Colour

  if (!(Quality_Flags & Q_SHADOW))
    return;

  /* Now, we perform the lighting calculations. */

  /* We assume here that Post_Process has propagated all parent
   textures to the object itself and that everything has some texture.
   Redirrect to the proper texture if its a material or checker texture */

  for (Texture = Ray_Intersection->Object->Texture;
  Texture->Type != PNF_TEXTURE;)
    switch (Texture->Type)
    {
    case TILE_TEXTURE:
      Texture = tiles_texture(&Ray_Intersection->IPoint,((TILES *)Texture));
      break;
    case MAT_TEXTURE:
      Texture = material_map(&Ray_Intersection->IPoint,((MATERIAL *)Texture));
      break;
    default:
      fprintf(stderr, "Bad texture type: %d\n", Texture->Type);
      close_all(); 
      exit(1);
    };

  Make_ColourA (&Filter_Colour, 1.0, 1.0, 1.0, 1.0);
  for (layer_number=1 , Layer = Texture;
  (Layer != NULL) && (fabs(Filter_Colour.Filter) > BLACK_LEVEL);
  layer_number++, Layer = Layer->Next_Layer)
    {
    if (Quality_Flags & Q_QUICKC)
      Layer_Pigment_Colour = QColour;
    else
      {
      Make_Colour (&Layer_Pigment_Colour, 0.0, 0.0, 0.0);
      Add_Pigment (&Layer_Pigment_Colour, Layer->Pigment, &Ray_Intersection->IPoint);
      }

    Filter_Colour.Red   *= Layer_Pigment_Colour.Red;
    Filter_Colour.Green *= Layer_Pigment_Colour.Green;
    Filter_Colour.Blue  *= Layer_Pigment_Colour.Blue;
    Filter_Colour.Filter *= Layer_Pigment_Colour.Filter;
    }

  Finish = Texture->Finish;

  /* For shadow rays, we have the filter colour now - time to return */
  if (fabs(Filter_Colour.Filter) < BLACK_LEVEL) 
    {
    Make_Colour (Colour, 0.0, 0.0, 0.0);
    return;
    }

  if (Finish->Refraction > 0.0) 
    {
    Colour->Red *= Filter_Colour.Red * Finish->Refraction * Filter_Colour.Filter;
    Colour->Green *= Filter_Colour.Green * Finish->Refraction * Filter_Colour.Filter;
    Colour->Blue *= Filter_Colour.Blue * Finish->Refraction * Filter_Colour.Filter;
    }
  else 
    {
    Colour->Red *= Filter_Colour.Red * Filter_Colour.Filter;
    Colour->Green *= Filter_Colour.Green * Filter_Colour.Filter;
    Colour->Blue *= Filter_Colour.Blue * Filter_Colour.Filter;
    }
  return;

  }

