/*****************************************************************************
*                hcmplx.c
*
*  This module implements hypercomplex Julia fractals.
*
*  This file was written by Pascal Massimino.
*
*  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 "povray.h"
#include "vector.h"
#include "povproto.h"
#include "fractal.h"
#include "spheres.h"
#include "hcmplx.h"



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

#ifndef Fractal_Tolerance
#define Fractal_Tolerance 1e-8
#endif


#define HMult(xr,yr,zr,wr,x1,y1,z1,w1,x2,y2,z2,w2)        \
    (xr) = (x1) * (x2) - (y1) * (y2) - (z1) * (z2) + (w1) * (w2);   \
    (yr) = (y1) * (x2) + (x1) * (y2) - (w1) * (z2) - (z1) * (w2);   \
    (zr) = (z1) * (x2) - (w1) * (y2) + (x1) * (z2) - (y1) * (w2);   \
    (wr) = (w1) * (x2) + (z1) * (y2) + (y1) * (z2) + (x1) * (w2);

#define HSqr(xr,yr,zr,wr,x,y,z,w)         \
    (xr) = (x) * (x) - (y) * (y) - (z) * (z) + (w) * (w) ;  \
    (yr) = 2.0 * ( (x) * (y) - (z) * (w) );     \
    (zr) = 2.0 * ( (z) * (x) - (w) * (y) );       \
    (wr) = 2.0 * ( (w) * (x) + (z) * (y) );



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



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

static int HReciprocal PARAMS((DBL * xr, DBL * yr, DBL * zr, DBL * wr, DBL x, DBL y, DBL z, DBL w));
static void HFunc PARAMS((DBL * xr, DBL * yr, DBL * zr, DBL * wr, DBL x, DBL y, DBL z, DBL w, FRACTAL *f));



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

static CMPLX exponent = {0,0};

/******** Computations with Hypercomplexes **********/



/*****************************************************************************
*
* FUNCTION
*
* INPUT
*
* OUTPUT
*
* RETURNS
*
* AUTHOR
*
*   Pascal Massimino
*
* DESCRIPTION
*
* CHANGES
*
******************************************************************************/

static int HReciprocal(xr, yr, zr, wr, x, y, z, w)
DBL *xr, *yr, *zr, *wr, x, y, z, w;
{
  DBL det, mod, xt_minus_yz;

  det = ((x - w) * (x - w) + (y + z) * (y + z)) * ((x + w) * (x + w) + (y - z) * (y - z));

  if (det == 0.0)
  {
    return (-1);
  }

  mod = (x * x + y * y + z * z + w * w);

  xt_minus_yz = x * w - y * z;

  *xr = (x * mod - 2 * w * xt_minus_yz) / det;
  *yr = (-y * mod - 2 * z * xt_minus_yz) / det;
  *zr = (-z * mod - 2 * y * xt_minus_yz) / det;
  *wr = (w * mod - 2 * x * xt_minus_yz) / det;

  return (0);
}



/*****************************************************************************
*
* FUNCTION Hfunc
*
* INPUT 4D Hypercomplex number, pointer to fractal object
*
* OUTPUT  calculates the 4D generalization of fractal->Complex_Function
*
* RETURNS void
*
* AUTHOR
*
*   Pascal Massimino
*
* DESCRIPTION
*   Hypercomplex numbers allow generalization of any complex->complex
*   function in a uniform way. This function implements a general
*   unary 4D function based on the corresponding complex function. 
*
* CHANGES
*  Generalized to use Complex_Function()   TW 
*
******************************************************************************/

static void HFunc(xr, yr, zr, wr, x, y, z, w, f)
DBL *xr, *yr, *zr, *wr, x, y, z, w;
FRACTAL *f;
{
  CMPLX a, b, ra, rb;
  
  /* convert to duplex form */
  a.x = x - w;
  a.y = y + z;
  b.x = x + w;
  b.y = y - z;
  
  if(f->Sub_Type == PWR_STYPE)
  {
     exponent = f->exponent;
  }
  
  /* apply function to each part */
  Complex_Function(&ra, &a, f);
  Complex_Function(&rb, &b, f);

  /* convert back */
  *xr = .5 * (ra.x + rb.x);
  *yr = .5 * (ra.y + rb.y);
  *zr = .5 * (ra.y - rb.y);
  *wr = .5 * (rb.x - ra.x);
}



/*****************************************************************************
*
* FUNCTION
*
* INPUT
*
* OUTPUT
*
* RETURNS
*
* AUTHOR
*
*   Pascal Massimino
*
* DESCRIPTION
*
* CHANGES
*
******************************************************************************/

/*------------------ z2 Iteration method ------------------*/

