/*
 * $Id: TriangleObject.java,v 1.15 1999/12/08 16:40:56 yamauchi Exp $
 *     (M\pi)^2 : Triangle Object
 *	Copyright 1999 By YAMAUCHI Hitoshi 山内 斉 
 *	@author YAMAUCHI Hitoshi 山内 斉
 *	
 * original source triangleObject.h, triangleObject.cc
 * 
 * 1999年7月29日(木) Primitive を書いた時には Interface を知らなかった
 * ので，おかしな構成になっている．今回クラスの構成を変更した．mpi2doc 
 * を参照
 *
 * 本来はパッケージも primitive などにして違うディレクトリに生成すべき
 * であろうが，とにかく今は Java へのそのままの変換を重要視する．
*/

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

import java.lang.String;
import java.lang.Math;
import java.io.Serializable;
import jp.ac.tohoku.is.archi.mpi2.util.Vector3;
import jp.ac.tohoku.is.archi.mpi2.util.IntVec3;
import jp.ac.tohoku.is.archi.mpi2.util.Assert;
import jp.ac.tohoku.is.archi.mpi2.util.Encodable;
import jp.ac.tohoku.is.archi.mpi2.util.ObjectDecoder;
import jp.ac.tohoku.is.archi.mpi2.sysparam.Mpi2Constants;
import jp.ac.tohoku.is.archi.mpi2.sysparam.Mpi2Enum;
import jp.ac.tohoku.is.archi.mpi2.sysparam.Config;


