/*
 * $Id: SphereObject.java,v 1.2 1999/12/08 16:40:56 yamauchi Exp $
 *     (M\pi)^2 : Sphere Object
 *	Copyright 1999 By YAMAUCHI Hitoshi 山内 斉 
 *	@author YAMAUCHI Hitoshi 山内 斉
 *	
 * original source sphereObject.h, sphereObject.cc
 * 
 * 球の実装
 */

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

import java.lang.String;
import java.lang.Math;
import java.io.Serializable;
import jp.ac.tohoku.is.archi.mpi2.util.Vector3;
import jp.ac.tohoku.is.archi.mpi2.util.IntVec3;
import jp.ac.tohoku.is.archi.mpi2.util.Assert;
import jp.ac.tohoku.is.archi.mpi2.util.Encodable;
import jp.ac.tohoku.is.archi.mpi2.util.ObjectDecoder;
import jp.ac.tohoku.is.archi.mpi2.sysparam.Mpi2Constants;
import jp.ac.tohoku.is.archi.mpi2.sysparam.Mpi2Enum;
import jp.ac.tohoku.is.archi.mpi2.sysparam.Config;


/**
 * 球の実装
 */
public final class SphereObject
extends ObjectPrimitive  
implements Mpi2Constants, Mpi2Enum, Config, Encodable
, Serializable
{
    private Vector3 mCenter;
    private double  mRadius;

    /*    virtual Vector3 getNormal() 
    {
	cerr + "Error! : sphereObjectClass.getNormal() can't get norm" 
	    + endl;
	commMpi2_exit(1);
	return(Vector3(-1.0, -1.0, -1.0));
    }
    */

    /**
     * constructors
     */
    /*
     * 球生成
     *   @param objectNumber 	物体番号
     *   @param materialID 	マテリアル
     *   @param spcpos 		球の存在する空間番号
     *   @param center 		球の中心座標
     *   @param radius 		半径
     *	複数の部分空間に存在する場合には，その空間数だけ球を作成する．
     *  球の表面が存在する空間だけで良いが，内部を含んでも良い．
     */
    public SphereObject(int objectID,   int materialID, IntVec3 spcpos,
			Vector3 center, double radius)
    {
	super(objectID, materialID, spcpos);
	this.mCenter = center;
	this.mRadius = radius;
    }
    /*
     * copy construct
     */
    public SphereObject(SphereObject rhs)
    {
	super(rhs.getObjectID(), rhs.getMaterialID(), rhs.peekSpcPos());
	this.mCenter = rhs.mCenter;
	this.mRadius = rhs.mRadius;
    }

    /*
     * 物体までの距離(if ret Double.MAX_VALUE =  not hit)
     *   ただし，部分空間内部で hit しない場合には no hit とする
     */
    public double getDistance(Ray aRay, Vector3 retHitPos)
    {
	Vector3 Lvec  = this.mCenter.minusNew(aRay.peekPos());
	double  Ldist = Lvec.getNorm(); // 球の中心と視点との距離
	// ray direction は正規化されているはず
	Assert.out(Math.abs((aRay.peekDir()).getNorm() - 1.0) < 1e-5, 
		   "Error!: ray direction is not normalized. SphereObject");
	
	double distance = Double.MAX_VALUE;

	if(Ldist > this.mRadius)
	{				
	    double  LRd = Lvec.innPrd(aRay.peekDir());
	    // 視点は球の外部にある
	    if(LRd <= 0.0)
	    {
		// 球の方向は ray の方向と 90 度以上ずれているため交差しない。
		return(Double.MAX_VALUE); // 90 度は含めた no hit
	    }
	    else
	    {
		double td2 = 
		   this.mRadius * this.mRadius - (Ldist * Ldist) + (LRd * LRd);
		if(td2 < 0.0)	// この不等号は <= であろうか?
		{
		    return(Double.MAX_VALUE); // no Hit
		}else
		{
		    distance = LRd - Math.sqrt(td2);

		    // retHitPos = aRay.peekPos() + distance * aRay.peekDir()
		    Vector3 r = new Vector3(aRay.peekDir());
		    r.mulScalerHere(distance);
		    r.plusHere(aRay.peekPos());
		    retHitPos.set(r);
		}
	    }
	}
	else if(Ldist < this.mRadius)
	{
	    // 視点は球の内部にある
	    double  LRd = Lvec.innPrd(aRay.peekDir());
	    double td2 = 
		this.mRadius * this.mRadius - (Ldist * Ldist) + (LRd * LRd);
	    distance = LRd + Math.sqrt(td2);	

	    // retHitPos = aRay.getPos() + distance * aRay.getDir();
	    Vector3 r = new Vector3(aRay.peekDir());
	    r.mulScalerHere(distance);
	    r.plusHere(aRay.peekPos());
	    retHitPos.set(r);
	}
	else if(aRay.isInSideObject())
	{
	    //透過光の場合 (球の表面裏に視点がある)
	    double  LRd = Lvec.innPrd(aRay.peekDir());
	    distance = 2.0 * Math.sqrt(LRd * LRd);

	    // retHitPos = aRay.getPos() + distance * aRay.getDir();
	    Vector3 r = new Vector3(aRay.peekDir());
	    r.mulScalerHere(distance);
	    r.plusHere(aRay.peekPos());
	    retHitPos.set(r);
	}
	else
	{
	    // 視点は球に接っしている。これは外れとする。
	    return(Double.MAX_VALUE);	// no Hit
	}
	
	// no hit の場合には Double.MAX_VALUE で return している．ここでは 
	// distance は Double.MAX_VALUE ではない．
	Assert.out(distance < Double.MAX_VALUE, 
		   "Error!: Distance must not be MAX_DOUBLE. (SphereObject)");
	
	// ここで hitpos が部分空間内部にあるのか判定し，部分空間内部に存
	// 在しない場合には，他の空間で hit しているので no hit とする．
	// しかし，部分空間情報や，cellLength についての情報は物体が持って
	// いるわけではないので，これは PE で判断することにする 
	
	return(distance);
    }
    
    public Vector3 newFlatRadiosityColor(Vector3 hitPos)
    {
	// NIN
	return(new Vector3(Vector3.ZERO));
    }

    public Vector3 newShadedRadiosityColor(Vector3 hitPos)
    {
	// NIN
	return(new Vector3(Vector3.ZERO));
    }

    /*
     * Hitpoint の法線ベクトル
     */ 
    public Vector3 peekNormal(Vector3 hitpos) 
    { 
	Vector3 vnormal = hitpos.minusNew(this.mCenter);
	vnormal.normalize();
	
	return(vnormal);	
    }

    /**
     * toString 
     */
    public String toString()
    {
	String s = 
	    " sph " + this.getObjectID() + " " + this.mCenter + " " + 
	    this.mRadius + " " + this.peekSpcPos();
	return(s);
    }

    /**
     * toStringWithHEad
     */
    public String toStringWithHead(String header)
    {
	String s = header + " # materialID = " + this.getMaterialID() + "\n" +
	    header + " sph " + this.getObjectID() + " " + this.mCenter + " " + 
	    this.mRadius + " " + this.peekSpcPos();
	return(s);
    }

    /*
     * mgf 形式の出力
     */ 
    public String getMgfForm(int outtype)
    {
	String s;	
	// Now for SphereObject, outtype is meanless.
	switch(outtype)
	{
	case NORMAL_ObjOut:
	case WITH_SHOT_RADOUT:
	case WITH_UNSHOT_RADOUT:
	    s = "sph " + this.getObjectID() + " " + this.mCenter + " " + 
		this.mRadius + " " + this.peekSpcPos();
	    return(s);
	default:
	    throw new RuntimeException("Internal Error! : " +
				       "SphereObject::getMgfForm : " +
				       "Illegal Output Type.");
	    // break;
	}	

    }

    /*
     * 物体の種類
     */
    public int  who() 
    {
	return(OBJ_SPHERE);
    }

    /*
     * 正しい空間にいるか
     */
    public boolean isAtValidSpcPos(Vector3 partmin, Vector3 subSpcSize,
				   int subdivnum)
    {
	// check spcpos num
	for(int i = 0; i < 3; i++)
	{
	    if((this.peekSpcPos().get(i) < 0) ||
	       (this.peekSpcPos().get(i) >= subdivnum))
	    {
		System.out.println("Error! :sph(sphere): spcpos " + 
				   "is out of define space.");
		return(false);
	    }
	}
	
	// check spcpos range
	int [] centerSpcpos = new int[3];
	for(int i = 0; i < 3; i++)
	{
	    // 球面中心空間位置
	    centerSpcpos[i] = 
		(int)((this.mCenter.get(i) - partmin.get(i)) / 
		      subSpcSize.get(i));
	    // 半径による空間位置範囲
	    int radSpcposBias = (int)(this.mRadius / subSpcSize.get(i)) + 1;
	    if((this.peekSpcPos().get(i) < centerSpcpos[i] - radSpcposBias) ||
	       (this.peekSpcPos().get(i) > centerSpcpos[i] + radSpcposBias))
	    {
		final String [] s = { "x", "y", "z" };
		String ss = 
		    "Error! : sph(sphere): spcpos " + this.peekSpcPos() + 
		    " illigal spcpos : " + s[i] + "\n" +
		    "center " + this.mCenter    + "\n" + 
		    "radius " + this.mRadius    + "\n" + 
		    "centerpos[" + i + "] = " + centerSpcpos[i] + "\n" + 
		    "rad bias " + radSpcposBias + "\n" + 
		    "spbspace size " + subSpcSize + "\n" + 
		    "partmin " + partmin;
		System.out.println(ss);
		return(false);
	    }
	}
	return(true);
    }


    /*------------------------------------------------------------
     * 通信用
     *------------------------------------------------------------*/
    /*
     * 通信用のタグを返す
     */
    public static int getTAG() 
    { 
	return(Mpi2Enum.TAG_SPHERE_OBJ);	
    }

    /**
     * For Encodable interface (and ObjectDecoder object).
     */
    final static int vectorSize = Vector3.ZERO.sizeInDouble();
    final static int intvecSize = IntVec3.ZERO.sizeInDouble();
    final static int doubleSize =
    1 +  			// Object ID
    1 +				// material ID
    + intvecSize		// spcpos size
    + vectorSize 		// 中心
    + 1;			// 半径
    public int sizeInDouble() 
    {
	return doubleSize;
    }
    public void encodeInDouble(double[] array, int offset) 
    {
	// SphereObject を construct するのに必要な分のみ送信する
	array[offset] = (double)this.getObjectID();
	offset++;

	array[offset] = (double)(this.getMaterialID());     
	offset++;

	(this.peekSpcPos()).encodeInDouble(array, offset);
	offset += intvecSize;
    
	this.mCenter.encodeInDouble(array, offset);
	offset += vectorSize;

	array[offset] = this.mRadius;     
	offset++;
    }
    static class SphereObjectDecoder implements ObjectDecoder {
    	public int sizeInDouble() {
	    return doubleSize;
	}
    	public Object decodeFromDouble(double[] array, int offset) 
	{
	    // 	for(int i = 0; i < array.length; i++)
	    // 	{
	    // 	    System.out.println("DEBUG Decode " + array[i]);
	    // 	}

	    ObjectDecoder vec3  = Vector3.getObjectDecoder();
	    ObjectDecoder ivec3 = IntVec3.getObjectDecoder();
	
	    int objectID = (int)(array[offset]);
	    offset++;

	    int materialID = (int)(array[offset]);
	    offset++;

	    IntVec3 spcpos = (IntVec3)(ivec3.decodeFromDouble(array, offset));
	    offset += intvecSize;
    
	    Vector3 center = (Vector3)(vec3.decodeFromDouble(array, offset));
	    offset += vectorSize;
    
	    double  radius = array[offset];
	    offset++;
	    
	    // 	System.out.println("SphObj::decode mID = " + materialID + 
	    // 			   ", spcpos " + spcpos + "\nv1 " + v1 + 
	    // 			   ", v2 " + v2 + ", v3 " + v3

	    return(new SphereObject(objectID, materialID, spcpos,
				    center, radius));
	}
    }
    private final static ObjectDecoder odInstance = new SphereObjectDecoder();
    public static ObjectDecoder getObjectDecoder() {
	return odInstance;
    }
}
