/* ****************************************************************
 *                         illum_mod.c
 * ****************************************************************
 *  MODULE PURPOSE:
 *      This module contains the source code for the illumination
 *      models discussed for in Section 4.2, Incremental Shading,
 *      Empirical Models, and in Section 4.3, Ray Tracing,
 *      Transitional Models.
 *
 *  MODULE CONTENTS:
 *      IM_init         - initialize the illumination models
 *      IM_bouknight    - Bouknight (1970) illumination model
 *      IM_phong        - Phong (1975) illumination model
 *      IM_blinn        - Blinn (1976) illumination model
 *      IM_whitted      - Whitted (1980) illumination model
 *      IM_hall         - Hall (1983) illumination model
 *      IM_exit         - finish with the illumination models
 *
 *  NOTES:
 *      > The illumination model is called once a
 *          point on a surface has been identified and the list
 *          of illuminating light sources has been generated.
 *
 *      > There exists a routine that the illumination models can
 *          call to get a color from any direction.  Specifically
 *          this is used for inquiring about the reflected or
 *          transmitted directions in the ray tracing models.
 *          This routine is passed the view vector for which the
 *          color is required.
 *
 *      > a common calling convention is used for ease in
 *          interchanging the illumination model routines.  Each
 *          routine is passed the location and orientation of the
 *          surface, a view vector, an interface material, a
 *          description of the lighting, and an array to receive
 *          the computed color.
 *
 *          The orientation of the surface is given by the surface
 *          normal which is ALWAYS directed to the 'outside' or
 *          reflected side of the material.
 *
 *          The view vector is specified by start position and
 *          direction vector.  During visibility computations the
 *          view vector is typically directed towards the surface.
 *          The direction cosines MUST be negated prior to calling
 *          the illumination model for consistency with the vector
 *          conventions used.
 *
 *          See 'illum_mod.h' for material details.
 *
 *          The light vector is a list of pointers to ILLUM_LGT
 *          structures terminated by a NULL pointer.  The first
 *          entry is taken as the ambient illumination.  Only
 *          light that is incident from the air side of a material
 *          can provide illumination.
 *
 *      > These models assume that the material structure is
 *          correctly loaded and that the surface is facing the
 *          viewer (N.V > 0) for illumination models that do not
 *          consider transparency.
 */
#include <stdio.h>
#include <math.h>
#include "illum_mod.h"
#include "F.h"

static int      num_samples = 0;
static int      (*get_color)() = NULL;
static double   *Fr_buffer = NULL;
static double   *Ft_buffer = NULL;

/* ****************************************************************
 * int IM_init (num_clr_samples, get_clr_routine)
 *  int     num_clr_samples         (in) - number of color samples
 *  int     (*get_clr_routine)()    (in) - routine to call to get
 *                                    the color from some direction
 *
 * Initializes the illumination model routine set, returns TRUE
 *  if successful and FALSE upon failure.
 */
IM_init (num_clr_samples, get_clr_routine)
int     num_clr_samples, (*get_clr_routine)();
{   char    *malloc();
    if (((num_samples = num_clr_samples) <= 0) ||
	  ((Fr_buffer = (double *)malloc((unsigned)(num_samples *
	    sizeof(double)))) == NULL) ||
	  ((Ft_buffer = (double *)malloc((unsigned)(num_samples *
	    sizeof(double)))) == NULL)) {
	(void)IM_exit();
	return FALSE;
    }
    get_color = get_clr_routine;
    return TRUE;
}

/* ****************************************************************
 * double *IM_bouknight (surface, V, matl, lgts, color)
 *  LINE        *surface    (in)  - surface for color computation
 *  LINE        *V          (in)  - view vector
 *  ILLUM_MATL  *matl       (in)  - material properties
 *  ILLUM_LGT   **lgts      (in)  - illuminating sources
 *  double      *color      (mod) - array to receive the color
 *
 * Evaluates the color using the Bouknight (1970) illumination
 *  model as described in Eq.(\*(sE).  Returns 'color' upon
 *  success and NULL upon failure.
 */