int Iteration_HCompl(IPoint, HCompl)
VECTOR IPoint;
FRACTAL *HCompl;
{
  int i;
  DBL yz, xw;
  DBL Exit_Value;
  DBL x, y, z, w;

  x = Sx[0] = IPoint[X];
  y = Sy[0] = IPoint[Y];
  z = Sz[0] = IPoint[Z];
  w = Sw[0] = (HCompl->SliceDist
                  - HCompl->Slice[X]*x 
                  - HCompl->Slice[Y]*y 
                  - HCompl->Slice[Z]*z)/HCompl->Slice[T]; 
  
  Exit_Value = HCompl->Exit_Value;

  for (i = 1; i <= HCompl->n; ++i)
  {
    yz = y * y + z * z;
    xw = x * x + w * w;

    if ((xw + yz) > Exit_Value)
    {
      return (FALSE);
    }

    Sx[i] = xw - yz + HCompl->Julia_Parm[X];
    Sy[i] = 2.0 * (x * y - z * w) + HCompl->Julia_Parm[Y];
    Sz[i] = 2.0 * (x * z - w * y) + HCompl->Julia_Parm[Z];
    Sw[i] = 2.0 * (x * w + y * z) + HCompl->Julia_Parm[T];

    w = Sw[i];
    x = Sx[i];

    z = Sz[i];
    y = Sy[i];
  }

  return (TRUE);
}



/*****************************************************************************
*
* FUNCTION
*
* INPUT
*
* OUTPUT
*
* RETURNS
*
* AUTHOR
*
*   Pascal Massimino
*
* DESCRIPTION
*
* CHANGES
*
******************************************************************************/

int D_Iteration_HCompl(IPoint, HCompl, Dist)
VECTOR IPoint;
FRACTAL *HCompl;
DBL *Dist;
{
  int i;
  DBL yz, xw;
  DBL Exit_Value, F_Value, Step;
  DBL x, y, z, w;
  VECTOR H_Normal;

  x = Sx[0] = IPoint[X];
  y = Sy[0] = IPoint[Y];
  z = Sz[0] = IPoint[Z];
  w = Sw[0] = (HCompl->SliceDist
                  - HCompl->Slice[X]*x 
                  - HCompl->Slice[Y]*y 
                  - HCompl->Slice[Z]*z)/HCompl->Slice[T]; 

  Exit_Value = HCompl->Exit_Value;

  for (i = 1; i <= HCompl->n; ++i)
  {
    yz = y * y + z * z;
    xw = x * x + w * w;

    if ((F_Value = xw + yz) > Exit_Value)
    {
      Normal_Calc_HCompl(H_Normal, i - 1, HCompl);

      VDot(Step, H_Normal, Direction);

      if (Step < -Fractal_Tolerance)
      {
        Step = -2.0 * Step;

        if ((F_Value > Precision * Step) && (F_Value < 30 * Precision * Step))
        {
          *Dist = F_Value / Step;

          return (FALSE);
        }
      }

      *Dist = Precision;

      return (FALSE);
    }

    Sx[i] = xw - yz + HCompl->Julia_Parm[X];
    Sy[i] = 2.0 * (x * y - z * w) + HCompl->Julia_Parm[Y];
    Sz[i] = 2.0 * (x * z - w * y) + HCompl->Julia_Parm[Z];
    Sw[i] = 2.0 * (x * w + y * z) + HCompl->Julia_Parm[T];

    w = Sw[i];
    x = Sx[i];

    z = Sz[i];
    y = Sy[i];
  }

  *Dist = Precision;

  return (TRUE);
}



/*****************************************************************************
*
* FUNCTION
*
* INPUT
*
* OUTPUT
*
* RETURNS
*
* AUTHOR
*
*   Pascal Massimino
*
* DESCRIPTION
*
* CHANGES
*
******************************************************************************/

void Normal_Calc_HCompl(Result, N_Max, fractal)
VECTOR Result;
int N_Max;
FRACTAL *fractal;
{
  DBL n1, n2, n3, n4;
  int i;
  DBL x, y, z, w;
  DBL xx, yy, zz, ww;
  DBL Pow;

  /*
   * Algebraic properties of hypercomplexes allows simplifications in
   * computations...
   */

  x = Sx[0];
  y = Sy[0];
  z = Sz[0];
  w = Sw[0];

  Pow = 2.0;

  for (i = 1; i < N_Max; ++i)
  {

    /*
     * For a map z->f(z), f depending on c, one must perform here :
     *
     * (x,y,z,w) * df/dz(Sx[i],Sy[i],Sz[i],Sw[i]) -> (x,y,z,w) ,
     *
     * up to a constant.
     */

    /******************* Case z->z^2+c *****************/

    /* the df/dz part needs no work */

    HMult(xx, yy, zz, ww, Sx[i], Sy[i], Sz[i], Sw[i], x, y, z, w);

    w = ww;
    z = zz;
    y = yy;
    x = xx;

    Pow *= 2.0;
  }

  n1 = Sx[N_Max] * Pow;
  n2 = Sy[N_Max] * Pow;
  n3 = Sz[N_Max] * Pow;
  n4 = Sw[N_Max] * Pow;

  Result[X] = x * n1 + y * n2 + z * n3 + w * n4;
  Result[Y] = -y * n1 + x * n2 - w * n3 + z * n4;
  Result[Z] = -z * n1 - w * n2 + x * n3 + y * n4;
}



