// 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.STLVector;
import net.cellcomputing.himawari.library.types.CqMatrix;
import net.cellcomputing.himawari.library.types.CqVector2D;
import net.cellcomputing.himawari.library.types.CqVector3D;
import net.cellcomputing.himawari.library.types.CqVector4D;

import static java.lang.StrictMath.sqrt;
import static net.cellcomputing.himawari.library.Float_h.FLT_EPSILON;
import static net.cellcomputing.himawari.library.Float_h.FLT_MAX;
import static net.cellcomputing.himawari.library.RiGlobal.*;
import static net.cellcomputing.himawari.library.EqSplitDir.*;
import static net.cellcomputing.himawari.library.types.PublicFunctions.*;

/**
 * 
 * 
 * 
 * @author NTT DATA Corporation
 */
public strictfp class CqSurfacePatchBilinear extends CqSurface{

	protected boolean	m_fHasPhantomFourthVertex;
	protected int	m_iInternalu;
	protected int	m_iInternalv;

	
	/**
	 * デフォルトコンストラクタ
	 */
	public CqSurfacePatchBilinear()
	{
		 super();
		 m_fHasPhantomFourthVertex = false;
		 m_iInternalu = -1 ;
		 m_iInternalv = -1 ;
	}
	
	/**
	 * コピーコンストラクタ
	 * 
	 * @param From	コピー元
	 */
	public CqSurfacePatchBilinear( CqSurfacePatchBilinear From )
	{
		super( From );
	    this.assignment( From );
	 }
	
	/* (non-Javadoc)
	 * @see net.cellcomputing.himawari.library.CqBasicSurface#destruct()
	 */
	public void	destruct()
	{}

	/**
	 * 代入演算子のオーバーロード
	 * 
	 * @param From	代入元
	 * @return	自分自身
	 */
	public CqSurfacePatchBilinear assignment( CqSurfacePatchBilinear From )
	{
	    super.assignment( From );

	    m_fHasPhantomFourthVertex = From.m_fHasPhantomFourthVertex;
	    m_iInternalu = From.m_iInternalu;
	    m_iInternalv = From.m_iInternalv;
	    return ( this );
	}


	/**
	 * fHasPhantomFourthVertexのセッタ
	 * 
	 * @param fHasPhantomFourthVertex　セットする値
	 */
	public void	SetfHasPhantomFourthVertex(boolean fHasPhantomFourthVertex)
    {
        m_fHasPhantomFourthVertex = fHasPhantomFourthVertex;
    }

	/**
	 * fHasPhantomFourthVertexのゲッタ
	 * 
	 * @return	現在の値
	 */
	public boolean fHasPhantomFourthVertex()
    {
        return(m_fHasPhantomFourthVertex);
    }

	/**
	 */
	public void GenNormals(){
	}
	
	
	/**
	 * Return the boundary extents in camera space of the surface patch
	 * @see net.cellcomputing.himawari.library.IqSurface#Bound()
	 */
	public	CqBound	Bound()
	{
	    assert( null != P() );

	    // Get the boundary in camera space.
	    CqVector3D	vecA = new CqVector3D( FLT_MAX, FLT_MAX, FLT_MAX );
	    CqVector3D	vecB = new CqVector3D( -FLT_MAX, -FLT_MAX, -FLT_MAX );
	    int i;
	    CqVector3D	vecV = new CqVector3D();
	    for ( i = 0; i < ( m_fHasPhantomFourthVertex ? 3 : 4 ); i++ )
	    {
	    	//nttdata 高速化
//	        CqVector3D	vecV = new CqVector3D( (CqVector4D)(P().pValue( i )[0]) );
	    	//CqVector3D	vecV = new CqVector3D( (CqVector4D)(P().pValue_get( i , 0 )) );
	    	vecV.assignment( (CqVector4D)(P().pValue_get( i , 0 )) );
	        if ( vecV.x < vecA.x ) vecA.x( vecV.x );
	        if ( vecV.y < vecA.y ) vecA.y( vecV.y );
	        if ( vecV.x > vecB.x ) vecB.x( vecV.x );
	        if ( vecV.y > vecB.y ) vecB.y( vecV.y );
	        if ( vecV.z < vecA.z ) vecA.z( vecV.z );
	        if ( vecV.z > vecB.z ) vecB.z( vecV.z );
	    }
	    CqBound	B = new CqBound();
	    B.vecMin().assignment( vecA );
	    B.vecMax().assignment( vecB );
	    return ( AdjustBoundForTransformationMotion( B ) );
	}
	
	
	/**
	 * Determine whether or not the patch is diceable
	 * @see net.cellcomputing.himawari.library.CqBasicSurface#Diceable()
	 */
	public boolean	 Diceable()
	{
	    assert( null != P() );

	    // If the cull check showed that the primitive cannot be diced due to crossing the e and hither planes,
	    // then we can return immediately.
	    if ( !m_fDiceable )
	        return ( false );

	    // Otherwise we should continue to try to find the most advantageous split direction, OR the dice size.
	    CqMatrix matCtoR = QGetRenderContext().matSpaceToSpace( "camera", "raster", new CqMatrix(), new CqMatrix(), QGetRenderContext().Time() );

	    // Convert the control hull to raster space.
	    CqVector2D avecHull[] = new CqVector2D[ 4 ];
	    
	    int i = 0;
	    float ShadingRate = pAttributes().GetFloatAttribute( "System", "ShadingRate" ) [ 0 ];

	    for ( i = 0; i < 4; i++ )
	        //avecHull[ i ] = new CqVector2D( matCtoR.multiply( (CqVector4D)(P().pValue( i )[0]) ) );
	    	avecHull[ i ] = new CqVector2D( matCtoR.multiply( (CqVector4D)(P().pValue_get( i ,0)) ) );

	    float uLen = 0;
	    float vLen = 0;

	    CqVector2D	Vec1 = new CqVector2D( avecHull[ 1 ].sub( avecHull[ 0 ] ) );
	    CqVector2D	Vec2 = new CqVector2D( avecHull[ 3 ].sub( avecHull[ 2 ] ) );
	    uLen = ( Vec1.Magnitude2() > Vec2.Magnitude2() ) ? Vec1.Magnitude2() : Vec2.Magnitude2();

	    Vec1.assignment( avecHull[ 2 ].sub( avecHull[ 0 ] ) );
	    Vec2.assignment( avecHull[ 3 ].sub( avecHull[ 1 ] ) );
	    vLen = ( Vec1.Magnitude2() > Vec2.Magnitude2() ) ? Vec1.Magnitude2() : Vec2.Magnitude2();

		if( ShadingRate>0 ){
			uLen = (float) sqrt( uLen  / ShadingRate);
			vLen = (float) sqrt( vLen  / ShadingRate);
		}
		else{
			uLen = 0;
			vLen = 0;
		}
		
		m_SplitDir = ( uLen > vLen ) ? new EqSplitDir(SplitDir_U) : new EqSplitDir(SplitDir_V);
		
	    // TODO: Should ensure powers of half to prevent cracking.
	    uLen = Math.max( Math.round( uLen ), 1 );
	    vLen = Math.max( Math.round( vLen ), 1 );

	    m_uDiceSize = (int)( uLen );
	    m_vDiceSize = (int)( vLen );
	    
		// Ensure power of 2 to avoid cracking
	    int[] binary = pAttributes().GetIntegerAttribute( "dice", "binary" );
	    if ( binary != null && binary[0] != 0 )
	    {
	        m_uDiceSize = (int)CEIL_POW2( m_uDiceSize );
	        m_vDiceSize = (int)CEIL_POW2( m_vDiceSize );
	    }

	    if ( uLen < FLT_EPSILON || vLen < FLT_EPSILON )
	    {
	        m_fDiscard = true;
	        return ( false );
	    }

	    float gs = 16.0f;
	    float[] poptGridSize = QGetRenderContext().optCurrent().GetFloatOption( "System", "SqrtGridSize" );
	    if( null != poptGridSize )
	        gs = poptGridSize[0];

		float gs2 = gs*gs;
		if( m_uDiceSize > gs2 || m_vDiceSize > gs2 || (m_uDiceSize * m_vDiceSize) > gs2 )
			return false;

	    return ( true );
	}

	/**
	 * Determine whether the passed surface is valid to be used as a
	 *  frame in motion blur for this surface.
	 * @see net.cellcomputing.himawari.library.CqBasicSurface#IsMotionBlurMatch(net.cellcomputing.himawari.library.CqBasicSurface)
	 */
	public boolean	IsMotionBlurMatch( CqBasicSurface pSurf )
    {
        return( false );
    }

	public	int	cUniform() 
    {
        return ( 1 );
    }
	public	int	cVarying() 
    {
        return ( 4 );
    }
	public	int	cVertex() 
    {
        return ( 4 );
    }
	public	int	cFaceVarying() 
    {
        // TODO Must work out what this value should be.
        return ( 1 );
    }

	
	/* (non-Javadoc)
	 * @see net.cellcomputing.himawari.library.CqBasicSurface#Split(net.cellcomputing.himawari.accessory.STLVector)
	 */
	public	int	Split( STLVector<CqBasicSurface> aSplits )
	{
	    aSplits.add( new CqSurfacePatchBilinear() );
	    aSplits.add( new CqSurfacePatchBilinear() );

	    if ( m_fHasPhantomFourthVertex )
	    {
	        aSplits.add( new CqSurfacePatchBilinear() );
	        aSplits.add( new CqSurfacePatchBilinear() );
	    }
	    boolean direction = SplitDir() == SplitDir_U;
	    boolean opposite = !direction;
	    int i;
	    for ( i = 0; i < ( m_fHasPhantomFourthVertex ? 4 : 2 ); i++ )
	    {
	        aSplits.get( i ).SetSurfaceParameters( this );
	        aSplits.get( i ).SetSplitDir( direction ? new EqSplitDir(SplitDir_V) : new EqSplitDir(SplitDir_U) );
	        aSplits.get( i ).SetEyeSplitCount( EyeSplitCount() );
	        aSplits.get( i ).m_fDiceable = true;
	    }

	    // Iterate through any use parameters subdividing and storing the second value in the target surface.

	    for ( CqParameter iUP :m_aUserParams)
	    {
	        CqParameter pNewA = ( iUP ).Clone();
	        CqParameter pNewB = ( iUP ).Clone();
	        ( iUP ).Subdivide( pNewA, pNewB, direction, this );

	        if ( m_fHasPhantomFourthVertex )
	        {
	            CqParameter pNewC = pNewA.Clone();
	            CqParameter pNewD = pNewA.Clone();
	            CqParameter pNewE = pNewB.Clone();
	            CqParameter pNewF = pNewB.Clone();
	            pNewA.Subdivide( pNewC, pNewD, opposite, this );
	            pNewB.Subdivide( pNewE, pNewF, opposite, this );

	            ((CqSurface)( aSplits.get( 0 ) )).AddPrimitiveVariable( pNewC );
	            ((CqSurface)( aSplits.get( 1 ) )).AddPrimitiveVariable( pNewD );
	            ((CqSurface)( aSplits.get( 2 ) )).AddPrimitiveVariable( pNewE );
	            ((CqSurface)( aSplits.get( 3 ) )).AddPrimitiveVariable( pNewF );

	            pNewA = null;
	            pNewB = null;
	        }
	        else
	        {
	        	((CqSurface)( aSplits.get( 0 ) )).AddPrimitiveVariable( pNewA );
	        	((CqSurface)( aSplits.get( 1 ) )).AddPrimitiveVariable( pNewB );
	        }
	    }

	    if ( m_fHasPhantomFourthVertex )
	    {
	    	//aSplits.pop_back();
//	        aSplits.remove(aSplits.size()); 2005/12/22 nttdata
	        aSplits.remove(aSplits.lastElement());
	        ((CqSurfacePatchBilinear)( aSplits.get( 0 ) ) ).m_fHasPhantomFourthVertex = false;
	        ((CqSurfacePatchBilinear)( aSplits.get( 1 ) ) ).m_fHasPhantomFourthVertex = true;
	        ((CqSurfacePatchBilinear)( aSplits.get( 2 ) ) ).m_fHasPhantomFourthVertex = true;

	        return ( 3 );
	    }
	    else
	    {
	    	((CqSurfacePatchBilinear)( aSplits.get( 0 ) ) ).m_fHasPhantomFourthVertex = false;
	    	((CqSurfacePatchBilinear)( aSplits.get( 1 ) ) ).m_fHasPhantomFourthVertex = false;

	        return ( 2 );
	    }
	}
	
	
	/**
	 * Split the patch into smaller patches.
	 * @see net.cellcomputing.himawari.library.IqSurface#PreSubdivide(net.cellcomputing.himawari.accessory.STLVector, boolean)
	 */
	public	int	PreSubdivide( STLVector< CqBasicSurface> aSplits, boolean u )
	{
	    aSplits.add( new CqSurfacePatchBilinear() );
	    aSplits.add( new CqSurfacePatchBilinear() );

	    return ( 2 );
	}
	
	
	/* (non-Javadoc)
	 * @see net.cellcomputing.himawari.library.IqSurface#PostDice(net.cellcomputing.himawari.library.CqMicroPolyGrid)
	 */
	public void	PostDice(CqMicroPolyGrid pGrid)
    {
        if(m_fHasPhantomFourthVertex)
            pGrid.SetfTriangular(true);
    }

}
