//------------------------------------------------------------
// $Id: volumeRayCast.cc,v 1.2.2.1 2000/06/14 15:00:10 yamauchi Exp $
//------------------------------------------------------------
//	volumeClass : ray casting 
//	1998/2/10
//	Copyright (C) 1996--1998 YAMAUCHI Hitoshi 山内 斉
//------------------------------------------------------------
#include <stdlib.h>
#include <math.h>
#include "SLList.h"

#include "searchNextSpcDDA.h"
#include "IntVec3.h"
#include "volume.h"
#include "volumeFuncs.h"

#ifndef ONLY_VOLREN
#include "mpi2_funcs.h"
#else
// external functions 
extern YorN isPosInBox(const Vector3& pos, 
		       const Vector3& minpos, 
		       const Vector3& maxpos);
extern isPosInBoxWithRes(const Vector3& pos, 
			 const Vector3& minpos, 
			 const Vector3& maxpos,
			 double res);
extern DIR_3D isHitBoundingBox(const Vector3& minBox,    
			       const Vector3& maxBox, 
			       const Vector3& rayOrigin,
			       const Vector3& rayDir,
			       Vector3 *pHitPoint);
#endif


//------------------------------------------------------------
// RP Block の処理のうち、volume との相互作用を計算する部分
//	ray と交差するこの空間に存在する volume を求め、距離の近い順に
//	並べる
//	call from processingOneRayTrace
//
// 	mHitVIdxCand が交差ボクセル index 候補となっているのは、この副
// 	空間に存在するとされる、あるいはこの空間で voxel と ray の相互
// 	作用を計算するのに必要とされる voxel との交差である。
//
//	-----+
//	--+---+
//        |  ||<-- voxel 
//      --+---+
//           |<--- 副空間境界
//	-----+
//
//  	上図のように voxel の境界と副空間の境界は一致しないことが多い。
//  	その場合には隣の副空間が voxel の情報を持っていて、そこで既に
//      その voxel の計算をしてしまっているかもしれない。その続きから
//      計算するために ray は mVoxelIdx を持つが、volume に交差するか
//      どうかはこの空間だけで判断する必要がある。そこでこの空間の持つ 
//      volume との交点が最初に mHitVIdxCand という候補になるわけであ
//      る。
//------------------------------------------------------------
// volume と交差情報の保持用
//------------------------------------------------------------
class VolumeClass;

class VolDistClass {
public:
    double       mDistance;	// 視点からの距離 if(dist < 0){no hit}
    VolumeClass *mpVol;		// volume
    IntVec3 	 mHitVIdxCand;	// 交差ボクセル index 候補
    double 	 mMaxDistance;	// volume 中を通過できる最大距離(視点から)
				// 物体と交差している場合にはここまで追跡
public:
    VolDistClass(void) : mDistance(-1.0), mpVol(NULL)
    {
	this->mHitVIdxCand.set(-5, -6, -7); // 不正な値
    }
};

//------------------------------------------------------------
// implementation
//------------------------------------------------------------
// pos は、ある stride で区切られた org からはじまる区画のどの index 
// にいるか
//                 
//	|----|----|----|----|----|----|----|
//     org          ^pos      -->|----|<--
//                                stride
//------------------------------
inline int getPosIdx(double org_pos, double stride, double position)
{
    // org_pos + stride * index $\leq$ position 
    //		->
    // index = \lfloor \frac{position - org_pos}{stride} \rfloor
    return((int)(floor((position - org_pos) / stride)));
}

//------------------------------------------------------------
// class methods
//------------------------------------------------------------
// pos はこの volume のどの要素の中に存在するか
// これは volume attr が答える
//------------------------------
IntVec3 VolumeClass::
getGlobalVolumeIndexFromPos(const Vector3& pos)
{
    // 0.0001 は エラー残差
    assert(isPosInBoxWithRes(pos, this->mSubGrid.getMinPos(),
			     this->mSubGrid.getMaxPos(), 0.0001) == YES); 
    int i;
    int ret[3];
    for(i = 0; i < 3; i++)
    {
	ret[i] = getPosIdx((this->mGlobalGrid.getMinPos()).get(i), 
			   this->mGlobalGrid.getVoxelEdgeLen(),
			   pos.get(i));
	assert(this->mGrid.getMinIdx(i) <= ret[i]);
	assert(ret[i] <= this->mGrid.getMaxIdx(i));

	// volume の場合には最大の部分に当った場合には一つ内が volume
	// index となる。
	if(ret[i] == this->mGrid.getMaxIdx(i))
	{
	    ret[i]--;
	    assert(this->mGrid.getMinIdx(i) <= ret[i]);
	}
    }

    return(IntVec3(ret[0], ret[1], ret[2]));
}


