//------------------------------------------------------------
// $Id: frustumSRG.cc,v 1.1.1.1 1999/11/25 06:26:09 yamauchi Exp $
//------------------------------------------------------------
// distribute secondary ray generation	
//	光の発生時に錐体を用いた分散レイを発生させる．
//
//	Original file is prbDist.cc by TOKUNAGA Mayumi
//	Changed to frustumSRG.cc by YAMAUCHI Hitoshi
//
//	Copyright (C) 1998  TOKUNAGA Mayumi  徳永 真由美
//	Copyright (C) 1998  YAMAUCHI Hitoshi 山内 斉
//---------------------------------------------------------
#include <math.h>
#include <time.h>
#include <sys/time.h>
#include "frustumSRG.h"

//------------------------------------------------------
// protected function
//------------------------------
// output message
//------------------------------
// void frustumSRGClass::
// outMessage(int messageType)
// {
//     static const char* pMes[] = {
// 	"Error! : illegal sample square resolution (must be > 0)"
// 	"Suggest : in view file. setnum frustum = 1 1 1.0 1.0"
// 		"frustumSRGClass::getMemory"
// }

//------------------------------------------------------
// public functions
//------------------------------
// constructor
//------------------------------
frustumSRGClass::
frustumSRGClass(void)
{
    this->mBaseSquareEdgeResol  = -1;
    this->mNumberOfTotalSamples = -1; // illegal value で初期化
    this->mpPixelBasePos   	= NULL;
    this->mpPixelSamplePos 	= NULL;
    this->mpWeight 		= NULL;
}

//------------------------------
// destructor
//------------------------------
frustumSRGClass::
~frustumSRGClass(void)
{
    if(this->mBaseSquareEdgeResol > 0)	// 初期化済みの場合
    {
	// to tokunaga 
	// 配列で確保したものは配列で delete しないとリークする 
	// それにこのクラスだけを見ていたら getMemory が呼ばれるか不明
	// なのでチェックが必要  
	delete [] this->mpPixelBasePos;
	delete [] this->mpPixelSamplePos;
	delete [] this->mpWeight;
	this->mpPixelBasePos 	= NULL;
	this->mpPixelSamplePos 	= NULL;
	this->mpWeight 		= NULL;
	this->mBaseSquareEdgeResol  = -1;
	this->mNumberOfTotalSamples = -1;
    }
}


// [0, m) の擬似乱数の発生．Yam 
inline static int rand_i(int max)
{
    return (int(drand48() * ((double)max)));
} 

//------------------------------------------------------------
// 分布関数
// 	frustum の底面に重みをつける
//	cos^n 乗則を用いる
// Z は常に 1.0 を仮定する．
//   @param x : frustum の底面上の位置 x
//   @param y : frustum の底面上の位置 y
//   @param pow_cos_n : Phone の cos の n 乗則のパラメータ n 
//------------------------------------------------------------
// inline 
static double genWeight(double x, double y, double pow_cos_n)
{
    assert(pow_cos_n >= 0.0);

    const double z_sqrt = 1.0;	// z = 1.0, z^2 = 1.0 * 1.0 = 1.0;

    // x^2 + y^2 + z^2 : これは大きな数の場合には問題になるが，大丈夫
    // だろう．(x^2 + y^2 + z^2 > MAX_DOUBLE && sqrt(x^2 + y^2 + z^2)
    // < MAX_DOUBLE の場合には問題ということしかし，hypot の 3 次元版
    // はない)
    double distance_from_reflent_point = sqrt(x * x + y * y + z_sqrt);
    double cos_theta = 1.0 / distance_from_reflent_point;

    return(pow(cos_theta, pow_cos_n)); 
}

//    double distance = tan(sqrt(x[0] * x[0] + x[1] * x[1]));
//    double m = 0.8;
//    return(exp((-1)*pow((distance/m),2.0)));
//    return(exp((-1)*(distance*distance)/(m*m)));

//------------------------------
// initialize : memory allocation and 乱数系列初期化
//   @param resolBaseSquareEdge frustum の底辺の一辺の解像度 
//------------------------------
void frustumSRGClass::
init(int resolBaseSquareEdge)
{
    const char *pEmes = 
      "Error!  : frustumSRGClass::init : illegal sample square resolution.\n";

    if(resolBaseSquareEdge < 1)
    {
 	cout << pEmes << "Suggest : in viewfile, "
	"x value must be > 0 of line `setnum frustum = x y fov cos_n'" << endl;
	exit(1);
    }

    // 底面の解像度は 2 で割れなくてはいけない
    if(resolBaseSquareEdge % 2 != 0)
    {
 	cout << pEmes << "Suggest : in viewfile, x value must be "
	"x % 2 == 0 of line `setnum frustum = x y fov cos_n'" << endl;
	exit(1);
    }

    // 初期化済みか?
    if(this->mBaseSquareEdgeResol < 0)
    {
	// 初期化済み(メモリ取得済み)の場合には開放
	delete [] this->mpPixelBasePos;
	delete [] this->mpPixelSamplePos;
	delete [] this->mpWeight;
    }
    
    this->mBaseSquareEdgeResol  = resolBaseSquareEdge;
    // 底面の pixel 数 = (1 辺の resol)^2
    this->mNumberOfTotalSamples = resolBaseSquareEdge * resolBaseSquareEdge;

    // allocation
    this->mpPixelBasePos   = new Vector3[this->mNumberOfTotalSamples];
    this->mpPixelSamplePos = new Vector3[this->mNumberOfTotalSamples];
    this->mpWeight 	   = new double [this->mNumberOfTotalSamples];

    // 乱数列初期化
    srand48(time(NULL));
}