double *IM_bouknight (surface, V, matl, lgts, color)
LINE        *surface, *V;
ILLUM_MATL  *matl;
ILLUM_LGT   **lgts;
double      *color;
{   int         ct;
    double      N_dot_L;
    LINE        L;

    /* load the ambient illumination */
    for (ct=0; ct<num_samples; ct++) {
      color[ct] = matl->Ka_scale * matl->Ka_spectral[ct] *
	(*lgts)->I_scale * (*lgts)->I_spectral[ct];
    }
    lgts++;

    /* load the diffuse component of the illumination.  Loop
     *  through the lights and compute (N.L).  If it is positive
     *  then the surface is illuminated.
     */
    while (*lgts != NULL) {
      if ((geo_line(&(surface->start),&((*lgts)->center),&L) > 0)
	  && ((N_dot_L=geo_dot(&(surface->dir),&(L.dir))) > 0)) {

	/* The surface is illuminated by this light.  Loop through
	 *  the color samples and sum the diffuse contribution for
	 *  this light to the the color.
	 */
	for (ct=0; ct<num_samples; ct++) {
	  color[ct] += matl->Kd_scale * matl->Kd_spectral[ct]
	    * N_dot_L * (*lgts)->I_scale * (*lgts)->I_spectral[ct];
	}
      }
      lgts++;
    }

    return color;
}

/* ****************************************************************
 * double *IM_phong (surface, V, matl, lgts, color)
 *  LINE        *surface    (in)  - surface for color computation
 *  LINE        *V          (in)  - view vector
 *  ILLUM_MATL  *matl       (in)  - material properties
 *  ILLUM_LGT   **lgts      (in)  - illuminating sources
 *  double      *color      (mod) - array to receive the color
 *
 * Evaluates the color using the Phong (1975) illumination
 *  model as described in Eq.(\*(rU).  Returns 'color' upon
 *  success and NULL upon failure.
 *
 * The actual Phong model results when the microfacet distribution
 *  D_phong() is used, and matl->Ks_spectral is the identity
 *  material.
 *
 * Using the microfacet distribution D_blinn() gives Blinn's
 *  interpretation of the Phong model per Eq.(\*(rI).
 */
double *IM_phong (surface, V, matl, lgts, color)
LINE        *surface, *V;
ILLUM_MATL  *matl;
ILLUM_LGT   **lgts;
double      *color;
{   int         ct;
    double      N_dot_L, D;
    LINE        L;

    /* load the ambient illumination */
    for (ct=0; ct<num_samples; ct++) {
      color[ct] = matl->Ka_scale * matl->Ka_spectral[ct] *
	(*lgts)->I_scale * (*lgts)->I_spectral[ct];
    }
    lgts++;

    /* load the diffuse and specular illumination components.
     *  Loop through the lights and compute (N.L).  If it is
     *  positive then the surface is illuminated.
     */
    while (*lgts != NULL) {
      if ((geo_line(&(surface->start),&((*lgts)->center),&L) > 0)
	  && ((N_dot_L=geo_dot(&(surface->dir),&(L.dir))) > 0)) {

	/* The surface is illuminated.  Compute the microfacet
	 *  distribution function.
	 */
	D = (*(matl->D))(&(surface->dir), &(L.dir), &(V->dir),
	  matl->D_coeff);

	/* Loop through the color samples and sum the diffuse
	 *  and specular contribution for this light to the the
	 *  color.
	 */
	for (ct=0; ct<num_samples; ct++) {
	  color[ct] +=
	    ((N_dot_L * matl->Kd_scale * matl->Kd_spectral[ct])
	    + (D * matl->Ks_scale * matl->Ks_spectral[ct]))
	    * (*lgts)->I_scale * (*lgts)->I_spectral[ct];
	}
      }
      lgts++;
    }

    return color;
}

/* ****************************************************************
 * double *IM_blinn (surface, V, matl, lgts, color)
 *  LINE        *surface    (in)  - surface for color computation
 *  LINE        *V          (in)  - view vector
 *  ILLUM_MATL  *matl       (in)  - material properties
 *  ILLUM_LGT   **lgts      (in)  - illuminating sources
 *  double      *color      (mod) - array to receive the color
 *
 * Evaluates the color using the Blinn (1977) illumination
 *  model as described in Eq.(\*(rV).  Returns 'color' upon
 *  success and NULL upon failure.  The microfacet distribution
 *  functions D_blinn(), D_gaussian(), and D_reitz are the three
 *  functions presented by Blinn.  The geometric attenuation
 *  function G_torrance() is the attenuation function used by Blinn.
 *  If matl->G is NULL then the geometric attenuation is omitted.
 *  The Fresnel reflectance is approximated using the Cook (1983)
 *  technique.
 */