//------------------------------
// この volume と ray が交差するかのテスト
//	castRay が呼ばれた時点で既に volume と ray が交差するかはテス
//	ト済みである。
//------------------------------
static YorN checkRayIsInVolume(const Vector3& subVolMinPos, 
			       const Vector3& subVolMaxPos,
			       const Vector3& rayPos,
			       const Vector3& rayDir)
{
    // ray が volume と最初に交差する位置
    Vector3 intersectPos;	
    
    // ray の始点は volume の外か中か
    if(isPosInBox(rayPos, subVolMinPos, subVolMaxPos) == YES) 
    {
	// ray の始点が volume 内部に存在する => 交差する
	return(YES);	    	
    }else
    {
	// ray の始点は volume の外にある:この ray は volume と交差するか?
	if(isHitBoundingBox(subVolMinPos, subVolMaxPos, rayPos, rayDir,
			    &intersectPos) == DIR_ILLEGAL)
	{
	    // 交差しなかった。なにもしないで返る。
	    return(NO);
	}else
	{
	    // 交差する
	    return(YES);	    
	}
    }
}

//------------------------------------------------------------
// cast ray 
//   @param *pRay 		: ray
//   @param limitDistance	: 最大どこまで追跡するか
//   @param *pRetColor		: 計算結果の色
//
//   maxDistance が存在するのは、volume 内に物体が存在した場合
//   にその物体に ray がぶつかった時点で積分を中止するため。
//	return : 
//	    ray がこの volume と交差した(計算した)か? した場合は YES
//
// DDA unit (プロセッサ)と DDA info (データ)の違いに注意
//------------------------------------------------------------
YorN VolumeClass::
castRay(Ray *pRay, const VolDistClass* const pVolDist, Vector3 *pRetColor)
{
    // ray とこの volume は交差するはず
    assert(checkRayIsInVolume(this->mSubGrid.getMinPos(), 
			      this->mSubGrid.getMaxPos(),
			      pRay->getPos(),	   pRay->getDir()) == YES);

    //------------------------------------------------------------
    // DDAinfo が初期化されているにもかかわらず、volume ID が異なる場
    // 合には volume が重なっているか DDAinfo を未使用にしていない。
    // そのような場合を check しておく。
    // DDAinfo が既に初期化されているのならばこの ray は隣の空間でも分割し
    // た元は同一の volume を trace してきたはず
    //
    // もし、volDDA が初期化されていれば、volID >= 0 かつ この ID と等しい
    // あるいは初期化されていない
    //------------------------------------------------------------
    assert(((pRay->isVolDDAinited() == YES) && 
	    ((pRay->getVolID() >= 0) && (this->getID() == pRay->getVolID())))
	   || (pRay->isVolDDAinited() == NO));
    
    if(pRay->getAccumOpacity() > 1.0)
    {
	// 不透明度が既に 1.0 を越えているので計算の必要はない
	// ここで打ち切った場合には後で DDA 途中の計算が省かれるので無
	// 理である
	// 打ち切らない場合には accumOpacity が 1 を越えてしまう．その
	// 場合，
	// 	double 	hereOpacity = c * (1.0 - accumOpacity);
	// で計算しているので opacity が負になる = color も負になるた
	// め何らかの処置が必要
	return(NO);
    }

    //------------------------------------------------------------
    // この volume を traverse するための DD ユニットを初期化
    // DD Unit は各 PE, volume ごとに状態が異なるので初期化する必要が
    // ある。
    // 一方 DDAinfo は一度初期化すれば同じ volume, 空間分割ならば初期
    // 化する必要はない。
    //------------------------------------------------------------
    searchNextSpcDDA nssDDAunit;

    int spcMinIdx[3], spcMaxIdx[3];
    for(int i = 0; i < 3; i++)
    {
	// grid の有効範囲は [spcMinIdx, spcMaxIdx] なのだが，DDAUnit 
	// は [min, max) として計算するので Max の方を 1 増やしておく．
	spcMinIdx[i] = (this->mSubGrid.getMinIdx()).get(i);
	spcMaxIdx[i] = (this->mSubGrid.getMaxIdx()).get(i) + 1; 
    }
    
    nssDDAunit.initWithoutObjInSpcMap(this->mGlobalGrid.getMinPos(),
				      this->mGlobalGrid.getVoxelEdgeLen(),
				      spcMinIdx, spcMaxIdx,
				      NoSkip_rayprop,
				      MultiStage_network);

    //------------------------------------------------------------
    // 距離の計算用
    //------------------------------------------------------------
    double nowDistFromRayOrg = pVolDist->mDistance;
    const double deltaDist   = ((pRay->getVolDDAInfo()).getDDAVec()).getNorm();
    

    //------------------------------------------------------------
    // 色の計算
    //------------------------------------------------------------
    Vector3 accumColor(0.0, 0.0, 0.0); // これらを隣の空間に送らねばならんぞー
    double  accumOpacity = pRay->getAccumOpacity();

    // IMPROVE ここは pointer にして直接 ray の中の DDAinfo,
    // spcpos を変更しても良い 
    IntVec3 hitGridIdx	  = pRay->getVoxelIdx();
    DDAInfoClass volDinfo = pRay->getVolDDAInfo();

    int hitVGridIdxInt[3];

    // grid の RGB は通常 [0, 255] の short int，depth を取得し，それ
    // で正規化する． 
    double parDepth = 1.0 / (double)(this->mGlobalGrid.getRGBDepth());
    while(nssDDAunit.isValidASpcpos(hitGridIdx) == YES)
    {
	// 色を加えて良い空間にいる

	//------------------------------------------------------------
	// voxel 色の計算，ここから for uchi
	//------------------------------
	hitGridIdx.getAsArray(hitVGridIdxInt);
	Vector3 col
	    = (this->getGlobalEffiGridIdxColor(hitVGridIdxInt)).getRGBColor();
	assert((col.get(Vector3::X) >= 0.0) && (col.get(Vector3::Y) >= 0.0) &&
	       (col.get(Vector3::Z) >= 0.0));

	// color vector の平均長 / depth を opacity とする : 色の平均
	// をとるというのは本来はおかしいが (3.0 = RGB 3 つの平均)
	// ここではこの平均を不透明度とする．
	double colorave = (col.get(Vector3::X) + col.get(Vector3::Y) +
			   col.get(Vector3::Z)) / 3.0;
	double hereOpacity = ((colorave * parDepth) * (1.0 - accumOpacity));
	accumColor 	   += (hereOpacity * col);
	accumOpacity       += hereOpacity;
	if(accumOpacity >= 1.0)
	{
	    pRay->setAccumOpacity(accumOpacity);
	    cout << "P";	// 不透明度の overflow の表示
	    break;
	}
	//------------------------------
	// voxel 色の計算はここまで
	//------------------------------------------------------------	

	nssDDAunit.forceMoveRayToNextSpace(&volDinfo,
					   pRay->getPos(), pRay->getDir(),
					   &hitGridIdx);
	// polygon などとの交差がある場合には Max 以上で計算を打ち切る．
	nowDistFromRayOrg += deltaDist;
	if(nowDistFromRayOrg > pVolDist->mMaxDistance)
	{
	    break;
	}
    }

    // assert(accumColor >= 0.0);
    assert(accumOpacity >= 0.0);

    (*pRetColor) = accumColor;
    pRay->setVolDDAInfo(volDinfo);
    pRay->setVoxelIdx(hitGridIdx);
    pRay->setAccumOpacity(accumOpacity);
    
    return(YES);
}