//------------------------------
// 回転前の (0.0, 0.0, 1.0) 方向を向く frustum の底辺位置初期化
//   @param frustumTopHalfRad    : 錐の頂角/2 (radian)
//   @param pow_cos_n		 : Phone の cos n 乗則の n 
//------------------------------
void frustumSRGClass::
setBaseSamplePosition(double halfFrustumTopRad, double pow_cos_n)
{
    // このオブジェクトは初期化済みでなくてはいけない
    assert(this->mBaseSquareEdgeResol > 0);  

    // 底面の幅の半分の長さ
    double halfEdge = sin(halfFrustumTopRad);

    // サンプリングピクセルの幅
    double widthOfSamplePixel = 
    	2.0 * halfEdge / (double)(this->mBaseSquareEdgeResol);
    assert(widthOfSamplePixel > 0.0);
    
    // pixel 中心を指すために pixel の半分の長さを計算しておく
    double halfWidthOfPixel = (widthOfSamplePixel / 2.0);
    
    int    pixelIdx = 0;
    double totalWeight = 0.0;	// 重みの正規化のために合計を計算する
    double x = - halfEdge + halfWidthOfPixel;
    for(int xidx = 0; xidx < this->mBaseSquareEdgeResol; xidx++)
    {
	double y = - halfEdge + halfWidthOfPixel;
	for(int yidx = 0; yidx < this->mBaseSquareEdgeResol; yidx++)
	{
	    this->mpPixelBasePos[pixelIdx].set(x, y, 1.0);
	    this->mpWeight[pixelIdx] = genWeight(x, y, pow_cos_n);
	    totalWeight += this->mpWeight[pixelIdx];

	    y += widthOfSamplePixel;
	    pixelIdx++;
	    assert(pixelIdx <= this->mNumberOfTotalSamples);
	}
	x += widthOfSamplePixel;
    }

    // 重みを正規化する
    for(int i = 0; i < this->mNumberOfTotalSamples; i++)
    {
	this->mpWeight[i] /= totalWeight;
    }
}
    

// void frustumSRGClass::
// probablty(int precise, double *min, double *max) 
// {
//     // precise = n = 底面の pixel 数
//     int i, j;
//     double h[2], x[2], val;
// 
//     // To Tokunaga 
//     // int n_sq = precise * precise;
//     // getMemory(n_sq) でしょう．まだ自分自身の初期化が完全でないのに
//     // 自分のメンバで初期化するのは危険ではないかな．あと noi は 
//     // double の必要はあるだろうか
//     this->noi =  pow((double) precise, 2.0); // n^2 (二乗ならかけ算を使う)
//     this->getMemory(int(this->noi));	     // 
//     
//     for(i = 0; i < 2; i++)
//     {
// 	h[i] = (max[i] - min[i]) / precise; // h = sampling pixel の幅
//     }
//     val=0.0;
//     int k = 0;
// 
//     for(i = 0; i < precise; i++) 
//     {
// 	x[0] = h[0]*i + min[0] + h[0]/2; // 底面の X 方向の位置
// 	for(j = 0; j < precise; j++)
// 	{
// 	    x[1] = h[1]*j +  min[1] + h[1]/2; // 底面の Y 方向の位置
// 	    this->mpPixelBasePos[k].set(x[0], x[1], 1.0); // 位置のセット
// 	    this->distributeProb[k] = f(x);	   // その位置の重みのセット
// 	    val += f(x);		   // 後に正規化するために全て加算
// 	    k++;			   // 重み・位置の 1 次元の配列の index
// 	}
//     }
// 
//     for(i = 0; i < ((int) this->noi); i++)
//     {
// 	//	cout << this->mpPixelBasePos[i].get(0) << " " <<
// 	//	    this->mpPixelBasePos[i].get(1) << " " <<
// 	//			this->distributeProb[i]  << endl;
// 	this->distributeProb[i] /= val;	// 正規化する
//     }
// }

