//------------------------------------------------------------
// $Id: PERayTracing.cc,v 1.1.1.1 1999/11/25 06:26:09 yamauchi Exp $ 
//------------------------------------------------------------
//	PERayTracing.cc : Processing Element Ray-Tracing stage
//	1999年1月25日(月) PE.cc より分離
//	Copyright (C) 1996ー1999 YAMAUCHI Hitoshi 山内 斉
//------------------------------------------------------------
#include "PE.h"
#include "mpi2communication.h"
#include "Option.h"
#include "volumeFuncs.h"
#include "mpi2_funcs.h"
#include "ProfileMon.h"		// for program marker array 

//------------------------------------------------------------    
// ray tracing sub functions
//------------------------------------------------------------    
// ray tracing のための初期化
//------------------------------
void PEClass::
initForRayTracing(void)
{
    // 初期状態では ray のキューには何も入っていてはいけないので調べる
    // (たとえば，radiosity の計算時の残りなどはあってはいけない．)
    assert(this->mRayPtrQueue.empty() != Q_NOT_EMPTY);

    this->mIsMultiPassStageOver = NO;
    if(this->mSysInfo.isRayTracingCalc() == NO)
    {
	// ray-tracing の計算をしないという場合の処理は正しくはどうす
	// れば良いのか決めていない．ここでは適当なパラメータを再設定
	// しているが，不正な状態にならないようにするという位の意味で
	// ある．ここを通る場合は単に exit しても良いかもしれない．Yam
	this->mViewData.setMaxRayRefParam(DEFAULT_MAX_REFLECTION_RAY_NUM,
					  DEFAULT_MIN_REFLECTION_COEFF_NORM2);
	this->mViewData.setDistributedRayNum(1);
	this->mViewData.setFrustumParam(DEFAULT_FRUSTUM_RES);
	if(Option::getInstance()->getVerboseMode() == YES)
	{
	    cout << genHd(VERBOSEHEAD, this->getID()) << 
	    "ray-tracing calculation is off. change view parameter." << endl;
	    this->mViewData.print(genHd(VERBOSEHEAD, this->getID()));
	}
    }

    // initialize Network Interface unit
    this->mNIUnit.initRayBuff(this->mSysInfo.getTotalPENum(),
			      this->mSysInfo.getProcRayTrAtOnce(), 
			      this->mSysInfo.getSendInt2FBAtOnce(),
			      this->getMyRank(),
			      SysStat::getInstance()->getHostRank(),
			      &(this->mRayPtrQueue));
    
    // initialize First Ray Generation Unit
    this->mRayTrFRGUnit.init(this->mSysInfo.getFirstRaysGenerationMethod(),
			     this->mViewData, this->mSpcParam, this->mID,
			     this->mSysInfo.getTotalPENum(),
			     this->getMyRank());

    if(this->mRayTrFRGUnit.canGenerateNextRay() == YES)
    {
	// 初期レイのセット: 視点が定義空間内の場合などは放射しないこ
	// とが明らかな場合があり、その場合にはレイの作成はできない。
	this->shotSomeFRGRays();
    }
}

//------------------------------
//   ray の処理 複数本のレイを処理する
//------------------------------
void PEClass::
processingRayTraces(void)
{
    // この関数が呼ばれる時には以下の条件を満たしている
    //     ・キューに一本はレイが存在している
    assert(this->mRayPtrQueue.empty()  == Q_NOT_EMPTY);
    
    // 以下の状況の時にレイを一本取り出してその処理を行なう。
    // 以下の条件はこの関数にエントリした時には満たされているので 
    // do{}while;型のループとなる。 
    // 	   ・レイがキューに存在する
    // 	   ・レイを処理した数が getProcRayTrAtOnce 以下
    int i = 0; // レイを処理した数
    static Ray *pRay;
    do {
	assert(this->mRayPtrQueue.length() > 0);
	pRay = this->mRayPtrQueue.deq();
	this->processingOneRayTrace(pRay);  
	i++;
    }while((this->mRayPtrQueue.empty()  == Q_NOT_EMPTY) && 
	   (i < this->mSysInfo.getProcRayTrAtOnce()));
    
    // Statistics.addRayTraceProcsAtOnce(i); FIXME この統計はとるか?
}


