/* ifs.c,v 1.2 1995/03/30 07:14:07 explorer Exp */

/*
 * Philippe Bekaert - bekaert@cs.kuleuven.ac.be - 19 may 93
 */


#include "libcommon/common.h"
#include <stdlib.h>
#include <math.h>
#include "geom.h"
#include "ifs.h"
#include "bounds.h"
#include "libcommon/sampling.h"
#include "libshade/viewing.h"

static Methods *iIfsMethods = NULL;
static Methods *iCIfsMethods = NULL;
static char     ifsName[] = "ifs";
static char     CifsName[] = "condensed ifs";
unsigned long   IfsTests, IfsHits, CIfsTests, CIfsHits;

#define OPTIMAL_BV		/* for better bounding spheres */

static int rangcmp _PROTO((voidconststar, voidconststar));
static int DoIfsIntersect _PROTO((Ifs *, Ray *, HitList *, Float, Float *));
static void IfsResolveAssoc _PROTO((Ifs *));

/*
 * return less than zero if first map has higher rang number than the second,
 * zero if two maps have equal rang numbers, greater than zero if first map
 * has lower rang number than the second
 */
static int
rangcmp(map1, map2)
     voidconststar map1, map2;
{
  return (((IfsMap *)map2)->rang - ((IfsMap *)map1)->rang);
}

/*
 * computes radius of smallest sphere with centre (x,y,z) which encloses the
 * n spheres with centre c[i] and radius r[i]: to be minimized
 */
static Float
REval(n, c, r, x, y, z)
     int             n;
     Vector         *c;
     Float          *r;
     Float           x, y, z;
{
  Vector          C;
  Float           R, R1;
  int             i;
  
  C.x = x;
  C.y = y;
  C.z = z;
  R = VecDist(&C, &c[0]) + r[0];
  for (i = 1; i < n; i++)
    if ((R1 = VecDist(&C, &c[i]) + r[i]) > R)
      R = R1;
  return R;
}

static Float
SmallestBounding(n, c, r, C)
     int             n;
     Vector         *c;
     Float          *r;
     Vector         *C;
{
  Float           R, R1, step;
  int             i, dir, go_on;
  
  /* initial guess for center point: gravitation centre of centre points */
  *C = c[0];
  for (i = 1; i < n; i++)
    VecAdd(*C, c[i], C);
  VecScale(1. / (float) n, *C, C);

  /* calculate radius and minimize */
  R = REval(n, c, r, C->x, C->y, C->z);
  step = R / 8.;
#ifdef OPTIMAL_BV
  do {
    dir = 0;
    do {
      go_on = FALSE;
      if (dir != -1 && (R1 = REval(n, c, r, C->x + step, C->y, C->z)) < R) {
	R = R1;
	C->x += step;
	dir = 1;
	go_on = TRUE;
      }
      if (dir != 1 && (R1 = REval(n, c, r, C->x - step, C->y, C->z)) < R) {
	R = R1;
	C->x -= step;
	dir = -1;
	go_on = TRUE;
      }
    } while (go_on);
    
    dir = 0;
    do {
      go_on = FALSE;
      if (dir != -1 && (R1 = REval(n, c, r, C->x, C->y + step, C->z)) < R) {
	R = R1;
	C->y += step;
	dir = 1;
	go_on = TRUE;
      }
      if (dir != 1 && (R1 = REval(n, c, r, C->x, C->y - step, C->z)) < R) {
	R = R1;
	C->y -= step;
	dir = -1;
	go_on = TRUE;
      }
    } while (go_on);

    dir = 0;
    do {
      go_on = FALSE;
      if (dir != -1 && (R1 = REval(n, c, r, C->x, C->y, C->z + step)) < R) {
	R = R1;
	C->z += step;
	dir = 1;
	go_on = TRUE;
      }
      if (dir != 1 && (R1 = REval(n, c, r, C->x, C->y, C->z - step)) < R) {
	R = R1;
	C->z -= step;
	dir = -1;
	go_on = TRUE;
      }
    } while (go_on);

    step /= 2.;
  } while (step > EPSILON);
#endif				/* OPTIMAL_BV */

  return R;
}

static void
IfsPartialBounding(nrmaps, maps, condensed, C, R, minsize, maxdepth)
     int             nrmaps, condensed, maxdepth;
     IfsMap         *maps;
     Vector         *C;
     Float          *R, minsize;
{
  int             i, N, d;
  Float          *r, R1, delta, maxcontractivity;
  Vector         *c, C1;

  /* largest Lipschitz constant of the maps */
  for (i = 0, maxcontractivity = 0.; i < nrmaps; i++)
    if (maps[i].contractivity > maxcontractivity)
      maxcontractivity = maps[i].contractivity;

  /*
   * for IFS without condensation: make new sphere which contains the images
   * of this sphere and repeat as long the new sphere gets smaller. For IFS
   * with condensation set: make new sphere which contains the old and it's
   * images and repeat as long as it gets larger
   */
  N = condensed ? nrmaps + 1 : nrmaps;
  c = (Vector *) Malloc(N * sizeof(Vector));
  r = (Float *) Malloc(N * sizeof(Float));

  for (delta = R1 = *R, C1 = *C, d = 0;
    delta > EPSILON && delta >= minsize * maxcontractivity && d <= maxdepth;
       delta *= maxcontractivity, d++) {
    if (!condensed)
      if (R1 > *R)
	break;
    *C = C1;
    *R = R1;

    for (i = 0; i < nrmaps; i++) {
      c[i] = *C;
      PointTransform(&c[i], &maps[i].matrix);
      r[i] = *R * maps[i].contractivity;
    }
    if (condensed) {
      c[nrmaps] = *C;
      r[nrmaps] = *R;
    }
    R1 = SmallestBounding(N, c, r, &C1);
  }
  if (condensed) {
    *C = C1;
    *R = R1;
  }
  free(c);
  free(r);

  *R += EPSILON;		/* to be on the safe side */
}

