/*
 * camera.c - This file contains all of the functions for doing camera work.
 *
 *  $Id: camera.c,v 1.26 2001/08/19 06:57:41 johns Exp $
 */

#include "machine.h"
#include "types.h"
#include "macros.h"
#include "vector.h"
#include "camera.h"
#include "util.h"
#include "intersect.h"

/* 
 * camera_init()
 *   take camera parameters stored in scene definition and do all 
 *   necessary initialization and whatever pre-calculation can be done. 
 */
void camera_init(scenedef *scene) {
  flt sx, sy;

  /* setup function pointer for camera ray generation */
  if (scene->antialiasing > 0) {
    scene->camera.cam_ray = (color (*)(void *, flt, flt)) cam_aa_perspective_ray;
  } else {
    scene->camera.cam_ray = (color (*)(void *, flt, flt)) cam_perspective_ray;
  }
#if 0
  } else {
    scene->camera.cam_ray = (color (*)(void *, flt, flt)) cam_orthographic_ray;
  }
#endif

  sx = (flt) scene->hres; 
  sy = (flt) scene->vres;
    
  /* calculate the width and height of the image plane in world coords */
  /* given the aspect ratio, image resolution, and zoom factor */
  scene->camera.px=((sx / sy) / scene->aspectratio) / scene->camera.camzoom;
  scene->camera.py=1.0 / scene->camera.camzoom;    

  /* caclulate the size of a pixel in world coordinates */
  scene->camera.psx = scene->camera.px / sx;
  scene->camera.psy = scene->camera.py / sy;

  /* save px/py as half the length to prevent doing divides at runtime */
  scene->camera.px /= 2.0; /* half length */
  scene->camera.py /= 2.0; /* half length */

  /* assuming viewvec is a unit vector, then the center of the */
  /* image plane is the camera center + vievec                 */
  scene->camera.projcent.x = scene->camera.center.x + scene->camera.viewvec.x;
  scene->camera.projcent.y = scene->camera.center.y + scene->camera.viewvec.y;
  scene->camera.projcent.z = scene->camera.center.z + scene->camera.viewvec.z;

  /* assuming viewvec is a unit vector, then the lower left    */
  /* corner of the image plane is calculated below             */
  scene->camera.lowleft.x = scene->camera.projcent.x +
                (-scene->camera.px * scene->camera.rightvec.x) +
                (-scene->camera.py * scene->camera.upvec.x)
                - scene->camera.center.x;
  scene->camera.lowleft.y = scene->camera.projcent.y +
                (-scene->camera.px * scene->camera.rightvec.y) +
                (-scene->camera.py * scene->camera.upvec.y)
                - scene->camera.center.y;
  scene->camera.lowleft.z = scene->camera.projcent.z +
                (-scene->camera.px * scene->camera.rightvec.z) +
                (-scene->camera.py * scene->camera.upvec.z)
                - scene->camera.center.z;

  scene->camera.iplaneright.x = scene->camera.psx * scene->camera.rightvec.x;
  scene->camera.iplaneright.y = scene->camera.psx * scene->camera.rightvec.y;
  scene->camera.iplaneright.z = scene->camera.psx * scene->camera.rightvec.z;
  
  scene->camera.iplaneup.x = scene->camera.psy * scene->camera.upvec.x;
  scene->camera.iplaneup.y = scene->camera.psy * scene->camera.upvec.y;
  scene->camera.iplaneup.z = scene->camera.psy * scene->camera.upvec.z;
}


/* 
 * camray_init() 
 *   initializes a camera ray which will be reused over and over
 *   by the current worker thread.  This includes attaching thread-specific
 *   data to this ray.
 */
void camray_init(scenedef *scene, ray *primary, unsigned long serial, 
                 unsigned long * mbox) {
  primary->add_intersection = add_regular_intersection; /* func ptr */
  primary->serial = serial;
  primary->mbox = mbox;
  primary->scene = scene;
  primary->depth = scene->raydepth;

  /* for perspective rendering without depth of field */
  primary->o = scene->camera.center;
}


/*
 * cam_aa_perspective_ray() 
 *  Generate a perspective camera ray, potentially incorporating
 *  antialiasing.
 */
color cam_aa_perspective_ray(ray * ry, flt x, flt y) {
  color col, avcol;
  int alias; 
  unsigned int rndval=1234567;
  scenedef * scene=ry->scene;
  flt scale;

  col=cam_perspective_ray(ry, x, y);   /* generate ray */

  /* perform antialiasing if enabled.. */
  for (alias=1; alias <= scene->antialiasing; alias++) {
    avcol=cam_perspective_ray(ry,
           x + (rt_rand(&rndval) / RT_RAND_MAX) - 0.5,
           y + (rt_rand(&rndval) / RT_RAND_MAX) - 0.5);

    col.r += avcol.r;       /* accumulate antialiasing samples */
    col.g += avcol.g;
    col.b += avcol.b;
  }

  /* average sample colors, back to range 0.0 - 1.0 */ 
  scale = 1.0 / (scene->antialiasing + 1.0); 
  col.r *= scale;
  col.g *= scale;
  col.b *= scale;

  return col;
}