double *IM_blinn (surface, V, matl, lgts, color)
LINE        *surface, *V;
ILLUM_MATL  *matl;
ILLUM_LGT   **lgts;
double      *color;
{   int         ct;
    double      N_dot_L, N_dot_V, D, G, *Fr;
    LINE        L;
    DIR_VECT    *T, *H;

    /* load the ambient illumination */
    for (ct=0; ct<num_samples; ct++) {
      color[ct] = matl->Ka_scale * matl->Ka_spectral[ct] *
	(*lgts)->I_scale * (*lgts)->I_spectral[ct];
    }
    lgts++;

    /* load the diffuse and specular illumination components.
     *  Loop through the lights and compute (N.L).  If it is
     *  positive then the surface is illuminated.
     */
    while (*lgts != NULL) {
      if ((geo_line(&(surface->start),&((*lgts)->center),&L) > 0)
	  && ((N_dot_L=geo_dot(&(surface->dir),&(L.dir))) > 0)) {

	/* The surface is illuminated.  Compute the microfacet
	 *  distribution, geometric attenuation, Fresnel
	 *  reflectance and (N.V) for the specular function.
	 */
	D = (*(matl->D))(&(surface->dir), &(L.dir), &(V->dir),
	  matl->D_coeff);
	if (matl->G == NULL)
	  G = 1.0;
	else
	  G = (*(matl->G))(&(surface->dir), &(L.dir),
	    &(V->dir), matl->G_coeff);
	H = geo_H(&(L.dir), &(V->dir));
	if (matl->conductor) {
	  Fr = F_approx_Fr(H, &(L.dir), matl->Ro, matl->nt,
	   matl->k, num_samples, matl->Ks_spectral, Fr_buffer);
	}
	else {
	  T = geo_rfr(&(V->dir),H, matl->nr, matl->nt);
	  Fr = F_approx_Fr_Ft(H, &(L.dir), T,
	    matl->Ro, matl->nr, matl->nt, num_samples,
	    matl->Ks_spectral, Fr_buffer, Ft_buffer);
	}

	/* Loop through the color samples and sum the diffuse
	 *  and specular contribution for this light to the the
	 *  color. Note the threshold on N_dot_V to prevent
	 *  divide by zero at grazing.
	 */
	if ((N_dot_V=geo_dot(&(surface->dir),&(V->dir))) > 0.0001){
	    for (ct=0; ct<num_samples; ct++) {
	      color[ct] +=
		((N_dot_L * matl->Kd_scale * matl->Kd_spectral[ct])
		+ ((D * G * matl->Ks_scale * Fr[ct]) / N_dot_V))
		* (*lgts)->I_scale * (*lgts)->I_spectral[ct];
	    }
	}
      }
      lgts++;
    }

    return color;
}

/* ****************************************************************
 * double *IM_whitted (surface, V, matl, lgts, color)
 *  LINE        *surface    (in)  - surface for color computation
 *  LINE        *V          (in)  - view vector
 *  ILLUM_MATL  *matl       (in)  - material properties
 *  ILLUM_LGT   *lgts       (in)  - illuminating sources
 *  double      *color      (mod) - array to receive the color
 *
 * Evaluates the color using the Whitted (1980) illumination
 *  model as described in Eq.(\*(e6).  Returns 'color' upon
 *  success and NULL upon failure.
 *
 * The actual Whitted model results when the microfacet
 *  distribution D_blinn() is used, and when both matl->Ks_spectral
 *  and matl->Kt_spectral are the identity material.
 *
 * The matl->Kt_scale and matl->Kt_spectral are required for this
 *  illumination model only.
 */
