import java.awt.*;
import java.awt.image.*;
import java.lang.*;


/*

    Orientation du repère :

    Y
    |_ X
   /
  -Z

*/


public class Voxel2 extends java.applet.Applet 
                    implements Runnable 
{    
    private MemoryImageSource backBuffer;
    private int backBufferData[];

    private ReadableImage heightMap;
    private ReadableImage texture;

    private boolean isComputing;
    private boolean readyToRun = false;
    private int dimX, dimY;
    private Camera camera = new Camera();
    private int altitudeNuages;

    // interpolation bilineaire
    private int interpolationPas;
    private int interpolationX[];
    private int interpolationY[];
    private int interpolationZone[]; // 0 = terrain, 1 = ciel
    
    /*
        Fonction modulo
     */

    private int modulo( int numero, int modulo )
    {
        while (numero>=modulo)
        {
            numero-=modulo;
        }

        while (numero<0)
        {
            numero+=modulo;
        }

        return numero;
    }


    /*
        Fonction d'initialisation
     */

    public void init(int X, int Y)
    {        
        this.getGraphics().drawString("Please wait ....", 50, 100);
    
        // HeightMap creation
        heightMap = new ReadableImage( "terrain.gif" );

        // Texture creation
        texture = new ReadableImage( "texture.gif" );

        // Initialise buffer & resize applet
        dimX = X;
        dimY = Y;

        resize(X,Y);

        backBufferData = new int[dimX*dimY];
        backBuffer = new MemoryImageSource(dimX, dimY, backBufferData, 0, dimX);
        backBuffer.setAnimated(true);

        // Initialise camera
        camera.init(0, 160, 0, 5, 0, 0, 0, 0, 150, 600, 400);

        // interpolation bilinéaire
        interpolationPas = 8;
        interpolationX = new int[(dimX+1)*(dimY+1)];
        interpolationY = new int[(dimX+1)*(dimY+1)];
        interpolationZone = new int[(dimX+1)*(dimY+1)];
    }

        
    public void update(Graphics g) 
    {
        paint(g);
    }

    public void paint(Graphics g) 
    {
        if( readyToRun == false ) 
        {
            g.drawString("Please wait ....", 50, 100);
        } 
        else 
        {
            calculateImage();
            getGraphics().drawImage( createImage(backBuffer), 0, 0, this );
        }
    }

    public void start() 
    {
        this.getGraphics().drawString("Initialisation", 10, 10);

        readyToRun = false;
        init(320, 320);
        readyToRun = true;
    }

    synchronized public void stop() 
    {
        notify();
    }

    public void lancerRayon( int screenX, int screenY,
                             double angleFOVy,
                             double Xdepart, double Ydepart, double Zdepart,
                             double RXdepart, double RYdepart, int step)
    {
        // Trouver les increments pour se deplacer sur le rayon
        double dx = step*Math.cos( RXdepart )*Math.sin( RYdepart );
        double dy = step*Math.sin( RXdepart );
        double dz = step*Math.cos( RXdepart )*Math.cos( RYdepart );

        // Variables indiquant ou l'on est actuellement sur le rayon
        double newPosX = Xdepart;
        double newPosY = Ydepart;
        double newPosZ = Zdepart;

        // increment lors du changement de ligne dans le backbuffer
        double angleIncrement = - 2*interpolationPas*angleFOVy / dimY;
        int nombreDeLignesSautees = 0;
    
        // Coordonnees actuelles dans la heightMap
        int heightMapX, heightMapY;
        int offset;
        int offset2 = screenY*(dimX+1) + screenX;

        for (int t=0; t<camera.getFarDistance(); t+=step)
        {
            heightMapX = modulo( (int)(newPosX), heightMap.getIconWidth());
            heightMapY = modulo( (int)(newPosZ), heightMap.getIconHeight());
		
            offset = heightMap.getIconWidth()*heightMapY + heightMapX;

            while ( (newPosY <= (heightMap.getBuffer()[offset] & 0x000000FF)) &&
                    (screenY >= 0) )
            {
                interpolationX[offset2] = (int)newPosX;
                interpolationY[offset2] = (int)newPosZ;
                interpolationZone[offset2] = 0;

                // On remonte de "step" pixel sur l'ecran.
                screenY -= interpolationPas;
                offset2 -= interpolationPas*(dimX+1);
                nombreDeLignesSautees++;
                newPosY+=interpolationPas;
            }
            
            // Recalculer dy 
            // ! optim : recalculer seulement si on a changé screenY
            dy = step*Math.sin( RXdepart + nombreDeLignesSautees*angleIncrement);
            
            // mettre a jour les positions sur le rayon
            newPosX += dx;
            newPosY += dy;
            newPosZ += dz;
        }

        //Nuages
        
        for (int i=screenY; i>=0; i-=interpolationPas)
        {
            interpolationX[offset2] = 0;
            interpolationY[offset2] = 0;
            interpolationZone[offset2] = 1;                
            
            offset2-=interpolationPas*(dimX+1);                
            nombreDeLignesSautees++;            
        }
    }

    public void calculateImage()
    {
        camera.update();
       
        int screenX = 0;
        int screenY = dimY-1;

        double angleFOVx = Math.atan( -dimY / (2*camera.getFOV()) );
        double angleFOVy = Math.atan( -dimX / (2*camera.getFOV()) );
        double angleIncrement = 2*interpolationPas*angleFOVy / dimX;

        double angleActuel = angleFOVy;

        for (screenX=0; screenX<dimX; screenX+=interpolationPas)
        {
            lancerRayon( screenX, dimY,
                         angleFOVy,
                         camera.getX(), camera.getY(), camera.getZ(), 
                         camera.getRXZ() + angleFOVx, 
                         camera.getRY() + angleActuel,
                         1);

            angleActuel += angleIncrement;
        }

        // interpolation proprement dite.

        double val_x, v1_x, v2_x, v3_x, v4_x;
        double val_y, v1_y, v2_y, v3_y, v4_y;
        double dvdx_y, dvdxdy_y;
        double dvdy_y, leftv_y;
        double dvdx_x, dvdxdy_x;
        double dvdy_x, leftv_x;
        
        int j, i, v, u;

        int red, green, blue;

        int offset = 0;
        int offset2 = 0;

        for (j=0;j<dimY;j+=interpolationPas)
        {
            for (i=0;i<dimX;i+=interpolationPas)
            {       
                offset = (dimX+1)*j+i;
		
		//interpolation pour les 4 coins de la zone de calcul
                v1_x = (interpolationX[offset]);
                v2_x = (interpolationX[offset+interpolationPas]);
                v3_x = (interpolationX[offset+(dimX+1)*interpolationPas]);
                v4_x = (interpolationX[offset+(dimX+1)*interpolationPas+interpolationPas]);
                
                v1_y = (interpolationY[offset]);
                v2_y = (interpolationY[offset+interpolationPas]);
                v3_y = (interpolationY[offset+(dimX+1)*interpolationPas]);
                v4_y = (interpolationY[offset+(dimX+1)*interpolationPas+interpolationPas]);
                
                // Pour les x
                dvdx_x = (-v1_x + v2_x) / interpolationPas;
                dvdy_x = (-v1_x + v3_x ) / interpolationPas;
                dvdxdy_x = (v1_x - v2_x - v3_x + v4_x) / (interpolationPas*interpolationPas);
                
                // Idem pour les y
                dvdx_y = (-v1_y + v2_y) / interpolationPas;
                dvdy_y = (-v1_y + v3_y ) / interpolationPas;
                dvdxdy_y = (v1_y - v2_y - v3_y + v4_y) / (interpolationPas*interpolationPas);
                
                leftv_x = v1_x;
                leftv_y = v1_y;
                
                for (v=j;v<j+interpolationPas;v++)
                {
                    val_x = leftv_x;
                    val_y = leftv_y;

                    for (u=i;u<i+interpolationPas;u++)
                    {
                        offset2 = (dimX+1)*v+u;

                        interpolationX[offset2] = (int)val_x;
                        val_x += dvdx_x;  
                        
                        interpolationY[offset2] = (int)val_y;
                        val_y += dvdx_y;
                                                
                        if (interpolationZone[offset] == 0)
                        {
                            int Y = modulo(interpolationY[offset2], texture.getIconHeight());
                            int X = modulo(interpolationX[offset2], texture.getIconWidth());

                            red = (texture.getBuffer()[texture.getIconWidth()*Y+X] >> 16) & 0x000000FF;
                            green = (texture.getBuffer()[texture.getIconWidth()*Y+X] >> 8) & 0x000000FF;
                            blue = texture.getBuffer()[texture.getIconWidth()*Y+X] & 0x000000FF;

                            backBufferData[dimX*v+u] = (255 << 24) | (red << 16) | (green << 8) | blue;
                        }
                        else if (interpolationZone[offset] == 1)
                        {
                            backBufferData[dimX*v+u] = (255 << 24) | (80 << 16) | (80 << 8) | 200;
                        }
                    }
                    
                    leftv_x+=dvdy_x;
                    dvdx_x+=dvdxdy_x;
                    
                    leftv_y+=dvdy_y;
                    dvdx_y+=dvdxdy_y;
                }
            }
        }

                // backBuffer.newPixels();

    }
    
    public void run() 
    {
    }
}