// 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 net.cellcomputing.himawari.library.RiGlobal.QGetRenderContext;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import net.cellcomputing.himawari.accessory.Valarray;
import net.cellcomputing.himawari.accessory.primitive.p_float;
import net.cellcomputing.himawari.accessory.primitive.p_int;
import net.cellcomputing.himawari.library.types.CqMatrix;
import net.cellcomputing.himawari.library.types.CqVector3D;
import net.cellcomputing.himawari.library.types.PublicFunctions;
import net.cellcomputing.himawari.util.HimawariLogger;


/**
 * @author NTT DATA Corporation
 *
 */
public strictfp class CqEnvironmentMap extends CqTextureMap {
    private CqMatrix	m_matWorldToScreen;		///< Matrix to convert points from world space to screen space.
	
    //environment.cppに定義されている変数
    public static final int max_no	=	30;
    
    public static final int	pz = 	1;
    public static final int	px = 	2;
    public static final int	py = 	4;
    public static final int	nx =	8;
    public static final int	ny =	16;
    public static final int	nz =	32;
    
    public static final int	edge01 =	3;
    public static final int	edge02 =	5;
    public static final int	edge03 =	9;
    public static final int	edge04 =	17;
    public static final int	edge12 =	6;
    public static final int	edge23 =	12;
    public static final int	edge34 =	24;
    public static final int	edge41 =	18;
    public static final int	edge51 =	34;
    public static final int	edge52 =	36;
    public static final int	edge53 =	40;
    public static final int	edge54 =	48;
    static CqVector3D[]	cube = new CqVector3D[ max_no ];	// Stores the projection of the reflected beam onto the cube.
    static int	cube_no; 		// Stores the number of points making up the projection.
    static float[][]	uv = new float[ max_no ][ 2 ];	// Stores the values of this projection for a given face.
    
    static {
    	for(int i=0;i<max_no;i++)
    		cube[i] = new CqVector3D();
    }

    static void get_face_intersection( CqVector3D normal, CqVector3D pt, p_int face )
//    面の交点を求める。
    {
        CqVector3D n = new CqVector3D(normal);
        float t;

        // Test nz direction
        if ( n.z < 0 )        	// Test intersection with nz
        {
            t = -0.5f / n.z;
            pt.x( n.x * t );
            pt.y( n.y * t );
            pt.z( n.z * t );
            if ( Math.abs( pt.x ) < 0.5 && Math.abs( pt.y ) < 0.5 )
            {
                face.value = nz;
                return ;
            }
        }
        else if ( n.z > 0 )        	// Test intersection with pz
        {
            t = 0.5f / n.z;
            pt.x( n.x * t );
            pt.y( n.y * t );
            pt.z( n.z * t );
            if ( Math.abs( pt.x ) < 0.5f && Math.abs( pt.y ) < 0.5f )
            {
                face.value = pz;
                return ;
            }
        }


        // Test ny direction
        if ( n.y < 0 )        	// Test intersection with ny
        {
            t = -0.5f / n.y;
            pt.x( n.x * t );
            pt.y( n.y * t );
            pt.z( n.z * t );
            if ( Math.abs( pt.x ) < 0.5f && Math.abs( pt.z ) < 0.5f )
            {
                face.value = ny;
                return ;
            }
        }
        else if ( n.y > 0 )        	// Test intersection with py
        {
            t = 0.5f / n.y;
            pt.x( n.x * t );
            pt.y( n.y * t );
            pt.z( n.z * t );
            if ( Math.abs( pt.x ) < 0.5f && Math.abs( pt.z ) < 0.5f )
            {
                face.value = py;
                return ;
            }
        }

        // Test nx direction
        if ( n.x < 0 )        	// Test intersection with nx
        {
            t = -0.5f / n.x;
            pt.x( n.x * t );
            pt.y( n.y * t );
            pt.z( n.z * t );
            if ( Math.abs( pt.y ) < 0.5f && Math.abs( pt.z ) < 0.5f )
            {
                face.value = nx;
                return ;
            }
        }
        else if ( n.x > 0 )        	// Test intersection with px
        {
            t = 0.5f / n.x;
            pt.x( n.x * t );
            pt.y( n.y * t );
            pt.z( n.z * t );
            if ( Math.abs( pt.y ) < 0.5f && Math.abs( pt.z ) < 0.5f )
            {
                face.value = px;
                return ;
            }
        }
    }

    static void get_edge_intersection( CqVector3D n1, CqVector3D n2, int edge, CqVector3D pt )
//    線の交点を求める。
    {
        float a, b, c;
        float x0, y0, z0, f, g, h;
        float denom, t;

        // Get plane eqn: ax+by+cz=0 from two normals n1 and n2
        a = n1.y * n2.z - n1.z * n2.y;
        b = n1.z * n2.x - n1.x * n2.z;
        c = n1.x * n2.y - n1.y * n2.x;

        // Set up line equation of edge.
        x0 = y0 = z0 = 0.0f;
        f = g = h = 0.0f;
        switch ( edge )
        {
        case edge01: x0 = z0 = 0.5f; g = 1; break;
        case edge02: y0 = z0 = 0.5f; f = 1; break;
        case edge03: x0 = -0.5f; z0 = 0.5f; g = 1; break;
        case edge04: y0 = -0.5f; z0 = 0.5f; f = 1; break;
        case edge12: x0 = y0 = 0.5f; h = 1; break;
        case edge23: x0 = -0.5f; y0 = 0.5f; h = 1; break;
        case edge34: x0 = y0 = -0.5f; h = 1; break;
        case edge41: x0 = 0.5f; y0 = -0.5f; h = 1; break;
        case edge51: x0 = 0.5f; z0 = -0.5f; g = 1; break;
        case edge52: y0 = 0.5f; z0 = -0.5f; f = 1; break;
        case edge53: x0 = z0 = -0.5f; g = 1; break;
        case edge54: y0 = z0 = -0.5f; f = 1; break;
        }

        // Return the intersection of the plane and edge
        denom = a * f + b * g + c * h;
        t = -( a * x0 + b * y0 + c * z0 ) / denom;
        pt.x( x0 + f * t );
        pt.y( y0 + g * t );
        pt.z( z0 + h * t );
    }


    static void project( int face )
//    環境マップのどの面か判定し、その部分選択する。
    {
        int i;
        switch ( face )
        {
        case pz:
            for ( i = 0; i < cube_no; i++ )
            {
                uv[ i ][ 0 ] = cube[ i ].x + 0.5f;
                uv[ i ][ 1 ] = -cube[ i ].y + 0.5f;
            }
            break;

        case px:
            for ( i = 0; i < cube_no; i++ )
            {
                uv[ i ][ 0 ] = -cube[ i ].z + 0.5f;
                uv[ i ][ 1 ] = -cube[ i ].y + 0.5f;
            }
            break;

        case py:
            for ( i = 0; i < cube_no; i++ )
            {
                uv[ i ][ 0 ] = cube[ i ].x + 0.5f;
                uv[ i ][ 1 ] = cube[ i ].z + 0.5f;
            }
            break;

        case nx:
            for ( i = 0; i < cube_no; i++ )
            {
                uv[ i ][ 0 ] = cube[ i ].z + 0.5f;
                uv[ i ][ 1 ] = -cube[ i ].y + 0.5f;
            }
            break;

        case ny:
            for ( i = 0; i < cube_no; i++ )
            {
                uv[ i ][ 0 ] = cube[ i ].x + 0.5f;
                uv[ i ][ 1 ] = -cube[ i ].z + 0.5f;
            }
            break;

        case nz:
            for ( i = 0; i < cube_no; i++ )
            {
                uv[ i ][ 0 ] = -cube[ i ].x + 0.5f;
                uv[ i ][ 1 ] = -cube[ i ].y + 0.5f;
            }
            break;
        }
    }


    
	public CqEnvironmentMap(final String strName){
		super(strName);
//		m_swidth = 2.0f ; 
//		m_twidth = 2.0f ;
		
	}
	
	public void destruct(){
		super.destruct();
	}
    public 	EqMapType	Type() 
    {
        return ( IsValid() ? new EqMapType(EqMapType.MapType_Environment) :new EqMapType( EqMapType.MapType_Invalid) );
    }

    public 	void	SampleMap( CqVector3D R1, CqVector3D swidth, CqVector3D twidth,
                            Valarray val, int index , p_float average_depth, p_float shadow_depth  ){
        // Check the memory and make sure we don't abuse it
        CriticalMeasure();

//        R1 = new CqVector3D(R1);
        if ( m_pImage != null/*m_XRes != 0*/ )
        {
            if ( Type().getValue() != EqMapType.MapType_LatLong )
            {
                CqVector3D	R2 = new CqVector3D();
                CqVector3D  R3 = new CqVector3D();
                CqVector3D  R4 = new CqVector3D();
                R2.assignment(R1.add(swidth));
                R3.assignment(R1.add( twidth));
                R4.assignment(R1.add(swidth).add(twidth));

                SampleMap( R1, R2, R3, R4, val );
            }
            else if ( Type().getValue() == EqMapType.MapType_LatLong )
            {
                CqVector3D V = new CqVector3D();
                V.assignment(R1);
                V.Unit();
                float ss1, tt1;
                float sswidth = swidth.Magnitude();
                float stwidth = twidth.Magnitude();

                ss1 = (float)(Math.atan2( V.y, V.x ) / ( 2.0f * RiGlobal.RI_PI ));  /* -.5 . .5 */
                ss1 = ss1 + 0.5f; /* remaps to 0 . 1 */
                tt1 = (float) (Math.acos( -V.z ) / RiGlobal.RI_PI);

                SampleMap( ss1, tt1, sswidth, stwidth, val );
            }
        }
    }
    public 	void	SampleMap( CqVector3D R1, CqVector3D R2, CqVector3D R3, CqVector3D R4,
                            Valarray val, int index , p_float average_depth , p_float shadow_depth){
        QGetRenderContext().Stats().TextureMapTimer().Start(); 

//    	QGetRenderContext() .Stats().TextureMapTimer().Stop();

        if ( m_pImage != null )
        {

            CqVector3D	last_R = new CqVector3D();
            CqVector3D  R = new CqVector3D();
            CqVector3D  pt = new CqVector3D();
            float	texture_area, total_area;
            int	i, j;
            int	projection_bit;		// Stores all the faces the reflected beam projects onto.
            int	edge, project_face;
            p_int last_face = new p_int();
            p_int current_face = new p_int();
            CqVector3D[] vertex_list =
                {
                    R1,
                    R2,
                    R3,
                    R4
                };

            val.resize( m_SamplesPerPixel );

            vertex_list[ 0 ].Unit();
            vertex_list[ 1 ].Unit();
            vertex_list[ 2 ].Unit();
            vertex_list[ 3 ].Unit();

            cube_no = 0;
            projection_bit = 0;
            total_area = 0.0f;

            // Find intersection the reflected vector from the last vertex in the list makes with the cube.
            last_R.assignment(vertex_list[ 3 ]);

            get_face_intersection( last_R, cube[ cube_no ], last_face );
            cube_no++;
            projection_bit |= last_face.value;

            for ( i = 0; i < 4; i++ )
            {
                R.assignment(vertex_list[ i ]);
                get_face_intersection( R, pt, current_face );

                // If the last reflected ray intersected a face different from the current
                // ray, we must find the intersection the beam makes with the corresponding edge.
                if ( current_face.value != last_face.value )
                {
                    edge = current_face.value | last_face.value;
                    get_edge_intersection( last_R, R, edge, cube[ cube_no ] );
                    cube_no++;
                    projection_bit |= current_face.value;
                }
                cube[ cube_no ].assignment( pt );
                cube_no++;
                last_face.value = current_face.value;
                last_R.assignment(R);
            }

            Valarray	run_val = new Valarray();
            run_val.resize( m_SamplesPerPixel );
            run_val.assignment(0.0f);
            val.assignment(0.0f);

            float facewidth = 1.0f / 3.0f;
            float faceheight = 1.0f * 0.5f;

            for ( i = 0, project_face = 1; i < 6; i++, project_face += project_face )
            {
                if ( (project_face & projection_bit) !=0 )
                {
                    // Get the projection in UVSpace on the project_face
                    project( project_face );
                    float s1, t1, s2, t2;
                    s1 = s2 = uv[ 0 ][ 0 ];
                    t1 = t2 = uv[ 0 ][ 1 ];
                    texture_area = 0.0f;

                    for ( j = 1; j < cube_no; j++ )
                    {
                        if ( uv[ j ][ 0 ] < s1 ) s1 = uv[ j ][ 0 ];
                        if ( uv[ j ][ 1 ] < t1 ) t1 = uv[ j ][ 1 ];
                        if ( uv[ j ][ 0 ] > s2 ) s2 = uv[ j ][ 0 ];
                        if ( uv[ j ][ 1 ] > t2 ) t2 = uv[ j ][ 1 ];
                    }
                    texture_area = ( s2 - s1 ) * ( t2 - t1 );
                    if ( texture_area <= 0.0 ) texture_area = 1.0f;

                    // Adjust for the appropriate section of the cube map.
                    // Clamp to the cubemap face, this is OK, as the wrap over a boundary is handled
                    // by the preceeding code which gets multiple samples for the cube if a sample crosses
                    // edges.
                    s1 = PublicFunctions.CLAMP( s1, 0.0f, 1.0f );
                    s2 = PublicFunctions.CLAMP( s2, 0.0f, 1.0f );
                    t1 = PublicFunctions.CLAMP( t1, 0.0f, 1.0f );
                    t2 = PublicFunctions.CLAMP( t2, 0.0f, 1.0f );
                    s1 = ( s1 * facewidth ) + ( facewidth * ( i % 3 ) );
                    s2 = ( s2 * facewidth ) + ( facewidth * ( i % 3 ) );
                    t1 = ( t1 * faceheight ) + ( faceheight * ( i / 3 ) );
                    t2 = ( t2 * faceheight ) + ( faceheight * ( i / 3 ) );

                    GetSample( s1, t1, s2, t2, run_val );

                    // Add the color contribution weighted by the area
                    val.assignAdd(run_val.mul(texture_area));
                    total_area += texture_area;
                }
            }
            // Normalize the weightings
            val.assignDiv( total_area);
        }
//        QGetRenderContext() .Stats().TextureMapTimer().Start();
        QGetRenderContext().Stats().TextureMapTimer().Stop(); 

    }

    public  CqMatrix GetMatrix( int which, int index)
    {

        return ( m_matWorldToScreen );
    }


    //作成したMIPMAPを吐き出すデバッグ用コード
//    public boolean CreateMIPMAP(boolean f){
//    	boolean ret = super.CreateMIPMAP(f);
//    	int i=0;
//    	for(i = 0; i <  m_apSegments.size();i++){
//    		String name = "temp" + i + ".png";
//    		BufferedImage buf = new BufferedImage((int)m_apSegments.get(i).m_Width,(int)m_apSegments.get(i).m_Height,BufferedImage.TYPE_INT_RGB);
//    		CqTextureMapBuffer b = m_apSegments.get(i);
//    		WritableRaster ras = buf .getRaster();
//    		int[] temp = new int[3];
//    		for(int j = 0; j< (int)b.m_Height; j++){
//    			for(int k = 0; k < (int)b.m_Width;k++){
//    				for(int mm = 0; mm<3 ;mm++){
//    					temp[mm] = (int)(b.GetValue(k,j,mm) * 255);
//    				}
//    	    		ras.setPixel(k,j,temp);
//
//    			}
//    		}
//    		System.out.println("#完了"+name);
//    		
//    		try {
//				ImageIO.write(buf,"png",new File(name));
//			} catch (IOException e) {
//				
//				e.printStackTrace();
//			}
//    	}
//    	return ret;
//    }
    

    public void ImageFilterVal2( CqTextureMapBuffer pData, int x, int y, int directory,  int m_xres, int m_yres, float[] accum )
    {
        Method pFilter = m_FilterFunc;

        int delta = ( 1 << directory );
        float div = 0.0f;
        float mul = 0f;
        float fx, fy;
        int isample;
        int xdelta = (int)Math.max(Math.floor(m_swidth) * (delta/2), 1);
        int ydelta = (int)Math.max(Math.floor(m_twidth) * (delta/2), 1);
        int xdelta2 = xdelta * 2;
        int ydelta2 = ydelta * 2;


        // Increase the precision after the middle (0.5 and up make sure we will hit the borner at 1.0)
        fx = (float) (x)/ (float) (m_xres-1);
       

        // Increase the precision after the middle (0.5 and up make sure we will hit the borner at 1.0)
        fy = (float) (y)/ (float) (m_yres-1 );
       

        // Clear the accumulator
//        accum.assign( SamplesPerPixel(), 0.0f );
        for(int i=0; i < accum.length;i++)
        	accum[i] = 0.0f;
        if ( directory != 0)
        {
            int i, j;

            for ( isample = 0; isample < SamplesPerPixel(); isample++ )
//                accum.setElementAt( 0f , isample );
            	accum[isample] = 0;
            /* From -twidth to +twidth */
            for ( j = - ydelta; j <= ydelta; j++ )
            {
                /* From -swidth to +swidth */
                for ( i = -xdelta; i <= xdelta; i++)
                {
                    /* find the filter value */
                    try {
						mul =  (Float)pFilter.invoke(null,  (float) i, (float) j, (float) xdelta2, (float) ydelta2 );
					} catch (IllegalArgumentException e) {
						HimawariLogger.outputException(e);
					} catch (IllegalAccessException e) {
						HimawariLogger.outputException(e);
					} catch (InvocationTargetException e) {
						HimawariLogger.outputException(e);
					}
                    if (mul == 0.0) continue;

                    /* find the value in the original image */
                    int ypos = (int) (fy*m_YRes/*ここをはずすとうまくいく*/-1) + j;
                    int xpos = (int) (fx*m_XRes/*ここをはずすとうまくいく*/-1) + i;
                    if (ypos < 0) continue;
                    if (xpos < 0) continue;
//こ                    if (ypos > (int) m_YRes - 1)continue;
//                    if (xpos > (int) m_XRes - 1) continue;
//                    if(xpos == m_XRes/3 -1 ) continue;
//                    if(ypos == m_YRes/2 -1) continue;
//こ                    if(xpos == m_XRes*2/3-1) continue;

                    //--Environment mapの境界線のバグを取るためのためためしコード
                    float targetX = fx* m_XRes-1;
                    float targetY = fy* m_YRes-1;
                    if(targetX <= m_XRes/3 -1 && xpos > m_XRes/3-1) continue;
                    if(targetX > m_XRes/3 -1 && xpos <= m_XRes/3-1) continue;
                    if(targetX <= m_XRes*2/3 -1 && xpos > m_XRes*2/3-1) continue;
                    if(targetX > m_XRes*2/3 -1 && xpos <= m_XRes*2/3-1) continue;
                    if(targetY <= m_YRes/2 -1 && ypos > m_YRes/2 -1) continue;
                    if(targetY > m_YRes/2 -1 && ypos <= m_YRes/2 -1) continue;
                    
                    if(ypos > m_YRes - 1) continue;
                    if(xpos > m_XRes - 1) continue;
//                    if(xpos >= (m_XRes/3 - xdelta-1) && xpos <= (m_XRes/3 +xdelta-1) ) continue;
//                    if(xpos >= (m_XRes*2/3 - xdelta-1) && xpos <= (m_XRes*2/3 +xdelta-1) ) continue;
//                   if(ypos >= m_YRes/2 -ydelta-1 && ypos <= m_YRes/2 +ydelta-1) continue;
                   //--ここまで
                    /* ponderate the value */
                    for ( isample = 0; isample < SamplesPerPixel(); isample++ )
//                        accum.setElementAt((pData.GetValue( xpos, ypos, isample ) * mul) + accum.elementAt(isample), isample) ;
                    	accum[isample] = (pData.GetValue( xpos, ypos, isample ) * mul) + accum[isample];
                    /* accumulate the ponderation factor */
                    div += mul;
                }
            }

            /* use the accumulated ponderation factor */
            for ( isample = 0; isample < SamplesPerPixel(); isample++ )
//                accum.setElementAt(accum.elementAt(isample) / (float)( div ) , isample );
            	if(div!= 0)	//追加 2006/01/27 白い枠線が出るバグ
            		accum[isample] = accum[isample] / (float)( div ) ;
        }
        else
        {
            /* copy the byte don't bother much */
            for ( isample = 0; isample < SamplesPerPixel(); isample++ )
//                accum.setElementAt( pData.GetValue( x, y, isample ) , isample );
            	accum[isample] = pData.GetValue( x, y, isample );
        }
    }

}