double *IM_whitted (surface, V, matl, lgts, color)
LINE        *surface, *V;
ILLUM_MATL  *matl;
ILLUM_LGT   **lgts;
double      *color;
{   int         inside = FALSE;
    int         ct;
    double      N_dot_L, D;
    LINE        L;

    /* figure out whether we are on the reflected or transmitted
     *  side of the material interface (outside or inside).  If
     *  we are inside a material, then there is no illumination
     *  from lights and such - skip directly to reflection and
     *  refraction contribution.
     */
    if ((geo_dot(&(surface->dir),&(V->dir)) < 0) ||
	(!matl->r_is_air)) {
	for (ct=0; ct<num_samples; ct++) color[ct] = 0.0;
	inside = TRUE;
	goto rfl_rfr;
    }

    /* If we are at interface between materials, neither of which
     *  is air, then skip directly to reflection and refraction
     */
    if (!matl->r_is_air) goto rfl_rfr;

    /* load the ambient illumination */
    for (ct=0; ct<num_samples; ct++) {
      color[ct] = matl->Ka_scale * matl->Ka_spectral[ct] *
	(*lgts)->I_scale * (*lgts)->I_spectral[ct];
    }
    lgts++;

    /* load the diffuse and specular illumination components.
     *  Loop through the lights and compute (N.L).  If it is
     *  positive then the surface is illuminated.
     */
    while (*lgts != NULL) {
      if ((geo_line(&(surface->start),&((*lgts)->center),&L) > 0)
	  && ((N_dot_L=geo_dot(&(surface->dir),&(L.dir))) > 0)) {

	/* The surface is illuminated.  Compute the microfacet
	 *  distribution function.
	 */
	D = (*(matl->D))(&(surface->dir), &(L.dir), &(V->dir),
	  matl->D_coeff);

	/* Loop through the color samples and sum the diffuse
	 *  and specular contribution for this light to the the
	 *  color.
	 */
	for (ct=0; ct<num_samples; ct++) {
	  color[ct] +=
	    ((N_dot_L * matl->Kd_scale * matl->Kd_spectral[ct])
	    + (D * matl->Ks_scale * matl->Ks_spectral[ct]))
	    * (*lgts)->I_scale * (*lgts)->I_spectral[ct];
	}
      }
      lgts++;
    }

    /* compute the contribution from the reflection and
     *  refraction directions.  Get a buffer to hold the
     *  computed colors, then process the reflected direction
     *  and the refracted direction.
     */
rfl_rfr:
    if (get_color != NULL) {
      char      *malloc();
      double    *Ir_or_It;
      LINE      V_new;
      DIR_VECT  *T;

      if ((Ir_or_It = (double *)malloc((unsigned)(num_samples *
	sizeof(double)))) == NULL) return color;

      /* get the reflected vector then ask for the color from
       *  the reflected direction.  If there is a color, then
       *  sum it with the current color
       */
      V_new.start = surface->start;
      V_new.dir = *(geo_rfl(&(V->dir),&(surface->dir)));
      if ((*get_color)(&V_new, Ir_or_It) != NULL)
	for (ct=0; ct<num_samples; ct++) color[ct] +=
	  Ir_or_It[ct] * matl->Ks_scale * matl->Ks_spectral[ct];

      /* if the material is transparent then get the refracted
       *  vector and ask for the color from the refracted
       *  direction.  If there is a color, then sum it with
       *  the current color
       */
      if (matl->transparent) {
	V_new.start = surface->start;
	if (inside)
	  T = geo_rfr(&(V->dir),&(surface->dir),
	    matl->nt, matl->nr);
	else
	  T = geo_rfr(&(V->dir),&(surface->dir),
	    matl->nr, matl->nt);
	if (T != NULL) {
	  V_new.dir = *T;
	  if ((*get_color)(&V_new, Ir_or_It) != NULL)
	    for (ct=0; ct<num_samples; ct++) color[ct] +=
	      Ir_or_It[ct] * matl->Kt_scale *
	      matl->Kt_spectral[ct];
	}
      }
      (void)free((char *)Ir_or_It);
    }

    return color;
}

/* ****************************************************************
 * double *IM_hall (surface, V, matl, lgts, color)
 *  LINE        *surface    (in)  - surface for color computation
 *  LINE        *V          (in)  - view vector
 *  ILLUM_MATL  *matl       (in)  - material properties
 *  ILLUM_LGT   *lgts       (in)  - illuminating sources
 *  double      *color      (mod) - array to receive the color
 *
 * Evaluates the color using the Hall (1983) illumination
 *  model as described in Eq.(\*(sF).  Returns 'color' upon
 *  success and NULL upon failure.
 *
 * The actual Hall model results when the microfacet
 *  distribution D_blinn() is used, and matl->D = NULL.
 *
 * The transmittance is computed from the reflectance, so
 *  matl->Kt_scale and matl->Kt_spectral are not used in the model.
 */