/*
 * Computes a sphere which is supposed to fit the attractor of the IFS quite
 * tightly
 */
static void
IfsBoundingSphere(ifs, R, C)
     Ifs            *ifs;
     Float          *R;
     Vector         *C;
{
  Float           R1;
  int             i, n, condensed;
  IfsMap         *m;

  condensed = ifs->condensation ? TRUE : FALSE;
  if (!condensed) {
    /*
     * first guess: a sphere with centre in the origin which contains all
     * it's images: The radius must be equal or larger
     *
     * |B| / (1-s)
     *
     * for each IFS map x -> Ax+B with contractivity s. hierarchical IFS: use
     * only the maps with highest rang number
     */
    C->x = C->y = C->z = 0.;
    for (i = 0, *R = 0.;
	 i < ifs->nrmaps && ifs->maps[i].rang == ifs->maps->rang;
	 i++)
      if ((R1 = VecNorm(ifs->maps[i].matrix.translate) /
	   (1. - ifs->maps[i].contractivity)) > *R)
	*R = R1;
  } else {
    /*
     * construct a sphere around the condensation bounding box
     */
    C->x = (ifs->condensation->bounds[HIGH][X] + ifs->condensation->bounds[LOW][X]) / 2.;
    C->y = (ifs->condensation->bounds[HIGH][Y] + ifs->condensation->bounds[LOW][Y]) / 2.;
    C->z = (ifs->condensation->bounds[HIGH][Z] + ifs->condensation->bounds[LOW][Z]) / 2.;
    *R = ifs->condensize / 2.;
  }

  /* process maps in groups of same rang */
  for (i = 0; i < ifs->nrmaps; i += n, condensed = TRUE) {
    /* count the number of maps with same rang number as ifs->maps[i] */
    for (m = &ifs->maps[i], n = 0; i + n < ifs->nrmaps && m[n].rang == m->rang; n++);
    /*
     * construct a bounding sphere for the partial IFS defined by these maps
     * of same rang
     */
    IfsPartialBounding(n, m, condensed, C, R, ifs->options.size, ifs->options.depth);
  }
}

/* global variables for depth first processing of the tree of images */
static IfsMap  *maps;
static int      nrmaps, maxdepth, condensation, oppenheimer_tree;
static Float    minsize, isize;
static Float    ibounds[2][3], cbounds[2][3], lbounds[2][3];
static Vector   ipoint;

static void
IfsBB(map, depth, size, rang)
     RSMatrix       *map;
     int             depth;
     Float           size;
     int             rang;
{
  int             i;
  Vector          p;
  Float           b[2][3];
  RSMatrix        m;
  Float           lambda[3], siz;

  if (depth >= maxdepth || (minsize > EPSILON && size <= minsize)) {
    if (oppenheimer_tree) {	/* enlarge with bounds of image of leaves */
      BoundsCopy(lbounds, b);
      BoundsTransform(map, b);
      BoundsEnlarge(ibounds, b);
    } else if (condensation) {	/* enlarge with bounds of condensation */
      BoundsCopy(cbounds, b);
      BoundsTransform(map, b);
      BoundsEnlarge(ibounds, b);
    } else {			/* enlarge with sphere with centre at image
				 * of ipoint */
      p = ipoint;
      PointTransform(&p, map);
      BoundsEnlargeSphere(ibounds, &p, size / 2);
    }
    return;
  }
  if (condensation) {		/* enlarge with bounds of image of
				 * condensation set */
    BoundsCopy(cbounds, b);
    BoundsTransform(map, b);
    BoundsEnlarge(ibounds, b);
  }
  for (i = 0; i < nrmaps; i++) {
    if (maps[i].rang < rang)
      break;			/* for hierarchical IFS */
    MatrixMult(&maps[i].matrix, map, &m);
    if (maps[i].uniform)
      siz = size * maps[i].contractivity;
    else {
      MatrixScaling(&m, lambda);
      siz = isize * Lipschitz(lambda);
    }
    IfsBB(&m, depth + 1, siz, maps[i].rang);
  }
}

