// File         : the_ltv.cxx
// Author       : Paul A. Koshevoy
// Created      : Sat Apr 24 12:04:15 MDT 2004
// Copyright    : (C) 2004
// License      : GPL.
// Description  : 

#ifndef THE_LTV_HXX_
#define THE_LTV_HXX_

// system includes:
#include <iostream>
#include <cmath>
#include <cassert>

// local includes:
#include "the_mirror.hxx"
#include "the_triangle.hxx"
#include "the_coord_sys.hxx"

using std::cout;
using std::cerr;
using std::endl;


//----------------------------------------------------------------
// the_ltv_t
// 
class the_ltv_t
{
public:
  the_ltv_t(const real_t aspect_ratio,
	    const real_t diagonal,
	    const real_t gamma_1):
    g1_(gamma_1),
    g2_(0.5 * M_PI - gamma_1),
    S1_(0.0),
    R1_(0.0),
    d1_(0.0)
  {
    // sanity check:
    if (!(g2_ - g1_ > 0.0))
    {
      cerr << "ERROR: gamma_2 is less then or equal to gamma_1, aborting..."
	   << endl;
      assert(false);
    }
    
    real_t height = diagonal / sqrt(1.0 + aspect_ratio * aspect_ratio);
    real_t width  = aspect_ratio * height;

    S1_ = 0.5 * height;
    R1_ = 0.5 * width;
    d1_ = S1_ * tan(2.0 * g1_);
  }
  
  virtual ~the_ltv_t() {}
  
  // calculate the UV pixel coordinate on the screen, given the current
  // angles alpha_1 and alpha_2
  virtual bool
  alpha_to_uv(const real_t & a1,
	      const real_t & a2,
	      real_t & u,
	      real_t & v) = 0;
  
  // calculate the minimum length of the second mirror:
  inline real_t m2_length() const
  { return 2.0 * S1_ * R1_ / (S1_ + d1_); }
  
  // screen dimensions:
  inline real_t width()  const { return R1_ + R1_; }
  inline real_t height() const { return S1_ + S1_; }
  
  // accessors:
  inline const real_t & g1() const { return g1_; }
  inline const real_t & g2() const { return g2_; }
  inline const real_t & S1() const { return S1_; }
  inline const real_t & R1() const { return R1_; }
  inline const real_t & d1() const { return d1_; }
  
protected:
  // helper function:
  void v4_to_uv(const v3x1_t & v4, real_t & u, real_t & v) const
  {
    u = (R1_ - v4.x()) / (2.0 * R1_);
    v = (2.0 * S1_ - v4.y()) / (2.0 * S1_);
  }
  
  // gamma_1, mimimum angle of incidence at the center of the reflection
  // axis of the second mirror m2:
  real_t g1_;
  
  // gamma_2, maximum angle of incidence at the center of the reflection
  // axis of the second mirror m2:
  real_t g2_;
  
  // the dimension of one-half of screen height:
  real_t S1_;
  
  // the dimension of one-half of screen width:
  real_t R1_;
  
  // the distance from the center of the lower edge of the screen to
  // the reflection point in the first mirror m1:
  real_t d1_;
};


//----------------------------------------------------------------
// the_fast_ltv_t
// 
class the_fast_ltv_t : public the_ltv_t
{
public:
  the_fast_ltv_t(const real_t aspect_ratio = 4.0 / 3.0,
		 const real_t diagonal = 20.0,
		 const real_t gamma_1 = 10.0 * M_PI / 180.0):
    the_ltv_t(aspect_ratio, diagonal, gamma_1)
  {}
  
  // virtual:
  bool
  alpha_to_uv(const real_t & a1, const real_t & a2, real_t & u, real_t & v)
  {
    // make sure alpha_2 falls within the legal range:
    if (a2 > g2_ || a2 < g1_) return false;
    
    // calculate the admissible alpha_1 angle limits:
    real_t a1_lim = atan2(R1_, S1_ + d1_ / sin(2.0 * a2));
    if (a1 > a1_lim && !(a1 > (2.0 * M_PI - a1_lim))) return false;
    
    v3x1_t v1(0.0, 0.0, -d1_);
    v3x1_t v2(S1_ * tan(a1), S1_, 0.0);
    v3x1_t v3(d1_ * tan(a1) / sin(2.0 * a2), d1_ / tan(2.0 * a2), d1_);
    v3x1_t v4 = v1 + v2 + v3;
    
    v4_to_uv(v4, u, v);
    return !(u < 0.0 || u > 1.0 || v < 0.0 || v > 1.0);
  }
};


