//------------------------------------------------------------
// $Id: hemicube.cc,v 1.2 2000/05/15 14:20:43 yamauchi Exp $
//------------------------------------------------------------
//	(Mπ)^2 System Hemi cube class
//	First Ray Generation Unit has Hemi cube.
//	(This is only use radiosity mode)
//      (C) 1993-1998 Copyright By YAMAUCHI Hitoshi 山内 斉
//	平成５年１０月２８日(木)
//	平成６年６月１８日(土)
//	平成８年９月６日(金)	(start SP2 mpi2 implementation)
//
// シミュレーション用の HC は一本づつレイの発生をしては停止することが
// できなくてはいけなかったが、今回はその必要がない。そのためいくつか
// の簡単化が可能である。
//------------------------------------------------------------
#include <math.h>
#include <assert.h>

#include "constants.h"
#include "hemicube.h"
#include "mpi2exit.h"
#include "ray.h"
#include "triangleObject.h"

//------------------------------------------------------------
// protected functions
//------------------------------------------------------------
// memory allocation
//------------------------------
void hemiCubeClass::
getMemory(void)
{
    assert((this->getHemres() > 1) || (this->getHemres() % 2 == 0)); //inited?
    
    //------------------------------
    // getting form factor values area
    // (this->mpFFcal[壁の種類][高さ][幅])
    //------------------------------
    this->mpFFval 		= new double **[WALL_XPLUS_e + 1]; // 天井と1壁
    //     天井
    this->mpFFval[WALL_CEILING_e] = new double  *[this->getHemres()];  
    int i;
    for(i = 0; i < this->getHemres(); i++)
    {
	this->mpFFval[WALL_CEILING_e][i] = new double[this->getHemres()];
    }
    //     壁を1つ
    // 壁の高さは上面の幅の半分
    this->mpFFval[WALL_XPLUS_e] = new double  *[this->getHemres() / 2];
    for(i = 0; i < (this->getHemres() / 2); i++) // 壁の高さは幅の半分
    {
	// 壁の幅は同じ
	this->mpFFval[WALL_XPLUS_e][i] = new double[this->getHemres()];
    }
    
    //------------------------------
    // getting Vector3 values    
    // (this->mpFFEOrgPos[壁の種類][高さ][幅])
    //------------------------------
    this->mpFFEOrgPos = new Vector3 **[HEMICUBE_PLANE_MAX_e];
    this->mpFFEPos    = new Vector3 **[HEMICUBE_PLANE_MAX_e];

    int w;
    for(w = WALL_CEILING_e; w < HEMICUBE_PLANE_MAX_e; w++)
    {
	int maxHeight = 0;		// 一辺の高さ
	if(WALL_CEILING_e == w)		// 天井は別
	{
	    maxHeight = this->getHemres();
	}else
	{
	    maxHeight = this->getHemres() / 2; // 天井以外
	}
	this->mpFFEOrgPos[w] = new Vector3*[maxHeight]; 
	this->mpFFEPos[w]    = new Vector3*[maxHeight]; 

	int h;			// h (height) は mHemres あるいは mHemres/2
	for(h = 0; h < maxHeight; h++)  // ここも maxHeight まで
	{
	    this->mpFFEOrgPos[w][h] = new Vector3[this->getHemres()];
	    this->mpFFEPos[w][h]    = new Vector3[this->getHemres()];
	}
    }
}

