/****************************************************************************
*                point.c
*
*  This module implements the point & spot light source primitive.
*
*  from Persistence of Vision(tm) Ray Tracer
*  Copyright 1996 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"
#include "point.h"
#include "matrices.h"
#include "objects.h"
#include "povray.h"



/*****************************************************************************
* Local preprocessor defines
******************************************************************************/



/*****************************************************************************
* Local typedefs
******************************************************************************/



/*****************************************************************************
* Static functions
******************************************************************************/

static DBL cubic_spline PARAMS(( DBL low,DBL high,DBL pos));
static int  All_Light_Source_Intersections PARAMS((OBJECT *Object, RAY *Ray, ISTACK *Depth_Stack));
static int  Inside_Light_Source PARAMS((VECTOR point, OBJECT *Object));
static void Light_Source_Normal PARAMS((VECTOR Result, OBJECT *Object, INTERSECTION *Inter));
static void Translate_Light_Source PARAMS((OBJECT *Object, VECTOR Vector, TRANSFORM *Trans));
static void Rotate_Light_Source PARAMS((OBJECT *Object, VECTOR Vector, TRANSFORM *Trans));
static void Scale_Light_Source PARAMS((OBJECT *Object, VECTOR Vector, TRANSFORM *Trans));
static void Transform_Light_Source PARAMS((OBJECT *Object, TRANSFORM *Trans));
static void Invert_Light_Source PARAMS((OBJECT *Object));
static void *Copy_Light_Source PARAMS((OBJECT *Object));
static void Destroy_Light_Source PARAMS((OBJECT *Object));

/*****************************************************************************
* Local variables
******************************************************************************/

static 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
};





/*****************************************************************************
*
* FUNCTION
*
*   All_Light_Source_Intersections
*
* INPUT
*   
* OUTPUT
*   
* RETURNS
*   
* AUTHOR
*
*   POV-Ray Team
*   
* DESCRIPTION
*
*   -
*
* CHANGES
*
*   -
*
******************************************************************************/

static 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_Bound (Ray, ((LIGHT_SOURCE *)Object)->Children->Bound))
    {
      if (All_Intersections (((LIGHT_SOURCE *)Object)->Children, Ray, Depth_Stack))
      {
        return(TRUE);
      }
    }
  }

  return(FALSE);
}



/*****************************************************************************
*
* FUNCTION
*
*   Inside_Light_Source
*
* INPUT
*   
* OUTPUT
*   
* RETURNS
*   
* AUTHOR
*
*   POV-Ray Team
*   
* DESCRIPTION
*
*   -
*
* CHANGES
*
*   -
*
******************************************************************************/

static 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);
}



/*****************************************************************************
*
* FUNCTION
*
*   Light_Source_Normal
*
* INPUT
*   
* OUTPUT
*   
* RETURNS
*   
* AUTHOR
*
*   POV-Ray Team
*   
* DESCRIPTION
*
*   -
*
* CHANGES
*
*   -
*
******************************************************************************/

static void Light_Source_Normal (Result, Object, Inter)
OBJECT *Object;
VECTOR Result;
INTERSECTION *Inter;
{
  if (((LIGHT_SOURCE *)Object)->Children != NULL)
  {
    Normal (Result, ((LIGHT_SOURCE *)Object)->Children,Inter);
  }
}



/*****************************************************************************
*
* FUNCTION
*
*   Translate_Light_Source
*
* INPUT
*   
* OUTPUT
*   
* RETURNS
*   
* AUTHOR
*
*   POV-Ray Team
*   
* DESCRIPTION
*
*   -
*
* CHANGES
*
*   -
*
******************************************************************************/

static void Translate_Light_Source (Object, Vector, Trans)
OBJECT *Object;
VECTOR Vector;
TRANSFORM *Trans;
{
  LIGHT_SOURCE *Light = (LIGHT_SOURCE *)Object;

  VAddEq (Light->Center, Vector);
  VAddEq (Light->Points_At, Vector);

  if (Light->Children != NULL)
  {
    Translate_Object (Light->Children, Vector, Trans);
  }
}



/*****************************************************************************
*
* FUNCTION
*
*   Rotate_Light_Source
*
* INPUT
*   
* OUTPUT
*   
* RETURNS
*   
* AUTHOR
*
*   POV-Ray Team
*   
* DESCRIPTION
*
*   -
*
* CHANGES
*
*   -
*
******************************************************************************/

static void Rotate_Light_Source (Object, Vector, Trans)
OBJECT *Object;
VECTOR Vector;
TRANSFORM *Trans;
{
  Transform_Light_Source(Object, Trans);
}