//----------------------------------------------------------------
// the_slow_ltv_t
// 
class the_slow_ltv_t : public the_ltv_t
{
public:
  the_slow_ltv_t(const real_t aspect_ratio = 4.0 / 3.0,
		 const real_t diagonal = 20.0,
		 const real_t gamma_1 = 10.0 * M_PI / 180.0):
    the_ltv_t(aspect_ratio, diagonal, gamma_1),
    m1_cyl_pt_(3),
    m1_tri_(1),
    m2_cyl_pt_(4),
    m2_tri_(2)
  {
    // setup the first mirror (m1 in the diagram):
    for (unsigned int i = 0; i < 3; i++)
    {
      real_t & radius = m1_cyl_pt_[i].x();
      real_t & angle  = m1_cyl_pt_[i].y();
      real_t & height = m1_cyl_pt_[i].z();
      
      radius = 1.0;
      angle  = M_PI / 2.0 + 2.0 * M_PI * real_t(i) / 3.0;
      height = -sin(angle);
    }
    
    // setup the second mirror (m2 in the diagram):
    m2_cs_ = the_cyl_coord_sys_t(m1_cs_.y_axis(),
				 m1_cs_.z_axis(),
				 m1_cs_.x_axis(),
				 p3x1_t(0.0, S1_, 0.0));
    
    real_t V3x_max = 0.5 * m2_length();
    m2_cyl_pt_[0] = p3x1_t(1.0, M_PI, -V3x_max);
    m2_cyl_pt_[1] = p3x1_t(1.0, M_PI,  V3x_max);
    m2_cyl_pt_[2] = p3x1_t(1.0,  0.0,  V3x_max);
    m2_cyl_pt_[3] = p3x1_t(1.0,  0.0, -V3x_max);
    
    // setup the first ray:
    v1_ = the_ray_t(p3x1_t(0.0, 0.0, d1_), v3x1_t(0.0, 0.0, -1.0));
  }
  
  // virtual:
  bool
  alpha_to_uv(const real_t & a1, const real_t & a2, real_t & u, real_t & v)
  {
    // setup the first mirror:
    the_cyl_coord_sys_t cs_a1(m1_cs_);
    cs_a1.rotate(m1_cs_.origin(), m1_cs_.z_axis(), -a1);
    
    m1_tri_[0].init(cs_a1.to_wcs(m1_cyl_pt_[0]),
		    cs_a1.to_wcs(m1_cyl_pt_[1]),
		    cs_a1.to_wcs(m1_cyl_pt_[2]));
    the_mirror_t m1(m1_tri_);
    
    // setup the second mirror:
    the_cyl_coord_sys_t cs_a2(m2_cs_);
    cs_a2.rotate(m2_cs_.origin(), m2_cs_.z_axis(), a2);
    
    m2_tri_[0].init(cs_a2.to_wcs(m2_cyl_pt_[0]),
		    cs_a2.to_wcs(m2_cyl_pt_[1]),
		    cs_a2.to_wcs(m2_cyl_pt_[2]));
    m2_tri_[1].init(cs_a2.to_wcs(m2_cyl_pt_[3]),
		    cs_a2.to_wcs(m2_cyl_pt_[0]),
		    cs_a2.to_wcs(m2_cyl_pt_[2]));
    the_mirror_t m2(m2_tri_);
    
    // first reflection ray:
    the_ray_t v2;
    if (m1.reflect(v1_, v2) == false)
    {
      // did not reflect from m1:
      return false;
    }
    
    // second reflection ray:
    the_ray_t v3;
    if (m2.reflect(v2, v3) == false)
    {
      // did not reflect from m2:
      return false;
    }
    
    // check for screen/ray intersection:
    the_plane_t screen(the_coord_sys_t(m1_cs_.x_axis(),
				       m1_cs_.y_axis(),
				       m1_cs_.z_axis(),
				       p3x1_t(0.0, 0.0, d1_)));
    real_t param = THE_REAL_MAX;
    
    if (screen.intersect(v3, param) == false)
    {
      // did not hit the screen:
      return false;
    }
    
    if (param < 0.0)
    {
      // the reflection is behind the screen:
      return false;
    }
    
    p3x1_t p4 = v3 * param;
    v3x1_t v4 = p4 - v1_.p();
    v4_to_uv(v4, u, v);
    return !(u < 0.0 || u > 1.0 || v < 0.0 || v > 1.0);
  }
  
private:
  // minor optimizations for the first mirror:
  the_cyl_coord_sys_t m1_cs_;
  the_array_t<p3x1_t> m1_cyl_pt_;
  the_array_t<the_triangle_t> m1_tri_;
  
  // minor optimizations for the second mirror:
  the_cyl_coord_sys_t m2_cs_;
  the_array_t<p3x1_t> m2_cyl_pt_;
  the_array_t<the_triangle_t> m2_tri_;
  
  // the first ray:
  the_ray_t v1_;
};


#endif // THE_LTV_HXX_