/*****************************************************************************
*
* FUNCTION
*
* INPUT
*
* OUTPUT
*
* RETURNS
*
* AUTHOR
*
*   Pascal Massimino
*
* DESCRIPTION
*
* CHANGES
*
******************************************************************************/

int F_Bound_HCompl(Ray, Fractal, Depth_Min, Depth_Max)
RAY *Ray;
FRACTAL *Fractal;
DBL *Depth_Min, *Depth_Max;
{
  return (Intersect_Sphere(Ray, Fractal->Center, Fractal->Radius_Squared, Depth_Min, Depth_Max));
}

/****************************************************************/
/*--------------------------- z3 -------------------------------*/
/****************************************************************/



/*****************************************************************************
*
* FUNCTION
*
* INPUT
*
* OUTPUT
*
* RETURNS
*
* AUTHOR
*
*   Pascal Massimino
*
* DESCRIPTION
*
* CHANGES
*
******************************************************************************/

int Iteration_HCompl_z3(IPoint, HCompl)
VECTOR IPoint;
FRACTAL *HCompl;
{
  int i;
  DBL Norm, xx, yy, zz, ww;
  DBL Exit_Value;
  DBL x, y, z, w;

  x = Sx[0] = IPoint[X];
  y = Sy[0] = IPoint[Y];
  z = Sz[0] = IPoint[Z];
  w = Sw[0] = (HCompl->SliceDist
                  - HCompl->Slice[X]*x 
                  - HCompl->Slice[Y]*y 
                  - HCompl->Slice[Z]*z)/HCompl->Slice[T]; 

  Exit_Value = HCompl->Exit_Value;

  for (i = 1; i <= HCompl->n; ++i)
  {
    Norm = x * x + y * y + z * z + w * w;

    /* is this test correct ? */
    if (Norm > Exit_Value)
    {
      return (FALSE);
    }

    /*************** Case: z->z^2+c *********************/
    HSqr(xx, yy, zz, ww, x, y, z, w);

    x = Sx[i] = xx + HCompl->Julia_Parm[X];
    y = Sy[i] = yy + HCompl->Julia_Parm[Y];
    z = Sz[i] = zz + HCompl->Julia_Parm[Z];
    w = Sw[i] = ww + HCompl->Julia_Parm[T];

  }

  return (TRUE);
}



/*****************************************************************************
*
* FUNCTION
*
* INPUT
*
* OUTPUT
*
* RETURNS
*
* AUTHOR
*
*   Pascal Massimino
*
* DESCRIPTION
*
* CHANGES
*
******************************************************************************/

int D_Iteration_HCompl_z3(IPoint, HCompl, Dist)
VECTOR IPoint;
FRACTAL *HCompl;
DBL *Dist;
{
  int i;
  DBL xx, yy, zz, ww;
  DBL Exit_Value, F_Value, Step;
  DBL x, y, z, w;
  VECTOR H_Normal;

  x = Sx[0] = IPoint[X];
  y = Sy[0] = IPoint[Y];
  z = Sz[0] = IPoint[Z];
  w = Sw[0] = (HCompl->SliceDist
                  - HCompl->Slice[X]*x 
                  - HCompl->Slice[Y]*y 
                  - HCompl->Slice[Z]*z)/HCompl->Slice[T]; 

  Exit_Value = HCompl->Exit_Value;

  for (i = 1; i <= HCompl->n; ++i)
  {
    F_Value = x * x + y * y + z * z + w * w;

    if (F_Value > Exit_Value)
    {
      Normal_Calc_HCompl_z3(H_Normal, i - 1, HCompl);

      VDot(Step, H_Normal, Direction);

      if (Step < -Fractal_Tolerance)
      {
        Step = -2.0 * Step;

        if ((F_Value > Precision * Step) && (F_Value < 30 * Precision * Step))
        {
          *Dist = F_Value / Step;

          return (FALSE);
        }
      }

      *Dist = Precision;

      return (FALSE);
    }

    /*************** Case: z->z^2+c *********************/

    HSqr(xx, yy, zz, ww, x, y, z, w);

    x = Sx[i] = xx + HCompl->Julia_Parm[X];
    y = Sy[i] = yy + HCompl->Julia_Parm[Y];
    z = Sz[i] = zz + HCompl->Julia_Parm[Z];
    w = Sw[i] = ww + HCompl->Julia_Parm[T];
  }

  *Dist = Precision;

  return (TRUE);
}