static void
IfsBoundingBox(ifs, bounds)
  Ifs            *ifs;
  Float           bounds[2][3];
{
  RSMatrix        unity_transform;

  MatrixInit(&unity_transform);
  maps = ifs->maps;
  nrmaps = ifs->nrmaps;
  condensation = (ifs->condensation != NULL);
  oppenheimer_tree = (ifs->leaves != NULL);
  maxdepth = ifs->options.depth;
  minsize = ifs->options.size;
  if (minsize < EPSILON)
    minsize = ifs->extent.r / 20;
  isize = ifs->condensation ? ifs->condensize : ifs->size;
  ipoint = ifs->extent.c;

  BoundsInit(ibounds);
  if (ifs->condensation)
    BoundsCopy(ifs->condensation->bounds, cbounds);
  if (ifs->leaves)
    BoundsCopy(ifs->leaves->bounds, lbounds);

  IfsBB(&unity_transform, 0, isize, -99999999);
  BoundsCopy(ibounds, bounds);
}

/* compute a bunding box and bounding sphere for the IFS attractor */
static void
IfsComputeExtent(ifs)
  Ifs            *ifs;
{
  Float           R, sphavpa, boxavpa;
  Vector          C, d;

  /* compute bounds of condensation set, if any */
  if (ifs->condensation) {
    GeomComputeBounds(ifs->condensation);
    if (UNBOUNDED(ifs->condensation))
      RLerror(RL_ABORT,
	      "Can't compute bounds of IFS with unbounded condensation.\n");
    
    d.x = ifs->condensation->bounds[HIGH][X]
      - ifs->condensation->bounds[LOW][X];
    d.y = ifs->condensation->bounds[HIGH][Y]
      - ifs->condensation->bounds[LOW][Y];
    d.z = ifs->condensation->bounds[HIGH][Z]
      - ifs->condensation->bounds[LOW][Z];
    ifs->condensize = VecNorm(d);
  }
  /* compute bounds of leaves, if any */
  if (ifs->leaves) {
    GeomComputeBounds(ifs->leaves);
    if (UNBOUNDED(ifs->leaves))
      RLerror(RL_ABORT, "Oppenheimer tree with unbounded leaves.\n");

    d.x = ifs->leaves->bounds[HIGH][X] - ifs->leaves->bounds[LOW][X];
    d.y = ifs->leaves->bounds[HIGH][Y] - ifs->leaves->bounds[LOW][Y];
    d.z = ifs->leaves->bounds[HIGH][Z] - ifs->leaves->bounds[LOW][Z];
    ifs->leavesize = VecNorm(d);
  }
  /* construct a sphere fitting tightly the attractor of the IFS */
  IfsBoundingSphere(ifs, &R, &C);

  ifs->extent.r = R;
  ifs->extent.rsq = R * R;
  ifs->extent.c = C;
  ifs->size = 2 * R;

  /* construct a box fitting tightly the attractor of the IFS */
  IfsBoundingBox(ifs, ifs->bounds);

  /*
   * compare average projected area of the box and the sphere. The avaerage
   * projected area for a convex volume is one fourth of the surface area of
   * the volume [Glassner, An Introduction to Ray Tracing, Academic Press,
   * 1989 p212]. The volume with the smallest average projected area will be
   * used for building a bounding volume hierarchy for the attractor of the
   * IFS
   */
  sphavpa = 3.14 * ifs->extent.rsq;
  d.x = ifs->bounds[HIGH][X] - ifs->bounds[LOW][X];
  d.y = ifs->bounds[HIGH][Y] - ifs->bounds[LOW][Y];
  d.z = ifs->bounds[HIGH][Z] - ifs->bounds[LOW][Z];
  boxavpa = (d.x * d.y + d.y * d.z + d.z * d.x) / 2;

  if (sphavpa < boxavpa)
    ifs->boxbounding = FALSE;
  else
    ifs->boxbounding = TRUE;
  if (ifs->options.boxbounding)
    ifs->boxbounding = TRUE;
  if (ifs->options.spherebounding)
    ifs->boxbounding = FALSE;
}

/*
 * for debugging or listing the volumes that will be rendered
 */
static void
TransPrint(trans, out)
     RSMatrix       *trans;
     FILE           *out;
{
  int             i;

  for (i = 0; i < 3; i++)
    fprintf(out, "%f %f %f   ",
	    (double) trans->matrix[i][0],
	    (double) trans->matrix[i][1],
	    (double) trans->matrix[i][2]);
  fprintf(out, "%f %f %f",
	  (double) trans->translate.x,
	  (double) trans->translate.y,
	  (double) trans->translate.z);
}

/* produce a list of volumes that will be rendered */
static FILE    *listfp;
static long     volcount;

