/*
 * $Id: HemiCube.java,v 1.5 2000/06/15 13:55:51 yamauchi Exp $
 *     (M\pi)^2 : HemiCube 
 *	Copyright 1999 By YAMAUCHI Hitoshi 山内 斉 
 *	@author YAMAUCHI Hitoshi 山内 斉
 *	
 * original source hemicube.h hemicube.cc
 *
 *	Hemi-Cube の実装
 * 
 * 1999年8月 4日(水)
 */

package jp.ac.tohoku.is.archi.mpi2.radiosity;

import  jp.ac.tohoku.is.archi.mpi2.util.IntVec3;
import  jp.ac.tohoku.is.archi.mpi2.util.Vector3;
import  jp.ac.tohoku.is.archi.mpi2.util.Assert;
import  jp.ac.tohoku.is.archi.mpi2.obj.ObjectPrimitive;
import  jp.ac.tohoku.is.archi.mpi2.obj.Ray;
import  jp.ac.tohoku.is.archi.mpi2.obj.TriangleObject;
import  jp.ac.tohoku.is.archi.mpi2.sysparam.Mpi2Constants;

/**
 * Hemi Cube
 * 今回の実装では、
 * 	1.HemiCube をある hemres で作成し、patch の Normal Vector を用
 * 	  いて初期化する。 
 * 	2.その後は、patch の Normal vector を与えて次の初期化を行うこ
 *	  とで再利用する。
 */

/**
 * Hemi-Cube
 * 	(この Hemi-cube は各パッチのために動的に作成、削除される)
 */