//------------------------------
// setFormFactor
//	Master Hemi-Cube(回転前の Hemi-Cube)のフォームファクタと各ピク
//	セル位置の設定 
// +--------------+ --
// |              | ^
// | Hemi-Cube    | | hight
// | Plane        | v
// +--------------+ --
// |<-- width --->|
//                  pixel の FF は mpFFval[plane][hight][width]
//------------------------------
void hemiCubeClass::
setFormFactor(void)
{
    const double dZMAX =  1.0;	// Hemi-Cube の高さ
    const double dZMIN =  0.0;	// -1.0    1.0   
    const double dXMIN = -1.0;	//   +-------+ ---  1.0 
    const double dXMAX =  1.0;	//   | Hemi- | 		 top view
    const double dYMIN = -1.0;	//   |  Cube |
    const double dYMAX =  1.0;	//   +-------+ --- -1.0 
    const double dCEIL_EDGE	//   |<----->| dCEIL_EDGE
	= dXMAX - dXMIN;
    const double dELEM_EDGE		   // dELEM_EDGE = 要素の一辺長
	= dCEIL_EDGE / double(this->getHemres());
    const double dELEM_BIAS	           // 要素の中心を取るためのbias
	= dELEM_EDGE / 2.0;

    const double dElemArea = dELEM_EDGE * dELEM_EDGE;   // 要素の面積:正方形

    double x  = 0.0;
    int    ix = 0;
    for(x =  dXMIN + dELEM_BIAS, ix = 0; // 天井部分 
	x <  dXMAX;
	x += dELEM_EDGE,         ix++)
    {
	assert(ix < this->getHemres());
	double y  = 0.0;
	int    iy = 0;
	for(y = dYMIN + dELEM_BIAS, iy = 0;
	    y < dYMAX;
	    y += dELEM_EDGE,        iy++)
	{
	    assert(iy < this->getHemres());
	    double dR_sqr = x * x + y * y + 1.0;
	    dR_sqr *= dR_sqr;
	    
	    this->mpFFval[WALL_CEILING_e][ix][iy] = dElemArea/(M_PI * dR_sqr); 
	    this->mpFFEOrgPos[WALL_CEILING_e][ix][iy].set(x, y, dZMAX);
	}
    }
    //----------------------------------------------------------
    //  横板の部分 xp(ulus), xm(inus), yp(ulus), ym(inus)のうち
    // 一つがわかればあとは同じ
    //----------------------------------------------------------

    double  z = 0.0;
    int    iz = 0;
    for(z =  dZMIN + dELEM_BIAS, iz = 0;
	z <  dZMAX;
	z += dELEM_EDGE,         iz++)
    {
	assert(iz < (this->getHemres() / 2));
	double  y = 0.0;
	int    iy = 0;
	for(y = dYMIN + dELEM_BIAS, iy = 0;
	    y < dYMAX;
	    y += dELEM_EDGE,        iy++)
	{
	    assert(iy < this->getHemres());
	    double dR_sqr = 1.0 + y * y + z * z;
	    dR_sqr *= dR_sqr;
	    this->mpFFval[WALL_XPLUS_e][iz][iy] = 
		(dElemArea * z) / (M_PI * dR_sqr);
	    this->mpFFEOrgPos[WALL_XPLUS_e][iz][iy].set( dXMAX, y, z);   
	    this->mpFFEOrgPos[WALL_XMINUS_e][iz][iy].set(dXMIN, y, z);   
	}
    }
    
    for(z =  dZMIN + dELEM_BIAS, iz = 0;	// z は高さ hight
	z <  dZMAX;
	z += dELEM_EDGE,         iz++)
    {
	assert(iz < (this->getHemres() / 2));
	for(x = dXMIN + dELEM_BIAS, ix = 0; // x は width
	    x < dXMAX;
	    x += dELEM_EDGE,               ix++)
	{
	    assert(ix < this->getHemres());
	    this->mpFFEOrgPos[WALL_YPLUS_e][iz][ix].set( x, dYMAX, z);
	    this->mpFFEOrgPos[WALL_YMINUS_e][iz][ix].set(x, dYMIN, z);
	}
    }
}