//------------------------------------------------------------
// ray と volume の交差点を求める
//	return : 交差する場合の距離 (負の場合には交差しない。)
//	arg:	 pIntersectPos : volume と ray の 交差点 ray の視点が 
//		  		 volume 内部ならば視点の位置
//		 pHitVoxelIdx  : ray が交差した voxel の index
//------------------------------
double getDistanceWithVolume(Ray *pRay, VolumeClass *pVol,
			     Vector3  *pIntersectPos,
			     IntVec3  *pHitVoxelIdx)
{
    // ray の始点は volume の外か中か
    if(isPosInBox(pRay->getPos(), 
		  (pVol->getEffiGrid()).getEfMinPos(), 
		  (pVol->getEffiGrid()).getEfMaxPos()) == YES) 
    {	
	// ray の始点が volume 内部に存在する

	// この ray の位置を交差点として返す (これは voxel の境界にな
	// いかもしれないので問題があるか?)
	(*pIntersectPos) = pRay->getPos(); 
	(*pHitVoxelIdx)  = pVol->getGlobalVolumeIndexFromPos(pRay->getPos());
	return(0.0);		
    }
    else
    {
	// ray の始点は volume の外にある:この ray は
	// volume の有効範囲と交差するか?
	if(isHitBoundingBox((pVol->getEffiGrid()).getEfMinPos(), 
			    (pVol->getEffiGrid()).getEfMaxPos(),
			    pRay->getPos(), pRay->getDir(),
			    pIntersectPos) == DIR_ILLEGAL)
	{
	    // 交差しなかった。なにもしないで返る。
	    return(-1.0);
	}else
	{
	    // 交差する
	    //   (pIntersectPos は isHitBoundingBox でセットされる)
	    (*pHitVoxelIdx) = 
		pVol->getGlobalVolumeIndexFromPos((*pIntersectPos));
	    Vector3 d = (*pIntersectPos) - (pRay->getPos());
	    double  dist = d.getNorm();
	    // return((*pIntersectPos) -(pRay->getPos()).getNorm()) 
	    // と書くとコンストラクタやデストラクタがいつ呼ばれるのか
	    // 気になってしまいこのように tmp の値を使ってしまう。
	    // 林原めぐみさんが結婚したとのこと。おめでとうございます。
	    // 1998年4月4日(土) 
	    assert(dist >= 0.0);
	    return(dist);
	}
    }
}

