/*
 * surfshade.c
 *
 * Copyright (C) 1989, 1991, Craig E. Kolb
 * 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".
 *
 * $Id: surfshade.c,v 4.0.1.2 92/01/10 14:08:28 cek Exp Locker: cek $
 *
 * $Log:	surfshade.c,v $
 * Revision 4.0.1.2  92/01/10  14:08:28  cek
 * patch3: Fixed additional composition problems.
 * 
 * Revision 4.0.1.1  91/12/13  11:54:51  cek
 * patch3: Fixed ordering of world/model xform composition.
 * patch3: Geometric normal now transformed correctly.
 * 
 * Revision 4.0  91/07/17  14:41:15  kolb
 * Initial version.
 * 
 */
#include "geom.h"
#include "surface.h"

/*
 * Compute surface properties from given hitlist
 * Returns TRUE if ray is entering object, FALSE otherwise.
 */
int
ComputeSurfProps(hitlist, ray, pos, norm, gnorm, surf, smooth)
HitList *hitlist;	/* Hit information (path through DAG) */
Ray *ray;		/* Ray in world space */
Vector *pos;		/* Intersection point */
Vector *norm, *gnorm;	/* shading normal, geometric normal (return values) */
Surface *surf;		/* Copy of surface to use, texture-modified */
int *smooth;
{
	HitNode *hp;
	int i;
	Ray rtmp;
	Geom *prim, *obj;
	Float k, kp;
	int texturing, transforming, entering;
	Trans prim2model, world2model;

	hp = hitlist->data;
	prim = hp->obj;

	/*
	 * Compute point of intersection in "primitive space".
	 */
	VecAddScaled(hp->ray.pos, hp->dist, hp->ray.dir, pos);

	/*
	 * Find normal to primitive at point of intersection.
	 */
	*smooth = PrimNormal(prim, pos, norm, gnorm);

	texturing = transforming = FALSE;

	/*
	 * Walk down hit list, constructing world<-->primitive transformation
	 * and determining if we need to perform texture mapping.
	 * The last node is the World node, which cannot be textured or
	 * transformed, so we skip it.
	 */
	for (i = 0, hp = hitlist->data; i < hitlist->nodes -1; hp++, i++) {
		obj = hp->obj;
		texturing = texturing || obj->texture;
		if (hp->dotrans) {
			/*
			 * Here we're actually computing prim2world.
			 * When finished, we invert it.
			 */
			if (transforming) {
				TransCompose(&world2model, &hp->trans,
					&world2model);
			} else {
				TransCopy(&hp->trans, &world2model);
				transforming = TRUE;
			}
		}
	}

	/*
	 * Determine if we're entering or exiting the surface,
	 * flipping surface normals if necessary.
	 */
	k = dotp(&hitlist->data[0].ray.dir, norm);
	if (*smooth) {
		/*
		 * If gnorm and shading norm differ and
		 * their dot products with the ray have
		 * different signs, use the geometric normal
		 * instead, ala Snyder & Barr's paper.
		 */
		kp = dotp(&hitlist->data[0].ray.dir, gnorm);
		if (k <= 0. && kp > 0. || k >= 0. && kp < 0.)
			k = kp;
	}

	if (k > 0.) {
		/* flip normals */
		VecScale(-1., *gnorm, gnorm);
		VecScale(-1., *norm, norm);
		/*
		 * Normal indicates that we're exiting.
		 * Only set entering to TRUE if csg has indicated
		 * that the ray is, indeed, entering.
		 */
		entering = (hitlist->data[0].enter == ENTERING);
	} else {
		/*
		 * Normal indicates that we're entering.
		 * Set entering flag as such unless csg has
		 * told us that we're exiting.
		 */
		entering = !(hitlist->data[0].enter == EXITING);
	}
		
	/*
	 * If there are no transformations, then world2model is identity.
	 */
	if (!transforming)
		TransInit(&world2model);
	/*
	 * If we're not performing texturing, we simply need to compute
	 * the normal and point of intersection to world space.
	 */
	if (!texturing) {
		/*
	 	 * At this point 'world2model' is really 'prim2world'.
		 */
		if (transforming) {
			NormalTransform(norm, &world2model.itrans);
			NormalTransform(gnorm, &world2model.itrans);
			VecAddScaled(ray->pos,
				     hitlist->data[hitlist->nodes -1].dist,
				     ray->dir, pos);
		}
		return entering;
	}
	/*
	 * world2model currently transforms from primitive to world space.
	 * Invert it to get transformation from world to primitive space.
	 */
	TransInvert(&world2model, &world2model);
	TransInit(&prim2model);
	rtmp = hitlist->data[0].ray;
	/*
	 * Walk down hitlist (from primitive up to World object),
	 * transforming hit point and shading normal and applying textures.
	 * Note that the texturing routines want gnorm in object space,
	 * so we don't transform the geometric normal until texturing
	 * is complete.
	 */
	for (hp = hitlist->data, i = 0; i < hitlist->nodes -1; i++, hp++) {
		obj = hp->obj;
		if (hp->dotrans) {
			NormalTransform(norm, &hp->trans.itrans);
			if (texturing) {
				/*
				 * Compose prim<-->model and world<-->model
				 * with current transformation.
				 */
				TransCompose(&prim2model, &hp->trans,
					&prim2model);
				TransCompose(&world2model, &hp->trans,
					&world2model);
				/*
				 * Transform point and ray to model space.
				 */
				PointTransform(pos, &hp->trans.trans);
				(void)RayTransform(&rtmp, &hp->trans.trans);
			}
		}
		/*
		 * Apply textures
		 */
		if (obj->texture)
			TextApply(obj->texture, prim, &rtmp, pos, norm,
				gnorm, surf, &prim2model, &world2model);
	}
	/* Transform geometric normal from object to world space. */
	NormalTransform(gnorm, &world2model.trans);
	return entering;
}
