/*
 * 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$
 *
 * $Log$
 */
#include "libobj/object.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 (return value) */
int *smooth;
{
	HitNode *hp;
	int i;
	Ray rtmp;
	Object *prim, *obj;
	Surface *stmp;
	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;
		if (obj->trans) {
			/*
			 * Here we're actually computing prim2world.
			 * When finished, we invert it.
			 */
			if (transforming)
				TransCompose(obj->trans, &world2model,
					&world2model);
			else
				TransCopy(obj->trans, &world2model);
			transforming = TRUE;
		}
		if (obj->texture)
			texturing = TRUE;
	}

	/*
	 * Get surface to use via the application-provided function.
	 */
	stmp = GetShadingSurf(hitlist);

	if (stmp == (Surface *)NULL)
		RLerror(RL_PANIC, "GetShadingSurf returned NULL!\n");
	
	/*
	 * Make a copy so it may be modified via texturing.
	 */
	*surf = *stmp;

	/*
	 * 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.
	 */
	for (hp = hitlist->data, i = 0; i < hitlist->nodes -1; i++, hp++) {
		obj = hp->obj;
		if (obj->trans) {
			NormalTransform(norm, &obj->trans->itrans);
			if (texturing) {
				/*
				 * Compose prim<-->model and world<-->model
				 * with current transformation.
				 */
				TransCompose(obj->trans, &prim2model,
					&prim2model);
				TransCompose(obj->trans, &world2model,
					&world2model);
				/*
				 * Transform point and ray to model space.
				 */
				PointTransform(pos, &obj->trans->trans);
				(void)RayTransform(&rtmp, &obj->trans->trans);
			}
		}
		/*
		 * Apply textures
		 */
		if (obj->texture)
			TextApply(obj->texture, prim, &rtmp, pos, norm,
				gnorm, surf, &prim2model, &world2model);
	}
	return entering;
}