//------------------------------
// RP Block の処理を行う。輝度計算の処理が必要なら行い、結果を Host へ
// 送信する。必要ならば他の PE への送信も行う
//   これは processingOneRadiosity とかなり共通部分があるが Hit 時の 
//   ray の処理が異る  
//------------------------------
void PEClass::
processingOneRayTrace(Ray *pRay)
{
    // 初期光分散の場合には ray の存在する空間を担当していない PE が 
    // DDA  の計算を行う可能性がある．しかし．これは FRG で ray を生成
    // した直後にその PE で DDA の計算を行って，物体の存在する空間に 
    // ray を送信することで回避すべき．したがって，この ray のいる空間
    // はこの PE の担当空間でなくてはならない 
    assert(this->isRayInValidSpace(pRay, 2) == YES);
    objBaseClass *pHitObj = this->calcInterSectionWithRayAndObj(pRay);
    
    // 輝度計算、二次レイ発生、次空間探索、送信
    if(pHitObj != NULL)
    {
	// 物体に ray が交差した
	materialClass *pMat = this->mMaterialSet.get(pHitObj->getMaterialID());

	// 物体の輝度を計算し、ray にセット
	this->calcNsetIntensityRayTr(pRay, pHitObj);

	// 更に反射屈折を行うか : 現在は ray は IC->SRG の流れになって
	// いる．
	if(this->newSecondaryRayTracingRays(pRay, pMat, pHitObj) == NO)
	{
	    // 反射も屈折の ray も[どちらも]生成されなかった。これまで
	    // 計算した輝度を Frame Buffer へ送信する。片方でも生成さ
	    // れた場合にはこれまでの輝度はどちらかの ray に託されてい
	    // る。それが最終的に消える時に Frame Buffer へ送信する。

	    this->insertIntensityIntoBuffer(pRay);
	}
	delete pRay;		// deleteRay!
	pRay = NULL;
	// 交差したので次空間へは行かない(2次 レイが後を受け継ぐ)  
    }else
    {
	// 次空間探索 : 空間を外れれば ray 消滅
	this->calcRayTrNextSpace(pRay);
    }	
}

//------------------------------
// この PE の担当する部分空間でのレイと物体の交差判定
//------------------------------
objBaseClass *PEClass::
calcInterSectionWithRayAndObj(Ray *pRay)
{
    // ray のいる空間はこの PE の担当空間か?
    if(this->isDealSpace(pRay->getSpcPos()) != YES)
    {
	pRay->print("DEBUG:");	// DEBUG
    }
    assert(this->isDealSpace(pRay->getSpcPos()) == YES);

    //------------------------------
    // 物体との交差判定 
    //------------------------------
    objectsArrayClass *pObjArray = this->mObjectSet.get(pRay->getSpcPos());
    int objNumInThisSpc = pObjArray->getLength();
    int i;
    double distanceBetweenRayNObject = PlusInfty;
    static Vector3 hitPoint;
    objBaseClass *pHitObj = NULL;
    for(i = 0; i < objNumInThisSpc; i++)
    {
	objBaseClass *pObj = pObjArray->get(i);
	if(pObj != pRay->getRadObj()) // ray の発生した面とは交差判定しない
	{
	    double tmpDist = pObj->getDistance(pRay, &hitPoint);
	    assert(tmpDist >= 0.0);

	    // 球のような物体の場合にはこの部分空間に存在しない部分で 
	    // hit する可能性がある．その場合を除く 
	    switch(pObj->who())
	    {
	    case TRIANGLE_OBJ_E:	
		break;
	    case SPHERE_OBJ_E:
		if(this->checkHitPosIsInThisSpcPos(hitPoint, pRay->getSpcPos())
		   == NO)
		{
		    tmpDist = PlusInfty;
		}
		break;
	    case NULL_OBJ_E:		
	    default:
		cout << "Internal Error! : " << endl;
		commMpi2_exit(1);
		break;
	    }
	    
	    if(distanceBetweenRayNObject > tmpDist)
	    {
		// tmpDist の方が近いので交差したobjを更新し、交点をセットする
		distanceBetweenRayNObject = tmpDist;
		pHitObj = pObj;
		pRay->setHitPos(hitPoint);
	    }
	}
    }

    //------------------------------
    // Volume との相互作用 
    //------------------------------
    SLList < VolumeClass * > *pVolList = 
    	this->mVolSet.getList(pRay->getSpcPos());
    if(!(pVolList->empty()))
    {
	// volume が存在した場合には相互作用を計算する
	processingOneRayTraceWithVolume(pRay, pVolList, 
					distanceBetweenRayNObject);
    }

    // もしかしたら volume の作用でここを NULL にすべきかも
    return(pHitObj);
}

