/* projector.c,v 1.1.1.1 1995/02/27 07:38:29 explorer Exp */

/*
 * 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.
 *
 */

#include "libcommon/common.h"
#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;
     Float dist;
     int noshadow;
{
  Color imagecolor;
  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]; /* 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);
}

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