/*
 * $Id: CalcRayTrSRG.java,v 1.5.4.1 2000/06/15 20:41:23 yamauchi Exp $
 *     (M\pi)^2 : 二次光発生
 *	Copyright 1999 By YAMAUCHI Hitoshi 山内 斉 
 *	@author YAMAUCHI Hitoshi 山内 斉
 *	
 * original source calcRayTrSRG.cc
 *
 * レイトレーシング法の二次光発生
 *	ray-tracing 法と radiosity 法では反射レイの生成に違いあり．
 *	二次レイ発生 for RayTracing
 * 
 * 1999年9月 1日(水)
 * 
 *-- もう 9 月だ．ドイツの返事が accept で嬉しい．
 *-- 環境が変化するのは不安だが，ここよりひどい場所があるとは思えない．
 *-- 楽しく研究ができるといいなあ．
 */

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

import	jp.ac.tohoku.is.archi.mpi2.common.*;
import	jp.ac.tohoku.is.archi.mpi2.sysparam.*;
import	jp.ac.tohoku.is.archi.mpi2.obj.*;
import	jp.ac.tohoku.is.archi.mpi2.util.*;

/**
 * 2次 ray-tracing ray 生成
 *     	輝度計算は IC で済んでいる
 *	(論文に書いている設計とは違い，このことは IC.SRG の接続を示し
 *	 ている．論文では反射毎に FrameBuffer へと送信しているが，実装
 *	 には ray 消滅の際にしか FrameBuffer への更新が起こらない．
 *		(see mpi2doc_implementation)
 */
public final class CalcRayTrSRG 
{
    /**
     * 反射による二次レイ発生
     *	現在錐体を利用した ray tracing ではない
     * @param ray 		IN 	入射レイ
     * @param minRayCoefNorm    IN  	反射時の最小エネルギ
     * @param hitobj 	    	IN	pRay との交差物体
     * @param reflectRayDir    	OUT	反射レイ方向ベクトル
     * @param reflectRayCoeff  	OUT	反射レイの輝度係数
     * @return 				
     */
    public static boolean 
    getReflectionRayDirRayTrace(Ray 		ray,
				double		minRayCoefNorm,
				ObjectPrimitive hitobj,
				Vector3      	reflectRayDir,
				Vector3		reflectRayCoeff,
				Material []	matArray)
    {
	Assert.out(hitobj != null, 
		   "Error!:nohitobj CalcRayTrSRG.getReflectionRayDirRayTrace");

	/*
	 * 鏡面反射減衰係数を計算する
	 */
	final Material mat = matArray[hitobj.getMaterialID()];
	Vector3 ref = (ray.peekRefCoeff()).mulVecNew(mat.peekSpecRef());
	reflectRayCoeff.set(ref);

	/*
	 * commented by tosho : これまでは容赦なく ray を生成していた
	 *   ある反射減衰係数以下の ray はこれ以上伝搬しない
	 */
	if((reflectRayCoeff).innPrd(reflectRayCoeff) < minRayCoefNorm)
	{
	    return(false);
	}

	/*
	System.out.println("DEBUG:refcoef " + reflectRayCoeff + ", inprd " + 
			   (reflectRayCoeff).innPrd(reflectRayCoeff) + 
			   " < min " +  minRayCoefNorm + ", raycoeff "
			   + ray.peekRefCoeff() + ", matspec " + 
			   (mat.peekSpecRef()));
			   */
	/*
	 * 反射方向を求める
	 * tokunaga change: 
	 * sphere の場合は getNormal で返ってくるベクトルが球の外を向
	 * いているので物体中にレイがいる場合は、ひとまず法線を逆向き
	 * にする。  
	 */
	Vector3 normal = null;
	switch(hitobj.who())
	{
	case ObjectPrimitive.OBJ_TRIANGLE:
	    normal = new Vector3(((TriangleObject)hitobj).peekNormal(null));
	    break;
	    
	case ObjectPrimitive.OBJ_SPHERE:
	    // normal = hitobj.peekNormal(ray.peekHitPos()); 
	    normal = new Vector3((((SphereObject)hitobj).
				  peekNormal(ray.peekHitPos())));
	    if(ray.isInSideObject())
	    {
		normal.mulScalerHere(-1.0); // 内部にいるので法線反転
	    }
	    break;
	
	case ObjectPrimitive.OBJ_NULL:
	default:
	    throw new RuntimeException("Internal Error! : illegal object : " +
				       "getReflectionRayDirRayTrace");
	}

	// 法線は正規化されているはず
	Assert.out(Math.abs(normal.getNorm() - 1.0) < 
		   Mpi2Constants.D_ERROR_RES, 
		   "Error!: normal vector must be normalized. reflect"); 
	/*
	 * 反射レイ方向の計算 
	 *  反射レイ方向 =
	 *  (-2.0 * ((ray.peekDir()).innPrd(normal)) * normal) + ray.peekDir();
	 */
	Vector3 rdir = 
	    normal.mulScalerNew(-2.0 * ((ray.peekDir()).innPrd(normal)));
	rdir.plusHere(ray.peekDir());
	reflectRayDir.set(rdir);

	return(true);
    }