static void
ListIt(map, depth, size, rang)
     RSMatrix       *map;
     int             depth;
     Float           size;
     int             rang;
{
  int             i;
  RSMatrix        m;
  Float           lambda[3];
  Float           siz;

  if (depth >= maxdepth || (minsize > EPSILON && size <= minsize)) {
    if (oppenheimer_tree)
      fprintf(listfp, "object leaf         ");
    else if (condensation)
      fprintf(listfp, "object condensation ");
    else
      fprintf(listfp, "object extent ");
    fprintf(listfp, "transform   ");
    TransPrint(map, listfp);
    fprintf(listfp, "\n");
    volcount++;
    return;
  }

  if (condensation) {
    fprintf(listfp, "object condensation ");
    fprintf(listfp, "transform   ");
    TransPrint(map, listfp);
    fprintf(listfp, "\n");
    volcount++;
  }

  for (i = 0; i < nrmaps; i++) {
    if (maps[i].rang < rang)
      break;
    MatrixMult(&maps[i].matrix, map, &m);
    if (maps[i].uniform)
      siz = size * maps[i].contractivity;
    else {
      MatrixScaling(&m, lambda);
      siz = isize * Lipschitz(lambda);
    }
    ListIt(&m, depth + 1, siz, maps[i].rang);
  }
}

static void
IfsList(ifs, filename)
  Ifs            *ifs;
  char           *filename;
{
  RSMatrix        unity_transform;

  /* initialise some global variables */
  MatrixInit(&unity_transform);
  maps = ifs->maps;
  nrmaps = ifs->nrmaps;
  condensation = (ifs->condensation != NULL);
  oppenheimer_tree = (ifs->leaves != NULL);
  maxdepth = ifs->options.depth;
  minsize = ifs->options.size;
  isize = ifs->condensation ? ifs->condensize : ifs->size;
  if (maxdepth > 999999 && minsize < EPSILON) {
    minsize = isize / 100;
    fprintf(stderr, "Warning: no stop criterium for IFS list of volumes. Using 'minsize %f'\n", (double) minsize);
    return;
  }
  /* open output file */
  if ((listfp = fopen(filename, "w")) == NULL) {
    fprintf(stderr, "IfsList: Cannot open %s for writing\n", filename);
    perror(ifs->options.listfilename);
    return;
  }
  fprintf(listfp, "/* maxdepth=%d, initial_size=%f, minsize=%f */\n\n",
	  maxdepth, (double) isize, (double) minsize);

  if (!ifs->boxbounding)
    fprintf(listfp, "/* ");
  fprintf(listfp, "name extent box  %f %f %f  %f %f %f",
	  (double) ifs->bounds[LOW][X],
	  (double) ifs->bounds[LOW][Y],
	  (double) ifs->bounds[LOW][Z],
	  (double) ifs->bounds[HIGH][X],
	  (double) ifs->bounds[HIGH][Y],
	  (double) ifs->bounds[HIGH][Z]);
  if (!ifs->boxbounding)
    fprintf(listfp, "*/");
  fprintf(listfp, "\n");

  if (ifs->boxbounding)
    fprintf(listfp, "/* ");
  fprintf(listfp, "name extent sphere  %f  %f %f %f",
	  (double) ifs->extent.r,
	  (double) ifs->extent.c.x,
	  (double) ifs->extent.c.y,
	  (double) ifs->extent.c.z);
  if (ifs->boxbounding)
    fprintf(listfp, "*/");
  fprintf(listfp, "\n");

  if (condensation)
    fprintf(listfp, "name condensation /* fill in the condensation-set object */\n");
  if (oppenheimer_tree)
    fprintf(listfp, "name leaf /* fill in the leaf object */\n");

  volcount = 0;
  fprintf(listfp, "\ngrid 1 1 1 /* change to your personal taste */\n");
  ListIt(&unity_transform, 0, isize, -99999999);
  fprintf(listfp, "end /* %ld objects */\n", volcount);

  fclose(listfp);
}

/*
 * normal weighting functions
 */
static          Float
HighPassNormalWeighting(Size, size)
  Float           Size, size;
{
  return (Size - size) / Size;
}

static          Float
LowPassNormalWeighting(Size, size)
  Float           Size, size;
{
  return size / Size;
}

static          Float
ConstantNormalWeighting(Size, size)
  Float           Size, size;
{
  return 1.;
}

/*
 * Create & return reference to an ifs.
 */