//------------------------------------------------------------
// qsort 用比較関数
// gcc は次の書き方でも通してくれたが、これは qsort の比較関数定義とは
// 型が合わないので中で明示的にキャストするのが正しいようだ。難しいねぇ。 
//
// static int comparDist(const VolDistClass *i, const VolDistClass *j)
//------------------------------------------------------------
static int comparDist(const void *i, const void *j)
{
    if(((VolDistClass *)i)->mDistance > ((VolDistClass *)j)->mDistance)
    {
	return(1);
    }
    else if(((VolDistClass *)i)->mDistance < ((VolDistClass *)j)->mDistance)
    {
	return(-1);
    }else
    {
	return(0);
    }
}

//------------------------------
// voxel index が範囲から外れる時に YES
//------------------------------
static YorN isOutGlovalVolRange(VolumeClass *pVol, Ray *pRay)
{
    IntVec3 vidx = pRay->getVoxelIdx();
    for(int xyz = 0; xyz < 3; xyz++)
    {
	if((vidx.get(xyz) < 0) ||
	   (pVol->getGlobalGridResSize(xyz) <= vidx.get(xyz)))
	{
	    // volume index is out of range
	    return(YES);
	}
    }
    return(NO);
}

//------------------------------
// 一つの volume と 1 つの ray の交差
// 	ray の色の更新含む
//
// hitVIdxCand は ray と交差する voxel index の候補である。
// ray が持っているものが正しい。しかし、ray は初期状態では 
// volume とまだ相互作用していないので ray は計算済みの voxel
// index を持たない。その場合には候補である hitVIdxCand を使う。
//
// @param pRay
// @param volDist これまでの距離，最大距離，交差 volume の情報
//------------------------------
static void castRayToVolume(Ray      *pRay, 	 // レイ
			    const VolDistClass& volDist) // 交差 volume の情報
{
    //----- 
    // DDAinfo が初期化されているにもかかわらず、volume ID が異なる場
    // 合には volume が重なっているか DDAinfo を未使用にしていない。
    // そのような場合を check しておく。
    // DDAinfo が既に初期化されているのならばこの ray は隣の空間でも分割し
    // た元は同一の volume を trace してきたはず
    // 隣からの続きか? 
    if(pRay->isVolDDAinited() == NO)
    {
	// 初期化されていない。初期化する。
	//   volume ID と交差 voxel をセット(DDA は cast すれば初期化される)
	assert(volDist.mpVol->checkGridIdxRangeForDEBUG(volDist.mHitVIdxCand)
	       == YES);
	pRay->setVolID(volDist.mpVol->getID()); 
	pRay->setVoxelIdx(volDist.mHitVIdxCand);
    }else
    {
	// 初期化されている。volume ID は同じか?
	if(pRay->getVolID() != volDist.mpVol->getID())
	{
	    // volume id が異なる。 => 続きではなかった
	    // 今は同じ空間を2つの volume が占めているのは不正とする
	    cerr << "Error! : volume [" << pRay->getVolID() << "] and ["
		<< volDist.mpVol->getID() << "] share a same space. " 
		" no volume can share same space." << endl;
	    abort();
	}else
	{
	    // 初期化されており,これまでと同じvolume ID.そのまま続きができる
	    // 	    cout << pRay->getID() << " " << pRay->getVoxelIdx() << 
	    // 		" has same volume ID" << endl;
	}
    }
    
    Vector3 vcolor(0.0, 0.0, 0.0);
    if(volDist.mpVol->castRay(pRay, &volDist, &vcolor) == YES)
    {
	// FIXHERE 色は現在 mono
	Vector3 col = pRay->getColor();
	vcolor   += col;
	// overflow の判定は frame buffer に書く時
	pRay->setColor(vcolor);
    }
    if(isOutGlovalVolRange(volDist.mpVol, pRay) == YES)
    {
	pRay->invalidateVolData();
    }
}