//------------------------------
// rotate hemi cube (by Eular rotation)
//	(Maybe, this code includes my oldest code in mpi2)
//------------------------------
void hemiCubeClass::
rotateHC(const Vector3& aPatchNrm)
{
    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 = aPatchNrm.get(Vector3::X); // パッチ法線ベクトル
    double dNy = aPatchNrm.get(Vector3::Y); /* nrmは正規化されている */
    double dNz = aPatchNrm.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;
    }

    /* -1.0 < sin,cos < 1.0 */
    assert( (-1.0 <= dCtheta) && (dCtheta <= 1.0) );
    assert( (-1.0 <= dStheta) && (dStheta <= 1.0) );
    assert( (-1.0 <= dCphi)   && (dCphi <= 1.0) );
    assert( (-1.0 <= dSphi)   && (dSphi <= 1.0) );

    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;

    int hcPlane;
    int maxHeight;
    for(hcPlane = WALL_CEILING_e; hcPlane < HEMICUBE_PLANE_MAX_e; hcPlane++)
    {
	if(hcPlane == WALL_CEILING_e)
	{
	    maxHeight = this->getHemres();
	}else
	{
	    maxHeight = this->getHemres() / 2; // 天井以外は高さ半分
	}
	int height;
	for(height = 0; height < maxHeight; height++)
	{
	    int width;
	    for(width = 0; width < this->getHemres(); width++)
	    {
		assert(isHemiCubePosXYZRight((hemicube_plane_type_e)hcPlane,
					     height, width)); 
		const Vector3& orgpos = 
		    this->mpFFEOrgPos[hcPlane][height][width];
		
		double x = orgpos.get(Vector3::X);
		double y = orgpos.get(Vector3::Y);
		double z = orgpos.get(Vector3::Z);
		
		double xt = a[0][0] * x + a[0][1] * y + a[0][2] * z; 
		double yt = a[1][0] * x + a[1][1] * y + a[1][2] * z;
		double zt = a[2][0] * x + a[2][1] * y + a[2][2] * z;
		
		this->mpFFEPos[hcPlane][height][width].set(xt, yt, zt);
	    }
	}
    }
}

//------------------------------
// generation rays using by rotetaed hemi-cube
//	Ray の運ぶ delta radiosity は \delta Fij * PatchArea
//------------------------------
inline Ray *hemiCubeClass::
genOneRay(int plane, int height, int width)
{
    assert(isHemiCubePosXYZRight(plane, height, width) == YES);

    Vector3   aPos(this->mGravityCenter);
    Vector3   aDir(this->mpFFEPos[plane][height][width]);
    aDir.VNormalize();            // レイのベクトルの正規化
    Vector3   aRadiosity(this->mLockingNotYetRad);
    
    // レイにはフォームファクタと放射パッチの面積が掛かっている
    int ffvalPlane = plane;
    if(plane > WALL_XPLUS_e)
    {
	ffvalPlane = WALL_XPLUS_e; // 側面の FF val は同じなので 1 つにしてある
    }
    aRadiosity = aRadiosity * 
	(this->mpFFval[ffvalPlane][height][width] * this->mPatchArea);
    
    // レイ生産 newray
    Ray *pNewRay = Ray::newForRadiosity(aPos, aDir, aRadiosity,
					this->mpPatch, this->mSpcpos
					//,  this->mPEID
					); 
    return(pNewRay);
}

//---------------
// distributed method
//	ray の放射面を次々変化させて同一の(に近い)方向に連続して ray 
//	を放射しないようにする
//	load balancing の効果があると考えられる方法  
//---------------
void hemiCubeClass::
distributeRadiation(Ray * pRetRays[])
{
    int plane, h, w;		// plane, height, width
    int ridx = 0;		// index for return ray 
    const int halfHemres = (this->getHemres() / 2);
    for(h = 0; h < halfHemres; h++)
    {
	for(w = 0; w < this->getHemres(); w++)	    
	{
	    // 天井は2本 : h * w と h + halfHemres * w の ray 
	    pRetRays[ridx] = this->genOneRay(WALL_CEILING_e, h, w);
	    ridx++;
	    pRetRays[ridx] = this->genOneRay(WALL_CEILING_e, h + halfHemres,w);
	    ridx++;
	    
	    // 天井でない部分
	    for(plane = WALL_XPLUS_e; plane < HEMICUBE_PLANE_MAX_e; plane++)
	    { 
		pRetRays[ridx] = this->genOneRay(plane, h, w);
		ridx++;
	    }
	}
    }
}

