/*
 * gradient.c
 *
 * 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;

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