/*****************************************************************************
*
* FUNCTION
*
*   Scale_Light_Source
*
* INPUT
*   
* OUTPUT
*   
* RETURNS
*   
* AUTHOR
*
*   POV-Ray Team
*   
* DESCRIPTION
*
*   -
*
* CHANGES
*
*   -
*
******************************************************************************/

static void Scale_Light_Source (Object, Vector, Trans)
OBJECT *Object;
VECTOR Vector;
TRANSFORM *Trans;
{
  Transform_Light_Source(Object, Trans);
}



/*****************************************************************************
*
* FUNCTION
*
*   Transform_Light_Source
*
* INPUT
*   
* OUTPUT
*   
* RETURNS
*   
* AUTHOR
*
*   POV-Ray Team
*   
* DESCRIPTION
*
*   -
*
* CHANGES
*
*   -
*
******************************************************************************/

static void Transform_Light_Source (Object, Trans)
OBJECT *Object;
TRANSFORM *Trans;
{
  DBL len;
  LIGHT_SOURCE *Light = (LIGHT_SOURCE *)Object;

  MTransPoint (Light->Center,    Light->Center,    Trans);
  MTransPoint (Light->Points_At, Light->Points_At, Trans);
  MTransPoint (Light->Axis1,     Light->Axis1,     Trans);
  MTransPoint (Light->Axis2,     Light->Axis2,     Trans);

  MTransDirection (Light->Direction, Light->Direction, Trans);

  /* Make sure direction has unit length. */

  VLength(len, Light->Direction);

  if (len > EPSILON)
  {
    VInverseScaleEq(Light->Direction, len);
  }

  if (Light->Children != NULL)
  {
    Transform_Object (Light->Children, Trans);
  }
}



/*****************************************************************************
*
* FUNCTION
*
*   Invert_Light_Source
*
* INPUT
*   
* OUTPUT
*   
* RETURNS
*   
* AUTHOR
*
*   POV-Ray Team
*   
* DESCRIPTION
*
*   -
*
* CHANGES
*
*   -
*
******************************************************************************/

static void Invert_Light_Source (Object)
OBJECT *Object;
{
  LIGHT_SOURCE *Light = (LIGHT_SOURCE *)Object;

  if (Light->Children != NULL)
  {
    Invert_Object (Light->Children);
  }
}



/*****************************************************************************
*
* FUNCTION
*
*   Create_Light_Source
*
* INPUT
*   
* OUTPUT
*   
* RETURNS
*   
* AUTHOR
*
*   POV-Ray Team
*   
* DESCRIPTION
*
*   -
*
* CHANGES
*
*   -
*
******************************************************************************/