/*****************************************************************************
*
* FUNCTION
*
* INPUT
*
* OUTPUT
*
* RETURNS
*
* AUTHOR
*
*   Pascal Massimino
*
* DESCRIPTION
*
* CHANGES
*
******************************************************************************/

void Normal_Calc_HCompl_z3(Result, N_Max, fractal)
VECTOR Result;
int N_Max;
FRACTAL *fractal;
{
  DBL n1, n2, n3, n4;
  int i;
  DBL x, y, z, w;
  DBL xx, yy, zz, ww;

  /*
   * Algebraic properties of hypercomplexes allows simplifications in
   * computations...
   */

  x = Sx[0];
  y = Sy[0];
  z = Sz[0];
  w = Sw[0];

  for (i = 1; i < N_Max; ++i)
  {
    /*
     * For a map z->f(z), f depending on c, one must perform here :
     * 
     * (x,y,z,w) * df/dz(Sx[i],Sy[i],Sz[i],Sw[i]) -> (x,y,z,w) ,
     * 
     * up to a constant.
     */

    /******************* Case z->z^2+c *****************/

    /* the df/dz part needs no work */

    HMult(xx, yy, zz, ww, Sx[i], Sy[i], Sz[i], Sw[i], x, y, z, w);

    x = xx;
    y = yy;
    z = zz;
    w = ww;
  }

  n1 = Sx[N_Max];
  n2 = Sy[N_Max];
  n3 = Sz[N_Max];
  n4 = Sw[N_Max];

  Result[X] = x * n1 + y * n2 + z * n3 + w * n4;
  Result[Y] = -y * n1 + x * n2 - w * n3 + z * n4;
  Result[Z] = -z * n1 - w * n2 + x * n3 + y * n4;
}



/*****************************************************************************
*
* FUNCTION
*
* INPUT
*
* OUTPUT
*
* RETURNS
*
* AUTHOR
*
*   Pascal Massimino
*
* DESCRIPTION
*
* CHANGES
*
******************************************************************************/

int F_Bound_HCompl_z3(Ray, Fractal, Depth_Min, Depth_Max)
RAY *Ray;
FRACTAL *Fractal;
DBL *Depth_Min;
DBL *Depth_Max;
{
  return F_Bound_HCompl(Ray, Fractal, Depth_Min, Depth_Max);
}

/*--------------------------- Inv -------------------------------*/


/*****************************************************************************
*
* FUNCTION
*
* INPUT
*
* OUTPUT
*
* RETURNS
*
* AUTHOR
*
*   Pascal Massimino
*
* DESCRIPTION
*
* CHANGES
*
******************************************************************************/

int Iteration_HCompl_Reciprocal(IPoint, HCompl)
VECTOR IPoint;
FRACTAL *HCompl;
{
  int i;
  DBL Norm, xx, yy, zz, ww;
  DBL Exit_Value;
  DBL x, y, z, w;

  x = Sx[0] = IPoint[X];
  y = Sy[0] = IPoint[Y];
  z = Sz[0] = IPoint[Z];
  w = Sw[0] = (HCompl->SliceDist
                  - HCompl->Slice[X]*x 
                  - HCompl->Slice[Y]*y 
                  - HCompl->Slice[Z]*z)/HCompl->Slice[T]; 

  Exit_Value = HCompl->Exit_Value;

  for (i = 1; i <= HCompl->n; ++i)
  {
    Norm = x * x + y * y + z * z + w * w;

    if (Norm > Exit_Value)
    {
      return (FALSE);
    }

    HReciprocal(&xx, &yy, &zz, &ww, x, y, z, w);

    x = Sx[i] = xx + HCompl->Julia_Parm[X];
    y = Sy[i] = yy + HCompl->Julia_Parm[Y];
    z = Sz[i] = zz + HCompl->Julia_Parm[Z];
    w = Sw[i] = ww + HCompl->Julia_Parm[T];

  }

  return (TRUE);
}



/*****************************************************************************
*
* FUNCTION
*
* INPUT
*
* OUTPUT
*
* RETURNS
*
* AUTHOR
*
*   Pascal Massimino
*
* DESCRIPTION
*
* CHANGES
*
******************************************************************************/

