/****************************************************************************
 *               splines.cpp
 *
 * Contributed by ???
 *
 * This module implements splines.
 *
 * from Persistence of Vision(tm) Ray Tracer version 3.6.
 * Copyright 1991-2003 Persistence of Vision Team
 * Copyright 2003-2009 Persistence of Vision Raytracer Pty. Ltd.
 *---------------------------------------------------------------------------
 * 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 developers. There are strict rules
 * regarding how you are permitted to use this file. These rules are contained
 * in the distribution and derivative versions licenses which should have been
 * provided with this file.
 *
 * These licences may be found online, linked from the end-user license
 * agreement that is located at http://www.povray.org/povlegal.html
 *---------------------------------------------------------------------------
 * 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.
 *---------------------------------------------------------------------------
 * $File: //depot/povray/3.5/source/splines.cpp $
 * $Revision: #27 $
 * $Change: 4808 $
 * $DateTime: 2009/06/03 08:16:08 $
 * $Author: calimet $
 * $Log$
 *****************************************************************************/

#include "frame.h"
#include "userio.h"
#include "splines.h"

BEGIN_POV_NAMESPACE

/*****************************************************************************
* Global Variables
******************************************************************************/


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


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


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


/*****************************************************************************
* Local functions
******************************************************************************/

DBL linear_interpolate(SPLINE_ENTRY * se, int i, int k, DBL p);
DBL quadratic_interpolate(SPLINE_ENTRY * se, int i, int k, DBL p);
DBL natural_interpolate(SPLINE_ENTRY * se, int i, int k, DBL p);
DBL catmull_rom_interpolate(SPLINE_ENTRY * se, int i, int k, DBL p);
int findt(SPLINE * sp, DBL Time);
void mkfree(SPLINE * sp, int i);
void Precompute_Cubic_Coeffs(SPLINE *sp);

/*****************************************************************************
*
* FUNCTION
*
*       Precompute_Cubic_Coeffs
*
* INPUT
*
*       sp : a pointer to the spline to compute interpolation coefficients for
*
* OUTPUT
*
* RETURNS
*
* AUTHOR
*
*   Mark Wagner
*
* DESCRIPTION
*
*       Computes the coefficients used in cubic_interpolate.
*
* CHANGES
*
******************************************************************************/

void Precompute_Cubic_Coeffs(SPLINE *sp)
{
    int i, k;
    DBL *h;
    DBL *b;
    DBL *u;
    DBL *v;

    h = (DBL *)POV_MALLOC(sp->Number_Of_Entries*sizeof(DBL), "Spline coefficient storage");
    b = (DBL *)POV_MALLOC(sp->Number_Of_Entries*sizeof(DBL), "Spline coefficient storage");
    u = (DBL *)POV_MALLOC(sp->Number_Of_Entries*sizeof(DBL), "Spline coefficient storage");
    v = (DBL *)POV_MALLOC(sp->Number_Of_Entries*sizeof(DBL), "Spline coefficient storage");

    for(k = 0; k < 5; k++)
    {
        for(i = 0; i <= sp->Number_Of_Entries - 2; i++)
        {
            h[i] = sp->SplineEntries[i+1].par - sp->SplineEntries[i].par;
            b[i] = (sp->SplineEntries[i+1].vec[k] - sp->SplineEntries[i].vec[k])/h[i];
        }
        u[1] = 2*(h[0]+h[1]);
        v[1] = 6*(b[1]-b[0]);
        for(i = 2; i <= sp->Number_Of_Entries - 2; i++)
        {
            u[i] = 2*(h[i]+h[i-1]) - (h[i-1]*h[i-1])/u[i-1];
            v[i] = 6*(b[i]-b[i-1]) - (h[i-1]*v[i-1])/u[i-1];
        }
        sp->SplineEntries[sp->Number_Of_Entries-1].coeff[k] = 0;
        for(i = sp->Number_Of_Entries-2; i > 0; i--)
        {
            sp->SplineEntries[i].coeff[k] = (v[i] - h[i]*sp->SplineEntries[i+1].coeff[k])/u[i];
        }
        sp->SplineEntries[0].coeff[k] = 0;
    }
    sp->Coeffs_Computed = true;

    POV_FREE(h);
    POV_FREE(b);
    POV_FREE(u);
    POV_FREE(v);
}


/*****************************************************************************
*
* FUNCTION
*
*       linear_interpolate
*
* INPUT
*
*       se : a pointer to the entries in the spline
*       i  : the first point to interpolate between
*       k  : which dimension of the 5D vector to interpolate in
*       p  : the parameter to interpolate the value for
*
* OUTPUT
*
* RETURNS
*
*       The value of the kth dimension of the vector linearly interpolated at p
*
* AUTHOR
*
*   Wolfgang Ortmann
*
* DESCRIPTION
*
* CHANGES
*
******************************************************************************/

