/*
 * ViewTransform.C - general view transformation.
 *
 * Copyright (C) 1992, Christoph Streit (streit@iam.unibe.ch)
 *                     University of Berne, Switzerland
 * 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".
 *
 */

#include "ViewTransform.h"
#include "Error.h"

//___________________________________________________________ ViewTransform


ViewTransform::ViewTransform(const Vector& Eye, const Vector& Lookat,
			     const Vector& Up, real Fov, int ResX, int ResY)
: eye(Eye), lookat(Lookat), up(Up), fov(Fov), resX(ResX), resY(ResY)
{
  buildView();
}

ViewTransform::ViewTransform(const BoundingBox& b, const Vector& Up, real Fov, 
			     int ResX, int ResY)
: up(Up), fov(Fov), resX(ResX), resY(ResY)
{
  real widthX2 = 0.5*(b.xmax()-b.xmin());
  real widthY2 = 0.5*(b.ymax()-b.ymin());
  real height2 = 0.5*(b.zmax()-b.zmin());

  if (widthX2 > widthY2) {
    // let's look to the xz-plane
    if (widthX2 >= height2)
      eye[1] = b.ymax() + widthX2/tan(dtor(fov/2));
    else
      eye[1] = b.ymax() + height2/tan(dtor(fov/2));
    eye[0] = lookat[0] = b.xmin() + widthX2;
    lookat[1] = b.ymax();
  } 
  else {
    // let's look to the yz-plane
    if (widthY2 >= height2)
      eye[0] = b.xmax() + widthY2/tan(dtor(fov/2));
    else
      eye[0] = b.xmax() + height2/tan(dtor(fov/2));
    eye[1] = lookat[1] = b.ymin() + widthY2;
    lookat[0] = b.xmax();
  }
  eye[2] = lookat[2] = b.zmin()+height2; 
  
  buildView();
}

Vector ViewTransform::transformWorld2Screen(const Vector& p)
{
  Vector vp = p*viewmat;

  if (equal(vp[2], 0)) 
    return Vector(0,0,0);

  real scale = widthOfViewplane/vp[2];
  vp[0] = resX*(0.5+vp[0]*scale);
  vp[1] = resY*(0.5+vp[1]*scale);

  return vp;
}

Vector ViewTransform::transformWorld2View(const Vector& p)
{
  return p*viewmat;
}

Vector ViewTransform::transformView2Screen(const Vector& p)
{
  if (equal(p[2], 0)) 
    return Vector(0,0,0);

  Vector vp = p;
  real scale = widthOfViewplane/vp[2];
  vp[0] = resX*(0.5+vp[0]*scale);
  vp[1] = resY*(0.5+vp[1]*scale);

  return vp;
}

// buildView assumes that eye, lookat, up, fov, resX, resY are already set!!!
void ViewTransform::buildView()
{
  Vector n,v,u;

  n = lookat - eye; n.normalize();
  v = up - (n^up)*n;
  if (v.normalize() == 0) 
    Error(ERR_PANIC, "The view and up directions are identical?");
  u = n * v; 
  widthOfViewplane = 0.5/(tan(dtor(fov)/2));

  viewmat(0,0) = u[0]; viewmat(1,0) = u[1]; viewmat(2,0) = u[2];
  viewmat(0,1) = v[0]; viewmat(1,1) = v[1]; viewmat(2,1) = v[2];
  viewmat(0,2) = n[0]; viewmat(1,2) = n[1]; viewmat(2,2) = n[2];
  viewmat(3,0) = -eye[0]*u[0] - eye[1]*u[1] - eye[2]*u[2];
  viewmat(3,1) = -eye[0]*v[0] - eye[1]*v[1] - eye[2]*v[2];
  viewmat(3,2) = -eye[0]*n[0] - eye[1]*n[1] - eye[2]*n[2];
}

ostream& operator<<(ostream& os, const ViewTransform& view)
{
  os << "Eye:    " << view.eye    << '\n'
     << "Lookat: " << view.lookat << '\n'
     << "Up:     " << view.up     << '\n'
     << "Fov:    " << view.fov    << '\n';

  return os;
}