    /**
     * 屈折による 2 次 ray-tracing ray 発生
     *
     * @param ray 		IN 	入射レイ
     * @param minRayCoefNorm    IN  	反射時の最小エネルギ
     * @param hitobj 	      	IN	ray との交差物体
     * @param refractRayDir    	OUT	屈折レイ方向ベクトル
     * @param refractRayCoeff  	OUT	屈折レイの輝度係数
     */
     public static boolean
     getRefractionRayDirRayTrace(Ray 	  		ray,
				 double 		minRayCoefNorm,
				 ObjectPrimitive	hitobj,
				 Vector3 		refractRayDir,
				 Vector3  	      	refractRayCoeff,
				 Material [] 		matArray)
    {
	// ray を copy して mat の透過係数を掛け、更新する
	final Material mat = matArray[hitobj.getMaterialID()];
	Vector3 refract = 
	    (ray.peekRefCoeff()).mulVecNew(mat.peekSpecTrans());
	refractRayCoeff.set(refract);

	if((refractRayCoeff).innPrd(refractRayCoeff) < minRayCoefNorm)
	{
	    return(false);
	}
    
	// 透過方向計算
	if(mat.getIdxRefraction() != 1.0)	
	{
	    // 屈折する 屈折レイ作成
	    Assert.out(hitobj != null, 
		       "Error!: hitobj must be. getRefractionRayDirRayTrace");
	    /*
	     * 物体内部にいる場合は屈折率の逆数を屈折率にする
	     * sphere の場合は反射レイと同じく法線ベクトルを逆向きに
	     */
	    Vector3 normal = null; // 法線は正規化されているはず
	    double  n;		// 屈折率
	    if(ray.isInSideObject())
	    {
		n = mat.getIdxRefraction(); 
	    
		switch(hitobj.who())
		{
		case ObjectPrimitive.OBJ_TRIANGLE:
		    normal = 
			new Vector3(((TriangleObject)hitobj).peekNormal(null));
		    break;
		
		case ObjectPrimitive.OBJ_SPHERE:
		    normal = new Vector3((((SphereObject)hitobj).
					  peekNormal(ray.peekHitPos())));
		    if(ray.isInSideObject())
		    {
			normal.mulScalerHere(-1.0); // 内部の時には反転
		    }
		    break;
		
		case ObjectPrimitive.OBJ_NULL:
		default:
		    throw new RuntimeException("Internal Error! : illegal " +
					       "object : " + 
					       "getRefractionRayDirRayTrace");
		}
	    }
	    else
	    {
		n = 1.0 / (mat.getIdxRefraction());

		switch(hitobj.who())
		{
		case ObjectPrimitive.OBJ_TRIANGLE:
		    normal = 
			new Vector3(((TriangleObject)hitobj).peekNormal(null));
		    break;
		
		case ObjectPrimitive.OBJ_SPHERE:
		    normal = new Vector3(((SphereObject)hitobj).
					 peekNormal(ray.peekHitPos())); 
		    if(ray.isInSideObject())
		    {
			normal.mulScalerHere(-1.0); // 内部の時には反転
		    }
		    break;
		
		case ObjectPrimitive.OBJ_NULL:
		default:
		    throw new RuntimeException("Internal Error! : illegal " +
					       "object : " + 
					       "getRefractionRayDirRayTrace");
		}
	    }

	    // 法線は正規化されているはず
	    Assert.out(Math.abs(normal.getNorm() - 1.0) < 
		       Mpi2Constants.D_ERROR_RES, 
		       "Error!: normal vector must be normalized. refract");
	
	    /*
	     * 屈折方向のベクトル T (I 入射ベクトル, N 物体法線ベクト
	     * ル, n 相対屈折率 >= 1.0)  
	     * T = (n (-N \dot I) - \sqrt(1 - n^2(1 - (N \dot I)^2)) N) + nI
	     */
	    double ndi = (ray.peekDir()).innPrd(normal); // (N \dot I)
	    double rt_2  =  1.0 - (n * n) * (1.0 - (ndi * ndi));
	    
	    // tokunaga changed:
	    if(rt_2 < 0.0) // \sqrt の中身が 0 未満の場合には透過光は発生しない
	    {
		return(false);
	    }
	    else
	    {
		double rt = Math.sqrt(rt_2);
		// 屈折方向(((n * (-ndi)) - rt) * normal) + (n * ray.getDir());
		normal.mulScalerHere((n * (-ndi)) - rt);
		Vector3 t = (ray.peekDir()).mulScalerNew(n);
		normal.plusHere(t);
		refractRayDir.set(t);
	    }
	}
	else
	{
	    // mat.mIdxRefraction == 1.0  : 透過率 1 無意味かな。
	    refractRayDir.set(new Vector3(ray.peekDir()));
	}

	// 実際の ray 発生や ray の物体内部外部位置変換はこの上の関数で行う
	return(true);
    }