double *IM_hall (surface, V, matl, lgts, color)
LINE        *surface, *V;
ILLUM_MATL  *matl;
ILLUM_LGT   **lgts;
double      *color;
{   int         inside = FALSE;
    int         ct;
    double      N_dot_L, D, G, *Fr;
    LINE        L;
    DIR_VECT    *T, *H, *Ht, pseudo_V;

    /* figure out whether we are on the reflected or transmitted
     *  side of the material interface (outside or inside).  If
     *  we are inside a material, then there may be illumination
     *  from outside.
     */
    if (geo_dot(&(surface->dir),&(V->dir)) < 0) {
	for (ct=0; ct<num_samples; ct++) color[ct] = 0.0;
	inside = TRUE;
    }

    /* If we are at interface between materials, neither of which
     *  is air, then skip directly to reflection and refraction
     */
    if (!matl->r_is_air) goto rfl_rfr;

    /* load the ambient illumination if we are not inside
     *  inside the material
     */
    if (!inside) {
      for (ct=0; ct<num_samples; ct++) {
	color[ct] = matl->Ka_scale * matl->Ka_spectral[ct] *
	  (*lgts)->I_scale * (*lgts)->I_spectral[ct];
      }
    }
    lgts++;

    /* load the diffuse and specular illumination components.
     *  Loop through the lights and compute (N.L).  If it is
     *  positive then the surface is illuminated.  If it is
     *  negative, then there may be transmitted illumination.
     */
    while (*lgts != NULL) {
      if ((geo_line(&(surface->start),&((*lgts)->center),&L) > 0)
	  && ((N_dot_L=geo_dot(&(surface->dir),&(L.dir))) > 0)
	  && !inside) {

	/* The surface is illuminated.  Compute the microfacet
	 *  distribution, geometric attenuation, Fresnel
	 *  reflectance and (N.V) for the specular function.
	 */
	D = (*(matl->D))(&(surface->dir), &(L.dir), &(V->dir),
	  matl->D_coeff);
	if (matl->G == NULL)
	  G = 1.0;
	else
	  G = (*(matl->G))(&(surface->dir), &(L.dir),
	    &(V->dir), matl->G_coeff);
	H = geo_H(&(L.dir), &(V->dir));
	if (matl->conductor) {
	  Fr = F_approx_Fr(H, &(L.dir), matl->Ro, matl->nt,
	    matl->k, num_samples, matl->Ks_spectral, Fr_buffer);
	}
	else {
	  T = geo_rfr(&(L.dir), H, matl->nr, matl->nt);
	  Fr = F_approx_Fr_Ft(H, &(L.dir), T,
	    matl->Ro, matl->nr, matl->nt, num_samples,
	    matl->Ks_spectral, Fr_buffer, Ft_buffer);
	}


	/* Loop through the color samples and sum the diffuse
	 *  and specular contribution for this light to the the
	 *  color.
	 */
	for (ct=0; ct<num_samples; ct++) {
	  color[ct] +=
	    ((N_dot_L * matl->Kd_scale * matl->Kd_spectral[ct])
	    + (D * G * matl->Ks_scale * Fr[ct]))
	    * (*lgts)->I_scale * (*lgts)->I_spectral[ct];
	}
      }
      else if ((N_dot_L > 0) && inside) {
	/* We are inside and the light is outside.  Compute
	 *  the transmitted contribution from the light
	 */
	if ((Ht = geo_Ht(&(L.dir),&(V->dir),matl->nr,
	    matl->nt)) != NULL) {
	  /* The microfacet distribution functions could
	   *  only be equated when cast in terms of the primary
	   *  vectors L, V, and N.  A pseudo_V vector is required
	   *  so that any of the distribution functions can be
	   *  applied.  Ht is the vector bisector between of the
	   *  angle between L and pseudo_V, thus pseudo_V can be
	   *  computed by reflecting L about Ht. Refer to the
	   *  text for details.
	   */
	  pseudo_V = *(geo_rfl(&(L.dir), Ht));
	  D = (*(matl->D))(&(surface->dir), &(L.dir),
	    &pseudo_V, matl->D_coeff);
	  Fr = F_approx_Fr_Ft(Ht, &(L.dir), &(V->dir),
	    matl->Ro, matl->nr, matl->nt, num_samples,
	    matl->Ks_spectral, Fr_buffer, Ft_buffer);
	  if (matl->G == NULL)
	    G = 1.0;
	  else {
	    /* To include the geometric attenuation, the view
	     *  vector direction must be reversed so that it
	     *  is to the same side of the surface as the
	     *  normal, see text for details.
	     */
	    pseudo_V.i = -V->dir.i;
	    pseudo_V.j = -V->dir.j;
	    pseudo_V.k = -V->dir.k;
	    G = (*(matl->G))(&(surface->dir), &(L.dir),
	      &pseudo_V, matl->G_coeff);
	  }
	  for (ct=0; ct<num_samples; ct++) {
	    color[ct] += (D * G * matl->Ks_scale * Ft_buffer[ct])
	      * (*lgts)->I_scale * (*lgts)->I_spectral[ct];
	  }
	}
      }
      lgts++;
    }

    /* compute the contribution from the reflection and
     *  refraction directions.  Get a buffer to hold the
     *  computed colors, then process the reflected direction
     *  and the refracted direction.
     */
rfl_rfr:
    if (get_color != NULL) {
      char      *malloc();
      double    *Ir_or_It;
      LINE      V_new;
      DIR_VECT  pseudo_N;

      if ((Ir_or_It = (double *)malloc((unsigned)(num_samples *
	sizeof(double)))) == NULL) return color;

      /* Determine the Fresnel reflectance and transmittance.
       *  If we are inside the material, then a pseudo normal
       *  is required that faces to the same side of the
       *  interface as the view vector.
       */
      if (matl->conductor) {
	Fr = F_approx_Fr(&(surface->dir), &(V->dir), matl->Ro,
	  matl->nt, matl->k, num_samples, matl->Ks_spectral,
	  Fr_buffer);
      }
      else if (inside) {
	T = geo_rfr(&(V->dir),&(surface->dir),
	    matl->nt, matl->nr);
	pseudo_N.i = -surface->dir.i;
	pseudo_N.j = -surface->dir.j;
	pseudo_N.k = -surface->dir.k;
	Fr = F_approx_Fr_Ft(&pseudo_N, &(V->dir), T,
	    matl->Ro, matl->nt, matl->nr, num_samples,
	    matl->Ks_spectral, Fr_buffer, Ft_buffer);
      }
      else {
	T = geo_rfr(&(V->dir),&(surface->dir),
	    matl->nr, matl->nt);
	Fr = F_approx_Fr_Ft(&(surface->dir), &(V->dir),
	    T, matl->Ro, matl->nr, matl->nt, num_samples,
	    matl->Ks_spectral, Fr_buffer, Ft_buffer);
      }

      /* get the reflected vector then ask for the color from
       *  the reflected direction.  If there is a color, then
       *  sum it with the current color
       */
      V_new.start = surface->start;
      V_new.dir = *(geo_rfl(&(V->dir),&(surface->dir)));
      if ((*get_color)(&V_new, Ir_or_It) != NULL) {
	for (ct=0; ct<num_samples; ct++) color[ct] +=
	  Ir_or_It[ct] * matl->Ks_scale * Fr[ct];
      }

      /* if the material is transparent then get the refracted
       *  vector and ask for the color from the refracted
       *  direction.  If there is a color, then sum it with
       *  the current color
       */
      if (matl->transparent && (T != NULL)) {
	V_new.start = surface->start;
	V_new.dir = *T;
	if ((*get_color)(&V_new, Ir_or_It) != NULL)
	  for (ct=0; ct<num_samples; ct++) color[ct] +=
	    Ir_or_It[ct] * matl->Kt_scale * Ft_buffer[ct];
      }
      (void)free((char *)Ir_or_It);
    }

    /* If we are inside a material that has a filter attenuation
     *  then apply the attenuation to the color.
     */
    if ( ((!inside) && ((Fr = matl->Ar_spectral) != NULL)) ||
	 ((inside) && ((Fr = matl->At_spectral) != NULL)) ) {
	double      dist;
	dist = sqrt ( ((surface->start.x - V->start.x) *
		       (surface->start.x - V->start.x)) +
		      ((surface->start.y - V->start.y) *
		       (surface->start.y - V->start.y)) +
		      ((surface->start.z - V->start.z) *
		       (surface->start.z - V->start.z)) );
	for (ct=0; ct<num_samples; ct++)
	    color[ct] *= pow (Fr[ct], dist);
    }

    return color;
}

/* ****************************************************************
 * int IM_exit ()
 *
 * Finishes use of the illumination models routines.
 */
IM_exit()
{
    if (Fr_buffer != NULL) {
	(void)free((char *)Fr_buffer); Fr_buffer = NULL;
    }
    if (Ft_buffer != NULL) {
	(void)free((char *)Ft_buffer); Ft_buffer = NULL;
    }
    num_samples = 0;
    get_color = NULL;
    return TRUE;
}
/* ************************************************************* */