GeomRef
IfsCreate(maps, condensation, leaves, options)
  IfsTransList   *maps;
  Geom           *condensation, *leaves;
  IfsOptions     *options;
{
  Ifs            *ifs;
  IfsTransList   *map;
  Float           contractivity, maxcontractivity;
  Float           lambda[3];
  int             n;

  /* first: count the number of maps */
  for (n = 0, map = maps; map; n++, map = map->next)
    ;
  if (n <= 0) {
    RLerror(RL_WARN, "Cannot create IFS with no maps.\n");
    return (GeomRef)NULL;
  }
  /* create Ifs structure */
  ifs = (Ifs *) share_malloc(sizeof(Ifs));
  ifs->condensation = condensation;	/* NULL = no cendensation */
  ifs->leaves = leaves;		/* if not NULL -> Oppenheimer tree */
  ifs->options = *options;

  /* allocate space for keeping the maps */
  ifs->nrmaps = n;
  ifs->maps = (IfsMap *) share_malloc(n * sizeof(IfsMap));

  /* the maps are in reverse order */
  ifs->animated = FALSE;
  ifs->uniform_maps = TRUE;
  maxcontractivity = 0.;
  for (n = ifs->nrmaps - 1, map = maps; map; n--, map = map->next) {
    ifs->maps[n].trans = map->trans;
    ifs->maps[n].transtail = map->transtail;
    ifs->maps[n].rang = map->rang;

    /* is the map animated ? */
    ifs->maps[n].animated = (map->trans->animated ||
			     map->trans->next != (Trans *) NULL);
    /* if so, the IFS is animated */
    if (ifs->maps[n].animated)
      ifs->animated = TRUE;
    /* if not, we can do some computations already */
    else {
      /*
       * "compose" the transformations - they are already composed during the
       * parsing, so, just copy them. They are also garanteed to be
       * non-singular.
       */
      MatrixCopy(&map->trans->trans, &ifs->maps[n].matrix);
      MatrixCopy(&map->trans->itrans, &ifs->maps[n].imatrix);

      /* compute the contractivity of the map */
      MatrixScaling(&ifs->maps[n].matrix, lambda);
      contractivity = Lipschitz(lambda);
      if (contractivity >= 1.)
	RLerror(RL_WARN,
		"Ifs map %d is not contractive (contractivity = %f)\n",
		n + 1, contractivity);
      if (contractivity > maxcontractivity)
	maxcontractivity = contractivity;
      ifs->maps[n].contractivity = contractivity;
      ifs->maps[n].uniform = Uniform(lambda);
      if (!ifs->maps[n].uniform)
	ifs->uniform_maps = FALSE;
    }
  }

  /* some procedures may never end for non-contractive IFS's */
  /*
   * if (maxcontractivity >= 1.0) { free(ifs->maps); free(ifs); return (Ifs
   * *)NULL; } just warn the user. we'll see ...
   */

  /*
   * for hierarchical IFS: sort maps to descending rang number (those maps
   * that can follow any other map will be processed first)
   */
  qsort(ifs->maps, ifs->nrmaps, sizeof(IfsMap), rangcmp);

  if (!ifs->animated)
    ifs->contractivity = maxcontractivity;

  /*
   * the IFS is also animated if its condensation is -- no code to support
   * this yet
   */
  /* ifs->animated |= ifs->condensation->animtrans; */

  /* if the IFS is not animated, compute its extent here */
  if (!ifs->animated && !ifs->condensation)
    IfsComputeExtent(ifs);

  /* normal weighting function for IFS without condensation */
  if (!ifs->condensation) {
    switch (ifs->options.normalweighting) {
    case HIGHPASS_NORMAL_WEIGHTING:
      ifs->normalweight = HighPassNormalWeighting;
      break;
    case LOWPASS_NORMAL_WEIGHTING:
      ifs->normalweight = LowPassNormalWeighting;
      break;
    case CONSTANT_NORMAL_WEIGHTING:
      ifs->normalweight = ConstantNormalWeighting;
      break;
    default:{
	RLerror(RL_WARN, "invalid normal weighting method.\n");
	free(ifs->maps);
	free(ifs);
	return (GeomRef)NULL;
      }
    }
  }
  ifs->frame = -1;		/* impossible value */

  return (GeomRef)ifs;
}

/* resolve geometric associates -- untested */
static void
IfsResolveAssoc(ifs)
  Ifs            *ifs;
{
  Trans           tr;
  Float           contractivity, maxcontractivity;
  Float           lambda[3];
  int             i;

  maxcontractivity = 0.;
  ifs->uniform_maps = TRUE;
  for (i = 0; i < ifs->nrmaps; i++) {
    if (ifs->maps[i].animated) {
      TransResolveAssoc(ifs->maps[i].trans);
      TransComposeList(ifs->maps[i].trans, &tr);
      ifs->maps[i].matrix = tr.trans;
      ifs->maps[i].imatrix = tr.itrans;

      /* compute the contractivity of the map */
      MatrixScaling(&ifs->maps[i].matrix, lambda);
      contractivity = Lipschitz(lambda);
      if (contractivity >= 1.)
	RLerror(RL_WARN, "Ifs map %d is not contractive (contractivity = %f)\n", i + 1, contractivity);
      ifs->maps[i].contractivity = contractivity;
      ifs->maps[i].uniform = Uniform(lambda);
    } else
      contractivity = ifs->maps[i].contractivity;

    if (contractivity > maxcontractivity)
      maxcontractivity = ifs->maps[i].contractivity;
    if (!ifs->maps[i].uniform)
      ifs->uniform_maps = FALSE;
  }

  ifs->contractivity = maxcontractivity;
  /*
   * if (ifs->contractivity >= 1.) RLerror(RL_ABORT, "Can't go on with
   * non-contractive IFS.\n");
   */
  ifs->frame = Sampling.framenum;
}

Methods *
IfsMethods()
{
  if (iIfsMethods == (Methods *) NULL) {
    iIfsMethods = MethodsCreate();
#if 0
    iIfsMethods->create = (GeomCreateFunc *) IfsCreate;
#endif
    iIfsMethods->methods = IfsMethods;
    iIfsMethods->name = IfsName;
    iIfsMethods->intersect = IfsIntersect;
    iIfsMethods->normal = IfsNormal;
    iIfsMethods->uv = NULL;	/* IfsUV; */
    iIfsMethods->enter = IfsEnter;
    iIfsMethods->bounds = IfsBounds;
    iIfsMethods->stats = IfsStats;
    iIfsMethods->checkbounds = TRUE;
    iIfsMethods->closed = FALSE;
  }
  return iIfsMethods;
}