public final class HemiCube 
implements Mpi2Constants 
{
    public final static int WALL_CEILING 	= 0; // 天井
    public final static int WALL_XPLUS		= 1; // 壁
    public final static int WALL_XMINUS		= 2;
    public final static int WALL_YPLUS		= 3;
    public final static int WALL_YMINUS		= 4;
    public final static int HEMICUBE_PLANE_MAX	= 5;
    
    // set in getMemory()
    private double   [][][]mFFval;	// FormFacterの値 壁のものは1つで良い
    private Vector3  [][][]mFFEOrgPos;	// それぞれのエレメントの回転前の位置
    private Vector3  [][][]mFFEPos;	// 回転後のそれぞれのエレメントの位置

    // hemi cube info
    private double   mElemArea;		// それぞれのエレメントの面積
    private int	     mHemres;		// Hemi-cube resolution

    private IntVec3  mPEID;		// 担当 PE ID
    
    // raiation patch informatio
    private Vector3  mPatchNormal; 	// 放射パッチの放射方向法線
    // private Vector3  mGravityCenter;	// 放射パッチの重心位置
    private double   mPatchArea;	// 放射パッチの面積
    private IntVec3  mSpcpos;		// 放射パッチの存在する副空間指標
    private TriangleObject mPatch;	// 放射パッチ
    private Vector3  mLockingNotYetRad;	// 未放射値のロック用


    /**
     * get hemi-cube resolution
     */
    private int getHemres()	 	
    {
	return(this.mHemres);	
    }

    /**
     * Hemi-Cube 用のメモリ取得 : 多次元配列は性能を悪化させるかも FIXME
     */ 
    private void getMemory()
    {
	Assert.out((this.getHemres() > 1)||(this.getHemres() % 2 == 0),
		   "HemiCube is already initialized"); // inited?
    
	//------------------------------
	// getting form factor values area
	// (this.mpFFcal[壁の種類][高さ][幅])
	//------------------------------
	this.mFFval = new double[2][][]; // 天井と 1 つの壁
	//     天井
	this.mFFval[WALL_CEILING] = new double [this.getHemres()][];  
	for(int i = 0; i < this.getHemres(); i++)
	{
	    this.mFFval[WALL_CEILING][i] = new double[this.getHemres()];
	}
	//     壁を1つ
	// 壁の高さは上面の幅の半分
	this.mFFval[WALL_XPLUS] = new double [this.getHemres() / 2][];
	for(int i = 0; i < (this.getHemres() / 2); i++) // 壁の高さは幅の半分
	{
	    // 壁の幅は同じ
	    this.mFFval[WALL_XPLUS][i] = new double[this.getHemres()];
	}
    
	//------------------------------
	// getting Vector3 values    
	// (this.mFFEOrgPos[壁の種類][高さ][幅])
	//------------------------------
	this.mFFEOrgPos = new Vector3 [HEMICUBE_PLANE_MAX][][];
	this.mFFEPos    = new Vector3 [HEMICUBE_PLANE_MAX][][];
	
	for(int w = WALL_CEILING; w < HEMICUBE_PLANE_MAX; w++)
	{
	    int maxHeight = 0;		// 一辺の高さ
	    if(WALL_CEILING == w)		// 天井は別
	    {
		maxHeight = this.getHemres();
	    }else
	    {
		maxHeight = this.getHemres() / 2; // 天井以外
	    }
	    this.mFFEOrgPos[w] = new Vector3 [maxHeight][]; 
	    this.mFFEPos[w]    = new Vector3 [maxHeight][]; 
	    
	    // h (height) は mHemres あるいは mHemres/2
	    for(int h = 0; h < maxHeight; h++)  // ここも maxHeight まで
	    {
		this.mFFEOrgPos[w][h] = new Vector3[this.getHemres()];
		this.mFFEPos[w][h]    = new Vector3[this.getHemres()];
	    }
	}
    }

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

	// 天井部分 
	int ix = 0;		// for(ix = 0; ; ix++)
	for(double x =  dXMIN + dELEM_BIAS; x <  dXMAX; x += dELEM_EDGE)
	{
	    int iy = 0;		// for(iy = 0; ; iy++)
	    for(double y = dYMIN + dELEM_BIAS; y < dYMAX; y += dELEM_EDGE)
	    {
		// assert(iy < this.getHemres());
		double dR_sqr = x * x + y * y + 1.0;
		dR_sqr *= dR_sqr;
		this.mFFval[WALL_CEILING][ix][iy] = 
		    dElemArea / (Math.PI * dR_sqr); 
		this.mFFEOrgPos[WALL_CEILING][ix][iy] = 
		    new Vector3(x, y, dZMAX);
		iy++;
	    }
	    ix++;
	}

	//----------------------------------------------------------
	//  横板の部分 xp(ulus), xm(inus), yp(ulus), ym(inus)のうち
	// 一つがわかればあとは同じ
	//----------------------------------------------------------
	int iz = 0;
	for(double z =  dZMIN + dELEM_BIAS; z < dZMAX; z += dELEM_EDGE)
	{
	    // assert(iz < (this.getHemres() / 2));
	    int iy = 0;
	    for(double y = dYMIN + dELEM_BIAS; y < dYMAX; y += dELEM_EDGE)
	    {
		// assert(iy < this.getHemres());
		double dR_sqr = 1.0 + y * y + z * z;
		dR_sqr *= dR_sqr;
		this.mFFval[WALL_XPLUS][iz][iy] = 
		    (dElemArea * z) / (Math.PI * dR_sqr);
		this.mFFEOrgPos[WALL_XPLUS][iz][iy] = new Vector3(dXMAX, y, z);
		this.mFFEOrgPos[WALL_XMINUS][iz][iy]= new Vector3(dXMIN, y, z);
		iy++;
	    }
	    iz++;
	}

	iz = 0;
	// z は高さ(hight)
	for(double z = dZMIN + dELEM_BIAS; z < dZMAX; z += dELEM_EDGE)
	{
	    // assert(iz < (this.getHemres() / 2));
	    ix = 0;		// x は width
	    for(double x = dXMIN + dELEM_BIAS; x < dXMAX; x += dELEM_EDGE)
	    {
		// assert(ix < this.getHemres());
		this.mFFEOrgPos[WALL_YPLUS][iz][ix] = new Vector3(x, dYMAX, z);
		this.mFFEOrgPos[WALL_YMINUS][iz][ix]= new Vector3(x, dYMIN, z);
		ix++;
	    }
	    iz++;
	}
    }
    
    /**
     * rotate hemi cube (by Eular rotation)
     * 	(Maybe, this code includes my oldest code in mpi2)
     * 
     * 	@param aPatchNrm : パッチの法線方向 こちらを HemiCube が向く
     */
    private void rotateHC(Vector3 aPatchNrm)
    {
	final double D_NON_DEF = -2.0;  // ありえない値
	// double a[3][3];		/* オイラー変換マトリクス  */
	double a00, a01, a02, a10, a11, a12, a20, a21, a22;
	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 = Math.sqrt(1.0 - 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.out((-1.0 <= dCtheta) && (dCtheta <= 1.0), "illegal Ctheta");
	Assert.out((-1.0 <= dStheta) && (dStheta <= 1.0), "illegal Stheta");
	Assert.out((-1.0 <= dCphi)   && (dCphi <= 1.0)  , "illegal Cphi");
	Assert.out((-1.0 <= dSphi)   && (dSphi <= 1.0)  , "illegal Sphi");
	
	a00 =   dCkai   * dCphi - dSkai * dCtheta * dSphi;
	a10 = - dSkai   * dCphi - dCkai * dCtheta * dSphi;
	a20 =   dStheta * dSphi;
	
	a01 =   dCkai   * dSphi + dSkai * dCtheta * dCphi;
	a11 = - dSkai   * dSphi + dCkai * dCtheta * dCphi;
	a21 = - dStheta * dCphi;
	
	a02 =   dSkai * dStheta;
	a12 =   dCkai * dStheta;
	a22 =   dCtheta;
	
	int maxHeight;
	for(int hcPlane =WALL_CEILING; hcPlane < HEMICUBE_PLANE_MAX; hcPlane++)
	{
	    if(hcPlane == WALL_CEILING)
	    {
		maxHeight = this.getHemres();
	    }else
	    {
		maxHeight = this.getHemres() / 2; // 天井以外は高さ半分
	    }
	    for(int height = 0; height < maxHeight; height++)
	    {
		for(int width = 0; width < this.getHemres(); width++)
		{
		    Assert.out(isHemiCubePosXYZRight(hcPlane, height, width),
			       "illegal HC Pos"); 
		    Vector3 orgposRef = 
			this.mFFEOrgPos[hcPlane][height][width];
		    
		    double x = orgposRef.get(Vector3.X);
		    double y = orgposRef.get(Vector3.Y);
		    double z = orgposRef.get(Vector3.Z);
		    
		    double xt = a00 * x + a01 * y + a02 * z; 
		    double yt = a10 * x + a11 * y + a12 * z;
		    double zt = a20 * x + a21 * y + a22 * z;
		    
		    this.mFFEPos[hcPlane][height][width] 
			= new Vector3(xt, yt, zt);
		}
	    }
	}
    }
    
    /**
     * generate rays using by Hemi-Cube : distributed method
     * 	ray の放射面を次々変化させて同一の(に近い)方向に連続して ray 
     *	を放射しないようにする
     *	load balancing の効果があると考えられる方法  
     */
    private void distributeRadiation(Ray [] retRays)
    {
	int ridx = 0;		// index for return ray 
	int halfHemres = (this.getHemres() / 2);
	for(int h = 0; h < halfHemres; h++)
	{
	    for(int w = 0; w < this.getHemres(); w++)	    
	    {
		// 天井は2本 : h * w と h + halfHemres * w の ray 
		retRays[ridx] = this.newOneRay(WALL_CEILING, h, w);
		ridx++;
		retRays[ridx] = this.newOneRay(WALL_CEILING, h +halfHemres, w);
		ridx++;

		// 天井でない部分
		for(int plane = WALL_XPLUS;plane < HEMICUBE_PLANE_MAX; plane++)
		{ 
		    retRays[ridx] = this.newOneRay(plane, h, w);
		    ridx++;
		}
	    }
	}
    }

    /**
     * generate rays using by Hemi-Cube : continuous method
     *	ray の放射面を変化させず、同一の(に近い)方向に連続して ray 
     *	を放射する
     *	load balancing の効果が薄いと考えられる方法  
     */
    private void continuousRadiation(Ray [] retRays)
    {
	int ridx = 0;		// index for return ray 
	for(int plane = WALL_CEILING; plane < HEMICUBE_PLANE_MAX; plane++)
	{ 
	    //(plane==WALL_CEILING)?(this.getHemres()):(this.getHemres()/2);
	    int maxHeight;
	    if(plane == WALL_CEILING)
	    {
		maxHeight = this.getHemres();
	    }else
	    {
		maxHeight = this.getHemres() / 2;
	    }
	    for(int h = 0; h < maxHeight; h++)
	    {
		for(int w = 0; w < this.getHemres(); w++)	    
		{
		    retRays[ridx] = this.newOneRay(plane, h, w);
		    ridx++;
		}
	    }
	}
    }

    /**
     * generate rays using by rotetaed hemi-cube
     *	Ray の運ぶ delta radiosity は \delta F_{ij} * PatchArea
     */
    private Ray newOneRay(int plane, int height, int width)
    {
	Assert.out(isHemiCubePosXYZRight(plane, height, width),
		   "illegal position is indicated : newOneRay");

	Vector3 newPos = this.mPatch.getNewGCenter();
	Vector3 newDir = new Vector3(this.mFFEPos[plane][height][width]);
	newDir.normalize();	// レイのベクトルの正規化
	Vector3 newRadiosity = new Vector3(this.mLockingNotYetRad);
    
	// レイにはフォームファクタと放射パッチの面積が掛かっている
	int ffvalPlane = plane;
	if(plane > WALL_XPLUS)
	{
	    ffvalPlane = WALL_XPLUS; // 側面の FF val は同じなので1つにしてある
	}

	// Rad = not yet radiated radiosity value (locked)
	// Rad = Rad * ((FF_{ij}[plane][h][w]) * patchArea)
	double ff_patcharea = 
	    this.mFFval[ffvalPlane][height][width] * this.mPatchArea;
	newRadiosity.mulScalerHere(ff_patcharea);
    
	// レイ生産 newray
	Ray newRay = Ray.newForRadiosity(newPos, newDir, newRadiosity,
					 this.mPatch,
					 new IntVec3(this.mSpcpos));
	return(newRay);
    }

    /**
     * エラーチェック
     */
    private boolean isHemiCubePosXYZRight(int plane, int height,  int width) 
    {
	if((plane < 0) || (plane >= HEMICUBE_PLANE_MAX))
	{
	    throw new RuntimeException("Error! : illegal plane = " + plane
				       + " at MHC position");
	    // return(false);
	}
	
	int MaxHeight = 0;
	if(plane != WALL_CEILING)
	{
	    MaxHeight = this.getHemres() / 2;
	}else
	{
	    MaxHeight = this.getHemres();
	}
	
	if((height < 0) || (height >= MaxHeight)) // 高さは変化する
	{
	    throw new RuntimeException("Error!: illegal height = " + height
				       + " at MHC position");
	    // return(false);
	}
	
	if((width < 0) || (width >= this.getHemres())) // 幅は変化なし
	{
	    throw new RuntimeException("Error!: illegal hemres = " + 
				       this.getHemres() + " width = "
				       + width + " at MHC position");
	    // return(false);	
	}
	return(true);
    }
    
    /**
     * construct
     */
    public HemiCube(int aHemres, IntVec3 PEID)
    {
	// host は hemi cube を持たない
	Assert.out(PEID.get(IntVec3.X) >= 0, "Host has no hemi-cube."); 
	
	if((aHemres < 2) || (aHemres % 2 != 0))
	{
	    String s = "Error! : illegal hemres value. hemres must be > 1 " 
		+ "and % 2 == 0, hemres = " + aHemres;
	    throw new RuntimeException(s);
	    // commMpi2_exit(1);
	}
	
	this.mFFval		= null;
	this.mFFEOrgPos		= null;
	this.mFFEPos		= null;
	
	this.mElemArea		= -1.0;
	this.mHemres		= aHemres;
	
	this.mPEID		= PEID;	
	
	this.mPatchNormal 	= null;
	// this.mGravityCenter	= null;
	this.mPatchArea		= 0.0;
	this.mSpcpos		= null;
	this.mPatch		= null;
	this.mLockingNotYetRad	= null;
	
	// memory allocation 
	this.getMemory();

	// System.out.println("DEBUG : " + this);
	
	// initialize master hemi-cube
	this.setFormFactor();

	// System.out.println("DEBUG : " + this);
    }

    
    /**
     * 発生する ray 数を返す : 1 ray/pixel 
     * 	発生 ray 数 = 壁 * 4 + 天井(壁の大きさは天井の半分) = 3 * 天井 
     */
    public int getGenRayNum()  
    { 
	return(3 * this.mHemres * this.mHemres); 
    }
    
    /**
     * 一気に一つの HemiCube 分の ray を発生させ，pRetRays にポインタ
     * をコピーして(ポインタの先は new されている)返す． 
     * pRetRays の領域は呼び出し側が確保して渡すこと．
     *	@param  radpatch 	放射を行なったパッチ
     *  @param  isDistributedRadiation 	ray 発生時の load balance
     *  @param  retRays		作成される ray の配列
     */
    public void genRays(TriangleObject radpatch,
			boolean isDistributedRadiation,
			Ray []  retRays)
    {
	// must normalized normal
	Assert.out(Math.abs((radpatch.peekNormal(null)).getNorm() - 1.0) < 
		   D_ERROR_RES,
		   "Error! : genRays : patch normal is not normalized.");
	Assert.out((retRays != null) && (retRays.length > 0), 
		   "return [] ray is not initialized");

	// patch はこの空間に常に存在するので reference で OK 
	
	this.mPatchNormal	= radpatch.peekNormal(null);
	// this.mGravityCenter	= radpatch.getNewGCenter();
	this.mPatchArea		= radpatch.getArea();
	
	// Patch には面積があるはず
	Assert.out(this.mPatchArea > 0.0, "Patch must have area");
	
	this.mSpcpos		= radpatch.peekSpcPos();
	Assert.out((this.mSpcpos.get(IntVec3.X) >= 0) && 
		   (this.mSpcpos.get(IntVec3.Y) >= 0) && 
		   (this.mSpcpos.get(IntVec3.Z) >= 0),
		   "Illegal spcpos of patch"); 
	this.mPatch = radpatch;
	
	//-- 放射中に他から radiosity値を受けても良いように放射値を lock する
	//-- コードは書いたが、現在、multi thread でないので lock しなくても
	//-- 安全である。いつか必要になるかもしれない。(キューから取る部分も 
	//-- atomic にしていないので、本当に MT になったら安全かはわからない) 
	//-- 7 Sept. 1996 Hitoshi Yamauchi 
	// critical region begin
	this.mLockingNotYetRad = 
	    radpatch.getNewClearNonRadAndUpdateRadiosity();
	radpatch.unsetInRadiationQueue(); //-- これが無かった1998年12月11日(金)
	// critical region end
	
	this.rotateHC(this.mPatchNormal);
	
	if(isDistributedRadiation == true)
	{
	    this.distributeRadiation(retRays);
	}else
	{  
	    this.continuousRadiation(retRays);
	}
    }

    public String toString()
    {
	String s = "----- HemiCube -----\n" +
	    "FormFactor value array [" + this.mFFval.length + "][" + 
	    this.mFFval[0].length + "][" + this.mFFval[0][0].length + "]\n" + 
	    "FF Elem orgpos   array [" + this.mFFEOrgPos.length + "][" + 
	    this.mFFEOrgPos[0].length + "][" + this.mFFEOrgPos[0][0].length +
	    "]\n" + 
	    "FF Elem pos      array [" + this.mFFEPos.length + "][" + 
	    this.mFFEPos[0].length + "][" + this.mFFEPos[0][0].length +"]\n" + 
	    "FF Elem Area " + this.mElemArea + "\n" + 
	    "hemres " + this.mHemres + "\n" + 
	    "PE id  " + mPEID + "\n" + 
	    "patch normal " + this.mPatchNormal +"\n" + 
	    // mGravityCenter
	    "patch area " + this.mPatchArea + "\n" + 
	    "space position " + this.mSpcpos + "\n" + 
	    // mPatch;	
	    "lock " + this.mLockingNotYetRad;
	return(s);
    }
    
    /**
     * for test of FormFactor value
     */
    public static void main(String [] args)
    {
	IntVec3 PEID = new IntVec3(0, 0, 0);
	int hemres = 10;
	HemiCube hc = new HemiCube(hemres, PEID);

	for(int x = 0; x < hc.getHemres(); x++)
	{
	    for(int y = 0; y < hc.getHemres(); y++)
	    {
		System.out.println("[" + x + "][" + y + "]= " +
				   hc.mFFval[WALL_CEILING][x][y]);
	    }
	}
    }
}