//------------------------------
// 一つの空間を 1 つの ray が通過する
// 	ray trace 
//	一つの空間には複数の volume が存在する可能性がある
//
// 1. ray と交差する volume を求める
// 2. 近い volume から順に ray cast していく
//
// @param pRay
// @param pVolList ray と交差する可能性のある volume のリスト
// @param distanceBtwRayNObj 物体と交差している場合にはその距離．交差
// 	  していない場合には PlusInfty 
//------------------------------
void processingOneRayTraceWithVolume(Ray *pRay, 
				     SLList < VolumeClass * > *pVolList,
				     const double distanceBtwRayNObj)
{
    assert(!(pVolList->empty())); // volume は存在するはず
    
    // ray が通過する volume を求める
    int volumeListLen = pVolList->length();
    assert(volumeListLen > 0);
    VolDistClass *pVidDist = new VolDistClass[volumeListLen];
    
    Pix pix;
    int hitVidIdx = 0;
    Vector3 intersectPos;
    IntVec3 hitVoxelIdx;
    for(pix = pVolList->first(); pix != NULL; pVolList->next(pix))
    {
	VolumeClass *pVol = (*pVolList)(pix);
	double dist = getDistanceWithVolume(pRay, pVol, 
					    &intersectPos, &hitVoxelIdx);
	// 視線正方向かつ distanceBtwRayNObj より小さい場合に volume 
	// に hit : 最大到達可能距離も記録する
	if((0.0 <= dist) && (dist < distanceBtwRayNObj))
	{  
	    // ray と volume が交差する(視点が volume に含まれることあり)
	    pVidDist[hitVidIdx].mpVol 	  	= pVol;
	    pVidDist[hitVidIdx].mDistance 	= dist;
	    pVidDist[hitVidIdx].mHitVIdxCand 	= hitVoxelIdx;	    
	    pVidDist[hitVidIdx].mMaxDistance 	= distanceBtwRayNObj;	    
	    hitVidIdx++;
	    assert(pVol->checkGridIdxRangeForDEBUG(hitVoxelIdx) == YES);
	}
    }	    

    // ray と交差した volume が存在しないなら終了。多数あれば距離で sort
    assert(hitVidIdx >= 0);
    if(hitVidIdx == 0)
    {
	return;			// 交差 volume は存在しない。
    }
    else if(hitVidIdx > 1)	
    {
	// 複数の交差 volume がある。距離で sort する。
	qsort((VolDistClass *)pVidDist, hitVidIdx,
	      sizeof(VolDistClass), 	 comparDist);
	// for DEBUG
	cout << "DEBUG: sort res : processingOneRayTraceWithVolume "
		"(volID, dist) " << endl;
	for(int d = 0; d < hitVidIdx; d++)
	{
	    cout << "(" << pVidDist[d].mpVol << ", " << 
		pVidDist[d].mDistance << ") ";
	}
	cout << endl;
	// for DEBUG
    }// else => hitVidIdx == 1 つまり一つしかないのでソートする必要はない

    // 近い volume から順に ray を cast し、ray の色を更新する
    for(int i = 0; i < hitVidIdx; i++)
    {
	castRayToVolume(pRay, pVidDist[i]);
    }
}