DBL linear_interpolate(SPLINE_ENTRY * se, int i, int k, DBL p)
{
    DBL p1, p2, v1, v2;
    p1 = se[i].par;
    p2 = se[i+1].par;
    v1 = se[i].vec[k];
    v2 = se[i+1].vec[k];
    return (p-p1)*(v2-v1)/(p2-p1)+v1;
}


/*****************************************************************************
*
* FUNCTION
*
*       quadratic_interpolate
*
* INPUT
*
*       se : a pointer to the entries in the spline
*       i  : the second point of three to interpolate between
*       k  : which dimension of the 5D vector to interpolate in
*       p  : the parameter to interpolate the value for
*
* OUTPUT
*
* RETURNS
*
*       The value of the kth dimension of the quadratic interpolation of the
*        vector at p
*
* AUTHOR
*
*   Wolfgang Ortmann
*
* DESCRIPTION
*
* CHANGES
*
******************************************************************************/

DBL quadratic_interpolate(SPLINE_ENTRY * se, int i, int k, DBL p)
{
  /* fit quadratic function to three point*/
  DBL n;
  DBL p1, p2, p3,
      v1, v2, v3,
      a, b, c;
  /* assignments to make life easier */
  p1=se[i-1].par;    p2=se[i].par;    p3=se[i+1].par;
  v1=se[i-1].vec[k]; v2=se[i].vec[k]; v3=se[i+1].vec[k];

  n=(p2-p1)*(p3-p1)*(p3-p2);

  /* MWW NOTE: I'm assuming these are correct.  I didn't write them */
  a=(-p2*v1+p3*v1
      +p1*v2-p3*v2
      -p1*v3+p2*v3) /n;
  b=( p2*p2*v1 - p3*p3*v1
      -p1*p1*v2 + p3*p3*v2
      +p1*p1*v3 - p2*p2*v3) /n;
  c=(-p2*p2*p3*v1+p2*p3*p3*v1
      +p1*p1*p3*v2-p1*p3*p3*v2
      -p1*p1*p2*v3+p1*p2*p2*v3) /n;
  return (a*p+b)*p+c; /* Fast way of doing ap^2+bp+c */
}


/*****************************************************************************
*
* FUNCTION
*
*       natural_interpolate
*
* INPUT
*
*       se : a pointer to the entries in the spline
*       i  : the first point in the interpolation interval
*       k  : which dimension of the 5D vector to interpolate in
*       p  : the parameter to interpolate the value for
*
* OUTPUT
*
* RETURNS
*
*       The value of the kth dimension of the natural cubic interpolation
*        of the vector at p
*
* AUTHOR
*
*       Mark Wagner
*
* DESCRIPTION
*
* CHANGES
*
******************************************************************************/

DBL natural_interpolate(SPLINE_ENTRY * se, int i, int k, DBL p)
{
    DBL h, tmp;
    h = se[i+1].par - se[i].par;
    tmp = se[i].coeff[k]/2.0 + ((p - se[i].par)*(se[i+1].coeff[k] - se[i].coeff[k]))/(6.0*h);
    tmp = -(h/6.0)*(se[i+1].coeff[k] + 2.0*se[i].coeff[k]) + (se[i+1].vec[k] - se[i].vec[k])/h + (p - se[i].par)*tmp;
    return se[i].vec[k] + (p - se[i].par)*tmp;
}


/*****************************************************************************
*
* FUNCTION
*
*       catmull_rom_interpolate
*
* INPUT
*
*       se : a pointer to the entries in the spline
*       i  : the first point in the interpolation interval
*       k  : which dimension of the 5D vector to interpolate in
*       p  : the parameter to interpolate the value for
*
* OUTPUT
*
* RETURNS
*
*       The value of the kth dimension of the Catmull-Rom interpolation of the
*        vector at p
*
* AUTHOR
*
*       Mark Wagner
*
* DESCRIPTION
*
* CHANGES
*
******************************************************************************/