//---------------
// continuous method
//	ray の放射面を変化させず、同一の(に近い)方向に連続して ray 
//	を放射する
//	load balancing の効果が薄いと考えられる方法  
//---------------
void hemiCubeClass::
continuousRadiation(Ray *pRetRays[])
{
    int plane, h, w;		// plane, height, width
    int ridx = 0;		// index for return ray 
    for(plane = WALL_CEILING_e; plane < HEMICUBE_PLANE_MAX_e; plane++)
    { 
	//(plane == WALL_CEILING_e)?(this->getHemres()):(this->getHemres()/2);
	int maxHeight;
	if(plane == WALL_CEILING_e)
	{
	    maxHeight = this->getHemres();
	}else
	{
	    maxHeight = this->getHemres() / 2;
	}
	for(h = 0; h < maxHeight; h++)
	{
	    for(w = 0; w < this->getHemres(); w++)	    
	    {
		pRetRays[ridx] = this->genOneRay(plane, h, w);
		ridx++;
	    }
	}
    }
}

//------------------------------
// 正しい指標を指しているかのテスト 正しい場合には YES
//------------------------------
YorN hemiCubeClass::
isHemiCubePosXYZRight(const int plane, const int height, const int width) const
{
    if(plane >= HEMICUBE_PLANE_MAX_e) // planeは符号無しなので正かの比較は無し
    {
	cout << "Error! : illegal plane = " << plane
	    << " at MHC position" << endl;
	return(NO);
    }
    
    int MaxHeight = 0;
    if(plane != WALL_CEILING_e)
    {
	MaxHeight = this->getHemres() / 2;
    }else
    {
	MaxHeight = this->getHemres();
    }
    
    if((height < 0) || (height >= MaxHeight)) // 高さは変化する
    {
	cout << "Error!: illegal height = " << height
	    << " at MHC position" << endl;
	return(NO);
    }
    
    if((width < 0) || (width >= this->getHemres())) // 幅は変化なし
    {
	cout << "Error!: illegal hemres = " << this->getHemres() << 
	    " width = " << width << " at MHC position" << endl;
	return(NO);	
    }
    return(YES);
}

//------------------------------------------------------------
// public functions
//------------------------------------------------------------
// constructor
//------------------------------
hemiCubeClass::
hemiCubeClass(int aHemres, const IntVec3& PEID)
{
    assert(PEID.get(IntVec3::X) >= 0); // host は hemi cube を持たない

    if((aHemres < 2) || (aHemres % 2 != 0))
    {
	cout << "Error! : illegal hemres value. " << 
	    "hemres must be > 1 and % 2 == 0, hemres = " << aHemres << endl;
	commMpi2_exit(1);
    }

    this->mpFFval	= NULL;
    this->mpFFEOrgPos	= NULL;
    this->mpFFEPos	= NULL;

    this->mElemArea	= -1.0;
    this->mHemres	= aHemres;

    this->mPEID		= PEID;	
    
    this->mPatchNormal.set(0.0, 0.0, 0.0);
    this->mGravityCenter.set(PlusInfty, PlusInfty, PlusInfty);
    this->mPatchArea	= 0.0;
    this->mSpcpos.set(-1, -1, -1);
    this->mpPatch	= NULL;
    this->mLockingNotYetRad.set(0.0, 0.0, 0.0);

    // memory allocation 
    this->getMemory();

    // initialize master hemi-cube
    this->setFormFactor();
}