int D_Iteration_HCompl_Reciprocal(IPoint, HCompl, Dist)
VECTOR IPoint;
FRACTAL *HCompl;
DBL *Dist;
{
  int i;
  DBL xx, yy, zz, ww;
  DBL Exit_Value, F_Value, Step;
  DBL x, y, z, w;
  VECTOR H_Normal;

  x = Sx[0] = IPoint[X];
  y = Sy[0] = IPoint[Y];
  z = Sz[0] = IPoint[Z];
  w = Sw[0] = (HCompl->SliceDist
                  - HCompl->Slice[X]*x 
                  - HCompl->Slice[Y]*y 
                  - HCompl->Slice[Z]*z)/HCompl->Slice[T]; 

  Exit_Value = HCompl->Exit_Value;

  for (i = 1; i <= HCompl->n; ++i)
  {
    F_Value = x * x + y * y + z * z + w * w;

    if (F_Value > Exit_Value)
    {
      Normal_Calc_HCompl_Reciprocal(H_Normal, i - 1, HCompl);

      VDot(Step, H_Normal, Direction);

      if (Step < -Fractal_Tolerance)
      {
        Step = -2.0 * Step;

        if ((F_Value > Precision * Step) && F_Value < (30 * Precision * Step))
        {
          *Dist = F_Value / Step;

          return (FALSE);
        }
      }

      *Dist = Precision;

      return (FALSE);
    }

    HReciprocal(&xx, &yy, &zz, &ww, x, y, z, w);

    x = Sx[i] = xx + HCompl->Julia_Parm[X];
    y = Sy[i] = yy + HCompl->Julia_Parm[Y];
    z = Sz[i] = zz + HCompl->Julia_Parm[Z];
    w = Sw[i] = ww + HCompl->Julia_Parm[T];

  }

  *Dist = Precision;

  return (TRUE);
}



/*****************************************************************************
*
* FUNCTION
*
* INPUT
*
* OUTPUT
*
* RETURNS
*
* AUTHOR
*
*   Pascal Massimino
*
* DESCRIPTION
*
* CHANGES
*
******************************************************************************/

void Normal_Calc_HCompl_Reciprocal(Result, N_Max, fractal)
VECTOR Result;
int N_Max;
FRACTAL *fractal;
{
  DBL n1, n2, n3, n4;
  int i;
  DBL x, y, z, w;
  DBL xx, yy, zz, ww;
  DBL xxx, yyy, zzz, www;

  /*
   * Algebraic properties of hypercomplexes allows simplifications in
   * computations...
   */

  x = Sx[0];
  y = Sy[0];
  z = Sz[0];
  w = Sw[0];

  for (i = 1; i < N_Max; ++i)
  {
    /******************* Case: z->1/z+c *****************/

    HReciprocal(&xx, &yy, &zz, &ww, Sx[i], Sy[i], Sz[i], Sw[i]);

    HSqr(xxx, yyy, zzz, www, xx, yy, zz, ww);

    HMult(xx, yy, zz, ww, x, y, z, w, -xxx, -yyy, -zzz, -www);

    x = xx;
    y = yy;
    z = zz;
    w = ww;
  }

  n1 = Sx[N_Max];
  n2 = Sy[N_Max];
  n3 = Sz[N_Max];
  n4 = Sw[N_Max];

  Result[X] = x * n1 + y * n2 + z * n3 + w * n4;
  Result[Y] = -y * n1 + x * n2 - w * n3 + z * n4;
  Result[Z] = -z * n1 - w * n2 + x * n3 + y * n4;
}



/*****************************************************************************
*
* FUNCTION
*
* INPUT
*
* OUTPUT
*
* RETURNS
*
* AUTHOR
*
*   Pascal Massimino
*
* DESCRIPTION
*
* CHANGES
*
******************************************************************************/

int F_Bound_HCompl_Reciprocal(Ray, Fractal, Depth_Min, Depth_Max)
RAY *Ray;
FRACTAL *Fractal;
DBL *Depth_Min, *Depth_Max;
{
  return F_Bound_HCompl(Ray, Fractal, Depth_Min, Depth_Max);
}

/*--------------------------- Func -------------------------------*/


/*****************************************************************************
*
* FUNCTION
*
* INPUT
*
* OUTPUT
*
* RETURNS
*
* AUTHOR
*
*   Pascal Massimino
*
* DESCRIPTION
*
* CHANGES
*
******************************************************************************/

int Iteration_HCompl_Func(IPoint, HCompl)
VECTOR IPoint;
FRACTAL *HCompl;
{
  int i;
  DBL Norm, xx, yy, zz, ww;
  DBL Exit_Value;
  DBL x, y, z, w;

  x = Sx[0] = IPoint[X];
  y = Sy[0] = IPoint[Y];
  z = Sz[0] = IPoint[Y];
  w = Sw[0] = (HCompl->SliceDist
                  - HCompl->Slice[X]*x 
                  - HCompl->Slice[Y]*y 
                  - HCompl->Slice[Z]*z)/HCompl->Slice[T]; 

  Exit_Value = HCompl->Exit_Value;

  for (i = 1; i <= HCompl->n; ++i)
  {
    Norm = x * x + y * y + z * z + w * w;

    if (Norm > Exit_Value)
    {
      return (FALSE);
    }

    HFunc(&xx, &yy, &zz, &ww, x, y, z, w, HCompl);

    x = Sx[i] = xx + HCompl->Julia_Parm[X];
    y = Sy[i] = yy + HCompl->Julia_Parm[Y];
    z = Sz[i] = zz + HCompl->Julia_Parm[Z];
    w = Sw[i] = ww + HCompl->Julia_Parm[T];

  }

  return (TRUE);
}