DBL catmull_rom_interpolate(SPLINE_ENTRY * se, int i, int k, DBL p)
{
    DBL dt = (se[i+1].par - se[i].par); /* Time between se[i] and se[i+1] */
    DBL u = (p - se[i].par)/dt;         /* Fractional time from se[i] to p */
    DBL dp0 = ((se[i].vec[k] - se[i-1].vec[k])/(se[i].par - se[i-1].par) + (se[i+1].vec[k] - se[i].vec[k])/(se[i+1].par - se[i].par))/2.0 * dt;
    DBL dp1 = ((se[i+2].vec[k] - se[i+1].vec[k])/(se[i+2].par - se[i+1].par) + (se[i+1].vec[k] - se[i].vec[k])/(se[i+1].par - se[i].par))/2.0 * dt;

    return (se[i].vec[k] * (2*u*u*u - 3*u*u + 1) +
            se[i+1].vec[k] * (3*u*u - 2*u*u*u) +
            dp0 * (u*u*u - 2*u*u + u) +
            dp1 * (u*u*u - u*u) );
}


/*****************************************************************************
*
* FUNCTION
*
*       findt
*
* INPUT
*
*       sp   : a pointer to a spline
*       Time : The parameter to search for
*
* OUTPUT
*
* RETURNS
*
*       The first spline entry with a parameter greater than Time
*
* AUTHOR
*
*   Wolfgang Ortmann
*
* DESCRIPTION
*
* CHANGES
*
*       Mark Wagner  6 Nov 2000 : Changed from linear search to binary search
*
******************************************************************************/

int findt(SPLINE * sp, DBL Time)
{
    int i, i2;
    SPLINE_ENTRY * se;
    se = sp->SplineEntries;
    if(sp->Number_Of_Entries == 0) return 0;
    
    if(Time <= se[0].par) return 0;

    if(Time >= se[sp->Number_Of_Entries-1].par) return sp->Number_Of_Entries;

    i = sp->Number_Of_Entries / 2;
    /* Bracket the proper entry */
    if( Time > se[i].par ) /* i is lower, i2 is upper */
    {
        i2 = sp->Number_Of_Entries-1;
        while(i2 - i > 1)
        {
            if(Time > se[i+(i2-i)/2].par)
                i = i+(i2-i)/2;
            else
                i2 = i+(i2-i)/2;
        }
        return i2;
    }
    else /* i is upper, i2 is lower */
    {
        i2 = 0;
        while(i - i2 > 1)
        {
            if(Time > se[i2+(i-i2)/2].par)
                i2 = i2+(i-i2)/2;
            else
                i = i2+(i-i2)/2;
        }
        return i;
    }
}


/*****************************************************************************
*
* FUNCTION
*
*       mkfree
*
* INPUT
*
*       sp : a pointer to the entries in the spline
*       i  : the index of the entry to make available
*
* OUTPUT
*
* RETURNS
*
* AUTHOR
*
*   Wolfgang Ortmann
*
* DESCRIPTION
*
*       Makes the spline entry at index i available for inserting a new entry
*       by moving all entries starting with that index down by one slot in the
*       array.
*
* CHANGES
*
******************************************************************************/

void mkfree(SPLINE * sp, int i)
{
    int j;
    SPLINE_ENTRY * se;
    se = sp->SplineEntries;
    
    for (j=sp->Number_Of_Entries; j>i; j--)
        se[j] = se[j-1];
}


/*****************************************************************************
*
* FUNCTION
*
*       Create_Spline
*
* INPUT
*
*       Type : the type of the new spline
*
* OUTPUT
*
* RETURNS
*
*       A pointer to the newly-created spline
*
* AUTHOR
*
*   Wolfgang Ortmann
*
* DESCRIPTION
*
* CHANGES
*
*       Mark Wagner  6 Nov 2000 : Added support for dynamic resizing of the
*               SplineEntry array.
*       Mark Wagner 25 Aug 2001 : Added support for pre-computing coefficients
*               of cubic splines
*
******************************************************************************/

SPLINE * Create_Spline(int Type)
{
    SPLINE * New;
    New = (SPLINE *)POV_MALLOC(sizeof(SPLINE), "spline");
    New->SplineEntries = (SPLINE_ENTRY *)POV_MALLOC(INIT_SPLINE_SIZE*sizeof(SPLINE_ENTRY), "spline entry");
    New->Max_Entries = INIT_SPLINE_SIZE;
    New->Number_Of_Entries = 0;
    New->Type = Type;
    New->Coeffs_Computed = false;
    New->Terms = 2;
    New->Cache_Valid = false;
    int i;
    for (i=0; i< New->Max_Entries; i++)
    {
      New->SplineEntries[i].par=-1e6;   //this should be a negative large number
    }

    return New;
}


/*****************************************************************************
*
* FUNCTION
*
*       Copy_Spline
*
* INPUT
*
*       Old : A pointer to the old spline
*
* OUTPUT
*
* RETURNS
*
*       A pointer to the new spline
*
* AUTHOR
*
*   Wolfgang Ortmann
*
* DESCRIPTION
*
* CHANGES
*
*       Mark Wagner  6 Nov 2000 : Added support for dynamic resizing of the
*               SplineEntry array
*       Mark Wagner 25 Aug 2001 : Added support for pre-computing coefficients
*               of cubic splines
*
******************************************************************************/

