//------------------------------------------------------------
// $Id: PERadiosity.cc,v 1.1.1.1 1999/11/25 06:26:09 yamauchi Exp $ 
//------------------------------------------------------------
//	PERadiosity.cc : Processing Element Radiosity stage
//	1999年1月25日(月) PE.cc より分離
//	Copyright (C) 1996--1999 YAMAUCHI Hitoshi 山内 斉
//------------------------------------------------------------
#include "PE.h"
#include "mpi2_funcs.h"
#include "volumeFuncs.h"
#include "Option.h"
#include "hemicube.h"
#include "ProfileMon.h"		// for profiling monitor

//------------------------------------------------------------
// sub functions for radisoity stage
//------------------------------------------------------------
// radiosity の処理 複数本の delta-radiosity ray を処理する
//    交差判定、次空間計算等を行い、送信する ray を NetworkInterface に送る
//------------------------------
void PEClass::
processingRadiosities(void)
{
    // この関数が呼ばれる時には以下の条件を満たしている
    //     ・キューに一本はレイが存在している
    assert(this->mRayPtrQueue.empty()  == Q_NOT_EMPTY);
    
    // 以下の状況の時にレイを一本取り出してその処理を行なう。
    // 以下の条件はこの関数にエントリした時には満たされているので 
    // do{}while;型のループとなる。 
    // 	   ・レイがキューに存在する
    // 	   ・レイを処理した数が getProcRadioAtOnce 以下
    // 	   ・バッファが空いている
    static Ray *pRay;
    int i = 0;
    do {
	assert(this->mRayPtrQueue.length() > 0);
	pRay = this->mRayPtrQueue.deq();
	this->processingOneRadiosity(pRay); 
	i++;
    }while((this->mRayPtrQueue.empty() == Q_NOT_EMPTY)  && 
	   (i < this->mSysInfo.getProcRadioAtOnce()));
    
    // FIXME Statistics.addRadioProcsAtOnce(i);
}

//------------------------------
// processing one radiosity packet
// 	これは processingOneRayTrace() とほぼ同じだが Hit した ray の
// 	処理が異なる  (Diff の行が異なる)
//      一つのレイを処理し、次空間があるならば NI へと渡す
//	Ray Processing Block の作業を行う
//------------------------------
void PEClass::
processingOneRadiosity(Ray *pRay)
{
    // ray の存在する空間位置が不正でないかを調べる。不正ならば exit
    assert(this->isRayInValidSpace(pRay, 1) == 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()) // 放射したその面とは交差判定しない
	{
	    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);
	    }
	}
    }
    
    // パッチ輝度計算、二次レイ発生、次空間探索、送信
    if(pHitObj != NULL) // 交差面がある
    {
	// 物体に ray が交差した
	// objBaseClass *pObj = pRay->getIntObj();

	materialClass *pMat = this->mMaterialSet.get(pHitObj->getMaterialID());
	if(pHitObj->who() == TRIANGLE_OBJ_E) //triangleのみがradiosity を受ける
	{
	    // Diffパッチの輝度計算
	    // 輝度を加算し，ray 中の deltaRadiosity をこの patch の面
	    // 積で割り，単位面積あたりの delta radiosity とする
	    this->calcNsetIntensityRadio(pRay, pMat, pHitObj);
	}

	// 更に反射屈折を行うか
	// ここでは ray tracing 法のように輝度を Frame Buffer へ送信す
	// る必要がないので ray が発生したかどうかは不問に付す
	this->newSecondaryRadiosityRays(pRay, pMat, pHitObj);
	delete pRay;		// やってきたレイは消滅 deleteRay!
	pRay = NULL;
    }else
    {
	YorN isSendRay = this->moveRayToNextSubspace(pRay);
	if(isSendRay == YES)	// ray 送信
	{
	    this->setRayToNI(pRay); // NIのバッファへためこむ & 送信
	}else
	{
	    // レイ消滅 deleteRay!
	    delete pRay;
	    pRay = NULL;
	}
    }	
}