Methods *
CIfsMethods()
{
  if (iCIfsMethods == (Methods *) NULL) {
    iCIfsMethods = MethodsCreate();
#if 0
    iCIfsMethods->create = (GeomCreateFunc *) IfsCreate;
#endif
    iCIfsMethods->methods = CIfsMethods;
    iCIfsMethods->name = CIfsName;
    iCIfsMethods->intersect = CIfsIntersect;
    iCIfsMethods->bounds = IfsBounds;
    iCIfsMethods->stats = CIfsStats;
    iCIfsMethods->checkbounds = TRUE;
    iCIfsMethods->closed = TRUE;/* depends on its condensation */
  }
  return iCIfsMethods;
}

/* Transformed Extends Lists */
static TEList  *
TENew()
{
  return (TEList *) Malloc(sizeof(TEList));
}

static void
TEDispose(p)
  TEList         *p;
{
  free(p);
}

static void
TEDisposeList(list)
  TEList         *list;
{
  TEList         *p;

  /* de-allocate nodes in reverse order of allocation */
  for (p = list; p->next; p = p->next) {
  }
  list = p;
  while (list) {
    p = list->prev;
    TEDispose(list);
    list = p;
  }
}

/* ray - sphere intersection */
static int
IfsIntersectSphere(sph, ray, mindist, maxdist)
  struct IfsExtent *sph;
  Ray            *ray;
  Float           mindist, *maxdist;
{
  Float           xadj, yadj, zadj;
  Float           b, d, t, s;

  xadj = sph->c.x - ray->pos.x;
  yadj = sph->c.y - ray->pos.y;
  zadj = sph->c.z - ray->pos.z;
  d = xadj * xadj + yadj * yadj + zadj * zadj - sph->rsq;

  b = xadj * ray->dir.x + yadj * ray->dir.y + zadj * ray->dir.z;
  t = b * b - d;
  if (t < 0.)
    return FALSE;
  t = (Float) sqrt((double) t);
  s = b - t;
  if (s > mindist) {
    if (s < *maxdist) {
      *maxdist = s;
      return TRUE;
    }
    return FALSE;
  }
  s = b + t;
  if (s > mindist && s < *maxdist) {
    *maxdist = s;
    return TRUE;
  }
  return FALSE;
}

static int
IfsExtentIntersect(ifs, ray, mindist, maxdist)
  Ifs            *ifs;
  Ray            *ray;
  Float           mindist, *maxdist;
{
  Vector          vtmp;

  VecAddScaled(ray->pos, mindist, ray->dir, &vtmp);
  if (ifs->boxbounding) {	/* test against solid box */
    if (OutOfBounds(&vtmp, ifs->bounds))
      return BoundsIntersect(ray, ifs->bounds, mindist, maxdist);
  } else {			/* test against solid sphere */
    VecSub(vtmp, ifs->extent.c, &vtmp);
    if (vtmp.x * vtmp.x + vtmp.y * vtmp.y + vtmp.z * vtmp.z > ifs->extent.rsq)
      return IfsIntersectSphere(&ifs->extent, ray, mindist, maxdist);
  }
  *maxdist = mindist;
  return TRUE;
}

/* for IFS without condensation */
static void
IfsExtentNormal(ifs, ray, dist, normal)
  Ifs            *ifs;
  Ray            *ray;
  Float           dist;
  Vector         *normal;
{
  Vector          pos;

  pos.x = ray->pos.x + dist * ray->dir.x;
  pos.y = ray->pos.y + dist * ray->dir.y;
  pos.z = ray->pos.z + dist * ray->dir.z;

  if (ifs->boxbounding) {	/* this produces nicer normals, so ... */
    normal->x = pos.x - (ifs->bounds[HIGH][X] + ifs->bounds[LOW][X]) / 2.;
    normal->y = pos.y - (ifs->bounds[HIGH][Y] + ifs->bounds[LOW][Y]) / 2.;
    normal->z = pos.z - (ifs->bounds[HIGH][Z] + ifs->bounds[LOW][Z]) / 2.;
    VecNormalize(normal);
  } else {
    normal->x = (pos.x - ifs->extent.c.x) / ifs->extent.r;
    normal->y = (pos.y - ifs->extent.c.y) / ifs->extent.r;
    normal->z = (pos.z - ifs->extent.c.z) / ifs->extent.r;
  }
}

/* an IFS without condensation set is a primitive object, so no hitlist here */
int
IfsIntersect(ref, ray, hl, mindist, maxdist)
     GeomRef         ref;
  Ray            *ray;
     HitList        *hl;  /* unused here */
  Float           mindist, *maxdist;
{
  Ifs *ifs = (Ifs *)ref;

  IfsTests++;

  if (DoIfsIntersect(ifs, ray, (HitList *) NULL, mindist, maxdist)) {
    IfsHits++;
    return TRUE;
  }

  return FALSE;
}