/*****************************************************************************
*
* FUNCTION
*
* INPUT
*
* OUTPUT
*
* RETURNS
*
* AUTHOR
*
*   Pascal Massimino
*
* DESCRIPTION
*
* CHANGES
*
******************************************************************************/

int D_Iteration_HCompl_Func(IPoint, HCompl, Dist)
VECTOR IPoint;
FRACTAL *HCompl;
DBL *Dist;
{
  int i;
  DBL xx, yy, zz, ww;
  DBL Exit_Value, F_Value, Step;
  DBL x, y, z, w;
  VECTOR H_Normal;

  x = Sx[0] = IPoint[X];
  y = Sy[0] = IPoint[Y];
  z = Sz[0] = IPoint[Z];
  w = Sw[0] = (HCompl->SliceDist
                  - HCompl->Slice[X]*x 
                  - HCompl->Slice[Y]*y 
                  - HCompl->Slice[Z]*z)/HCompl->Slice[T]; 

  Exit_Value = HCompl->Exit_Value;

  for (i = 1; i <= HCompl->n; ++i)
  {
    F_Value = x * x + y * y + z * z + w * w;

    if (F_Value > Exit_Value)
    {
      Normal_Calc_HCompl_Func(H_Normal, i - 1, HCompl);

      VDot(Step, H_Normal, Direction);

      if (Step < -Fractal_Tolerance)
      {
        Step = -2.0 * Step;

        if ((F_Value > Precision * Step) && F_Value < (30 * Precision * Step))
        {
          *Dist = F_Value / Step;

          return (FALSE);
        }
      }

      *Dist = Precision;

      return (FALSE);
    }

    HFunc(&xx, &yy, &zz, &ww, x, y, z, w, HCompl);

    x = Sx[i] = xx + HCompl->Julia_Parm[X];
    y = Sy[i] = yy + HCompl->Julia_Parm[Y];
    z = Sz[i] = zz + HCompl->Julia_Parm[Z];
    w = Sw[i] = ww + HCompl->Julia_Parm[T];

  }

  *Dist = Precision;

  return (TRUE);
}



/*****************************************************************************
*
* FUNCTION
*
* INPUT
*
* OUTPUT
*
* RETURNS
*
* AUTHOR
*
*   Pascal Massimino
*
* DESCRIPTION
*
* CHANGES
*
******************************************************************************/

void Normal_Calc_HCompl_Func(Result, N_Max, fractal)
VECTOR Result;
int N_Max;
FRACTAL *fractal;
{
  DBL n1, n2, n3, n4;
  int i;
  DBL x, y, z, w;
  DBL xx, yy, zz, ww;
  DBL xxx, yyy, zzz, www;

  /*
   * Algebraic properties of hypercomplexes allows simplifications in
   * computations...
   */

  x = Sx[0];
  y = Sy[0];
  z = Sz[0];
  w = Sw[0];

  for (i = 1; i < N_Max; ++i)
  {
    /**************** Case: z-> f(z)+c ************************/

    HFunc(&xx, &yy, &zz, &ww, Sx[i], Sy[i], Sz[i], Sw[i], fractal);

    HMult(xxx, yyy, zzz, www, xx, yy, zz, ww, x, y, z, w);

    x = xxx;
    y = yyy;
    z = zzz;
    w = www;
  }

  n1 = Sx[N_Max];
  n2 = Sy[N_Max];
  n3 = Sz[N_Max];
  n4 = Sw[N_Max];

  Result[X] = x * n1 + y * n2 + z * n3 + w * n4;
  Result[Y] = -y * n1 + x * n2 - w * n3 + z * n4;
  Result[Z] = -z * n1 - w * n2 + x * n3 + y * n4;
}



/*****************************************************************************
*
* FUNCTION
*
* INPUT
*
* OUTPUT
*
* RETURNS
*
* AUTHOR
*
*   Pascal Massimino
*
* DESCRIPTION
*
* CHANGES
*
******************************************************************************/

int F_Bound_HCompl_Func(Ray, Fractal, Depth_Min, Depth_Max)
RAY *Ray;
FRACTAL *Fractal;
DBL *Depth_Min, *Depth_Max;
{
  return F_Bound_HCompl(Ray, Fractal, Depth_Min, Depth_Max);
}