//------------------------------------------------------------
// initialize for ForRadiosity 
//------------------------------------------------------------
void PEClass::
initForRadiosity(void)
{
    // patch の初期化
    this->initializePatches();	// calcRadioFRG.cc

    // initialize Network Interface unit
    this->mNIUnit.initRayBuff(this->mSysInfo.getTotalPENum(),
			      this->mSysInfo.getProcRadioAtOnce(), 
			      this->mSysInfo.getSendInt2FBAtOnce(),
			      this->getMyRank(),
			      SysStat::getInstance()->getHostRank(),
			      &(this->mRayPtrQueue));

    this->mNIUnit.initPEStatHostCommBuff(&(this->mHostCommRecvQueue));
    
    // volume の初期化のために volume へのポインタリストを渡す．
    SLList < VolumeClass * > volumeList;
    this->mVolSet.getAllElementAsList(&volumeList);
    if(!(volumeList.empty()))
    {
	initializeVolumes(&volumeList);
    }

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


//------------------------------------------------------------
// do radisoity
//------------------------------------------------------------
void PEClass::
doRadiosity(void)
{
    ProfileMon *pPfm = ProfileMon::getInstance();
    pPfm->startStage(ProfileMon::ProfSt_radiosity);
    
    // 最初に未放射だったエネルギの総計
    const double totalInitNonRadEnergy = 
    	getTotalUnshotRadiosityEnergy(&(this->mObjList));
    this->initForRadiosity();
    
    while(this->isMultiPassStageEnd() == NO)
    {
	// まずは他から受信する
	this->recvRays();		
	
	// 送信する : 概念的にはここで送信であるが，実は Network
	// Interface に	enqueue された時点でキュー長の比較が行なわれ，
	// 送信を発行すべきならば送信はその時に発行されている．
	// this->sendRays();	
	
	// processing
	if(this->mRayPtrQueue.empty() == Q_NOT_EMPTY)
	{ 
	    // 処理するべき ray 有り
	    assert(this->isIdle() == NO);
	    pPfm->occurEv(ProfileMon::ProfEv_ProcRadRays);

	    // this->toBusy();
	    this->processingRadiosities(); // buffer に空きがある分処理する
	    this->globalLoadBalance();     // ray の総量で ray の発生制限を行う
	}
	else if(this->mRadPatchQueue.empty() == Q_NOT_EMPTY)
	{
	    // キューにレイなし && 放射パッチあり -> 最大放射パッチから放射する
	    // 放射が存在するので busy にする。この toBusy は不要かも
	    // しれないが放射が少ないので overhead にならないだろう 
	    this->toBusy(); 
	    
	    // レイを発生する前に host から ray の発生の停止要求がある
	    // かを調べる 
	    this->probNgetHostCommand();
	    if(this->mIsStopGenNewRay == NO) // host から停止要請はない
	    {
		pPfm->occurEv(ProfileMon::ProfEv_genHemiCube);
		this->radiateRadFromMaxRadPatch(); // ray 発生 
		// include ProfileMon::outHemiCubeInfo()
	    }
	}
	else if(this->mNIUnit.isSendRayBufAllEmpty() == NO)
	{
	    // レイ無し && 放射無し && バッファにレイが残っている
	    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.flushPEStatSendBuff();   // PEStat の flush
	    if(this->mNIUnit.isSendingPEStat() == NO)
	    {
		// 受信レイなし && 処理するべき自己 ray 無し && 送信終了
		// && 受信終了 (ray && PEStat)
		// == 全ての仕事が終了
		if(this->isIdle() == NO)
		{
		    this->toIdle();
		    pPfm->occurEv(ProfileMon::ProfEv_Idle);		    
		}else
		{
		    // idle 状態のまま
		    // host のコマンドがあれば受ける
		    this->probNgetHostCommand(); 
		    if(this->isMultiPassStageEnd() == YES)
		    {	// 終了コマンドを受けた場合にはキューは空
			assert(this->mRayPtrQueue.empty() != Q_NOT_EMPTY);
			assert(this->mRadPatchQueue.empty()!= Q_NOT_EMPTY);
		    }
		}
	    }
	    pollingSleep();
	}else
	{
	    this->toBusy();
	}
    }

    // 閾値以下の未放射エネルギの処理
    this->finalizeForRadiosity(totalInitNonRadEnergy);

    pPfm->endStage(ProfileMon::ProfSt_radiosity);
}

//------------------------------------------------------------
// finalize for radisity
//------------------------------------------------------------
void PEClass::
finalizeForRadiosity(double totalInitNonRadEnergy)
{
    // 放射したエネルギと未放射エネルギの出力
    double unshotRadEnergy = getTotalUnshotRadiosityEnergy(&(this->mObjList));

    cout << genHd(NORMALHEAD, this->getID()) << "unshot radenergy (start "
    	    "end) = " << totalInitNonRadEnergy << " " << unshotRadEnergy 
	    << endl;

    // 未放射ラジオシティ値を flush しラジオシティ値に加える
    Pix pix;
    for(pix = this->mObjList.first(); pix != NULL; this->mObjList.next(pix))
    {
	objBaseClass *pObj = this->mObjList(pix);
	switch(pObj->who())
	{
	case TRIANGLE_OBJ_E:
	    pObj->flushUnshotRadiosityNInitVerInt();	    
	    break;	    
	case SPHERE_OBJ_E:
	case NULL_OBJ_E:
	default:
	    break;
	}
    }

    if(this->mSysInfo.isRadiosityCalc() == NO)
    {				// radiosity calculation is not executed
	if(Option::getInstance()->getVerboseMode() == YES)
	{
	    cout << genHd(VERBOSEHEAD, this->getMyRank()) << "flushed each "
	    	"patch emittion (skipping radiosity stage)." << endl;
	}
    }else
    {
	delete this->mpHemiCube;
	this->mpHemiCube = NULL;
	
	if(Option::getInstance()->getVerboseMode() == YES)
	{
	    cout << genHd(VERBOSEHEAD, this->getMyRank()) << 
	    "destroing hemicube. finalize radiosity stage." << endl;
	}
    }
}