SPLINE * Copy_Spline(SPLINE * Old)
{
    SPLINE * New;
    New = (SPLINE *)POV_MALLOC(sizeof(SPLINE), "spline");
    
    New->SplineEntries = (SPLINE_ENTRY *)POV_MALLOC(Old->Number_Of_Entries*sizeof(SPLINE_ENTRY), "spline entry");
    POV_MEMCPY(New->SplineEntries, Old->SplineEntries, Old->Number_Of_Entries*sizeof(SPLINE_ENTRY));

    New->Max_Entries = Old->Number_Of_Entries;
    New->Number_Of_Entries = Old->Number_Of_Entries;
    New->Type = Old->Type;
    New->Coeffs_Computed = Old->Coeffs_Computed;
    New->Terms = Old->Terms;
    New->Cache_Valid = false; // we don't copy the cache so mark it as invalid

    return New;
}


/*****************************************************************************
*
* FUNCTION
*
*       Destroy_Spline
*
* INPUT
*
*       Spline : A pointer to the spline to delete
*
* OUTPUT
*
* RETURNS
*
* AUTHOR
*
*   Wolfgang Ortmann
*
* DESCRIPTION
*
*       Deletes the SplineEntry array
*
* CHANGES
*
******************************************************************************/

void Destroy_Spline(SPLINE * Spline)
{
    POV_FREE(Spline->SplineEntries);
    POV_FREE(Spline);
}


/*****************************************************************************
*
* FUNCTION
*
*       Insert_Spline_Entry
*
* INPUT
*
*       sp : a pointer to the spline to insert the new value in
*       p  : the value of the parameter at that point
*       v  : a 5D vector that is the value of the spline at that parameter
*
* OUTPUT
*
* RETURNS
*
* AUTHOR
*
*   Wolfgang Ortmann
*
* DESCRIPTION
*
*       Inserts a value into the given spline, sorting the entries in order by
*               increasing p
*       If a value of the parameter already exists, that value is overwritten
*
* CHANGES
*
*       Mark Wagner  8 Nov 2000 : Added dynamic sizing of the SplineEntry array
*          If a value of the parameter already exists, that value is overwritten
*       Mark Wagner 25 Aug 2001 : Modified for compatibility with new method of
*          computing cubic splines.
*       Mark Wagner 30 Aug 2001 : Fixed a bug with over-writing of parameter 
*          values.
*
******************************************************************************/

void Insert_Spline_Entry(SPLINE * sp, DBL p, EXPRESS v)
{
    int i, k;

    /* Reset the Coeffs_Computed flag.  Inserting a new point invalidates 
     *  pre-computed coefficients */
    sp->Coeffs_Computed = false;
    sp->Cache_Valid = false;
    /* If all space is used, reallocate */
    if(sp->Number_Of_Entries >= sp->Max_Entries)
    {
        sp->Max_Entries += INIT_SPLINE_SIZE;
        sp->SplineEntries = (SPLINE_ENTRY *)POV_REALLOC(sp->SplineEntries, sp->Max_Entries * sizeof(SPLINE_ENTRY), "Temporary Spline Entries");
        for (i = sp->Number_Of_Entries; i < sp->Max_Entries; i++)
        {
          sp->SplineEntries[i].par=-1e6;
        }
    }
    i = findt(sp, p);
    /* If p is already in spline, replace */
    /* The clause after the || is needed because findt returns sp->Number_Of_Entries
     * if p is greater than OR EQUAL TO the highest par in the spline */
    if(sp->Number_Of_Entries != 0 && ((sp->SplineEntries[i].par == p) || (i == sp->Number_Of_Entries && sp->SplineEntries[i-1].par == p)))
    {
        for(k=0; k<5; k++)
            sp->SplineEntries[i].vec[k] = v[k];
    }
    else
    {
        mkfree(sp, i);
        sp->SplineEntries[i].par = p;

        for(k=0; k<5; k++)
            sp->SplineEntries[i].vec[k] = v[k];

        sp->Number_Of_Entries += 1;
    }
}


