// 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.accessory.STLArrayList;
import net.cellcomputing.himawari.accessory.STLVector;
import net.cellcomputing.himawari.library.types.CqMatrix;
import net.cellcomputing.himawari.library.types.CqVector3D;
import net.cellcomputing.himawari.util.HimawariLogger;
import static java.lang.StrictMath.*;
import static net.cellcomputing.himawari.shaderexecenv.GlobalShaderExecEnv.*;
import static net.cellcomputing.himawari.library.CqStats.*;
import static net.cellcomputing.himawari.library.RiGlobal.*;
import static net.cellcomputing.himawari.library.EqSplitDir.*;
import static net.cellcomputing.himawari.library.EqEnvVars.*;
import static net.cellcomputing.himawari.library.EqIntIndex.*;
import static net.cellcomputing.himawari.library.EqCSGNodeType.*;

/**
 * 
 * Abstract base surface class, which provides interfaces to geometry.<BR>
 * 抽象的なベース表面のクラス。(そのクラスはインタフェースをジオメトリ提供します)
 * 
 * @author NTT DATA Corporation
 */
public abstract strictfp class CqBasicSurface extends IqSurface
{
	//***********************************************
	//** コンストラクタ
	//***********************************************
	/** 
	 * デフォルトコンストラクタ
	 */
	public CqBasicSurface()
	{
		m_fDiceable	= true ;
		m_fDiscard 	= false;
		m_EyeSplitCount = 0;
		m_pAttributes = null ;
		m_SplitDir = new EqSplitDir();
		m_SplitDir.setValue( SplitDir_U );

		// Set a refernce with the current attributes.
		//　現在のアトリビュートを設定。
		m_pAttributes = (CqAttributes)( QGetRenderContext().pattrCurrent() );
		m_pAttributes.AddRef();
		
		m_pTransform = QGetRenderContext().ptransCurrent();
		
		m_CachedBound = false;
		
		// If the current context is a solid node, and is a 'primitive', attatch this surface to the node.
		//　ソリッドコンテキストの中にいるかどうか。
		if ( QGetRenderContext().pconCurrent().isSolid() )
		{
			CqModeBlock pSolid = QGetRenderContext().pconCurrent();
			//自分自身がプリミティブノードをもつかどうか。
			if ( pSolid.pCSGNode().NodeType().getValue() == CSGNodeType_Primitive )
			{
				m_pCSGNode = pSolid.pCSGNode();
			}
			else
			{
				String objname =  "unnamed" ;
				final String[] pattrName = m_pAttributes.GetStringAttribute( "identifier", "name" );
				
				if ( pattrName != null )
					objname = pattrName[ 0 ];
				
				logger.warning("Primitive \"" + objname + "\" defined when not in 'Primitive' solid block \n");
			}
		}
		
		STATS_INC( GPR_allocated );
		STATS_INC( GPR_current );
		int cGprim = STATS_GETI( GPR_current );
		int cPeak = STATS_GETI( GPR_peak );
		STATS_SETI( GPR_peak, cGprim > cPeak ? cGprim : cPeak );
	}
	
	
	/**
	 * コピーコンストラクタ。
	 * @param From	コピー元
	 */
	public CqBasicSurface( final CqBasicSurface From )
	{
		this.assignment( From );
	
		// Set a reference with the donors attributes.
		m_pAttributes = From.m_pAttributes;
		m_pAttributes.AddRef();
		
		m_pTransform = From.m_pTransform;
		
		m_CachedBound = From.m_CachedBound;
		m_Bound = From.m_Bound;
		
		STATS_INC( GPR_allocated );
		STATS_INC( GPR_current );
		int cGprim = STATS_GETI( GPR_current );
		int cPeak = STATS_GETI( GPR_peak );
		STATS_SETI( GPR_peak, cGprim > cPeak ? cGprim : cPeak );
	}
	
	
	/**
	 * 
	 * デストラクタ 
	 * 
	 */
	public void destruct()
	{
		// Release our reference on the current attributes.
		if ( m_pAttributes != null)
			m_pAttributes.Release();
		
		m_pAttributes = null;
		STATS_DEC( GPR_current );
	}
	
	
	/** 
	 * GPrimをマイクロポリゴン化する
	 * @return 新しく作成されたマイクロポリゴン
	 */
	abstract public CqMicroPolyGridBase Dice();

	
	/** 
	 * GPrimを分割(スプリット)する。<BR>
	 * Split this GPrim into a number of other GPrims.
	 * 
	 * @param aSplits	 生成されたGPrimを持つベクタ。　<BR>A reference to a CqBasicSurface array to fill in with the new GPrim pointers.
	 * @return 分割されて生成されたGPrimの数	<BR>Integer count of new GPrims created.
	 */
	abstract public int	Split( STLVector<CqBasicSurface > aSplits );
	
	/** 
	 * 現時点でのサイズでGPrimのダイシングが可能かどうかを返す。
	 * Determine whether this GPrim is diceable at its current size.
	 */
	abstract public boolean	Diceable();
	
	
	/**
	 * 
	 */
	public void	Reset(){}
	
	
	/**
	 * 通っている表面が、モーションブラーにおけるフレームとして使用されているために有効かどうかを返す。<br>
	 * Determine whether the passed surface is valid to be used as a<BR>
     *  frame in motion blur for this surface.
	 * 
	 * @param pSurf	判断する面
	 * @return	true/false
	 */
	abstract public boolean	IsMotionBlurMatch( CqBasicSurface pSurf );
	
	
	/**
	 * 「識別子」「名前」など指定された場合は、プリミティブの型名を返す。<br>それ以外は"not named"　を返す。<BR>
	 * 	Return the name of this primitive surface if specified as<BR>
	 * 	a "identifier" "name" attribute, otherwise return "not named"
	 * 
	 * @return	strName	型名
	 * @see net.cellcomputing.himawari.library.IqSurface#strName()
	 */
	public String strName()
	{
		String[] pattrLightName = pAttributes().GetStringAttribute( "identifier", "name" );
		String strName =  "not named" ;
		
		if ( pattrLightName != null ) 
			strName = pattrLightName[ 0 ];
		
		return ( strName );
	}
	
	
	/** 
	 * 必要な標準シェーダ変数を求める。<BR>
	 * Work out which standard shader variables<BR> 
	 * 	this surface requires by looking at the shaders.
	 */
	public int	Uses()
	{
		int Uses = gDefUses | QGetRenderContext().pDDmanager().Uses();
		IqShader pshadSurface = pAttributes().pshadSurface(QGetRenderContextI().Time());
		IqShader pshadDisplacement = pAttributes().pshadDisplacement(QGetRenderContextI().Time());
		IqShader pshadAtmosphere = pAttributes().pshadAtmosphere(QGetRenderContextI().Time());
		
		if ( pshadSurface != null && pshadDisplacement != null && pshadAtmosphere != null )
			return ( 0 );
		
		if ( pshadSurface != null ) 		
			Uses |= pshadSurface.Uses();
		if ( pshadDisplacement != null ) 	
			Uses |= pshadDisplacement.Uses();
		if ( pshadAtmosphere != null )		
			Uses |= pshadAtmosphere.Uses();
		
		// Just a quick check, if it uses dPdu/dPdv must also use du/dv
		//　dPdu/dPdvを含むかどうかのチェック。
		if ( USES( Uses, EnvVars_dPdu ) ) 
			Uses |= ( 1 << EnvVars_du );
		if ( USES( Uses, EnvVars_dPdv ) ) 
			Uses |= ( 1 << EnvVars_dv );
		// Just a quick check, if it uses du/dv must also use u/v
		//　du/dvを使用するべきかどうかのチェック。
		if ( USES( Uses, EnvVars_du ) ) 
			Uses |= ( 1 << EnvVars_u );
		if ( USES( Uses, EnvVars_dv ) ) 
			Uses |= ( 1 << EnvVars_v );
		
		return ( Uses );
	}
	
	
	/* (non-Javadoc)
	 * @see net.cellcomputing.himawari.library.IqSurface#PreDice(int, int)
	 */
	public void	PreDice( int uDiceSize, int vDiceSize )
	{}
	
	
	/* (non-Javadoc)
	 * @see net.cellcomputing.himawari.library.IqSurface#DiceAll(net.cellcomputing.himawari.library.CqMicroPolyGrid)
	 */
	public int	DiceAll( CqMicroPolyGrid pGrid ){ 
		return(0); 
	}
	
	
	/* (non-Javadoc)
	 * @see net.cellcomputing.himawari.library.IqSurface#NaturalDice(net.cellcomputing.himawari.library.CqParameter, int, int, net.cellcomputing.himawari.library.IqShaderData)
	 */
	public void	NaturalDice( CqParameter pParameter, int uDiceSize, int vDiceSize, IqShaderData pData )
	{}
	
	
	/* (non-Javadoc)
	 * @see net.cellcomputing.himawari.library.IqSurface#PostDice(net.cellcomputing.himawari.library.CqMicroPolyGrid)
	 */
	public void	PostDice(CqMicroPolyGrid pGrid)
	{}
	
	
	/* (non-Javadoc)
	 * @see net.cellcomputing.himawari.library.IqSurface#PreSubdivide(java.util.Vector, boolean)
	 */
	public int	PreSubdivide( STLVector<CqBasicSurface> aSplits, boolean u )
	{
		return ( 0 );
	}
	
	
	/* (non-Javadoc)
	 * @see net.cellcomputing.himawari.library.IqSurface#NaturalSubdivide(net.cellcomputing.himawari.library.CqParameter, net.cellcomputing.himawari.library.CqParameter, net.cellcomputing.himawari.library.CqParameter, boolean)
	 */
	public void	NaturalSubdivide( CqParameter pParam, CqParameter pParam1, CqParameter pParam2, boolean u )
	{}
	
	
	/* (non-Javadoc)
	 * @see net.cellcomputing.himawari.library.IqSurface#PostSubdivide(java.util.Vector)
	 */
	public void	PostSubdivide(STLVector<CqBasicSurface> aSplits)
	{}
	
	
	/* (non-Javadoc)
	 * @see net.cellcomputing.himawari.library.IqSurface#RenderComplete()
	 */
	public void	RenderComplete()
	{}
	
	
	/**
	 * 表面の完成後、トリム曲線の準備をする。<br>
	 * Prepare the trim curve once the surface has been completed.
	 * 
	 */
	public void	PrepareTrimCurve()
	{}
	
	/**
	 * u方向のダイシングサイズの取得。<br>
	 * Get the value of the dice size in u, <br>
	 * determined during a Diceable() call 
	 * 
	 * @return	m_uDiceSize	u方向のダイスサイズ
	 */
	public int uDiceSize()
	{
		return( m_uDiceSize );
	}
	
	/**
	 * u方向のダイシングサイズの取得。<br>
	 * Get the value of the dice size in v, <br>
	 * determined during a Diceable() call
	 * 
	 * @return	m_vDiceSize	v方向のダイスサイズ
	 */
	public int vDiceSize()
	{
		return( m_vDiceSize );
	}
	
	/**
	 * 与えられた頂点の表面パラメータ値を取得。
	 * Get the surface paramter values for the given vertex index. 
	 * Used when constructing a surface using "Pz" point specification.
	 * 
	 * @param index	頂点インデックス
	 * @return	CqVector3D	表面パラメータ値
	 */
	public CqVector3D	SurfaceParametersAtVertex( int index )
	{
		return ( new CqVector3D( 0, 0, 0 ) );
	}
	
	
	/**
	 * GPriｍのアトリビュートの取得。<br>
	 * Get a pointer to the attributes state associated with this GPrim.<br>
	 * 
	 * @return	m_pAttributes	アトリビュート<Br>A pointer to a CqAttributes class.<br>
	 * @see net.cellcomputing.himawari.library.IqSurface#pAttributes()
	 */
	public IqAttributes pAttributes()
	{
		return (IqAttributes) ( m_pAttributes );
	}
	
	
	/**
	 * GPriｍのトランスフォームの取得。<br>
	 * Get a pointer to the transformation state associated with this GPrim.
	 * 
	 * @return m_pTransform トランスフォーム<br>A pointer to a CqTransform class.
	 * @see net.cellcomputing.himawari.library.IqSurface#pTransform()
	 */
	public IqTransform pTransform()
	{
		return ( (IqTransform)( m_pTransform ) );
	}
	
	
	/**
	 * 与えられた表面の表面パラメータをコピーする。<br>
	 * Copy the local surface parameters from the donor surface.
	 * 
	 * @param From	コピーする表面
	 */
	public	void	SetSurfaceParameters( final CqBasicSurface From )
	{
		// If we already have attributes, unreference them now as we don't need them anymore.
		if ( m_pAttributes != null) {
			m_pAttributes.Release();
		}
		
		// Now store and reference our new attributes.
		m_pAttributes = From.m_pAttributes;
		m_pAttributes.AddRef();
		m_pTransform = From.m_pTransform ;
		m_pCSGNode = From.m_pCSGNode;
	}
	
	
	/**
	 * GPrimがダイシング不可であると強制設定する。<br>
	 * Force this GPrim to be undiceable,<br> 
	 * usually if it crosses the epsilon and eye plane.
	 * 
	 */
	public	void	ForceUndiceable()
	{
		m_fDiceable = false; 
		m_EyeSplitCount++;
	}
	
	
	/**
	 * 
	 * Query if this primitive has been marked as undiceable by the eyesplit check.
	 * 
	 * @return	!m_fDiceable
	 */
	public boolean	IsUndiceable()
	{
		return ( !m_fDiceable );
	}
	
	
	/** 
	 * 
	 * Force this GPrim to be discarded, usually if it has been split too many times due to crossing the epsilon and eye planes..
	 *
	 */
	public	void	Discard()
	{
		m_fDiscard = true;
	}
	
	
	/**
	 *  
	 * Copy the information about splitting and dicing from the specified GPrim.
	 * 
	 * @param From A CqBasicSurface reference to copy the information from.
	 */
	public void CopySplitInfo( final CqBasicSurface From )
	{
		m_uDiceSize = From.m_uDiceSize;
		m_vDiceSize = From.m_vDiceSize;
		m_SplitDir.setValue( From.m_SplitDir.getValue() );
	}
	
	/**
	 * 
	 *  Determine whether this GPrim is to be discardrd.
	 *
	 *	@return m_fDiscard
	 */
	public boolean	fDiscard()
	{
		return ( m_fDiscard );
	}
	
	
	/**
	 * 
	 * Get the number of times this GPrim has been split because if crossing the epsilon and eye planes.
	 * 
	 * @return	m_EyeSplitCount
	 */
	public int	EyeSplitCount()
	{
		return ( m_EyeSplitCount );
	}
	
	
	/**
	 * 
	 * Set the number of times this GPrim has been split because if crossing the epsilon and eye planes.
	 * 
	 * @param EyeSplitCount
	 */
	public void	SetEyeSplitCount( int EyeSplitCount )
	{
		m_EyeSplitCount = EyeSplitCount;
	}
	
	
	/**
	 * Get the precalculated split direction.
	 * 
	 * @return	m_SplitDir.getValue()
	 */
	public int	SplitDir()
	{
		return ( m_SplitDir.getValue() );
	}
	
	
	/**
	 *  Set the precalculated split direction.
	 * 
	 * @param SplitDir
	 */
	public void	SetSplitDir( EqSplitDir SplitDir )
	{
//		m_SplitDir = SplitDir;
		m_SplitDir.setValue( SplitDir.getValue() );
	}
	
	
	/**
	 * Cache the calculated bound for further reference
	 * 
	 * @param pBound	The calculated bound in hybrid raster/camera space
	 */
	public void CacheRasterBound( CqBound pBound )
	{
		m_Bound.assignment( pBound ); 
		m_CachedBound = true;
	}
	
	
	/**
	 * Retrieve the cached bound. If it has never been cached then we
	 * throw an error as this is probably a bug.
	 * 
	 * @return	m_Bound	The object bound in hybrid raster/camera space
	 */
	public CqBound	GetCachedRasterBound()
	{
		if ( m_CachedBound == false && m_fDiceable )
		{
			logger.critical("No cached bound available");
		}
		
		return m_Bound;
	}
	
	/**
	 * 
	 * 
	 * 
	 * @return	m_CachedBound
	 */
	public boolean	fCachedBound()
	{
		return ( m_CachedBound );
	}
	
	
	/**
	 * 
	 * Adjust the bound of the quadric taking into account transformation motion blur.
	 * 
	 * @param B
	 * @return	Bm
	 */
	public CqBound	AdjustBoundForTransformationMotion( final CqBound B )
	{
		CqBound Bm = new CqBound( B );
		
		if( pTransform().cTimes() > 1 )
		{
			CqMatrix matCameraToObject0 = QGetRenderContext().matSpaceToSpace( "camera", "object", new CqMatrix(), pTransform().matObjectToWorld( pTransform().Time( 0 ) ), pTransform().Time( 0 ) );
			CqBound B0 = new CqBound( B );
			B0.Transform( matCameraToObject0 );
			
			int i;
			for( i = 1; i < pTransform().cTimes(); i++ )
			{
				CqBound Btx = new CqBound( B0 );
				CqMatrix matObjectToCameraT = QGetRenderContext().matSpaceToSpace( "object", "camera",new CqMatrix(), pTransform().matObjectToWorld( pTransform().Time( i ) ), pTransform().Time( i ) );
				Btx.Transform( matObjectToCameraT );
				Bm.Encapsulate( Btx );
			}
		}
		
		return ( Bm );
	}
	
	/**
	 * 
	 * 代入演算子のオーバーロード
	 * 
	 * @param From	代入するオブジェクト
	 * @return	this	代入されたオブジェクト
	 */
	public CqBasicSurface	assignment( final CqBasicSurface From )
	{
		m_fDiceable = From.m_fDiceable;
		m_EyeSplitCount = From.m_EyeSplitCount;
		m_fDiscard = From.m_fDiscard;
		
		SetSurfaceParameters( From );
		
		return ( this );
	}
	
	/**
	 * 自分自身が持つCSGノード
	 * 立体の一部でない場合はnullを返す。
	 * 
	 * @return	m_pCSGNode	属するCSGノード
	 */
	public CqCSGTreeNode pCSGNode()
	{
		return ( m_pCSGNode );
	}
	
	public boolean			m_fDiceable;		///< GPrimがダイシング可・不可を示すフラグ >>		Flag to indicate that this GPrim is diceable.
	public boolean			m_fDiscard;			///< GPrim要・不要かを示すフラグ >>	 Flag to indicate that this GPrim is to be discarded.
	public int				m_EyeSplitCount;	///< イプシロンと中心が交差している時にGPrimを分割する回数 >> 	The number of times this GPrim has been split because if crossing the epsilon and eye planes.
	
	protected CqAttributes 	m_pAttributes;	///< GPrimの属性状態 >>	 Pointer to the attributes state associated with this GPrim.
	protected CqTransform m_pTransform;		///< GPrimの変化状態 >> 	Pointer to the transformation state associated with this GPrim.
	
	protected int			m_uDiceSize;		///< ダイスサイズ(マイクロポリゴン) >> 	Calculated dice size to achieve an appropriate shading rate.
	protected int			m_vDiceSize;		///< ダイスサイズ(マイクロポリゴン)　>> 	Calculated dice size to achieve an appropriate shading rate.
	
	protected EqSplitDir	m_SplitDir;			///< GPrimの分割情報　>> 	Grim The direction to split this GPrim to achieve best results.
	protected boolean		m_CachedBound;		///< バウンディングボリュームが作られているかどうかを示す。>>	　Whether or not the bound has been cached
	protected CqBound		m_Bound = new CqBound();			///< オブジェクトのバウンド情報 >>	 The cached object bound
	protected CqCSGTreeNode	m_pCSGNode;		///<　立体の一部ならばプリミティブノードのオブジェクト、一部でないならnullを持つ >>	 Pointer to the 'primitive' CSG node this surface belongs to, NULL if not part of a solid.
	protected static float  m_fGridSize = (float)sqrt( 256.0 );   ///< standard sqrt(gridsize);
	protected HimawariLogger logger = HimawariLogger.getLogger();
	
}