//------------------------------
// DDA 計算．ray が定義空間を越えた場合には ray 消滅
//------------------------------
void PEClass::
calcRayTrNextSpace(Ray *pRay)
{
    // 交差する物体は存在しない 次空間探索 (PEGetNExtSpace.cc)
    YorN isSendRay = this->moveRayToNextSubspace(pRay);
 
    if(isSendRay == YES)	// ray 送信
    {
	this->setRayToNI(pRay); // NIのバッファへためこむ & 送信
    }else
    {
	// 輝度をフレームバッファシステムへ送信
	this->insertIntensityIntoBuffer(pRay);	    
	delete pRay;	// deleteRay!
	pRay = NULL;
    }
}

//------------------------------
// レイを一定数発生する for ray-tracing
//------------------------------
void PEClass::
shotSomeFRGRays(void)
{
    // 呼び出したということは最小限 1 本は発生可能であるはず
    assert(this->mRayTrFRGUnit.canGenerateNextRay() == YES);

    int i;
    const int maxGenRaysAtOnce = 
	this->mSysInfo.getProcRayTrAtOnce() * GEN_FRAYS_FACTOR_BY_COMMUN;
    for(i = 0; i < maxGenRaysAtOnce; i++)
    {
	// 一本 ray を発生させる
	Ray *pRay = this->mRayTrFRGUnit.newNextRay();

	// 複数の PE で共同して ray を発生させる場合には ray を発生さ
	// せてそれが担当空間と交差するかを試験しなくてはいけないので、
	// たとえスクリーン上であっても実際にこの PE が ray を発生させ
	// られない場合がある。そのような場合には NULL が帰ってくる。
	if(pRay != NULL)	
	{
	    if(this->isDealSpace(pRay->getSpcPos()) == YES)
	    {
		// ray 発生時にこの PE の担当空間にいる
		// ここで計算する
		this->setRayPtrToThisPE(pRay);
	    }else 
	    {
		// ray 発生時にこの PE の担当空間にいない
		if(this->isObjectInSpc(pRay->getSpcPos()) == YES)
		{
		    // オブジェクトがこの ray の現在存在する空間に存在
		    // する 
		    // 担当 PE へ送信する : NI キューへ
		    this->setRayToNI(pRay); 
		}
		else
		{
		    // オブジェクトはこの ray の現在存在する空間には存
		    // 在しない．次空間探索を行う 
		    this->calcRayTrNextSpace(pRay);
		}
	    }
	}else
	{
	    // ray は発生しなかった．することはない．
	}
	if(this->mRayTrFRGUnit.canGenerateNextRay() == NO)
	{
	    // ray の発生終了
	    if(Option::getInstance()->getVerboseMode() == YES)
	    {
		cout << genHd(VERBOSEHEAD, this->getID()) << "generate rays( " 
		<< this->mRayTrFRGUnit.getTotalGenRays() << " ) done."<< endl;
	    }
	    break;
	}
    }
}