    /**
     * secondRayGenWithoutDistribute
     * 分散無しの二次光放射
     * 
     * 	@param 	ray 		反射前のレイ
     * 	@param 	hitobj 		交差物体
     *	@param	viewdat		視線情報
     */
    public static boolean 
    secondRayGenWithoutDistribute(Ray 		  	ray, 
				  ObjectPrimitive 	hitobj,
				  ViewData 		viewdat,
				  PE 			pe)
    {
	boolean isGenerateRay = false;	// レイは発生したか? 

	// この ray は反射・屈折回数を越えているか
	if(ray.getReflectedNum() >= viewdat.getMaxRayRefNum())
	{				// 越えている。ray は発生しない。
	    Assert.out(isGenerateRay == false, 
		       "Error!: This ray already excesses reflected number.");
	    return(isGenerateRay);
	}
	
	/*
	 * 反射 2 次 ray-tracing ray を new し、PE の処理キューへ登録
	 */
	Vector3 reflectedRayDir 	= new Vector3();
	Vector3 reflectedRayCoeff	= new Vector3();
	boolean isGenReflectRay = 
	    getReflectionRayDirRayTrace(ray, 
					viewdat.getMinRayRefCoeffNorm(),
					hitobj, 
					reflectedRayDir,
					reflectedRayCoeff, pe.peekMatArray());
	if(isGenReflectRay == true)	// ray を発生した。
	{
	    // 反射レイ作成 newRay! 
	    // このコンストラクタで作成したレイは一度当った物体には交差しない 
	    Ray newHanshaRay = 
		Ray.newForReflectionRayTracing(ray, 
					       reflectedRayDir, 
					       reflectedRayCoeff,
					       hitobj);
	
	    // IC で計算した輝度を託す
	    newHanshaRay.setColor(new Vector3(ray.peekColor()));
	    pe.setRayToThisPE(newHanshaRay);		   
	    isGenerateRay = true;
	    // 反射 ray-tracing レイ生成を数えるのならばここ Hint TOKUNAGA
	}

	/**
	 * 透過 2 次 refraction レイを new し、PE の処理キューへ登録
	 */
	Vector3 refractedRayDir 	= new Vector3();
	Vector3 refractedRayCoeff	= new Vector3();
	boolean isGenRefractedRay = 
	    getRefractionRayDirRayTrace(ray, 
					viewdat.getMinRayRefCoeffNorm(),
					hitobj, 
					refractedRayDir,
					refractedRayCoeff, pe.peekMatArray());
	if(isGenRefractedRay == true)
	{
	    Ray newToukaRay = Ray.newForReflectionRayTracing(ray, 
							     refractedRayDir, 
							     refractedRayCoeff,
							     hitobj);
	    newToukaRay.changeInOut();// 屈折の場合には物体の中と外の状態を変換
	
	    // ray を発生した。既に輝度は託されているのか?
	    if(isGenerateRay == false)
	    {			// 託されていない。輝度を託す
		newToukaRay.setColor(new Vector3(ray.peekColor()));  
	    }
	    pe.setRayToThisPE(newToukaRay);
	    isGenerateRay = true;
	    // 屈折 ray-tracing ray 生成を数えるのならばここ Hint TOKUNAGA
	}
	return(isGenerateRay);
    }

    /**
     * 二次光放射で分散するかしないか
     */
    public static boolean newSecondaryRayTracingRays(Ray 	     ray, 
						     ObjectPrimitive hitobj, 
						     ViewData	     viewdat,
						     PE  	     pe)
    {
	final Material mat = (pe.peekMatArray())[hitobj.getMaterialID()];
	if(mat.getFrustumHalfTopRad() > 0.0)
	{
	    // 分散二次光放射 
	    Mpi2Option.getInstance().
		verboseOut("NIN secondRayGenWithDistrbute");
	    // return(this.secondRayGenWithDistrbute(ray, mat, hitobj));
	    return(false);
	}
	else
	{
	    // 二次光放射の分散放射無し
	    return(CalcRayTrSRG.
		   secondRayGenWithoutDistribute(ray, hitobj, viewdat, pe));
	}
    }
}
