/*
 * projector.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".
 *
 * Projector light source itself written and copyrighted by Greg Spencer and 
 * given to the Public Domain, with the above conditions.
 *
 * $Id$
 *
 * $Log$
 */
#include "light.h"
#include "projector.h"
#ifdef __SASC
#define M_PI PI
#endif

static LightMethods *iProjectorMethods = NULL;

Projectorlight *
ProjectorCreate(from, to, up, image, uangle, vangle, falloff)
Vector *from, *to, *up;
ImageText *image;
Float uangle,vangle;
int falloff;
{
	Projectorlight *projector; /* pointer for projector object */

	/* allocate space for projector */
	projector = (Projectorlight *)share_malloc(sizeof(Projectorlight));

	projector->pos = *from;
	VecSub(*to, *from, &projector->dir);
	if (VecNormalize(&projector->dir) == 0.) {
		RLerror(RL_ABORT,"Invalid projectorlight specification.\n");
		return (Projectorlight *)NULL;
	}

	/*
	 * If angles are zero, set it to 0.5 degrees -- just to keep from
	 * exploding...
	 */
	if ((uangle==0.0)||(vangle==0.0)) uangle=vangle=0.5;

	/* note: these are solid angles, not spherical coords */
	projector->uangle = ((uangle*M_PI)/180.0);
	projector->vangle = ((vangle*M_PI)/180.0);
	projector->v = *up;

	/* find a u direction for the projector from v and direction */
	VecNormCross(up,&projector->dir,&projector->u);

	/* if falloff is true, then use r^2 falloff, otherwise ignore it */
	projector->falloff=falloff;

	projector->image = image;
	return projector;
}

LightMethods *
ProjectorMethods()
{
	if (iProjectorMethods == (LightMethods *)NULL) {
		iProjectorMethods = LightMethodsCreate();
		iProjectorMethods->intens = ProjectorIntens;
		iProjectorMethods->dir = ProjectorDirection;
	}
	return iProjectorMethods;
}

/*
 * Calculate intensity ('color') of light reaching 'pos' from light 'lp'.
 * The projectorlight is 'dist' units from 'pos' along 'dir'.
 *
 * Returns TRUE if non-zero illumination, FALSE otherwise.
 */
int
ProjectorIntens(projector, lcolor, cache, ray, dist, noshadow, color)
Projectorlight *projector;
ShadowCache *cache;
Ray *ray;
Color *lcolor, *color;
int noshadow;
Float dist;
{
    Color imagecolor;
    extern Float ProjectorAtten();
    Float dist2;

    color->r = lcolor->r;
    color->g = lcolor->g;
    color->b = lcolor->b;

    /*
     * Compute projectorlight color -- return if outside image
     */
    if (ProjectorImageApply(projector, ray, &imagecolor)
	== FALSE) return FALSE;
    /*
     * If outside of projector, return FALSE.
     */
    if (imagecolor.r+imagecolor.g+imagecolor.b == 0.)
	return FALSE;
    if (Shadowed(color, lcolor, cache, ray, dist, noshadow))
	return FALSE;

    if (projector->falloff) {
	dist2= dist*dist;
	if (dist2 < 1.0) dist2=1.0; /* below 1.0, falloff is closer to 1:1 */
	color->r *= imagecolor.r/dist2;
	color->g *= imagecolor.g/dist2;
	color->b *= imagecolor.b/dist2;
    }
    else {
	color->r *= imagecolor.r;
	color->g *= imagecolor.g;
	color->b *= imagecolor.b;
    }
    return TRUE;
}

/*
 * Map Image to the 'dir' direction
 * adapted from ImageTextApply.
 */
int
ProjectorImageApply(pl, ray, imagecolor)
Projectorlight *pl;
Ray *ray;
Color *imagecolor;
{
	Float fx, fy; /* sub-pixel coords */
	ImageText *text; /* imagetext input */
	Float outval[4], outval_u[4], outval_v[4]; /* returned color values */
	Float u, v; /* whole-valued u-v coords */
	Float rdotu, rdotv, rdotp; /* dot products */
	int ix, iy; /* integer u-v components */
	int rchan, gchan, bchan; /* position of channels in input */
	Vector rdir; /* ray direction */

	text=pl->image;
	rdir.x= -ray->dir.x;
	rdir.y= -ray->dir.y;
	rdir.z= -ray->dir.z;
	rdotp = dotp(&rdir, &pl->dir);
	/*
	 * Check to see if ray is behind projectorlight.
	 * also eliminates the possibility of a divide by zero
	 * later on (if we come in exactly at 90 degrees from
	 * light direction)
	 */
	if (rdotp <= 0.000001) return FALSE;

	/*
	 * pos is position of origin of the ray
	 * pl->pos is the position of the projector
	 * pl->dir is the direction the projector points in
	 * dir is the direction from the ray origin to the projector
	 */

	rdotu= dotp(&rdir,&pl->u);
	rdotv= dotp(&rdir,&pl->v);

	u= text->tileu/2.0+atan(rdotu/rdotp)/pl->uangle;
	v= text->tilev/2.0+atan(rdotv/rdotp)/pl->vangle;

	/*
	 * Handle tiling at this point.
	 * (merely checks to see if we're outside the tile space)
	 */

	if (TileValue(text->tileu, text->tilev, u, v)) return FALSE;

	u -= floor(u); /* remove all but fractional portion */
	v -= floor(v);
	fx = u * (Float) text->image->width; /* multiply by size */
	fy = v * (Float) text->image->height;
	ix = fx; /* round to integer */
	iy = fy;
	fx = fx - (Float) ix; /* find out sub-pixel address */
	fy = fy - (Float) iy;

	/* handle alpha */
	if (text->image->has_alpha) {
		/* Alpha channel is 0 */
		rchan = 1;
		gchan = 2;
		bchan = 3;
	} else {
		rchan = 0;
		gchan = 1;
		bchan = 2;
	}

	/* handle grey-scale */
	if (text->image->chan == 1) {
		gchan = rchan;
		bchan = rchan;
	}

	/* interpolate a sub-pixel value from the image */
	ImageIndex(text->image, ix, iy, fx, fy, text->smooth, outval);

	/*
	 * escape when alpha is zero, 'cause there is no change
	 */
	if (text->image->has_alpha && (outval[0] < 0.001))
		return FALSE;

	imagecolor->r=outval[rchan];
	imagecolor->g=outval[gchan];
	imagecolor->b=outval[bchan];
	return TRUE;
}

void
ProjectorDirection(lp, pos, dir, dist)
Projectorlight *lp;
Vector *pos, *dir;
Float *dist;
{
	/*
	 * Calculate dir from position to center of light source.
	 */
	VecSub(lp->pos, *pos, dir);
	*dist = VecNormalize(dir);
}

ProjectorMethodRegister(meth)
UserMethodType meth;
{
	if (iProjectorMethods)
		iProjectorMethods->user = meth;
}