public final class TriangleObject extends ObjectPrimitive 
implements Mpi2Constants, Mpi2Enum, Config, Encodable
, Serializable
{
    // there is super class info

    // geometry info.
    private Vector3 [] mVertexPos;	// 頂点 [3]
    // (must be calculate)
    private Vector3 [] mEdge;		// エッジのベクトル(正規化しない) [3]
    private Vector3    mNrm;		// パッチ法線ベクトル
    private Vector3 [] mEdgeNrm; 	// エッジから内部を見るベクトル [3]
    private double     mPlaneConst; 	// 平面の定数
    private double  [] mEdgeConst; 	// パッチに垂直なエッジの平面定数 [3]
    private double     mArea;		// パッチ面積
    
    // radiosity value
    private Vector3    mNonRad; 	// 未放射ラジオシティ値    
    private Vector3    mRadiosity;	// ラジオシティ値
    private boolean    mIsInRadiationQ; // for lock (in radiation queue or not)

    // for shading 
    private Vector3 [] mIntensity;	// 各頂点の輝度 [3]
    private int	    [] mAddedIntensityNum; // 輝度を足した数 [3]


    private double  getPlaneConstant() 
    {
	return(this.mPlaneConst);
    }
    private Vector3 peekEdgeNormal(int i)
    {
	return(this.mEdgeNrm[i]);
    }
    private double  getEdgeConstant(int i) 
    {
	return(this.mEdgeConst[i]);
    }

    /** 
     * initialize inner object
     * 形状に関する情報を求める
     */
    private void calcGeometoryInfo()
    {
	//------------------------------
	// エッジのベクトル(正規化していない)
	//------------------------------
	this.mEdge = new Vector3[3];
	this.mEdge[0] = (this.mVertexPos[1]).minusNew(this.mVertexPos[0]);
	this.mEdge[1] = (this.mVertexPos[2]).minusNew(this.mVertexPos[1]);
	this.mEdge[2] = (this.mVertexPos[0]).minusNew(this.mVertexPos[2]);

	//------------------------------
	// パッチ法線ベクトル
	//------------------------------
	this.mNrm = this.mEdge[0].outPrdNew(this.mEdge[1]);
	this.mNrm.normalize();

	//------------------------------
	// エッジから外部を見るベクトル
	//------------------------------
	this.mEdgeNrm = new Vector3[3];
	this.mEdgeNrm[0] = (this.mNrm).outPrdNew(this.mEdge[0]);
	this.mEdgeNrm[0].normalize();
    
	this.mEdgeNrm[1] = this.mNrm.outPrdNew(this.mEdge[1]);
	this.mEdgeNrm[1].normalize();
	
	this.mEdgeNrm[2] = this.mNrm.outPrdNew(this.mEdge[2]);
	this.mEdgeNrm[2].normalize();
	
	//------------------------------
	// Nrm・P1 = - d: plane constant
	//------------------------------
	this.mPlaneConst = (this.mNrm).innPrd(this.mVertexPos[0]);
	
	//------------------------------
	// パッチ法線に垂直なエッジを含む平面の定数 N・v1 = d_e1
	//------------------------------
	this.mEdgeConst    = new double[3];
	this.mEdgeConst[0] = (this.mEdgeNrm[0]).innPrd(this.mVertexPos[0]);
	this.mEdgeConst[1] = (this.mEdgeNrm[1]).innPrd(this.mVertexPos[1]);
	this.mEdgeConst[2] = (this.mEdgeNrm[2]).innPrd(this.mVertexPos[2]);
	
	//------------------------------
	// 三角形の面積 ... tripatch.Area.tex 参照
	// S = \frac{1}{2} ab sin(\theta) 
	//   = \frac{1}{2} \sqrt{|a|^2 |b|^2 - (a \dot b)^2} 
	// (overflow が問題だが)
	//------------------------------
	double aSquare = (this.mEdge[0]).innPrd(this.mEdge[0]); // これは大きさ
	double bSquare = (this.mEdge[2]).innPrd(this.mEdge[2]); // で符号は良い
	double aDotb   = 
	    (this.mEdge[0]).innPrd((Vector3.ZERO).minusNew(this.mEdge[2]));
	this.mArea = 0.5 * Math.sqrt(aSquare * bSquare - aDotb * aDotb);
    }
    
    /**
     * Constructor
     */
    public TriangleObject(int objectID, int materialID, IntVec3 spcpos,
			  Vector3 v1, Vector3 v2, Vector3 v3,// geometry
			  Vector3 nonRad) 		     // radiosity
    {
	// super class
	// 	this.mObjectID  = rhs.mObjectID;	
	// 	this.mMaterial  = material;
	// 	this.mSpcPos    = spcpos;
	super(objectID, materialID, spcpos);
	
	// geometry
	this.mVertexPos    = new Vector3[3];
	this.mVertexPos[0] = v1;
	this.mVertexPos[1] = v2;	
	this.mVertexPos[2] = v3;
	this.calcGeometoryInfo();

	// radiosity
	this.mNonRad	= new Vector3(Vector3.ZERO);
	this.mRadiosity	= new Vector3(Vector3.ZERO);
	this.mIsInRadiationQ = false;
	this.addUnshotRadiosity(nonRad); // これによりエネルギ初期化

	// shading 
	this.mIntensity = new Vector3[3];     // 各頂点の輝度 [3]
	this.mAddedIntensityNum = new int[3]; // 輝度を足した数 [3]
	for(int i = 0; i < 3; i++)
	{
	    this.mIntensity[i] = new Vector3();
	    this.mAddedIntensityNum[i] = 0;
	}
    }

    /**
     * copy constructor 
     */
    public TriangleObject(TriangleObject rhs)
    {
	// super class
	// 	this.mObjectID  = rhs.mObjectID;	
	// 	this.mMaterial  = rhs.mMaterial;	// material は共有
	//	this.mSpcPos    = new Vector3(rhs.mSpcPos);
	super(rhs.getObjectID(), rhs.getMaterialID(), rhs.peekSpcPos());
	
	// geometry
	this.mVertexPos = new Vector3[3];
	this.mEdge     	= new Vector3[3];
	this.mEdgeNrm  	= new Vector3[3];
	this.mEdgeConst	= new double[3];
	int i;
	for(i = 0; i < 3; i++)
	{
	    this.mVertexPos[i]   	= new Vector3(rhs.mVertexPos[i]);
	    this.mEdge[i]     	= new Vector3(rhs.mEdge[i]);
	    this.mEdgeNrm[i]  	= new Vector3(rhs.mEdgeNrm[i]);
	    this.mEdgeConst[i]	= rhs.mEdgeConst[i];
	}

	this.mNrm		= new Vector3(rhs.mNrm);
	this.mPlaneConst	= rhs.mPlaneConst;
	this.mArea		= rhs.mArea;

	// radiosity value
	this.mNonRad		= new Vector3(rhs.mNonRad); 
	this.mRadiosity		= new Vector3(rhs.mRadiosity);
	this.mIsInRadiationQ	= rhs.mIsInRadiationQ;

	// for shading 
	this.mIntensity		= new Vector3[3];
	this.mAddedIntensityNum	= new int[3];
	for(i = 0; i < 3; i++)
	{
	    this.mIntensity[i]		= new Vector3(rhs.mIntensity[i]);
	    this.mAddedIntensityNum[i]	= rhs.mAddedIntensityNum[i];
	}
    }
    
    //------------------------------------------------------------
    // radiosity
    //------------------------------------------------------------
    /**
     * rad には既にフォームファクタが掛かっている
     * radiosity 未放射値を加える
     */
    public void addUnshotRadiosity(Vector3 rad)
    {
	this.mNonRad.plusHere(rad);
    }
    
    /**
     * 未放射の radiosity 値を radiosity 値に加え、各頂点の輝度を初期
     * 化する(計算終了時に利用する)   
     * 計算終了時に radiosity 値を update する (その時に未放射のエネル
     * ギを返す) 
     *   @return 最後まで放射しなかった分の放射エネルギを返す
     */
    public double flushUnshotRadiosityNInitVerInt()
    {
	// 最後まで放射しなかったエネルギ
	double nonRadEnergy = this.getNonRadEnergy(); 

	// 未放射の分をこのパッチの明るさとして加える
	(this.mRadiosity).plusHere(this.mNonRad);
	this.mNonRad.set(Vector3.ZERO);

	// 各頂点の初期値は radiosity そのもので加えた数は 1 
	this.mIntensity[0].set(this.mRadiosity);
	this.mIntensity[1].set(this.mRadiosity);
	this.mIntensity[2].set(this.mRadiosity);
	this.mAddedIntensityNum[0] = 1;
	this.mAddedIntensityNum[1] = 1;
	this.mAddedIntensityNum[2] = 1;

	return(nonRadEnergy);
    }

    /**
     * この patch の energy 1998年12月 9日(水)
     *   この計算式が妥当かどうかもまた昔から謎なんだよな
     *   (まあ，Vector3(R,G,B) * Area は良いとしても)
     *	Graphics Gems の II の Progressive Refinement 実装例ではこうし
     *	ているようだ． 1998年12月11日(金)
     */
    public double getNonRadEnergy()
    {
	double ret = this.mArea * (this.mNonRad.get(Vector3.X) +
				   this.mNonRad.get(Vector3.Y) +
				   this.mNonRad.get(Vector3.Z)); 
	return(ret);	
    }
    
    /**
     * radiation queue に何度も入れないように(lock & unlock)
     */
    public void setInRadiationQueue() 
    {	
	Assert.out(this.mIsInRadiationQ == false, "setInradiationQueue");
	this.mIsInRadiationQ = true; // lock
    }
    /**
     * unlock
     */
    public void unsetInRadiationQueue()
    {
	Assert.out(this.mIsInRadiationQ == true, 
		   "not in queue: unsetInRadiationQueue");
	this.mIsInRadiationQ = false; // unlock
    }
    /**
     * is locking now?
     */
    public boolean isInRadiationQueue()
    { 
	return(this.mIsInRadiationQ);
    }
    /**
     * 面積を返す
     */
    public double getArea() 
    { 
	return(this.mArea);     
    }
    
    /**
     * 重心 (HemiCube の放射中心位置を返す．その都度計算するようにする．
     */
    public Vector3 getNewGCenter()
    {
	// ([0] + [1] + [2]) / 3.0
	Vector3 g = (this.mVertexPos[0]).plusNew(this.mVertexPos[1]);
	g.plusHere(this.mVertexPos[2]);
	g.divScalerHere(3.0);
	return(g);
    }

    /** 
     *  パッチの未放射 radioosity 値を返す
     *	ラジオシティ値増加 + 未放射クリア
     *	ここまで atomic な操作(本来はキューから出すことも atomic だが、
     *	それは上位のルーチンで保つ) 
     */
    public Vector3 getNewClearNonRadAndUpdateRadiosity()
    {
	this.mRadiosity.plusHere(this.mNonRad); // ラジオシティ値増加
	Vector3 retNonRad = new Vector3(this.mNonRad);	// 未放射値保存
	this.mNonRad.set(0.0, 0.0, 0.0); // 未放射値クリア
	return(retNonRad);			
    }

    // const Vector3 getNonRad()  { return(this.mNonRad);	}

    /**
     * 各頂点の輝度を平均化する
     */
    public void calcVerIntAveVal()
    {
	for(int i = 0; i < 3; i++)
	{
	    // mIntensity[i] /= (double)(this.mAddedIntensityNum[i]);
	    this.mIntensity[i].
		divScalerHere((double)(this.mAddedIntensityNum[i]));
	}
    }	
    
    //------------------------------------------------------------
    // ObjectPrimitive abstract methods
    //------------------------------------------------------------
    /**
     * 物体までの距離(if ret PlusInfty =  not hit)
     */
    public double getDistance(Ray aRay, Vector3 hitposRef)
    {
	double distance = Double.MAX_VALUE; // 交差点までの距離          
	double tpDotRay = 0.0;	 // 内積の値                  
    
	// 三角形ポリゴンを含む平面との交差       
	tpDotRay = (this.peekNormal(null)).innPrd(aRay.peekDir());
	if(tpDotRay >= -(D_ERROR_RES))
	{				// 見えない(裏か交差しない)
	    return(Double.MAX_VALUE);
	}

	// 三角形ポリゴンを含む平面との交差点との距離
	distance = (this.getPlaneConstant() - 
		    (this.peekNormal(null)).innPrd(aRay.peekPos())) / tpDotRay;
	
	if(distance <= Mpi2Constants.D_ERROR_RES)
	{				// 見えない(後ろにある)
	    return(Double.MAX_VALUE);
	}
    
	// 交差点を求める
	// hitpos = (apRay.getDir()) * distance + (apRay.getPos());
	hitposRef.set(aRay.peekDir());
	hitposRef.mulScalerHere(distance);
	hitposRef.plusHere(aRay.peekPos());
    
	// エッジの検査(三角形の内部か?)
	for(int i = 0; i < 3; i++)
	{
	    double dTes = (this.peekEdgeNormal(i)).innPrd(hitposRef) - 
		this.getEdgeConstant(i);
	    if(dTes < 0.0)
	    {			// ここに来たら三角パッチの外にある = 外れ
		return(Double.MAX_VALUE);
	    }
	}
	// Hit
	return(distance); 
    }

    public Vector3 peekNormal(Vector3 hitpos) 
    {
	return(this.mNrm);	    
    }

    /**
     *  porigonID と頂点の位置があっていたら輝度を加える
     */
    public void addIntensityToVertex(VertexIntensity vint)
    {
	if(this.getObjectID() == vint.getPolygonID())
	{
	    for(int i = 0; i < 3; i++)
	    {
		if(this.mVertexPos[i].equals(vint.peekVertexPos()))
		{
		    this.mIntensity[i].plusHere(vint.peekIntensity());
		    this.mAddedIntensityNum[i]++;
		    break;		// 頂点は必ず違うので一つ合えば終了
		}
	    }
	}	
    }
    
    /**
     * ポリゴンで一様な Radiosity 値と頂点の位置情報を取り出す
     *	各輝度を取り出さないのは輝度の増加の時に各輝度は増加していくた
     *	めに互いに送り合うと正しい値にならない。
     *
     *	dest spcpos は正しくないものが入っている
     *	返り値は src (= この頂点のいた) 空間位置
     */
    public void getRadVer(VertexIntensity [] radiosityVertexInfo3)
    {
	final IntVec3 illegalSpcPos = new IntVec3(-1, -2, -3);

	for(int i = 0; i < 3; i++)
	{
	    Assert.out(this.mIntensity[i].get(Vector3.X) >= 0.0, "getRadVer");
	    radiosityVertexInfo3[i].set(this.getObjectID(),  
					new IntVec3(illegalSpcPos),
					this.mRadiosity, 
					this.mVertexPos[i]);
	}	
    }

    /**
     * 各頂点と各輝度を取り出す
     *	dest spcpos は正しくないものが入っている
     *	返り値は src (= この頂点のいた) 空間位置
     */
    private final static IntVec3 IllegalSpcPos = new IntVec3(-1, -2, -3);
    public void getVerIntensity(VertexIntensity [] intensityVertexInfo3)
    {
	for(int i = 0; i < 3; i++)
	{
	    Assert.out(this.mIntensity[i].get(Vector3.X) >= 0.0,
		       "getVerIntensity");
	    intensityVertexInfo3[i].set(this.getObjectID(),    
					new IntVec3(IllegalSpcPos),
					this.mIntensity[i], 
					this.mVertexPos[i]);
	}
    }

    /**
     * flat shading 
     */
    public Vector3 newFlatRadiosityColor(Vector3 hitPos)
    {
	return(new Vector3(this.mRadiosity));
    }
    
    /*------------------------------------------------------------
     * Gouroud shading 
     *-----------------------------------------------------------*/
    /**
     * 誤差によるエラーが発生した場合の値の出力
     */
    private void showvers(Vector3 hitpos, Vector3 hvec, Vector3 evec,
			  Vector3 top,    Vector3 side, Vector3 other,
			  int asX, int asY, double oeh, double t, double s)
    {
	String str = 
	    "hitpos        " + hitpos + "\n" + 
	    "to hitpos vec " + hvec   + "\n" + 
	    "edge vector   " + evec   + "\n" + 
	    "vertex top    " + top    + "\n" + 
	    "vertex side   " + side   + "\n" + 
	    "vertex other  " + other  + "\n" + 
	    "asX           " + asX    + "\n" + 
	    "asY           " + asY    + "\n" + 
	    "oeh           " + oeh    + "\n" + 
	    "t             " + t      + "\n" + 
	    "s             " + s;
	System.out.println(str);
    }


    /**
     * Gouroud shading calculation
     *
     * hit point の位置の輝度を返す
     * 	Gouroud shading
     *
     * たっきーが言うには三角形であってかつ三角形上に交点がある場合で、交
     * 点が top 頂点に一致しない場合ならばこの計算は必ず成功するはずだとい
     * うことである。(0 で割るような状態がでない)
     * (私もそう思うが証明ができないのでそうでない場合にも対応するように書
     * いた。)   
     *
     */
    public Vector3 newShadedRadiosityColor(Vector3 hitPos)
    {
	final double ERRTOL = 1.0e-20; // error tolerance 
	
	int sidever, otherver, topver, asX, asY; 
	otherver = sidever = asX = asY = -1; // 本来は不要だがjavacが文句を言う

	// find (asX, asY) plane
	for(topver = 0; topver < 3; topver++)
	{
	    sidever  = (topver + 1) % 3;
	    otherver = (topver + 2) % 3;
	    asX = asY = -1;
	    for(int i = 0; i < 3; i++)
	    {
		//// 三点が異なる点に見えるように X,Y を選択する
		if(Math.abs(this.mVertexPos[topver  ].get(i) - 
			    this.mVertexPos[sidever ].get(i)) > 1e-6)
		{			//// top, side の異なる軸であり、
		    for(int j = 0; j < 3; j++)
		    {
			if(i != j)	// i 軸は決定している
			{
			    if(Math.abs(this.mVertexPos[topver  ].get(j) -
					this.mVertexPos[otherver].get(j)) 
			       > 1e-6)
			    {
				asX = i;
				asY = j;
				i = 3; // 内ループ脱出 : goto で出るべきかな
				break;
			    }
			}
		    }
		}
	    }
	    if(asX >= 0)
	    {
		break;
	    }
	}
	if(asX < 0)
	{
	    if(SHADE_ERR_OUT)
	    {
		System.out.println("Warning! : can't solve this triangle. asX("
				   + asX + ") < 0) but ignored and average " +
				   "intensity.");
	    }
	    // Vector3 aveInt = (this.mIntensity[0] + this.mIntensity[1] +
	    //		      this.mIntensity[2]) / 3.0;  
	    Vector3 aveInt = this.mIntensity[0].plusNew(this.mIntensity[1]);
	    aveInt.plusHere(this.mIntensity[2]);
	    aveInt.divScalerHere(3.0);  

	    return(aveInt);
	}
    
	if((Math.abs(this.mVertexPos[topver].get(Vector3.X) - 
		     hitPos.get(Vector3.X)) < ERRTOL) && 
	   (Math.abs(this.mVertexPos[topver].get(Vector3.Y) - 
		     hitPos.get(Vector3.Y)) < ERRTOL) && 
	   (Math.abs(this.mVertexPos[topver].get(Vector3.Z) - 
		     hitPos.get(Vector3.Z)) < ERRTOL))
	{
	    // top と頂点が一致の時にはそのまま
	    return(this.mIntensity[topver]); 
	}
    
	// Vector3 hvec = hitPos - this.mVertexPos[topver];
	Vector3 hvec = hitPos.minusNew(this.mVertexPos[topver]);
	// Vector3 evec = this.mVertexPos[otherver] - this.mVertexPos[sidever];
	Vector3 evec = 
	    this.mVertexPos[otherver].minusNew(this.mVertexPos[sidever]);
	double ex    = evec.get(asX);
	double ey    = evec.get(asY);
	double hx    = hvec.get(asX);
	double hy    = hvec.get(asY);
	double Tx    = this.mVertexPos[topver].get(asX);  // P0x (top vertex)
	double Ty    = this.mVertexPos[topver].get(asY);  // P0y
	double Sx    = this.mVertexPos[sidever].get(asX); // P1x (side vertex)
	double Sy    = this.mVertexPos[sidever].get(asY); // P1y
	double oeh   = ex * hy - ey * hx; // t を求める時の分母
	
	if(Math.abs(oeh) < ERRTOL)		
	{				// この選択ではこうならないはず
	    if(SHADE_ERR_OUT)
	    {
		String s = "Error! : can't solve this triangle. oeh near 0\n" +
		    "ignored and average intensity (t, s is not calced)";
		System.out.println(s);
	    }
	    if(InDEBUG)
	    {
		this.showvers(hitPos, hvec, evec, 
			      this.mVertexPos[topver],
			      this.mVertexPos[sidever],
			      this.mVertexPos[otherver], 
			      asX, asY, oeh, -1.0, -1.0);
	    }
	    // Vector3 aveInt = (this.mIntensity[0] + this.mIntensity[1] +
	    //		      this.mIntensity[2]) / 3.0;  
	    Vector3 aveInt = this.mIntensity[0].plusNew(this.mIntensity[1]);
	    aveInt.plusHere(this.mIntensity[2]);
	    aveInt.divScalerHere(3.0);  

	    return(aveInt);
	    // commMpi2_exit(1);
	}
	double t = (Tx * hy - Ty * hx - Sx * hy + Sy * hx) / oeh;
	double s;
	if(Math.abs(hx) > Math.abs(hy))
	{
	    s = (Sx - Tx + t * ex) / hx;
	}else
	{
	    s = (Sy - Ty + t * ey) / hy; // hx が小さすぎる場合には hy で計算
	}
	// Vector3 intensityP = (1.0 - t) * this.mIntensity[sidever]
	// 	    + t * this.mIntensity[otherver];
	Vector3 intensityP = this.mIntensity[sidever].mulScalerNew(1.0 - t);
	intensityP.plusHere(this.mIntensity[otherver].mulScalerNew(t));

	// Vector3 intensityH =  (1.0 - (1.0 / s)) * this.mIntensity[topver]
	//    + (1.0 / s) * intensityP;
	Vector3 intensityH = 
	    this.mIntensity[topver].mulScalerNew(1.0 - (1.0 / s));
	intensityH.plusHere(intensityP.mulScalerNew(1.0 / s));
	
	if(InDEBUG)
	{
	    if((t < -0.0001) || (s < -0.0001))
	    {
		System.out.println("Error! : illegal t or s");
		this.showvers(hitPos, hvec, evec, 
			      this.mVertexPos[topver], 
			      this.mVertexPos[sidever],
			      this.mVertexPos[otherver], 
			      asX, asY, oeh, t, s);
	    }
	}

	// 輝度計算に失敗した場合には各頂点の平均値を輝度とする。
	if((intensityH.get(Vector3.X) < 0.0) || 
	   (intensityH.get(Vector3.Y) < 0.0) || 
	   (intensityH.get(Vector3.Z) < 0.0))
	{
	    if(SHADE_ERR_OUT)
	    {
		System.out.println("Warning! : calcRayTrIC.intensity " + 
				   intensityH + ": < 0.0");
	    }
	    // intensityH = (this.mIntensity[0] + this.mIntensity[1] +
	    // 		  this.mIntensity[2]) / 3.0;
	    intensityH = this.mIntensity[0].plusNew(this.mIntensity[1]);
	    intensityH.plusHere(this.mIntensity[2]);
	    intensityH.divScalerHere(3.0);

	    if(InDEBUG)
	    {
		this.showvers(hitPos, hvec, evec, 
			      this.mVertexPos[topver],
			      this.mVertexPos[sidever],  
			      this.mVertexPos[otherver], 
			      asX, asY, oeh, t, s);
	    }
	}
	return(intensityH);
    }

    
    /**
     * toString 
     */
    public String toString()
    {
	String s = 
	    " f " + this.getObjectID() + " " + this.mVertexPos[0] + " " 
	    +   this.mVertexPos[1] + " " + this.mVertexPos[2] + " "
	    +   this.peekSpcPos();
	return(s);
    }

    /**
     * toString 
     */
    public String toStringWithHead(String head)
    {
	String s = head + " # materialID = " + this.getMaterialID() + "\n"
	    + head + " f " + this.getObjectID() + " " + this.mVertexPos[0] +
	    " " + this.mVertexPos[1] + " " + this.mVertexPos[2] + " "  +
	    this.peekSpcPos();
	return(s);
    }

    /**
     * to String with type 
     */
    public String getMgfForm(int outputType)
    {
	String s;
	switch(outputType)
	{
	case NORMAL_ObjOut:
	    s = "f " + this.getObjectID() + " " + 
		this.mVertexPos[0] + " " + this.mVertexPos[1] + " " + 
		this.mVertexPos[2] + this.peekSpcPos();
	    return(s);
	
	case WITH_SHOT_RADOUT:
	    s = "F " + this.getObjectID() + " " + 
		this.mVertexPos[0] + " " + this.mVertexPos[1] + " " + 
		this.mVertexPos[2] + " " +
		this.peekSpcPos()    + " " + this.mRadiosity;
	    return(s);
	
	case WITH_UNSHOT_RADOUT:
	    s = "F " + this.getObjectID() + " " + 
		this.mVertexPos[0] + " " + this.mVertexPos[1] + " " + 
		this.mVertexPos[2] + " " +
		this.peekSpcPos()    + " " + this.mNonRad;
	    return(s);

	default:
	    throw new RuntimeException("Internal Error! : " +
				       "TriangleObject::getMgfForm : " +
				       "Illegal Output Type.");
	    // break;
	}	
    }

    public int who() 
    {
	return(OBJ_TRIANGLE);
    }

    /**
     * 正しい空間に存在しているかを調べる
     */
    public boolean isAtValidSpcPos(Vector3 partmin, Vector3 subSpcSize, 
				   int subdivnum)
    {
	// check spcpos num
	for(int i = 0; i < 3; i++)
	{
	    if((this.peekSpcPos().get(i) < 0) || 
	       (this.peekSpcPos().get(i) >= subdivnum))
	    {
		throw new RuntimeException("Error! :f(Triangle): " +
					   "spcpos is out of define space.");
		// return(false);
	    }
	}

	// check vertex range
	double [] min = new double[3];
	double [] max = new double[3];
	for(int i = 0; i < 3; i++)
	{
	    min[i] = partmin.get(i) + 
		(double)(this.peekSpcPos().get(i))  * subSpcSize.get(i);
	    max[i] = partmin.get(i) + 
		(double)(this.peekSpcPos().get(i) + 1) * subSpcSize.get(i);
	}
	final double err_tolerance = subSpcSize.get(0) * 1e-3; // 1 % までは OK
	
	for(int ver = 0; ver < 3; ver++)
	{
	    for(int i = 0; i < 3; i++)
	    {
		if((this.mVertexPos[ver].get(i) < (min[i] - err_tolerance)) ||
		   (this.mVertexPos[ver].get(i) > (max[i] + err_tolerance)))
		{
		    final String [] s = { "x", "y", "z", };
		    System.out.println("Error! :f(triangle): vertex["
				       + ver + "](" + s[i] + ") is not "
				       + "in legal spcpos");
		    System.out.println("It must be in [" + min[i] + 
				       ", " + max[i] + "]. (tolerance= "
				       + err_tolerance + ")");
		    return(false);
		}
	    }
	}
	return(true);
    }

    /*
     * 通信用のタグを返す
     */
    public static int getTAG() 
    { 
	return(Mpi2Enum.TAG_TRIANGLE_OBJ);	
    }

    /**
     * For Encodable interface (and ObjectDecoder object).
     */
    final static int vectorSize = Vector3.ZERO.sizeInDouble();
    final static int intvecSize = IntVec3.ZERO.sizeInDouble();
    final static int doubleSize =
    1 				// material ID
    + intvecSize		// spcpos size
    + 3 * vectorSize 		// 3 頂点
    + vectorSize		// non Rad
    + 1; 			// Object ID
    public int sizeInDouble() 
    {
	return doubleSize;
    }
    public void encodeInDouble(double[] array, int offset) 
    {
	// TriangleObject を construct するのに必要な分のみ送信する
	array[offset] = (double)this.getObjectID();
	offset++;

	array[offset] = (double)(this.getMaterialID());     
	offset++;
	
	(this.peekSpcPos()).encodeInDouble(array, offset);
	offset += intvecSize;
    
	this.mVertexPos[0].encodeInDouble(array, offset);
	offset += vectorSize;
	this.mVertexPos[1].encodeInDouble(array, offset);
	offset += vectorSize;
	this.mVertexPos[2].encodeInDouble(array, offset);
	offset += vectorSize;

	this.mNonRad.encodeInDouble(array, offset);
	offset += vectorSize;

  }
  static class TriangleObjectDecoder implements ObjectDecoder {
    public int sizeInDouble() {
	return doubleSize;
    }
    public Object decodeFromDouble(double[] array, int offset) {

// 	for(int i = 0; i < array.length; i++)
// 	{
// 	    System.out.println("DEBUG Decode " + array[i]);
// 	}

	ObjectDecoder vec3  = Vector3.getObjectDecoder();
	ObjectDecoder ivec3 = IntVec3.getObjectDecoder();

	int objectID =   (int)(array[offset]); 
	offset++;

	int materialID = (int)(array[offset]);
	offset++;

	IntVec3 spcpos = (IntVec3)(ivec3.decodeFromDouble(array, offset));
	offset += intvecSize;
    
	Vector3 v1 = (Vector3)(vec3.decodeFromDouble(array, offset));
	offset += vectorSize;
    
	Vector3 v2 = (Vector3)(vec3.decodeFromDouble(array, offset));
	offset += vectorSize;
    
	Vector3 v3 = (Vector3)(vec3.decodeFromDouble(array, offset));
	offset += vectorSize;

	Vector3 nonrad = (Vector3)(vec3.decodeFromDouble(array, offset));
	offset += vectorSize;

// 	System.out.println("TriObj::decode mID = " + materialID + 
// 			   ", spcpos " + spcpos + "\nv1 " + v1 + 
// 			   ", v2 " + v2 + ", v3 " + v3 + 
// 			   "\nnonrad " + nonrad + ", objID " + objectID);

	return(new TriangleObject(objectID, materialID, spcpos, 
				  v1, v2, v3, nonrad));
    }
  }
  private final static ObjectDecoder odInstance
    = new TriangleObjectDecoder();
  public static ObjectDecoder getObjectDecoder() {
    return odInstance;
  }
}

