// Aqsis
// Copyright (c) 1997 - 2001, Paul C. Gregory
//
// Contact: pgregory@aqsis.com
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public
// License as published by the Free Software Foundation; either
// version 2 of the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// General Public License for more details.
//
// You should have received a copy of the GNU General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

/**
 * Copyright (C) 2006-2007  NTT DATA CORPORATION
 * 
 * Version: 1.0.0 2007/04/01
 *  
 */
package net.cellcomputing.himawari.library;

import net.cellcomputing.himawari.library.types.CqMatrix;
import net.cellcomputing.himawari.library.SqTransformation;

import static net.cellcomputing.himawari.library.RiGlobal.*;


/**
 * 
 * グラフィックス状態の変換を定義するためのコンテナ。
 * 
 */
public strictfp class CqTransform extends CqMotionSpec<SqTransformation> implements IqTransform
{
	public static class Set {};
	public static class ConcatCurrent {};
	public static class SetCurrent {};
	
	private boolean		m_IsMoving = true;			///< Flag indicating this transformation describes a changing transform.
	private CqMatrix		m_StaticMatrix = new CqMatrix();		///< Matrix storing the transformation should there be no motion involved.
	private boolean		m_Handedness = true;		///< Current coordinate system orientation.　掌性を示す。trueなら右手系。
	
	
	//************************************************************************//
	//*** コンストラクタの定義
	//************************************************************************//
	
	/**
	 * デフォルトコンストラクタ。
	 */
	public CqTransform()  
	{
		super(new SqTransformation());
		m_IsMoving = false;
	}
	
//	/** Copy constructor.
//	*/
//	public CqTransform( final CqTransform From )
//	{
//	super( From );
//	
//	m_IsMoving = From.m_IsMoving;
//	m_StaticMatrix = From.m_StaticMatrix;
//	m_Handedness = From.m_Handedness;
//	/*    *this = From;
//	
//	// Get the state of the transformation at the last stack entry, and use this as the default value for new timeslots.
//	// if the previous level has motion specification, the value will be interpolated.
//	CqMatrix matOtoWLast;
//	TqBool handLast = TqFalse;
//	if ( !Transform_stack.empty() )
//	{
//	TqFloat time = QGetRenderContext()->Time();
//	matOtoWLast =  Transform_stack.front() ->matObjectToWorld( time );
//	handLast =  Transform_stack.front() ->GetHandedness( time );
//	}
//	
//	SqTransformation ct;
//	ct.m_Handedness = handLast;
//	ct.m_matTransform = matOtoWLast;
//	SetDefaultObject( ct );
//	*/
//	}
	
	/**
	 * コピーコンストラクタ。<br>
	 * メンバ変数のコピー以外にデフォルトオブジェクトの初期化を行う。
	 * 
	 * @param From	コピー元
	 */
	public CqTransform( final CqTransform From )
	{
		super(From);
		m_IsMoving = From.m_IsMoving;
		m_StaticMatrix.assignment( From.m_StaticMatrix );
		m_Handedness = From.m_Handedness;
		
		InitialiseDefaultObject( From );
	}
	
	
	/**
	 * 指定した移動に対して移動行列を適用して初期化するコンストラクタ。
	 * 
	 * @param From	移動オブジェクト
	 * @param time	フレーム時間
	 * @param matTrans	移動を示す4x4行列
	 * @param set	matTransを「設定」することを示すマーカークラス。
	 */
	public CqTransform( final CqTransform From, float time, final CqMatrix matTrans, final Set set )
	
	{
		super(From);
		m_IsMoving = From.m_IsMoving ;
		m_StaticMatrix.assignment( From.m_StaticMatrix );
		m_Handedness =  From.m_Handedness;
		
		SetTransform( time, matTrans );
	}
	
	/**
	 * 指定した移動に対して移動行列を結合して初期化するコンストラクタ。
	 * 
	 * @param From	移動オブジェクト
	 * @param time	フレーム時間
	 * @param matTrans	移動を示す4x4行列
	 * @param concatCurrent	matTransを「結合」することを示すマーカークラス。
	 */
	public CqTransform( final CqTransform From, float time, final CqMatrix matTrans, final ConcatCurrent concatCurrent )
	{
		super(From);  
		m_IsMoving = From.m_IsMoving;
		m_StaticMatrix.assignment( From.m_StaticMatrix );
		m_Handedness = From.m_Handedness;
		
		ConcatCurrentTransform( time, matTrans );
	}
	
	/**
	 * 指定した移動に対して、現在の座標系における移動行列を適用して初期化するコンストラクタ。
	 * 
	 * @param From	移動オブジェクト
	 * @param time	フレーム時間
	 * @param matTrans	移動を示す4x4行列
	 * @param setCurrent	matTransを「現在の座標系として設定」することを示すマーカークラス。
	 */
	public CqTransform( final CqTransform From, float time, final CqMatrix matTrans, final SetCurrent setCurrent )
	
	{
		super(From);
		m_IsMoving = From.m_IsMoving;
		m_StaticMatrix.assignment( From.m_StaticMatrix );
		m_Handedness =  From.m_Handedness;
		
		SetCurrentTransform( time, matTrans );
	}
	
	//************************************************************************//
	//*** メソッドの定義
	//************************************************************************//
	
	
	/**
	 * 指定時間のオブジェクトに対して、現在の座標系における移動（Transform）を設定する。
	 * 
	 * @param time	フレーム時間
	 * @param matTrans	移動を示す4x4行列
	 */
	public void	SetCurrentTransform( float time, final CqMatrix matTrans )
	{
		float det = matTrans.Determinant();
		boolean flip = ( !matTrans.fIdentity() && det < 0 );
		
		SqTransformation ct = new SqTransformation();
		ct.m_matTransform.assignment( matTrans );
		
		if ( QGetRenderContext().pconCurrent().fMotionBlock() )
		{
			AddTimeSlot( time, ct );
			m_IsMoving = true;
		}
		else
		{
			if( m_IsMoving )
			{
				AddTimeSlot( time, ct );
			}
			else
			{
				m_StaticMatrix.assignment( matTrans );
				m_Handedness = flip;
				ct.m_Handedness = flip;
				SetDefaultObject( new SqTransformation(ct) );
			}
		}
	}
	
	/**
	 * リセットする。<br>
	 * 引数 makeStatic がtrueの場合、全てのオブジェクトが破棄され、matとhandは内部的に保持される。<br>
	 * falseの場合、全てのオブジェクトが指定された（現在の座標系における）移動に設定される。
	 * 
	 * @param mat	移動を示す4x4行列
	 * @param hand	掌性
	 * @param makeStatic	全てのオブジェクトを破棄するかどうか。
	 */
	public void	ResetTransform(final CqMatrix mat, boolean hand, boolean makeStatic)
	{
		if( makeStatic )
		{
			Reset();
			m_IsMoving=false;
			m_StaticMatrix.assignment( mat );
			m_Handedness = hand;
		}
		else
		{
			int i;
			for(i=0; i<iTimes(); i++)
				SetCurrentTransform(Time(i), mat);
		}
	}
	
	final public void	ResetTransform(final CqMatrix mat, boolean hand)
	{
		ResetTransform(mat,hand,true);
	}
	
	
	private static CqMatrix matInt = new CqMatrix();
	/**
	 * 
	 * @see net.cellcomputing.himawari.library.IqTransform#matObjectToWorld(float)
	 */
	public 	CqMatrix	matObjectToWorld( float time )
	{
		if(m_IsMoving)
		{
			matInt = GetMotionObjectInterpolated( time ).m_matTransform;
			return ( new CqMatrix( matInt ) );
		}
		else
			return ( new CqMatrix( m_StaticMatrix ) );
	}
	
	/**
	 * 指定位置に格納されているフレーム時間を返す。
	 * @see net.cellcomputing.himawari.library.CqMotionSpec#Time(int)
	 */
	public	float	Time( int index )
	{
		return( super.Time( index ) );
	}
	
	/**
	 * タイムスロットに格納されている数を返す。
	 * @see net.cellcomputing.himawari.library.CqMotionSpec#iTimes()
	 */
	public	int	cTimes()
	{
		if(m_IsMoving)
			return( super.iTimes() );
		else
			return( 1 );
	}
	
	
	/**
	 * 指定時間の掌性を取得する。
	 * 
	 * @see net.cellcomputing.himawari.library.IqTransform#GetHandedness(float)
	 */
	public boolean GetHandedness( float time )
	{
		if( m_IsMoving )
			return ( GetMotionObject( time ).m_Handedness );
		else
			return ( m_Handedness );
	}
	
	//************************************************************************//
	//*** CqMotionSpecの抽象メソッドのオーバーライド
	//************************************************************************//
	
	/**
	 * 移動オブジェクトのクリア。
	 * 
	 * @param A	SqTransformationオブジェクト
	 */
	public void ClearMotionObject( SqTransformation A )
	{
		
	}	
	
	/**
	 * ２つの移動オブジェクトを結合する。<br>
	 * 具体的には移動は行列で示されるので、その積を求める。	
	 * 
	 * @param A	SqTransformationオブジェクト
	 * @param B	SqTransformationオブジェクト
	 * @return	結合結果
	 */
	public SqTransformation ConcatMotionObjects( final SqTransformation A, final SqTransformation B )
	{
		SqTransformation res = new SqTransformation();
		res.m_matTransform = A.m_matTransform.multiply(B.m_matTransform);
		boolean flip = ( B.m_matTransform.Determinant() < 0 );
		res.m_Handedness = (flip)? !A.m_Handedness : A.m_Handedness;
		return ( res );
	}
	
	/**
	 * ２つの移動オブジェクトの間を補完する。<br>
	 * 単純にAを返す。
	 * 
	 * @param Fraction	誤差比率（内部的には無視）
	 * @param A	補完対象より前の位置にある移動オブジェクト
	 * @param B	補完対象より後の位置にある移動オブジェクト（内部的には無視）
	 * @return	補完結果（A）
	 */
	public SqTransformation LinearInterpolateMotionObjects( float Fraction, final SqTransformation A, final SqTransformation B )
	{
		// TODO: Should we do anything with this???
		return ( A );
	}
	
	
	/**
	 * 
	 * デフォルトオブジェクトを初期化。<br>
	 * 指定されたCqTransformオブジェクトから最後のオブジェクトの状態を取得し、それをデフォルトオブジェクトとする。
	 * 
	 * @param From	初期化の材料となるCqTransformオブジェクト
	 */
	private void InitialiseDefaultObject( final CqTransform From )
	{
		// Get the state of the transformation at the last stack entry, and use this as the default value for new timeslots.
		// if the previous level has motion specification, the value will be interpolated.
		float time = QGetRenderContext().Time();
		CqMatrix matOtoWLast =  From.matObjectToWorld( time );
		boolean handLast =  From.GetHandedness( time );
		
		SqTransformation ct = new SqTransformation();
		ct.m_Handedness = handLast;
		ct.m_matTransform = matOtoWLast;
		SetDefaultObject( ct );
	}
	
	/**
	 * 指定時間のオブジェクトに対して、カメラ座標系における移動（Transform）を設定する。
	 * 
	 * @param time	フレーム時間
	 * @param matTrans	移動を示す4x4行列
	 */
	private void	SetTransform( float time, final CqMatrix matTrans )
	{
		float det = matTrans.Determinant();
		boolean flip = ( !matTrans.fIdentity() && det < 0 );
		CqMatrix matCtoW = QGetRenderContext().matSpaceToSpace("world", "camera",new CqMatrix(),new CqMatrix(), QGetRenderContext().Time());
		float camdet = matCtoW.Determinant();
		boolean camhand = ( !matCtoW.fIdentity() && camdet < 0 );
		
		if ( QGetRenderContext().pconCurrent().fMotionBlock() )
		{
			SqTransformation ct = new SqTransformation();
			ct.m_Handedness = (flip)? !camhand : camhand;
			ct.m_matTransform.assignment( matTrans );
			AddTimeSlot( time, ct );
			m_IsMoving = true;
		}
		else
		{
			// If not in a motion block, but we are moving, apply the transform to all keys.
			if( m_IsMoving )
			{
				CqMatrix mat0 = matObjectToWorld(Time(0));
				
				SqTransformation ct = new SqTransformation();
				ct.m_Handedness = (flip)? !camhand : camhand;
				boolean hand0 = ct.m_Handedness;
				ct.m_matTransform.assignment( matTrans );
				
				AddTimeSlot( Time(0), ct );
				int i;
				for(i=1; i<iTimes(); i++)
				{
					CqMatrix matOffset = mat0.multiply( matObjectToWorld(Time(i)).Inverse() );
					ct.m_matTransform = matOffset.multiply( matTrans );
					boolean flip2 = ( matOffset.Determinant() < 0 );
					ct.m_Handedness = (flip2)? !hand0 : hand0;
					AddTimeSlot( Time(i), new SqTransformation(ct) );
				}
			}
			else
			{
				m_StaticMatrix.assignment( matTrans );
				m_Handedness = (flip)? !camhand : camhand;
			}
		}
	}
	
	/**
	 * 指定時間のオブジェクトに対して引数matTransを結合する。<br>
	 * 
	 * @param time	フレーム時間
	 * @param matTrans	移動を示す4x4行列
	 */
	private void	ConcatCurrentTransform( float time, final CqMatrix matTrans )
	{
		float det = matTrans.Determinant();
		boolean flip = ( !matTrans.fIdentity() && det < 0 );
		
		SqTransformation ct = new SqTransformation();
		ct.m_matTransform.assignment( matTrans );
		ct.m_Handedness = (flip)? !m_Handedness : m_Handedness;
		
		// If we are actually in a motion block, and we already describe a moving transform,
		// concatenate this transform with the existing one at that time slot,
		// ConcatTimeSlot will take care of making sure that the matrix is initially set to the 
		// static matrix, as long as we ensure that the default is kept up to date.
		if ( QGetRenderContext().pconCurrent().fMotionBlock() )
		{
			ConcatTimeSlot( time, ct );
			m_IsMoving = true;
		}
		else
			// else, if we are moving, apply this transform at all time slots, otherwise apply to static matrix.
		{
			if( m_IsMoving )
				ConcatAllTimeSlots( ct );
			else
			{
				m_StaticMatrix.assignment( m_StaticMatrix.multiply( matTrans ) );
				m_Handedness = (flip)? !m_Handedness : m_Handedness;
				ct.m_Handedness = m_Handedness;
				SetDefaultObject( ct );
			}
		}
	}
	
//	/**
//	 * 
//	 * 指定時間の掌性を反転
//	 * 
//	 * @param time	フレーム時間
//	 */
//	private void FlipHandedness(float time )
//	{
//		m_Handedness = !m_Handedness;
//	}
	
	
	
	
	
	
}