//------------------------------
// メモリ解放
//------------------------------
hemiCubeClass::
~hemiCubeClass()
{
    int i;
    // FF値の領域(天井)を解放
    for(i = 0; i < this->getHemres(); i++)
    {
	delete [] this->mpFFval[WALL_CEILING_e][i];
    }
    // FF値の領域(壁)を解放
    for(i = 0; i < (this->getHemres() / 2); i++) // 壁の高さは幅の半分
    {
	delete [] this->mpFFval[WALL_XPLUS_e][i];
    }
    delete [] this->mpFFval[WALL_CEILING_e];
    delete [] this->mpFFval[WALL_XPLUS_e];
    delete [] this->mpFFval;
    this->mpFFval = NULL;	// meanless but comfortable for me
    
    // 位置を保持している領域の解放
    int w;
    for(w = WALL_CEILING_e; w < HEMICUBE_PLANE_MAX_e; w++)
    {
	int maxHeight = 0;	// 一辺の高さ
	if(WALL_CEILING_e == w)	// 天井は別
	{
	    maxHeight = this->getHemres();
	}else
	{
	    maxHeight = this->getHemres() / 2; // 天井以外
	}
	int h;			// h (height) は mHemres あるいは mHemres/2
	for(h = 0; h < maxHeight; h++)  // ここも maxHeight まで
	{
	    delete [] this->mpFFEOrgPos[w][h];
	    delete [] this->mpFFEPos[w][h];
	}
	delete [] this->mpFFEOrgPos[w];
	delete [] this->mpFFEPos[w];
    }
    delete [] this->mpFFEOrgPos;
    delete [] this->mpFFEPos;
    this->mpFFEOrgPos = NULL;	// meanless but comfortable for me
    this->mpFFEPos    = NULL;	// meanless but comfortable for me
}


//------------------------------
// 放射パッチの情報により、Hemi-Cube を回転させ、ray を取り出す
//	Ray のポインタ配列に ray を new して返す
//	Ray のポインタ配列領域は呼び出す側で確保すること
//------------------------------
void hemiCubeClass::
genRays(objBaseClass *const apPatch, 	  // 放射パッチ
	const YorN isDistributedRadiation,// ray の発生で load balance するか
	Ray *pRetRays[])		  // 作成される ray のポインタ配列
{
    // must normalized normal
    assert(fabs((apPatch->getNormal()).getNorm() - 1.0) < 1e-5); 
    assert(pRetRays != NULL);

    this->mPatchNormal		= apPatch->getNormal();
    this->mGravityCenter	= apPatch->getGCenter();
    this->mPatchArea		= apPatch->getArea();
    assert(this->mPatchArea > 0.0); // 面積はあるはず

    this->mSpcpos	= apPatch->getSpcPos();
    assert((this->mSpcpos.get(IntVec3::X) >= 0) && 
	   (this->mSpcpos.get(IntVec3::Y) >= 0) && 
	   (this->mSpcpos.get(IntVec3::Z) >= 0)); 

    // 放射中に他から radiosity値を受けても良いように放射値を lock する
    // コードは書いたが、現在、multi thread でないので lock しなくても
    // 安全である。いつか必要になるかもしれない。(キューから取る部分も 
    // atomic にしていないので、本当に MT になったら安全かはわからない) 
    // 7 Sept. 1996 Hitoshi Yamauchi 
    // critical region begin
    this->mLockingNotYetRad = apPatch->getNClearNonRadAndUpdateRadiosity();
    apPatch->unsetInRadiationQueue(); // これが無かった 1998年12月11日(金)
    // critical region end

    this->rotateHC(this->mPatchNormal);

    if(isDistributedRadiation == YES)
    {
	this->distributeRadiation(pRetRays);
    }else
    {  
	this->continuousRadiation(pRetRays);
    }
}


//------------------------------------------------------------
// main test
//------------------------------------------------------------
#ifdef TEST_HEMICUBE
void hemiCubeClass::
test()
{
    for(int x = 0; x < this->getHemres(); x++)
    {
	for(int y = 0; y < this->getHemres(); y++)
	{
	    cout << "[" << x << "][" << y << "]= " <<
	    this->mpFFval[WALL_CEILING_e][x][y] << endl;
	}
    }
}

void commMpi2_exit(int err_num)
{
    exit(err_num);
}

int main()
{
    IntVec3 PEID(0, 0, 0);
    const int hemres = 10;
    hemiCubeClass *pHemiCube = new hemiCubeClass(hemres, PEID);
    pHemiCube->test();
    return(0);
}
#endif				// #ifdef TEST_HEMICUBE
//
// g++ -I ../mpi2dataReader/ -D TEST_HEMICUBE hemicube.cc 
//
