/****************************************************************************
*                point.c
*
*  This module implements the point & spot light source primitive.
*
*  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"

METHODS Light_Source_Methods =
  { 
  All_Light_Source_Intersections,
  Inside_Light_Source, Light_Source_Normal,
  Copy_Light_Source,
  Translate_Light_Source, Rotate_Light_Source,
  Scale_Light_Source, Transform_Light_Source, Invert_Light_Source,
  Destroy_Light_Source
};

static DBL cubic_spline PARAMS(( DBL low,DBL high,DBL pos));

int All_Light_Source_Intersections (Object, Ray, Depth_Stack)
OBJECT *Object;
RAY *Ray;
ISTACK *Depth_Stack;
  {
  if (((LIGHT_SOURCE *)Object)->Children != NULL)
    if (Ray_In_Bounds (Ray, ((LIGHT_SOURCE *)Object)->Children->Bound))
      if (All_Intersections (((LIGHT_SOURCE *)Object)->Children, Ray, Depth_Stack))
        return (TRUE);

  return (FALSE);
  }

int Inside_Light_Source (IPoint, Object)
VECTOR *IPoint;
OBJECT *Object;
  {
  if (((LIGHT_SOURCE *)Object)->Children != NULL)
    if (Inside_Object (IPoint, ((LIGHT_SOURCE *)Object)->Children))
      return (TRUE);

  return (FALSE);
  }

void Light_Source_Normal (Result, Object, IPoint)
OBJECT *Object;
VECTOR *Result, *IPoint;
  {
  if (((LIGHT_SOURCE *)Object)->Children != NULL)
    Normal (Result, ((LIGHT_SOURCE *)Object)->Children,IPoint);
  }

void Translate_Light_Source (Object, Vector)
OBJECT *Object;
VECTOR *Vector;
  {
  VAddEq (((LIGHT_SOURCE *) Object)->Center, *Vector);
  VAddEq (((LIGHT_SOURCE *) Object)->Points_At, *Vector);

  Translate_Object (((LIGHT_SOURCE *)Object)->Children, Vector);
  }

void Rotate_Light_Source (Object, Vector)
OBJECT *Object;
VECTOR *Vector;
  {
  TRANSFORM Trans;

  Compute_Rotation_Transform (&Trans, Vector);
  Transform_Light_Source(Object, &Trans);
  }

void Scale_Light_Source (Object, Vector)
OBJECT *Object;
VECTOR *Vector;
  {
  TRANSFORM Trans;

  Compute_Scaling_Transform (&Trans, Vector);
  Transform_Light_Source(Object, &Trans);
  }

void Invert_Light_Source (Object)
OBJECT *Object;
  {
  Invert_Object (((LIGHT_SOURCE *)Object)->Children);
  }

void Transform_Light_Source (Object, Trans)
OBJECT *Object;
TRANSFORM *Trans;
  {
  MTransPoint (&((LIGHT_SOURCE *) Object)->Center,
    &((LIGHT_SOURCE *) Object)->Center, Trans);
  MTransPoint (&((LIGHT_SOURCE *) Object)->Points_At,
    &((LIGHT_SOURCE *) Object)->Points_At, Trans);
  MTransPoint (&((LIGHT_SOURCE *) Object)->Axis1,
    &((LIGHT_SOURCE *) Object)->Axis1, Trans);
  MTransPoint (&((LIGHT_SOURCE *) Object)->Axis2,
    &((LIGHT_SOURCE *) Object)->Axis2, Trans);
  Transform_Object (((LIGHT_SOURCE *)Object)->Children, Trans);
  }

void Destroy_Light_Source (Object)
OBJECT *Object;
  {
  int i;

  if (((LIGHT_SOURCE *)Object)->Light_Grid != NULL) 
    { 
    for (i = 0; i < ((LIGHT_SOURCE *)Object)->Area_Size1; i++)
      free(((LIGHT_SOURCE *)Object)->Light_Grid[i]);

    free(((LIGHT_SOURCE *)Object)->Light_Grid);
    } 

  Destroy_Object (((LIGHT_SOURCE *)Object)->Children);
  free (Object);
  }

COLOUR **Create_Light_Grid (Size1, Size2)
int Size1, Size2;
  {
  COLOUR **New;
  int i;

  New = (COLOUR **)malloc (Size1 * sizeof (COLOUR *));
  if (New == NULL)
    MAError ("area light");

  for (i = 0; i < Size1; i++) 
    {
    New[i] = (COLOUR *)malloc (Size2 * sizeof (COLOUR));
    if (New[i] == NULL)
      MAError ("area light");
    }

  return (New);
  }

LIGHT_SOURCE *Create_Light_Source ()
  {
  LIGHT_SOURCE *New;

  if ((New = (LIGHT_SOURCE *) malloc (sizeof (LIGHT_SOURCE))) == NULL)
    MAError ("light_source");

  INIT_OBJECT_FIELDS(New, LIGHT_OBJECT, &Light_Source_Methods)
    New->Children = NULL;
  New->No_Shadow_Flag = TRUE;

  Make_Colour(&New->Colour,1.0,1.0,1.0);
  Make_Vector(&New->Center,0.0,0.0,0.0);
  Make_Vector(&New->Points_At,0.0,0.0,1.0);
  Make_Vector(&New->Axis1,0.0,0.0,1.0);
  Make_Vector(&New->Axis2,0.0,1.0,0.0);
  New->Coeff   = 10.0;
  New->Radius  = 0.35;
  New->Falloff = 0.35;
  New->Next_Light_Source    = NULL;
  New->Shadow_Cached_Object = NULL;
  New->Light_Grid           = NULL;
  New->Light_Type = POINT_SOURCE;
  New->Area_Light = FALSE;
  New->Jitter     = FALSE;
  New->Track      = FALSE;
  New->Area_Size1 = 0;
  New->Area_Size2 = 0;
  New->Adaptive_Level = 100;
  return (New);
  }

void *Copy_Light_Source (Old)
OBJECT *Old;
  {
  LIGHT_SOURCE *New;
  int i, j;

  New = Create_Light_Source ();
  *New = *(LIGHT_SOURCE *)Old;

  New->Next_Light_Source = NULL;

  New->Children = Copy_Object (((LIGHT_SOURCE *)Old)->Children);

  if (((LIGHT_SOURCE *)Old)->Light_Grid != NULL) 
    { 
    New->Light_Grid = Create_Light_Grid (((LIGHT_SOURCE *)Old)->Area_Size1,
      ((LIGHT_SOURCE *)Old)->Area_Size2);

    for (i = 0; i < ((LIGHT_SOURCE *)Old)->Area_Size1; i++)
      for (j = 0; j < ((LIGHT_SOURCE *)Old)->Area_Size2; j++)
      New->Light_Grid[i][j] = ((LIGHT_SOURCE *)Old)->Light_Grid[i][j];
    } 

  return (New);
  }

/* Cubic spline that has tangents of slope 0 at x == low and at x == high.
   For a given value "pos" between low and high the spline value is returned */
