/*
 * $Id: CalcRadioSRG.java,v 1.5 2000/06/15 13:55:51 yamauchi Exp $
 *     (M\pi)^2 : CalcRadioSRG 
 *	Copyright 1999 By YAMAUCHI Hitoshi 山内 斉 
 *	@author YAMAUCHI Hitoshi 山内 斉
 *	
 * original source calcRadioSRG.cc
 *
 * 	Secondary Rays Generation Unit
 *	ラジオシティ法における二次光の発生部分．
 *
 * 1999年8月18日(水)
 *
 *-- 今日はこの階には既に誰もいない．いや正確には私と多田先生のみである．
 *-- 確かに夏休みとはいえ，平日の夜に誰もいない大学というものがあるのだ
 *-- な．私は大学に来るより他にすることがなかったという稀な方の人間かも
 *-- しれぬ．多田先生とこれはいったいどういうことなのだろうかということ
 *-- について話をした．
 *--
 *-- Mahler の 3 番は Bernstein, NewYork Phillharmonic のものを入手した．
 */

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

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

/*
 * ラジオシティ時の反射による二次レイ発生
 *	現在錐体を利用した ray tracing ではない
 */
public final class CalcRadioSRG implements Mpi2Constants {
    /**
     * 反射ラジオシティ Ray 作成
     */
    private static Ray newReflectionRadiosityRays(Ray orgray,
						  double minRayCoefNorm,
						  ObjectPrimitive hitObj,
						  Material [] matArray)
    {
	Assert.out(hitObj != null, 
		   "Error!: no hit obj, newReflectionRadiosityRays");

	final Material mat = matArray[hitObj.getMaterialID()];

	// 鏡面反射減衰係数を計算する
	Vector3 afterReflectRadiosity = 
	    orgray.peekColor().mulVecNew(mat.peekSpecRef());

	/*
	 * commented by tosho : これまでは容赦なく ray を生成していた
	 * ある反射減衰係数以下のエネルギの ray はこれ以上伝搬しない
	 * この部分，minRayCoefNorm は ray-tracing と同じであるのは誤り
	 * かもしれない． 
	 */
	if(afterReflectRadiosity.innPrd(afterReflectRadiosity)
	   < minRayCoefNorm)
	{
	    return(null);
	}

	// 反射方向を求める
	final Vector3 normal = hitObj.peekNormal(orgray.peekHitPos());

	// 法線は正規化されているはず
	Assert.out(Math.abs(normal.getNorm() - 1.0) < D_ERROR_RES, 
		   "Error!: normal vector must be normalized. reflection");

	//
	// Vector3 reflectedRayDir = 
	// 	(-2.0 * ((orgray.getDir()).dInn_prd(normal)) * normal) + 
	// 	 orgray.getDir();
	//
	double coeff = (-2.0 * ((orgray.peekDir()).innPrd(normal)));
	Vector3 reflectedRayDir = normal.mulScalerNew(coeff);
	reflectedRayDir.plusHere(orgray.peekDir());

	// 反射レイ作成 newRay! for radiosity 色もセットする
	// このコンストラクタで作成したレイは一度当った物体には交差しない 
	Ray newRay = Ray.newForReflectionRadiosity(orgray, reflectedRayDir, 
						   afterReflectRadiosity, 
						   hitObj);
	return(newRay);
    }