LIGHT_SOURCE *Create_Light_Source ()
{
  int i;
  LIGHT_SOURCE *New;

  New = (LIGHT_SOURCE *)POV_MALLOC(sizeof (LIGHT_SOURCE), "light_source");

  INIT_OBJECT_FIELDS(New, LIGHT_OBJECT, &Light_Source_Methods)

  New->Children = NULL;

  Set_Flag(New, NO_SHADOW_FLAG);

  Make_Colour(New->Colour,    1.0, 1.0, 1.0);
  Make_Vector(New->Direction, 0.0, 0.0, 0.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->Fade_Distance = 0.0;
  New->Fade_Power    = 0.0;

  New->Next_Light_Source    = NULL;
  New->Light_Grid           = NULL;
  New->Shadow_Cached_Object = 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;

  New->Atmospheric_Attenuation = FALSE;
  New->Atmosphere_Interaction  = TRUE;

  for (i = 0; i < 6; i++)
  {
    New->Light_Buffer[i] = NULL;
  }

  return (New);
}



/*****************************************************************************
*
* FUNCTION
*
*   Copy_Light_Source
*
* INPUT
*   
* OUTPUT
*   
* RETURNS
*   
* AUTHOR
*
*   POV-Ray Team
*   
* DESCRIPTION
*
*   -
*
* CHANGES
*
*   -
*
******************************************************************************/

static void *Copy_Light_Source (Old)
OBJECT *Old;
{
  int i, j;
  LIGHT_SOURCE *New;
  LIGHT_SOURCE *Light = (LIGHT_SOURCE *)Old;

  New = Create_Light_Source();

  /* Copy light source. */

  *New = *(LIGHT_SOURCE *)Old;

  New->Next_Light_Source = NULL;

  New->Children = Copy_Object (((LIGHT_SOURCE *)Old)->Children);

  if (Light->Light_Grid != NULL)
  {
    New->Light_Grid = Create_Light_Grid(Light->Area_Size1, Light->Area_Size2);

    for (i = 0; i < Light->Area_Size1; i++)
    {
      for (j = 0; j < Light->Area_Size2; j++)
      {
        Assign_Colour(New->Light_Grid[i][j], Light->Light_Grid[i][j]);
      }
    }
  }

  return (New);
}



/*****************************************************************************
*
* FUNCTION
*
*   Destroy_Light_Source
*
* INPUT
*   
* OUTPUT
*   
* RETURNS
*   
* AUTHOR
*
*   POV-Ray Team
*   
* DESCRIPTION
*
*   -
*
* CHANGES
*
*   -
*
******************************************************************************/

static void Destroy_Light_Source (Object)
OBJECT *Object;
{
  int i;
  LIGHT_SOURCE *Light = (LIGHT_SOURCE *)Object;

  if (Light->Light_Grid != NULL)
  {
    for (i = 0; i < Light->Area_Size1; i++)
    {
      POV_FREE(Light->Light_Grid[i]);
    }

    POV_FREE(Light->Light_Grid);
  }

  Destroy_Object(Light->Children);

  POV_FREE(Object);
}



/*****************************************************************************
*
* FUNCTION
*
*   Create_Light_Grid
*
* INPUT
*   
* OUTPUT
*   
* RETURNS
*   
* AUTHOR
*
*   POV-Ray Team
*   
* DESCRIPTION
*
*   -
*
* CHANGES
*
*   -
*
******************************************************************************/

COLOUR **Create_Light_Grid (Size1, Size2)
int Size1, Size2;
{
  int i;
  COLOUR **New;

  New = (COLOUR **)POV_MALLOC(Size1 * sizeof (COLOUR *), "area light");

  for (i = 0; i < Size1; i++)
  {
    New[i] = (COLOUR *)POV_MALLOC(Size2 * sizeof (COLOUR), "area light");
  }

  return (New);
}



/*****************************************************************************
*
* FUNCTION
*
*   cubic_spline
*
* INPUT
*   
* OUTPUT
*   
* RETURNS
*   
* AUTHOR
*
*   POV-Ray Team
*   
* DESCRIPTION
*
*   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.
*
* CHANGES
*
*   -
*
******************************************************************************/

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);
    }
  }

  /* This never happens. [DB] */

/*
  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;
}



/*****************************************************************************
*
* FUNCTION
*
*   Attenuate_Light
*
* INPUT
*   
* OUTPUT
*   
* RETURNS
*   
* AUTHOR
*
*   POV-Ray Team
*   
* DESCRIPTION
*
*   -
*
* CHANGES
*
*   Jan 1995 : Added attenuation due to atmospheric scattering and light
*              source distance. Added cylindrical light source. [DB]
*
******************************************************************************/

DBL Attenuate_Light (Light, Ray, Distance)
LIGHT_SOURCE *Light;
RAY *Ray;
DBL Distance;
{
  DBL len, k, costheta;
  DBL Attenuation = 1.0;
  VECTOR P, V1;

  /* If this is a spotlight then attenuate based on the incidence angle. */

  switch (Light->Light_Type)
  {
    case SPOT_SOURCE:

      VDot(costheta, Ray->Direction, Light->Direction);

      costheta *= -1.0;

      if (costheta > 0.0)
      {
        Attenuation = pow(costheta, Light->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->Radius > 0.0)
        {
          Attenuation *= cubic_spline(Light->Falloff, Light->Radius, costheta);
        }
/*
        Debug_Info("Atten: %lg\n", Attenuation);
*/
      }
      else
      {
        Attenuation = 0.0;
      }

      break;

    case CYLINDER_SOURCE:

      VSub(V1, Ray->Initial, Light->Center);

      VDot(k, V1, Light->Direction);

      if (k > 0.0)
      {
        VLinComb2(P, 1.0, V1, -k, Light->Direction);

        VLength(len, P);

        if (len < Light->Falloff)
        {
          len = 1.0 - len / Light->Falloff;

          Attenuation = pow(len, Light->Coeff);

          if (Light->Radius > 0.0)
          {
            Attenuation *= cubic_spline(1.0 - Light->Radius / Light->Falloff, 1.0, len);
          }
        }
        else
        {
          Attenuation = 0.0;
        }
      }
      else
      {
        Attenuation = 0.0;
      }

      break;
  }

  if (Attenuation > 0.0)
  {
    /* Attenuate light due to light source distance. */

    if ((Light->Fade_Power > 0.0) && (fabs(Light->Fade_Distance) > EPSILON))
    {
      Attenuation *= 2.0 / (1.0 + pow(Distance / Light->Fade_Distance, Light->Fade_Power));
    }
  }

  return(Attenuation);
}