//------------------------------
// ray tracing
//------------------------------
void PEClass::
doRayTracing(void)
{
    if(Option::getInstance()->getVerboseMode() == YES)
    {
	cout << genHd(VERBOSEHEAD, this->getMyRank()) << 
	    "PE in do raytracing OK" << endl;
    }

    ProfileMon *pPfm = ProfileMon::getInstance();
    pPfm->startStage(ProfileMon::ProfSt_raytracing);
    
    this->initForRayTracing();
    if(Option::getInstance()->getVerboseMode() == YES)
    {
	cout << genHd(VERBOSEHEAD, this->getMyRank()) << "PE in inited OK"
	<< endl;
    }

    // init status 
    this->initBusyIdleStat();
    this->mIsStopGenNewRay = NO; // host からの ray の発生停止要求はない
    this->mSendingRayStopComToHost = NO; // host への ray の発生停止要求未提出

    while(this->isMultiPassStageEnd() == NO)
    {
	this->recvRays(); // まずは他から受信する
	// 次に他へ送信する
	// 今回は ray が enqueue された時点で送信すべきならば送信する
	// ため，ここでの send はない
	// this->sendRays(); 
	// this->mNIUnit.flushIntensitySendBuf();// 輝度をホストへ送信する
	
	// processing
	if(this->mRayPtrQueue.empty() == Q_NOT_EMPTY)
	{
	    // 受信レイなし && 処理するべき自己 ray がある
	    // (RP Block) ray を処理する。輝度計算、送信含む。
	    assert(this->isIdle() == NO);
	    pPfm->occurEv(ProfileMon::ProfEv_ProcRayRays);

	    this->processingRayTraces(); 
	    this->globalLoadBalance(); // ray の量により ray の発生制限を行う
	}
	else if(this->mRayTrFRGUnit.canGenerateNextRay() == YES)
	{
	    // キューにレイなし、未発射レイあり
	    this->toBusy();

	    // レイを発生する前に host から ray の発生の停止要求がある
	    // かを調べる 
	    this->probNgetHostCommand();
	    if(this->mIsStopGenNewRay == NO) // host から停止要請はない
	    {
		pPfm->occurEv(ProfileMon::ProfEv_genRays); 
		this->shotSomeFRGRays();
	    }
	}
	else if(this->mNIUnit.isSendRayBufAllEmpty() == NO)
	{
	    // 受信レイなし && buff にレイが残っている
	    assert(this->isIdle() == NO);
	    this->flushSendRaysBuffer(); // 送信 buf に空きがあり可能なら送信
	    pPfm->occurEv(ProfileMon::ProfEv_SendingWait);

 	}else if((this->mNIUnit.isNoSendRay() == YES) && // 送信中ではない && 
 		 (this->mNIUnit.isNoRecvRay() == YES))   // 受信中ではない
 	{
	    // 輝度と PEStat の flush
	    this->mNIUnit.flushIntensitySendBuf(); // 輝度 の flush
	    this->mNIUnit.flushPEStatSendBuff();   // PEStat の flush

	    if((this->mNIUnit.isSendingIntensity() == NO) && 
	       (this->mNIUnit.isSendingPEStat()    == NO))
	    {
		// 受信レイなし && 処理するべき自己 ray 無し && 送信終了
		// && 受信終了 (ray && 輝度 && PEStat)
		// == 全ての仕事が終了
		if(this->isIdle() == NO) // busy -> idle
		{
		    this->toIdle();		
		    assert(this->isIdle() == YES);
		    pPfm->occurEv(ProfileMon::ProfEv_Idle);		    
		}else
		{
		    // 全て処理なく idle のままであった
		    // もし終了のコマンドを受けたら endOfRayTracing を YES に
		    // 変更させる 
		    this->probNgetHostCommand(); 
		    if(this->isMultiPassStageEnd() == YES)
		    {
			// 終了コマンドを受けた場合にはキューは空
			assert(this->mRayPtrQueue.empty() != Q_NOT_EMPTY);
		    }
		}
	    }
	    pollingSleep();
	}else
	{
	    this->toBusy();	    
	}
    }
#ifdef FOR_STANDALONE
    this->toIdle();
#endif
    this->mNIUnit.flushIntensitySendBuf();  // 輝度をホストへ送信する
    if(Option::getInstance()->getVerboseMode() == YES)
    {
	cout << genHd(VERBOSEHEAD, this->getMyRank())<<"out of PE loop"<< endl;
    }
    pPfm->endStage(ProfileMon::ProfSt_raytracing);
}