    /**
     * 屈折による二次レイ発生
     */
    private static Ray newRefractionRadiosityRays(Ray orgray,
						  double minRayCoefNorm,
						  ObjectPrimitive hitObj,
						  Material [] matArray)
    {
	// ray を copy して pMat の透過係数を掛け、更新する
	final Material mat = matArray[hitObj.getMaterialID()];
	Vector3 afterRefractRadiosity = 
	    (orgray.peekColor()).mulVecNew(mat.peekSpecTrans());

	// エネルギが小さすぎて計算する必要がない場合
	if(afterRefractRadiosity.innPrd(afterRefractRadiosity) 
	   < minRayCoefNorm)
	{
	    return(null);
	}
    
	// 透過方向計算
	Vector3 refractedRayDir = null;
	if(mat.getIdxRefraction() != 1.0)	
	{
	    // 屈折する 屈折レイ作成
	    Assert.out(hitObj != null, 
		       "Error!: no hit obj, newRefractionRadiosityRays");
	    Vector3 normal = hitObj.peekNormal(orgray.peekHitPos());

	    //法線は正規化されているはず
	    Assert.out(Math.abs(normal.getNorm() - 1.0) < D_ERROR_RES, 
		       "Error!: normal vector must be normalized. refraction");
	    
	    // 屈折方向のベクトル T (I 入射ベクトル, N 物体法線ベクト
	    // ル, n 相対屈折率 >= 1.0)  
	    // T = (n (-N \dot I) - \sqrt(1 - n^2(1 - (N \dot I)^2)) N) + nI
	    double n   = mat.getIdxRefraction(); // 屈折率
	    double ndi = (orgray.peekDir()).innPrd(normal); // (N \dot I)
	    double rt  = Math.sqrt(1.0 - (n * n) * (1.0 - (ndi * ndi)));
	    // refractedRayDir = 
	    //	(((n * (-ndi)) - rt) * normal) + (n * orgray.getDir());
	    refractedRayDir = normal.mulScalerNew((n * (-ndi)) - rt);
	    Vector3 dir = (orgray.peekDir()).mulScalerNew(n); 
	    refractedRayDir.plusHere(dir);
	}else
	{
	    // mat.getIdxRefraction() == 1.0  : 透過率 1 無意味かな。
	    refractedRayDir = orgray.peekDir();
	}

	//-- 透過レイ作成 newRay! for radiosity 色もセットする
	//-- このコンストラクタで作成したレイは一度当った物体には交差しない 
	//-- 基本的に初期化は refraction も refraction も同じ
	Ray newRay = 
	    Ray.newForReflectionRadiosity(orgray, refractedRayDir, 
					  afterRefractRadiosity, hitObj);
	return(newRay);
    }

    /**
     * 2次 radiosity ray 生成
     *     	輝度計算は IC で済んでいる
     *	(論文に書いている設計とは違い，このことは IC.SRG の接続を示し
     *	 ている．論文では反射毎に FrameBuffer へと送信しているが，実装
     *	 には ray 消滅の際にしか FrameBuffer への更新が起こらない．
     *		(see mpi2doc_implementation)
     * 
     * @param orgRay 物体と交差したレイ
     * @param hitObj レイと交差した物体
     * @param pe     レイが発生した場合にはこの PE にレイをセットする
     */
    public static boolean newSecondaryRadiosityRays(Ray orgRay, 
						    ObjectPrimitive hitObj, 
						    PE  pe)
    {
	// レイは発生したか? 
	boolean isGenerateRay = false;
	final ViewData viewdat = pe.peekViewData();

	// この ray は反射・屈折回数を越えているか
	if(orgRay.getReflectedNum() >= viewdat.getMaxRayRefNum())
	{				// 越えている。ray は発生しない。
	    return(isGenerateRay);
	}
    
	/*
	 *  反射 2 次 radiosity ray を new し、PE の処理キューへ登録
	 */
	Ray newHanshaRay = 
	    newReflectionRadiosityRays(orgRay, 
				       viewdat.getMinRayRefCoeffNorm(),
				       hitObj, pe.peekMatArray());
	if(newHanshaRay != null)	// ray を発生した．キューへ
	{
	    pe.setRayToThisPE(newHanshaRay);		   
	    isGenerateRay = true;
	    // 反射 radiosity ray 生成を数えるのならばここ Hint TOKUNAGA
	}

	/*
	 * 透過 2 次 radiosity ray を new し、PE の処理キューへ登録
	 */
	Ray newToukaRay = 
	    newRefractionRadiosityRays(orgRay, 
				       viewdat.getMinRayRefCoeffNorm(),
				       hitObj, pe.peekMatArray());
	if(newToukaRay != null)
	{
	    pe.setRayToThisPE(newToukaRay);
	    isGenerateRay = true;
	    // 屈折レイ生成を数えるのならばここ Hint TOKUNAGA
	}
	return(isGenerateRay);
    }
}
