// 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 static java.lang.Math.abs;
import static java.lang.Math.min;
import static net.cellcomputing.himawari.library.RiGlobal.QGetRenderContext;
import net.cellcomputing.himawari.accessory.STLVector;
import net.cellcomputing.himawari.accessory.primitive.p_float;
import net.cellcomputing.himawari.library.types.CqVector2D;
import net.cellcomputing.himawari.library.types.CqVector3D;

/**
 * 
 * [V}CN|SπΫΆ·ιNXB
 * 
 * @author NTT DATA Corporation
 */
public strictfp class CqMicroPolygonMotion extends CqMicroPolygon
{
	private CqBoundList m_BoundList  = new CqBoundList(); ///< List of bounds to get a tighter fit.
	private boolean  m_BoundReady;       ///< Flag indicating the boundary has been initialised.
	private STLVector<p_float> m_Times  = new STLVector<p_float>(p_float.class);
	private STLVector<CqMovingMicroPolygonKey> m_Keys  = new STLVector<CqMovingMicroPolygonKey>(CqMovingMicroPolygonKey.class);
	private boolean  m_fTrimmed;        ///< Flag indicating that the MPG spans a trim curve.
	
	
	public CqMicroPolygonMotion(){
		super();
		m_BoundReady = false;
	}
	
	
	/** 
	 * Store the vectors of the micropolygon at the specified shutter time.
	 * @param vA 3D Vector.
	 * @param vB 3D Vector.
	 * @param vC 3D Vector.
	 * @param vD 3D Vector.
	 * @param time Float shutter time that this MPG represents.
	 */
	public void AppendKey( final CqVector3D vA, final CqVector3D vB, final CqVector3D vC, final CqVector3D vD, float time )
	{
	    // Add a new planeset at the specified time.
	    CqMovingMicroPolygonKey pMP = new CqMovingMicroPolygonKey( vA, vB, vC, vD );
	    m_Times.add( new p_float(time) );
	    m_Keys.add( pMP );
	    if ( m_Times.size() == 1 )
	        m_Bound = new CqBound( pMP.GetTotalBound() );
	    else 
	        m_Bound.Encapsulate( pMP.GetTotalBound() );
	}
	
	public void DeleteVariables( boolean all )
	{}
	
	
	//yόzaqsis1.01Α****************************************************
	// Overrides from CqMicroPolygon
//	/** 
//	 * Determinde whether the 2D point specified lies within this micropolygon.
//	 * @param vecP 2D vector to test for containment.
//	 * @param Depth Place to put the depth if valid intersection.
//	 * @param time The frame time at which to check containment.
//	 * @return Boolean indicating sample hit.
//	 */
//	public boolean fContains( final CqVector2D vecP, p_float Depth, float time )
//	{
//	    int iIndex = 0;
//	    float Fraction = 0.0f;
//	    boolean Exact = true;
//
//	    if ( time > m_Times.firstElement().value )
//	    {
//	        if ( time >= m_Times.lastElement().value )
//	            iIndex = m_Times.size() - 1;
//	        else
//	        {
//	            // Find the appropriate time span.
//	            iIndex = 0;
//	            while ( time >= m_Times.get( iIndex + 1 ).value )
//	                iIndex += 1;
//	            Fraction = ( time - m_Times.get(  iIndex ).value ) / ( m_Times.get(  iIndex + 1 ).value - m_Times.get(  iIndex ).value );
//	            Exact = ( m_Times.get(  iIndex ).value == time );
//	        }
//	    }
//
//	    if ( Exact )
//	    {
//	        CqMovingMicroPolygonKey pMP1 = m_Keys.get( iIndex );
//	        return ( pMP1.fContains( vecP, Depth, time ) );
//	    }
//	    else
//	    {
//	        float F1 = 1.0f - Fraction;
//	        CqMovingMicroPolygonKey pMP1 = m_Keys.get( iIndex );
//	        CqMovingMicroPolygonKey pMP2 = m_Keys.get( iIndex + 1 );
//	        // Check against each line of the quad, if outside any then point is outside MPG, therefore early exit.
//	        float r1, r2, r3, r4;
//	        float x = vecP.x, y = vecP.y;
//	        float x0 = ( F1 * pMP1.m_Point0.x ) + ( Fraction * pMP2.m_Point0.x ),
//	                y0 = ( F1 * pMP1.m_Point0.y ) + ( Fraction * pMP2.m_Point0.y ),
//	                x1 = ( F1 * pMP1.m_Point1.x ) + ( Fraction * pMP2.m_Point1.x ),
//	                y1 = ( F1 * pMP1.m_Point1.y ) + ( Fraction * pMP2.m_Point1.y );
//	        float x0_hold = x0;
//	        float y0_hold = y0;
//	        if ( ( r1 = ( y - y0 ) * ( x1 - x0 ) - ( x - x0 ) * ( y1 - y0 ) ) <= 0 ) return ( false );
//	        x0 = x1;
//	        y0 = y1;
//	        x1 = ( F1 * pMP1.m_Point2.x ) + ( Fraction * pMP2.m_Point2.x );
//	        y1 = ( F1 * pMP1.m_Point2.y ) + ( Fraction * pMP2.m_Point2.y );
//	        if ( ( r2 = ( y - y0 ) * ( x1 - x0 ) - ( x - x0 ) * ( y1 - y0 ) ) <= 0 ) return ( false );
//	        x0 = x1;
//	        y0 = y1;
//	        x1 = ( F1 * pMP1.m_Point3.x ) + ( Fraction * pMP2.m_Point3.x );
//	        y1 = ( F1 * pMP1.m_Point3.y ) + ( Fraction * pMP2.m_Point3.y );
//	        if ( ( r3 = ( y - y0 ) * ( x1 - x0 ) - ( x - x0 ) * ( y1 - y0 ) ) < 0 ) return ( false );
//
//	        // Check for degeneracy.
//	        if ( ! ( x1 == x0_hold && y1 == y0_hold ) )
//	            if ( ( r4 = ( y - y1 ) * ( x0_hold - x1 ) - ( x - x1 ) * ( y0_hold - y1 ) ) < 0 ) return ( false );
//
//	        CqVector3D vecN = ( pMP1.m_N.mul( F1 ) ) .add( ( pMP2.m_N.mul( Fraction ) ) );
//	        float D = ( F1 * pMP1.m_D ) + ( Fraction * pMP2.m_D );
//	        Depth.value = ( D - ( vecN.x * vecP.x ) - ( vecN.y * vecP.y ) ) / vecN.z;
//
//	        return ( true );
//	    }
//	}
	//******************************************************************
	
	
	public void CalculateTotalBound()
	{
		assert( null != m_Keys.get( 0 ) );

		m_Bound.assignment( m_Keys.get( 0 ).GetTotalBound() );
		for ( CqMovingMicroPolygonKey i : m_Keys )
			m_Bound.Encapsulate( i.GetTotalBound() );
	}
	
	public CqBound GetTotalBound()
	{
		return new CqBound( m_Bound );
	}
	
	public int cSubBounds()
	{
		if ( !m_BoundReady )
			BuildBoundList();
		return ( m_BoundList.Size() );
	}
	
	public CqBound SubBound( int iIndex, p_float time )
	{
		if ( !m_BoundReady )
			BuildBoundList();
		assert( iIndex < m_BoundList.Size() );
		time.value = m_BoundList.GetTime( iIndex );
		return ( m_BoundList.GetBound( iIndex ) );
	}
	
	/**
	 *  Calculate a list of 2D bounds for this micropolygon,
	 */
	public void BuildBoundList()
	{
	    float opentime = QGetRenderContext() .optCurrent().GetFloatOptionIndex( "System", "Shutter" , 0 ).value;
	    float closetime = QGetRenderContext() .optCurrent().GetFloatOptionIndex( "System", "Shutter" , 1 ).value;
	    float shadingrate = pGrid() .pAttributes() .GetFloatAttribute( "System", "ShadingRate" ) [ 0 ];

		m_BoundList.Clear();

	    assert( null != m_Keys.get( 0 ) );

		// compute an approximation of the distance travelled in raster space,
		// we use this to guide how many sub-bounds to calcuate. note, it's much
		// better for this to be fast than accurate, it's just a guide.
		float dx = abs(m_Keys.firstElement().m_Point0.x - m_Keys.lastElement().m_Point0.x);
		float dy = abs(m_Keys.firstElement().m_Point0.y - m_Keys.lastElement().m_Point0.y);
		int d;
		if( shadingrate>0 ) {
			d = (int)((dx + dy) / shadingrate) + 1; // d is always >= 1
		}
		else{
			d = 1;
		}

		int timeRanges = CqBucket.NumTimeRanges();
		int divisions = min(d, timeRanges);
		float dt = (closetime - opentime) / divisions;
		float time = opentime + dt;
		int startKey = 0;
		int endKey = 1;
		CqBound bound = new CqBound( m_Keys.get(startKey).GetTotalBound() );

	    m_BoundList.SetSize( divisions );

		// create a bound for each time period.
		for(int i = 0; i < divisions; i++)
		{
			// find the fist key with a time greater than our end time.
			while(time > m_Times.get(endKey).value && endKey < m_Keys.size() - 1)
				++endKey;

			// interpolate between this key and the previous to get the
			// bound at our end time.
			int endKey_1 = endKey - 1;
			final CqBound end0 = m_Keys.get(endKey_1).GetTotalBound();
			float end0Time = m_Times.get(endKey_1).value;
			final CqBound end1 = m_Keys.get(endKey).GetTotalBound();
			float end1Time = m_Times.get(endKey).value;

			float mix = (time - end0Time) / (end1Time - end0Time);
			CqBound mid = new CqBound(end0);
			mid.vecMin() .assignAdd( (end1.vecMin().sub( end0.vecMin() )).mul( mix ) );
			mid.vecMax() .assignAdd( (end1.vecMax().sub( end0.vecMax() )).mul( mix ) );

			// combine with our starting bound.
			bound.Encapsulate(mid);

			// now combine the bound with any keys that fall between our start
			// and end times.
			while(startKey < endKey_1)
			{
				startKey++;
				bound.Encapsulate(m_Keys.get(startKey).GetTotalBound());
			}

			m_BoundList.Set( i, bound, time - dt );

			// now set our new start to our current end ready for the next bound.
			bound.assignment( mid );
			time += dt;
		}

		m_BoundReady = true;

	}
	
	//yόz aqsis 1.01ΟX***************************************************
	/**
	 *  Sample the specified point against the MPG at the specified time.
	 * @param vecSample 2D vector to sample against.
	 * @param time Shutter time to sample at.
	 * @param D Storage for depth if valid hit.
	 * @return Boolean indicating smaple hit.
	 */
//	public boolean Sample( final CqVector2D vecSample, p_float D, float time )
//	{

	public boolean Sample( final SqSampleData sample, p_float D, float time, boolean UsingDof )
	{
		//aqsis1.01 Α****************************************************************
//	    if ( fContains( vecSample, D, time ) )
//	    {
//	        // Now check if it is trimmed.
//	        if ( IsTrimmed() )
//	        {
//	            // Get the required trim curve sense, if specified, defaults to "inside".
//
//	            ///TODO: Implement trimming of motion blurred surfaces!
//	            			const CqString * pattrTrimSense = pGrid() .pAttributes() .GetStringAttribute( "trimcurve", "sense" );
//	            			CqString strTrimSense( "inside" );
//	            			if ( pattrTrimSense != 0 ) strTrimSense = pattrTrimSense[ 0 ];
//	            			TqBool bOutside = strTrimSense == "outside";
//
//	            			CqVector2D vecUV = ReverseBilinear( vecSample );
//	             
//	            			float u, v;
//	             
//	            			pGrid() .u() .GetFloat( u, m_Index );
//	            			pGrid() .v() .GetFloat( v, m_Index );
//	            			CqVector2D uvA( u, v );
//	             
//	            			pGrid() .u() .GetFloat( u, m_Index + 1 );
//	            			pGrid() .v() .GetFloat( v, m_Index + 1 );
//	            			CqVector2D uvB( u, v );
//
//	            			pGrid() .u() .GetFloat( u, m_Index + pGrid() .uGridRes() + 1 );
//	            			pGrid() .v() .GetFloat( v, m_Index + pGrid() .uGridRes() + 1 );
//	            			CqVector2D uvC( u, v );
//	             
//	            			pGrid() .u() .GetFloat( u, m_Index + pGrid() .uGridRes() + 2 );
//	            			pGrid() .v() .GetFloat( v, m_Index + pGrid() .uGridRes() + 2 );
//	            			CqVector2D uvD( u, v );
//	             
//	            			CqVector2D vR = BilinearEvaluate( uvA, uvB, uvC, uvD, vecUV.x(), vecUV.y() );
//	             
//	            			if ( pGrid() .pSurface() .bCanBeTrimmed() && pGrid() .pSurface() .bIsPointTrimmed( vR ) && !bOutside )
//	            				return ( TqFalse );
//	        }
	        //*****************************************************************************
	
		final CqVector2D vecSample = sample.m_Position;
		CqHitTestCache hitTestCache = new CqHitTestCache();
		CqVector3D[] points = new CqVector3D[4];
		for( int i=0; i<4; i++ )	points[i] = new CqVector3D();
		
		// Calculate the position in time of the MP.
		int iIndex = 0;
	    float Fraction = 0.0f;
	    boolean Exact = true;

	    if ( time > m_Times.firstElement().value )
	    {
	        if ( time >= m_Times.lastElement().value )
	            iIndex = m_Times.size() - 1;
	        else
	        {
	            // Find the appropriate time span.
	            iIndex = 0;
	            while ( time >= m_Times.get( iIndex + 1 ).value )
	                iIndex += 1;
	            Fraction = ( time - m_Times.get( iIndex ).value ) / ( m_Times.get( iIndex + 1 ).value - m_Times.get( iIndex ).value );
	            Exact = ( m_Times.get( iIndex ).value == time );
	        }
	    }

	    // Then adjust for DoF if necessary and setup a hittestcache structure for hit testing.
		if ( Exact )
	    {
	        CqMovingMicroPolygonKey pMP1 = m_Keys.get( iIndex );
			points[1].assignment( pMP1.m_Point0 );
			points[2].assignment( pMP1.m_Point1 );
			points[3].assignment( pMP1.m_Point2 );
			points[0].assignment( pMP1.m_Point3 ); 
	    }
	    else
	    {
	        float F1 = 1.0f - Fraction;
	        CqMovingMicroPolygonKey pMP1 = m_Keys.get( iIndex );
	        CqMovingMicroPolygonKey pMP2 = m_Keys.get( iIndex + 1 );
			points[1].assignment(( pMP1.m_Point0.mul( F1 ) ).add( pMP2.m_Point0.mul( Fraction ) ));
			points[2].assignment(( pMP1.m_Point1.mul( F1 ) ).add( pMP2.m_Point1.mul( Fraction ) ));
			points[3].assignment(( pMP1.m_Point2.mul( F1 ) ).add( pMP2.m_Point2.mul( Fraction ) ));
			points[0].assignment(( pMP1.m_Point3.mul( F1 ) ).add( pMP2.m_Point3.mul( Fraction ) ));
	    }

		if(UsingDof)
		{
			CqVector2D coc = new CqVector2D(); 
			QGetRenderContext().GetCircleOfConfusion(points[0].z, coc);
			points[0].x(points[0].x - ( coc.x * sample.m_DofOffset.x ));
			points[0].y(points[0].y - ( coc.y * sample.m_DofOffset.y ));

			QGetRenderContext().GetCircleOfConfusion(points[1].z, coc);
			points[1].x(points[1].x - ( coc.x * sample.m_DofOffset.x ));
			points[1].y(points[1].y - ( coc.y * sample.m_DofOffset.y ));

			QGetRenderContext().GetCircleOfConfusion(points[2].z, coc);
			points[2].x(points[2].x - ( coc.x * sample.m_DofOffset.x ));
			points[2].y(points[2].y - ( coc.y * sample.m_DofOffset.y ));

			QGetRenderContext().GetCircleOfConfusion(points[3].z, coc);
			points[3].x(points[3].x - ( coc.x * sample.m_DofOffset.x ));
			points[3].y(points[3].y - ( coc.y * sample.m_DofOffset.y ));
		}
		CacheHitTestValues( hitTestCache, points);

	    if ( fContains(vecSample, D, time) )
	    {
	        // Now check if it is trimmed.
	        if ( IsTrimmed() )
	        {
	            // Get the required trim curve sense, if specified, defaults to "inside".
	            // TODO: Implement trimming of motion blurred surfaces!
	        }

	        //-----------------------------------------------
	        if ( pGrid() .fTriangular() )
	        {
				CqVector3D vA = new CqVector3D(), vB = new CqVector3D();
				pGrid().TriangleSplitPoints( vA, vB, time );
				float Ax = vA.x;
				float Ay = vA.y;
				float Bx = vB.x;
				float By = vB.y;

				float v = ( Ay - By ) * vecSample.x + ( Bx - Ax ) * vecSample.y + ( Ax * By - Bx * Ay );
				if( v <= 0 )
					return ( false );
	        }
	        return ( true );
	    }
	    else
	        return ( false );

	}
	
	public void MarkTrimmed()
	{
		m_fTrimmed = true;
	}
	
	public boolean IsMoving()
	{
		return true;
	}
	
	//yόzaqsis1.01ΗΑ***************************
	public final boolean IsDegenerate()
	{
		return ( m_Keys.get( 0 ).IsDegenerate() );
	}
	//************************************************
	
	public int cKeys()
	{
		return(m_Keys.size());
	}
	
	public float Time(int index)
	{
		assert(index < m_Times.size());
		return(m_Times.get(index).value);
	}
	
	public CqMovingMicroPolygonKey Key(int index)
	{
		assert(index < m_Keys.size());
		return(m_Keys.get(index));
	}
	
//	private CqMicroPolygonMotion( final CqMicroPolygonMotion From )
//	{}
	
}