/* an IFS with condensation is like an aggregate */
int
CIfsIntersect(ref, ray, hitlist, mindist, maxdist)
     GeomRef         ref;
  Ray            *ray;
  HitList        *hitlist;
  Float           mindist, *maxdist;
{
  Ifs *ifs = (Ifs *)ref;

  CIfsTests++;

  if (DoIfsIntersect(ifs, ray, hitlist, mindist, maxdist)) {
    CIfsHits++;
    return TRUE;
  }

  return FALSE;
}

/*
 * Ray/ifs intersection test.
 */
static int
DoIfsIntersect(ifs, ray, hitlist, mindist, maxdist)
  Ifs            *ifs;
  Ray            *ray;
  HitList        *hitlist;
  Float           mindist, *maxdist;
{
  TEList         *list, *closest, *p, *q, *new;
  Float           nmindist, nmaxdist, distfac, pixelsize;
  Ray             newray;
  Vector          normal;
  int             n, intersection_found, construct_children;
  Float           lambda[3];
  Geom           *obj;
  HitNode        *np;
  IfsMap         *map;

  /*
   * if (ifs->animated && !equal(ifs->time, ray->time)) {
   * IfsResolveAssoc(ifs); IfsComputeExtent(ifs); ifs->time = ray->time; }
   */
  /* does the ray intersect the IFS's extent ? */
  distfac = 1.;
  newray = *ray;
  nmaxdist = *maxdist * distfac;
  if (!IfsExtentIntersect(ifs, &newray, EPSILON, &nmaxdist))
    return FALSE;

  /* put the extent on the list */
  new = TENew();
  MatrixInit(&new->matrix);
  MatrixInit(&new->imatrix);
  new->ray = newray;
  new->distfac = distfac;
  new->dist = nmaxdist / distfac;
  new->size = ifs->size;
  new->depth = 0;
  new->rang = -99999999;
  if (!ifs->condensation) {
    IfsExtentNormal(ifs, &newray, nmaxdist, &normal);
    NormalTransform(&normal, &new->imatrix);
    VecScale((*ifs->normalweight) (ifs->size, new->size), normal, &new->normal);
  }
  new->next = new->prev = (TEList *) NULL;
  list = new;
  intersection_found = FALSE;

  /* while list not empty */
  while (list) {
    /* find closest entry in list */
    closest = list;
    for (p = list->next; p; p = p->next)
      if (p->dist < closest->dist)
	closest = p;

    construct_children = TRUE;
    pixelsize = PixelSize(ray, closest->dist);

    /*
     * does the (inverse transformed) ray intersect the condensation set (if
     * any) ?
     */
    if (ifs->condensation) {
      /*
       * Oppenheimer trees: if TE small enough, than test against leave, not
       * against condensation
       */
      obj = ifs->condensation;
      if (ifs->condensize * closest->size / ifs->size < ifs->options.size ||
	  closest->depth >= ifs->options.depth) {
	if (ifs->leaves)
	  obj = ifs->leaves;
	construct_children = FALSE;
      }
      /* instantiate the object */
      nmindist = mindist * closest->distfac;
      nmaxdist = *maxdist * closest->distfac;
      if (intersect(obj, &closest->ray, hitlist, nmindist, &nmaxdist)) {
	*maxdist = nmaxdist / closest->distfac;
	intersection_found = TRUE;

	if (*maxdist <= closest->dist)
	  fprintf(stderr, "impossible situation: closest = %p, closest->dist = %f\n", closest, closest->dist);
	/*
	 * if (closest->depth > 0) MatrixInvert(&closest->matrix,
	 * &closest->imatrix);
	 */
	np = &hitlist->data[hitlist->nodes - 1];
	if (np->dotrans) {
	  MatrixMult(&closest->matrix, &np->trans.trans, &np->trans.trans);
	  MatrixMult(&np->trans.itrans, &closest->imatrix, &np->trans.itrans);
	} else {
	  MatrixCopy(&closest->matrix, &np->trans.trans);
	  MatrixCopy(&closest->imatrix, &np->trans.itrans);
	}
	np->dotrans = TRUE;

	/*
	 * unless we don't need the *closest* intersection (shadow-rays!!) we
	 * cannot stop here because the generated hierarchy of extents is
	 * *not* well-behaved: it is possible that we find a closer
	 * intersection later.
	 */
	if (ray->type == SHADOW_RAY) {
	  TEDisposeList(list);
	  return TRUE;
	}
	/*
	 * But we can remove all the transformed extents which are behind
	 * this one. Note that at least one TE (closest) stays in the list.
	 */
	for (p = list; p;) {
	  if (p->dist > *maxdist + EPSILON) {
	    if (p->next)
	      p->next->prev = p->prev;
	    if (p->prev)
	      p->prev->next = p->next;
	    else
	      list = p->next;
	    q = p->next;
	    TEDispose(p);
	    p = q;
	  } else
	    p = p->next;
	}
      }
    }
    /* or is the (transformed) extent smaller than one pixel ? */
    if (closest->size < pixelsize || closest->size < ifs->options.size || closest->depth >= ifs->options.depth) {
      if (!ifs->condensation &&
	  closest->dist >= mindist && closest->dist <= *maxdist) {
	intersection_found = TRUE;
	*maxdist = closest->dist;
	/* remember hierarchically computed normal and intersection position */
	ifs->normal = closest->normal;
	VecNormalize(&ifs->normal);
	VecAddScaled(ray->pos, *maxdist, ray->dir, &ifs->intersectpos);
	/* and we can stop here */
	TEDisposeList(list);
	return TRUE;
      }
      /* anyway, we don't construct the children of the transformed extent */
      construct_children = FALSE;
    }
    /*
     * construct the (transformed) extent's "children" and add any that
     * intersect to the list
     */
    if (construct_children)
      for (n = 0, map = ifs->maps; n < ifs->nrmaps; n++, map++) {
	/*
	 * for hierarchical IFS: use only maps of equal or higher rang. The
	 * maps are sorted to descending rang number
	 */
	if (map->rang < closest->rang)
	  break;
	distfac = closest->distfac;
	newray = closest->ray;
	distfac *= RayTransform(&newray, &map->imatrix);
	nmaxdist = *maxdist * distfac;

	if (IfsExtentIntersect(ifs, &newray, EPSILON * distfac, &nmaxdist)) {
	  /* add the "transformed extent" to the list */
	  new = TENew();
	  new->ray = newray;
	  new->distfac = distfac;
	  new->dist = nmaxdist / distfac;
	  new->depth = closest->depth + 1;
	  new->rang = map->rang;
	  /* pre-multiplication !! */
	  if (ifs->condensation || !ifs->uniform_maps)
	    MatrixMult(&map->matrix, &closest->matrix, &new->matrix);
	  MatrixMult(&closest->imatrix, &map->imatrix, &new->imatrix);

	  /* compute size of transformed extent */
	  if (map->uniform)
	    new->size = closest->size * map->contractivity;
	  else {
	    MatrixScaling(&new->matrix, lambda);
	    new->size = ifs->size * Lipschitz(lambda);
	  }

	  /*
	   * if IFS without condensation, compute hierarchically composed
	   * normal
	   */
	  if (!ifs->condensation) {
	    IfsExtentNormal(ifs, &newray, nmaxdist, &normal);
	    NormalTransform(&normal, &new->imatrix);
	    VecAddScaled(closest->normal,
			 (*ifs->normalweight) (ifs->size, new->size),
			 normal,
			 &new->normal);
	  }
	  /* add to list */

	  new->prev = list->prev;
	  list->prev = new;
	  new->next = list;
	  list = new;
	}
      }

    /* remove closest from list */
    if (closest->next)
      closest->next->prev = closest->prev;
    if (closest->prev)
      closest->prev->next = closest->next;
    else
      list = closest->next;	/* closest was the first item */
    TEDispose(closest);
  }

  return intersection_found;
}