/*****************************************************************************
*
* FUNCTION  Complex transcental functions
*
* INPUT     pointer to source complex number
*
* OUTPUT    fn(input)
*
* RETURNS   void
*
* AUTHOR
*
*   Tim Wegner
*
* DESCRIPTION  Calculate common functions on complexes
*   Since our purpose is fractals, error checking is lax.            
*
* CHANGES
*
******************************************************************************/

void Complex_Mult (target, source1, source2)
CMPLX *target;
CMPLX *source1;
CMPLX *source2;
{
  DBL tmpx;
  tmpx = source1->x * source2->x - source1->y * source2->y;
  target->y = source1->x * source2->y + source1->y * source2->x;
  target->x = tmpx;
}

void Complex_Div (target, source1, source2)
CMPLX *target;
CMPLX *source1;
CMPLX *source2;
{
  DBL mod,tmpx,yxmod,yymod;
  mod = Sqr(source2->x) + Sqr(source2->y);
  if (mod==0)
     return;
  yxmod = source2->x/mod;
  yymod = - source2->y/mod;
  tmpx = source1->x * yxmod - source1->y * yymod;
  target->y = source1->x * yymod + source1->y * yxmod;
  target->x = tmpx;
} /* End Complex_Mult() */

void Complex_Exp (target, source)
CMPLX *target;
CMPLX *source;
{
  DBL expx;
  expx = exp(source->x);
  target->x = expx * cos(source->y);
  target->y = expx * sin(source->y);
} /* End Complex_Exp() */

void Complex_Sin (target, source)
CMPLX *target;
CMPLX *source;
{
  target->x = sin(source->x) * cosh(source->y);
  target->y = cos(source->x) * sinh(source->y);
} /* End Complex_Sin() */

void Complex_Sinh (target, source)
CMPLX *target;
CMPLX *source;
{
  target->x = sinh(source->x) * cos(source->y);
  target->y = cosh(source->x) * sin(source->y);
} /* End Complex_Sinh() */


void Complex_Cos (target, source)
CMPLX *target;
CMPLX *source;
{
  target->x = cos(source->x) * cosh(source->y);
  target->y = -sin(source->x) * sinh(source->y);
} /* End Complex_Cos() */

void Complex_Cosh (target, source)
CMPLX *target;
CMPLX *source;
{
  target->x = cosh(source->x) * cos(source->y);
  target->y = sinh(source->x) * sin(source->y);
} /* End Complex_Cosh() */


void Complex_Log (target, source)
CMPLX *target;
CMPLX *source;
{
  DBL mod,zx,zy;
  mod = sqrt(source->x * source->x + source->y * source->y);
  zx = log(mod);
  zy = atan2(source->y,source->x);

  target->x = zx;
  target->y = zy;
} /* End Complex_Log() */

void Complex_Sqrt(target, source)
CMPLX *target;
CMPLX *source;
{
  DBL mag;
  DBL theta;

  if(source->x == 0.0 && source->y == 0.0)
  {
    target->x = target->y = 0.0;
  }
  else
  {   
    mag   = sqrt(sqrt(Sqr(source->x) + Sqr(source->y)));
    theta = atan2(source->y, source->x) / 2;
    target->y = mag * sin(theta);
    target->x = mag * cos(theta);
  }
} /* End Complex_Sqrt() */

/* rz=Arcsin(z)=-i*Log{i*z+sqrt(1-z*z)} */
void Complex_ASin(target, source)
CMPLX *target;
CMPLX *source;
{
  CMPLX tempz1,tempz2;

  Complex_Mult(&tempz1, source, source);
  tempz1.x = 1 - tempz1.x; tempz1.y = -tempz1.y; 
  Complex_Sqrt( &tempz1, &tempz1);

  tempz2.x = -source->y; tempz2.y = source->x;
  tempz1.x += tempz2.x;  tempz1.y += tempz2.y;

  Complex_Log( &tempz1, &tempz1);
  target->x = tempz1.y;  target->y = -tempz1.x;   
}   /* End Complex_ASin() */


void Complex_ACos(target, source)
CMPLX *target;
CMPLX *source;
{
  CMPLX temp;

  Complex_Mult(&temp, source, source);
  temp.x -= 1;
  Complex_Sqrt(&temp, &temp);

  temp.x += source->x; temp.y += source->y;
  
  Complex_Log(&temp, &temp);
  target->x = temp.y;  target->y = -temp.x; 
}   /* End Complex_ACos() */

void Complex_ASinh(target, source)
CMPLX *target;
CMPLX *source;
{
  CMPLX temp;

  Complex_Mult (&temp, source, source);
  temp.x += 1;   
  Complex_Sqrt (&temp, &temp);
  temp.x += source->x; temp.y += source->y; 
  Complex_Log(target, &temp);
}  /* End Complex_ASinh */