//------------------------------------------------------------
// rotate : HemiCube の回転と同じルーチンを利用 tokunaga changed 
//	@param rayDir : rayDir 方向を中心に向かって拡散レイ放射
//------------------------------------------------------------
void frustumSRGClass::
rotate(const Vector3& rayDir)
{
    const double D_NON_DEF = -2.0; // ありえない値
    double a[3][3];		   /* オイラー変換マトリクス  */
    double dCtheta = D_NON_DEF;	   /* θ(x' axis)     */
    double dStheta = D_NON_DEF;
    double dCkai   = D_NON_DEF;    /* χ(y' axis)     */
    double dSkai   = D_NON_DEF;
    double dCphi   = 1.0;	   /* φ(z' axis)=0.0 */
    double dSphi   = 0.0;
    
    double dNx = rayDir.get(Vector3::X);
    double dNy = rayDir.get(Vector3::Y);
    double dNz = rayDir.get(Vector3::Z);

    dCtheta = dNz;
    dStheta = sqrt( 1 - dNz * dNz ); /* とりあえず正に仮定 */
    if(0.0 == dStheta)
    {
	dSkai = 0.0;
	dCkai = 1.0;
    }else
    {
	dSkai   = dNx / dStheta;
	dCkai   = dNy / dStheta;
    }

    // 回転用変換行列初期化
    a[0][0] =   dCkai   * dCphi - dSkai * dCtheta * dSphi;
    a[1][0] = - dSkai   * dCphi - dCkai * dCtheta * dSphi;
    a[2][0] =   dStheta * dSphi;

    a[0][1] =   dCkai   * dSphi + dSkai * dCtheta * dCphi;
    a[1][1] = - dSkai   * dSphi + dCkai * dCtheta * dCphi;
    a[2][1] = - dStheta * dCphi;

    a[0][2] =   dSkai * dStheta;
    a[1][2] =   dCkai * dStheta;
    a[2][2] =   dCtheta;
    
    for(int i = 0; i < this->mNumberOfTotalSamples; i++)
    {
	// double x = this->distributePoint[i].get(Vector3::X) * rad * route_2;
	// double y = this->distributePoint[i].get(Vector3::Y) * rad * route_2;
	// double z = this->distributePoint[i].get(Vector3::Z) * rad * route_2;
	// cout << x << " " << y << " " << this->distributeProb[i] << endl;

 	double x = this->mpPixelBasePos[i].get(Vector3::X);
 	double y = this->mpPixelBasePos[i].get(Vector3::Y);
 	double z = this->mpPixelBasePos[i].get(Vector3::Z);

	double x2 = a[0][0] * x + a[0][1] * y + a[0][2] * z; 
	double y2 = a[1][0] * x + a[1][1] * y + a[1][2] * z;
	double z2 = a[2][0] * x + a[2][1] * y + a[2][2] * z;
	
	this->mpPixelSamplePos[i].set(x2, y2, z2);
    }
}

/*
 この徳永のコードは何をしているのだろうか

    int i;
    //tokunaga changed
    //四角錐の底面から rad の範囲内で サンプリング
    double weight_all = 0.0;

    //    double route_2 = sqrt(2);
    for(i = 0; i <sampleNum; i++)
    {
	//double x = this->mpPixelBasePos[i].get(Vector3::X) * rad * route_2;
	//double y = this->mpPixelBasePos[i].get(Vector3::Y) * rad * route_2;
	//double z = this->mpPixelBasePos[i].get(Vector3::Z) * rad * route_2;
	
	int j = rand_i((int)this->noi);
	while(fabs(this->mpPixelBasePos[j].get(Vector3::X) * 
		   this->mpPixelBasePos[j].get(Vector3::Y)) > rad * rad) 
	{
	    j = rand_i((int)this->noi);
	}
	double x = this->mpPixelBasePos[j].get(Vector3::X); 
	double y = this->mpPixelBasePos[j].get(Vector3::Y);
	double z = this->mpPixelBasePos[j].get(Vector3::Z); 

	double x2 = a[0][0] * x + a[0][1] * y + a[0][2] * z; 
	double y2 = a[1][0] * x + a[1][1] * y + a[1][2] * z;
	double z2 = a[2][0] * x + a[2][1] * y + a[2][2] * z;
	
	this->mpPixelSamplePos[i].set(x2, y2, z2);
	this->Weight[i] = this->distributeProb[j];
	weight_all += this->distributeProb[i];
    }
    for(i = 0; i < sampleNum; i++)
    {
        this->Weight[i] /= weight_all;
    }
*/

//------------------------------------------------------------
// test
//------------------------------------------------------------
/*
void main()
{
    int n = 20;
    double a[2], b[2];
    Vector3 *point;
    
    double width = tan(M_PI/8);
    a[0] = -width;    a[1] = -width;
    b[0] = width;     b[1] = width;
    
    frustumSRGClass sPoint;
    sPoint.probablty(n, a, b);
    Vector3 x_axis(1/sqrt(2), 1/sqrt(2), 0.0);
    sPoint.rotate(x_axis);
    
    point = new Vector3 [n*n];
    point = sPoint.getmpPixelSamplePos();
    
    int i;
    int sample = sPoint.getSampleNum();
//    for(i = 0; i < sample; i++)
//	printf("%f %f %f \n", point[i].get(Vector3::X),
//	       point[i].get(Vector3::Y),point[i].get(Vector3::Z));
}
*/