/*****************************************************************************
*
* FUNCTION
*
*       Get_Spline_Value
*
* INPUT
*
*       sp : a pointer to the spline to interpolate
*       p  : the parameter to interpolate the value for
*       v  : a pointer to a 5D vector to store the interpolated values in
*
* OUTPUT
*
*       v  : a pointer to a 5D vector to store the interpolated values in
*
* RETURNS
*
*       The value of the first element of the interpolated vector
*
* AUTHOR
*
*   Wolfgang Ortmann
*
* DESCRIPTION
*
*       Interpolates the spline at the given point.  If the point is outside the
*               range of parameters of the spline, returns the value at the
*               appropriate endpoint (ie. no extrapolation).  If there are not
*               enough points in the spline to do the desired type of
*               interpolation, does the next best type (cubic->quadratic->linear).
*
* CHANGES
*
*       Mark Wagner  8 Nov 2000 : Complete overhaul.  I am apalled at the
*               problems I found in the original implementation
*       Mark Wagner  25 Aug 2001 : Changed interpolation method to pre-compute
*               coefficients for cubic splines.
*       Mark Wagner  24 Feb 2002 : Added support for Catmull-Rom interpolation
*               Re-arranged the code to make future additions cleaner by moving
*                more of the code into the "switch" statement
*
******************************************************************************/

DBL Get_Spline_Val(SPLINE *sp, DBL p, EXPRESS v, int *Terms)
{
    int i, k;
    int last;
    SPLINE_ENTRY * se;

    *Terms = sp->Terms;

    if(!sp->Coeffs_Computed)
    {
        switch(sp->Type)
        {
            case NATURAL_SPLINE:
                Precompute_Cubic_Coeffs(sp);
                break;
            default:
                break;
        }
    }

	// check if the value is in the cache
	if((sp->Cache_Point == p) && (sp->Cache_Type == sp->Type))
	{
		if(sp->Cache_Valid == true) // doing this here is more efficient as it is rarely false [trf]
		{
			Assign_Express(v, sp->Cache_Data);
			return sp->Cache_Data[0];
		}
	}

	// init some cache data
	sp->Cache_Valid = false;
	sp->Cache_Type = sp->Type;
	sp->Cache_Point = p;

    last = sp->Number_Of_Entries-1;
    se = sp->SplineEntries;
    
    if(last == 0)
    {/* if only one entry then return this */
        for(k=0; k<5; k++)
            v[k] = se[0].vec[k];
        return se[0].vec[0];
    }

    /* Find which spline segment we're in.  i is the control point at the end of the segment */
    i = findt(sp, p);

    switch(sp->Type)
    {
        case LINEAR_SPLINE:
            for(k=0; k<5; k++)
            {
                /* If outside spline range, return first or last point */
                if(i == 0)
                    v[k] = se[0].vec[k];
                else if(i > last)
                    v[k] = se[last].vec[k];
                /* Else, normal case */
                else
                    v[k] = linear_interpolate(se, i-1, k, p);
            }
            break;
        case QUADRATIC_SPLINE:
            for(k=0; k<5; k++)
            {
                /* If outside the spline range, return the first or last point */
                if(i == 0)
                    v[k] = se[0].vec[k];
                else if(i > last)
                    v[k] = se[last].vec[k];
                /* If not enough points, reduce order */
                else if(last == 1)
                    v[k] = linear_interpolate(se, i-1, k, p);
                /* Normal case: between the second and last points */
                else if(i > 1)
                {
                    v[k] = quadratic_interpolate(se, i-1, k, p);
                }
                else /* Special case: between first and second points */
                {
                    v[k] = quadratic_interpolate(se, i, k, p);
                }
            }
            break;
        case NATURAL_SPLINE:
            for(k=0; k<5; k++)
            {
                /* If outside the spline range, return the first or last point */
                if(i == 0)
                    v[k] = se[0].vec[k];
                else if(i > last)
                    v[k] = se[last].vec[k];
                /* Else, normal case.  cubic_interpolate can handle the case of not enough points */
                else
                    v[k] = natural_interpolate(se, i-1, k, p);
            }
            break;
        case CATMULL_ROM_SPLINE:
            for(k=0; k<5; k++)
            {
                /* If only two points, return their average */
                if(last == 1)
                    v[k] = (se[0].vec[k] + se[1].vec[k])/2.0;
                /* Catmull-Rom: If only three points, return the second one */
                /* Catmull-Rom: Can't interpolate before second point or after next-to-last */
                else if(i < 2)
                    v[k] = se[1].vec[k];
                else if(i >= last)
                    v[k] = se[last-1].vec[k];
                /* Else, normal case */
                else
                    v[k] = catmull_rom_interpolate(se, i-1, k, p);
            }
            break;
        default:
            Error("Unknown spline type %d found.\n", sp->Type);

    }

	// put data in cache
	Assign_Express(sp->Cache_Data, v);
	sp->Cache_Valid = true;

    return v[0];
}

END_POV_NAMESPACE