/* rz=Arccosh(z)=Log(z+sqrt(z*z-1)} */
void Complex_ACosh (target, source)
CMPLX *target;
CMPLX *source;
{
  CMPLX tempz;
  Complex_Mult(&tempz, source, source);
  tempz.x -= 1; 
  Complex_Sqrt (&tempz, &tempz);
  tempz.x = source->x + tempz.x; tempz.y = source->y + tempz.y;
  Complex_Log (target, &tempz);
}   /* End Complex_ACosh() */

/* rz=Arctanh(z)=1/2*Log{(1+z)/(1-z)} */
void Complex_ATanh(target, source)
CMPLX *target;
CMPLX *source;
{
  CMPLX temp0,temp1,temp2;
   
  if( source->x == 0.0)
  {
    target->x = 0;
    target->y = atan( source->y);
    return;
  }
  else
  {
    if( fabs(source->x) == 1.0 && source->y == 0.0)
    {
      return;
    }
    else if( fabs( source->x) < 1.0 && source->y == 0.0)
    {
      target->x = log((1+source->x)/(1-source->x))/2;
      target->y = 0;
      return;
    } 
    else
    {
      temp0.x = 1 + source->x; temp0.y = source->y;
      temp1.x = 1 - source->x; temp1.y = -source->y; 
      Complex_Div(&temp2, &temp0, &temp1);
      Complex_Log(&temp2, &temp2);
      target->x = .5 * temp2.x; target->y = .5 * temp2.y;
      return;
    }
  }
}   /* End Complex_ATanh() */

/* rz=Arctan(z)=i/2*Log{(1-i*z)/(1+i*z)} */
void Complex_ATan(target, source)
CMPLX *target;
CMPLX *source;
{
  CMPLX temp0,temp1,temp2,temp3;
  if( source->x == 0.0 && source->y == 0.0)
    target->x = target->y = 0;
  else if( source->x != 0.0 && source->y == 0.0){
    target->x = atan(source->x);
    target->y = 0;
  }
  else if( source->x == 0.0 && source->y != 0.0){
    temp0.x = source->y;  temp0.y = 0.0;
    Complex_ATanh(&temp0, &temp0);
    target->x = -temp0.y; target->y = temp0.x;
  } 
  else if( source->x != 0.0 && source->y != 0.0)
  {
    temp0.x = -source->y; temp0.y = source->x; 
    temp1.x = 1 - temp0.x; temp1.y = -temp0.y;   
    temp2.x = 1 + temp0.x; temp2.y = temp0.y; 

    Complex_Div(&temp3, &temp1, &temp2);
    Complex_Log(&temp3, &temp3);
    target->x = -temp3.y * .5; target->y = .5 * temp3.x; 
  }
}   /* End Complex_ATanz() */

void Complex_Tan(target, source)
CMPLX *target;
CMPLX *source;
{
  DBL x, y, sinx, cosx, sinhy, coshy, denom;
  x = 2 * source->x;
  y = 2 * source->y;
  sinx = sin(x); cosx = cos(x);
  sinhy = sinh(y); coshy = cosh(y);
  denom = cosx + coshy;
  if(denom == 0)
    return;
  target->x = sinx/denom;
  target->y = sinhy/denom;
}   /* End Complex_Tan() */

void Complex_Tanh(target, source)
CMPLX *target;
CMPLX *source;
{
  DBL x, y, siny, cosy, sinhx, coshx, denom;
  x = 2 * source->x;
  y = 2 * source->y;
  siny = sin(y); cosy = cos(y);
  sinhx = sinh(x); coshx = cosh(x);
  denom = coshx + cosy;
  if(denom == 0)
    return;
  target->x = sinhx/denom;
  target->y = siny/denom;
}   /* End Complex_Tanh() */


void Complex_Power(target, source1, source2) 
CMPLX *target;
CMPLX *source1;
CMPLX *source2;
{
  CMPLX cLog, t;
  DBL e2x;

  if(source1->x == 0 && source1->y == 0) 
  {
    target->x = target->y = 0.0;
    return;
  }

  Complex_Log (&cLog, source1);
  Complex_Mult (&t, &cLog, source2);

  if(t.x < -690)
    e2x = 0;
  else
    e2x = exp(t.x);
  target->x = e2x * cos(t.y);
  target->y = e2x * sin(t.y);
}

#if 1
void Complex_Pwr(target, source) 
CMPLX *target;
CMPLX *source;
{
  Complex_Power(target, source, &exponent);
}

#else
void Complex_Pwr(target, source) 
CMPLX *target;
CMPLX *source;
{
  /* the sqr dunction for testing */
  Complex_Mult(target, source, source);
}
#endif
