/* gradient.c,v 1.1.1.1 1995/02/27 07:38:41 explorer Exp */

/*
 * Copyright (C) 1989, 1991, Craig E. Kolb, 1992, Lawrence Coffin
 * All rights reserved.
 *
 * This software may be freely copied, modified, and redistributed
 * provided that this copyright notice is preserved on all copies.
 *
 * You may not distribute this software, in whole or in part, as part of
 * any commercial product without the express consent of the authors.
 *
 * There is no warranty or other guarantee of fitness of this software
 * for any purpose.  It is provided solely "as is".
 * 
 */

#include "libcommon/common.h"
#include "libcommon/color.h"
#include "texture.h"
#include "gradient.h"

#define sign(x)  ((x) < 0 ? (-1) : (1))

Gradient *
GradientCreate(surf1, surf2, Shape, start, stop, GradType, Rand)
     Surface *surf1, *surf2;
     Float start, stop;
     int Shape, GradType, Rand;
{
  Gradient *grad;
  
  if (start > stop){
    RLerror(RL_WARN,"Bad Gradient range\n");
    return (Gradient *)NULL;
  }
  
  grad = (Gradient *)Malloc(sizeof(Gradient));
  grad->surf1 = surf1;
  if (surf1 == surf2){
    grad->surf2 = (Surface *)NULL;
  }
  else {
    grad->surf2 = surf2;
  }
  grad->start = start;
  grad->stop = stop;
  grad->shape = Shape;
  grad->type = GradType;
  grad->rand = Rand;
  
  return grad;
}

/*
 * Basically this texture works by calculating the distance of the point
 * "pos" according to the texture's shape.  Then the distance is normalized
 * between the starting and stoping distances for the texture and changed
 * according to the gradient type.  Finally, if the resulting distance
 * is between 0 and 1, the original surface is replaced with the texture's
 * first surface the blended with the second surface according to the
 * distance -- final surface = surf1*(1-dist) + surf2*dist
 */
void
GradientApply(grad,prim,ray,pos,norm,gnorm,surf)
     Gradient *grad;
     Geom *prim;
     Ray *ray;
     Vector *pos, *norm, *gnorm;
     Surface *surf;
{
  Float len, side;
  
  len = 0.0;  /* this COULD be unitialized in the code below...  */

  /* calculate the distance of the point according to the shape */
  if (grad->shape == PLANAR) {
    len = pos->z;
    side = sqrt(pos->x*pos->x + pos->y*pos->y);
  } else if (grad->shape == RADIAL) {
    len = sqrt(pos->x*pos->x + pos->y*pos->y);
    if (pos->x != 0.0) {
      side = atan(pos->y/pos->x);
    } else
      side = PI/2.0;
  } else if (grad->shape == SPHERICAL) {
    len = sqrt(pos->x*pos->x + pos->y*pos->y + pos->z*pos->z);
    
    side = sqrt(pos->x*pos->x + pos->y*pos->y);
    if (pos->z != 0.0)
      side = atan(side/pos->z);
    else
      side = PI/2.0;
  }
  
  /* normalize it between the start and stop distances */
  len = (len - grad->start)/(grad->stop - grad->start);
  
  /* add random noise based on the point in 3-space */
  if (grad->rand)
    len += Noise3(pos);
  
  /* modify according to Type -- linear remains the same */
  if (grad->type == LOG)
    len = sqrt(len);
  else if (grad->type == REVLOG)
    len = len*len*sign(len);
  
  /* check to see if the point is between 0 and 1 */
  if (len < 0 - EPSILON || len > 1 + EPSILON)
    /* point is outside of texture range */
    return;
  
  /* set surface to first surface */
  *surf = *grad->surf1;
  
  /* check to see if there is a second surface */
  if (grad->surf2 == (Surface *)NULL)
    return;
  
  /* blend the two surfaces based on the length of len */
  SurfaceBlend(surf,grad->surf2,1-len,len);
}