#define VecEqual(a, b)  ( (equal((a).x, (b).x)) && (equal((a).y, (b).y)) && (equal((a).z, (b).z)) )

/*
 * Compute normal to ifs at position of last intersection found.
 */
int
IfsNormal(ref, pos, nrm, gnrm)
     GeomRef         ref;
  Vector         *pos, *nrm, *gnrm;
{
  Ifs *ifs = (Ifs *)ref;

  /* this situation does not happen in rayshade.4.0.6 */
  if (!VecEqual(*pos, ifs->intersectpos))
    RLerror(RL_ABORT, "Can't compute ifs normal in other point than last intersection.\n");

  /* normal is the hierarchically computed normal */
  if (ifs->normal.x == 0. && ifs->normal.y == 0. && ifs->normal.z == 0.)
    ifs->normal.z = 1.;		/* may happen when the whole IFS is smaller
				 * than one pixel */
  *nrm = *gnrm = ifs->normal;
  return FALSE;
}


/*
 * Determine if ray enters (TRUE) or leaves (FALSE) ifs at position of
 * last intersection.
 */
int
IfsEnter(ref, ray, mind, hitd)
  GeomRef         ref;
  Ray            *ray;
  Float           mind, hitd;
{
  return TRUE;
}

void
IfsBounds(ref, bounds)
  GeomRef         ref;
  Float           bounds[2][3];
{
  Ifs *ifs = (Ifs *)ref;

  if (ifs->animated)
    IfsResolveAssoc(ifs);

  if (ifs->animated || ifs->condensation)
    IfsComputeExtent(ifs);

  BoundsCopy(ifs->bounds, bounds);

  /* produce a list of volumes that will be rendered */
  if (ifs->options.listfilename)
    IfsList(ifs, ifs->options.listfilename);
}

char *
IfsName()
{
  return ifsName;
}

char *
CIfsName()
{
  return CifsName;
}

void
IfsStats(tests, hits)
  unsigned long  *tests, *hits;
{
  *tests = IfsTests;
  *hits = IfsHits;
}

void
CIfsStats(tests, hits)
  unsigned long  *tests, *hits;
{
  *tests = CIfsTests;
  *hits = CIfsHits;
}

void
IfsMethodRegister(meth)
  UserMethodType  meth;
{
  if (iIfsMethods)
    iIfsMethods->user = meth;
}