static DBL cubic_spline(low, high, pos)
DBL low, high, pos;
  {
  /* Check to see if the position is within the proper boundaries */
  if (pos < low)
    return 0.0;
  else if (pos > high)
    return 1.0;
  if (high == low)
    return 0.0;

  /* Normalize to the interval 0->1 */
  pos = (pos - low) / (high - low);

  /* See where it is on the cubic curve */
  return (3 - 2 * pos) * pos * pos;
  }

DBL Attenuate_Light (Light_Source, Light_Source_Ray)
LIGHT_SOURCE *Light_Source;
RAY *Light_Source_Ray;
  {
  DBL Len,costheta;
  DBL Attenuation = 1.0;
  VECTOR Spot_Direction;

  /* If this is a spotlight then attenuate based on the incidence angle */
  if (Light_Source->Light_Type == SPOT_SOURCE) 
    {
    VSub(Spot_Direction, Light_Source->Points_At, Light_Source->Center);
    VLength(Len, Spot_Direction);
    if (Len > 0.0) 
      {
      VInverseScale(Spot_Direction, Spot_Direction, Len);
      VDot(costheta, Light_Source_Ray->Direction, Spot_Direction);
      costheta *= -1.0;
      if (costheta > 0.0) 
        {
        Attenuation = pow(costheta, Light_Source->Coeff);
        /* If there is a soft falloff region associated with the light then
               do an interpolation of values between the hot center and the
               direction at which light falls to nothing. */
        if (Light_Source->Radius > 0.0)
          Attenuation *= cubic_spline(Light_Source->Falloff,
            Light_Source->Radius,
            costheta);
        /* printf("Atten: %lg\n", Attenuation); */
        }
      else
        Attenuation = 0.0;
      }
    else
      Attenuation = 0.0;
    } 
  return(Attenuation);
  }    