/*
 * cam_perspective_ray() 
 *  Generate a perspective camera ray, no antialiasing
 */
color cam_perspective_ray(ray * ry, flt x, flt y) {
  flt rdx, rdy, rdz, len;
  scenedef * scene=ry->scene;

  /* starting from the lower left corner of the image plane, we move the   */
  /* center of the pel we're calculating:                       */ 
  /* lowerleft + (rightvec * X_distance) + (upvec * Y_distance) */
  /* rdx/y/z are the ray directions (unnormalized)              */
  rdx = scene->camera.lowleft.x + 
                (x * scene->camera.iplaneright.x) + 
                (y * scene->camera.iplaneup.x);

  rdy = scene->camera.lowleft.y + 
                (x * scene->camera.iplaneright.y) + 
                (y * scene->camera.iplaneup.y);

  rdz = scene->camera.lowleft.z + 
                (x * scene->camera.iplaneright.z) + 
                (y * scene->camera.iplaneup.z);

  /* normalize the ray direction vector */
  len = 1.0 / sqrt(rdx*rdx + rdy*rdy + rdz*rdz);
  ry->d.x = rdx * len;
  ry->d.y = rdy * len;
  ry->d.z = rdz * len;

  /* initialize ray attributes for a primary ray */
  ry->maxdist = FHUGE;         /* unbounded ray */
  ry->opticdist = 0.0;         /* ray is just starting */
  ry->flags = RT_RAY_REGULAR;  /* camera only generates primary rays */
  ry->serial++;                /* increment the ray serial number */

  /* trace the ray */
  reset_intersection(ry);
  intersect_objects(ry);
  return scene->shader(ry);
}



/*
 * cam_orthographic_ray() 
 *  Generate an orthographic camera ray, no antialiasing
 */
color cam_orthographic_ray(ray * ry, flt x, flt y) {
  flt rdx, rdy, rdz, len;
  scenedef * scene=ry->scene;

  /* the ray direction is always coaxial with the view direction */
  rdx = scene->camera.viewvec.x; 
  rdy = scene->camera.viewvec.y;
  rdz = scene->camera.viewvec.z;

  /* normalize the ray direction vector */
  len = 1.0 / sqrt(rdx*rdx + rdy*rdy + rdz*rdz);
  ry->d.x = rdx * len;
  ry->d.y = rdy * len;
  ry->d.z = rdz * len;

  /* starting from the lower left corner of the image plane, we move the   */
  /* center of the pel we're calculating:                       */ 
  /* lowerleft + (rightvec * X_distance) + (upvec * Y_distance) */
  /* rdx/y/z are the ray directions (unnormalized)              */
  ry->o.x = scene->camera.lowleft.x + 
                (x * scene->camera.iplaneright.x) + 
                (y * scene->camera.iplaneup.x) - scene->camera.viewvec.x;

  ry->o.y = scene->camera.lowleft.y + 
                (x * scene->camera.iplaneright.y) + 
                (y * scene->camera.iplaneup.y) - scene->camera.viewvec.y;

  ry->o.z = scene->camera.lowleft.z + 
                (x * scene->camera.iplaneright.z) + 
                (y * scene->camera.iplaneup.z) - scene->camera.viewvec.z;

  /* initialize ray attributes for a primary ray */
  ry->maxdist = FHUGE;         /* unbounded ray */
  ry->opticdist = 0.0;         /* ray is just starting */
  ry->flags = RT_RAY_REGULAR;  /* camera only generates primary rays */
  ry->serial++;                /* increment the ray serial number */

  /* trace the ray */
  reset_intersection(ry);
  intersect_objects(ry);
  return scene->shader(ry);
}



void camerasetup(camdef * camera, flt zoom,
        vector center, vector viewvec, vector upvec) {
  vector newupvec;
  vector newviewvec;
  vector newrightvec;

  VCross(&upvec, &viewvec, &newrightvec);
  VNorm(&newrightvec);

  VCross(&viewvec, &newrightvec, &newupvec);
  VNorm(&newupvec);

  newviewvec=viewvec;
  VNorm(&newviewvec);


  camera->camzoom=zoom;
  camera->center=center;
  camera->viewvec=newviewvec;
  camera->rightvec=newrightvec;
  camera->upvec=newupvec;
}


void cameraposition(camdef * camera, vector center, vector viewvec, 
                    vector upvec) {
  vector newupvec;
  vector newviewvec;
  vector newrightvec;

  VCross(&upvec, &viewvec, &newrightvec);
  VNorm(&newrightvec);

  VCross(&viewvec, &newrightvec, &newupvec);
  VNorm(&newupvec);

  newviewvec=viewvec;
  VNorm(&newviewvec);

  camera->center=center;
  camera->viewvec=newviewvec;
  camera->rightvec=newrightvec;
  camera->upvec=newupvec;
}

void getcameraposition(camdef * camera, vector * center, vector * viewvec, 
                    vector * upvec, vector * rightvec) {

  *center = camera->center;
  *viewvec = camera->viewvec;
  *upvec = camera->upvec;
  *rightvec = camera->rightvec;
}


