/**
 * Lesson10.java
 *
 * Author: Mattias Ekstrand
 * Date: 09/04/2001
 *
 * Port of the NeHe OpenGL Tutorial (Lesson 10: "Moving Bitmaps In 3D Space")
 * to Java using the GL4Java interface to OpenGL. Much of the code is reused
 * from Darren Hodges port of Lession 9. :)
 *
 */


import java.applet.*;
import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.*;
import java.util.StringTokenizer;

//GL4Java classes
import gl4java.GLContext;
import gl4java.awt.GLAnimCanvas;
import gl4java.utils.textures.*;


public class Lesson10 extends Applet
{
    //Our rendering canvas
    //We are using GLAnimCanvas because we want the canvas
    //to be constantly redrawn
    renderCanvas canvas = null;


    /**
     * void init()
     *
     * Initialise the applet.
     */
    
    public void init()
	{   
        //We will use BorderLayout to layout the applet components
        setLayout(new BorderLayout());
        
        //Create our canvas and add it to the center of the applet
        canvas = new renderCanvas(getSize().width, getSize().height);
        add("Center", canvas);
	}
	
	
    /**
     * void start()
     *
     * Start the applet.
     */
	public void start()
    {
        //Start animating the canvas
        canvas.start();
    }

	
    /**
     * void stop()
     *
     * Stop the applet.
     */
    public void stop()
    {
        //Stop animating the canvas
        canvas.stop();
    }
    
    
    /**
     * void destroy()
     *
     * Destroy the applet.
     */
    public void destroy()
    {
        //Stop animating the canvas
        canvas.stop();
        //Destroy the canvas
        canvas.destroy();
    }



    private class renderCanvas extends GLAnimCanvas implements KeyListener
    {
    	boolean	light = true; //Lighting ON/OFF
    	boolean lp = false; //L Pressed?
	boolean fp = false; //F Pressed?
	boolean blend = true; //Blending ON/OFF
	boolean bp = false; //B Pressed?

        final float piover180 = 0.0174532925f;
        float heading;
        float xpos;
        float zpos;
        
        float yrot = 0.0f;		//Y Rotation
        float walkbias = 0.0f;
        float walkbiasangle = 0.0f;
        float lookupdown = 0.0f;
	float z = -8.0f;	                //Depth Into The Screen
		
	//Ambient light
	float[] LightAmbient = { 0.5f, 0.5f, 0.5f, 1.0f };

	//Diffuse light
	float[] LightDiffuse = { 1.0f, 1.0f, 1.0f, 1.0f };
	
	//Light position
	float[] LightPosition = { 0.0f, 0.0f, 2.0f, 1.0f };	
	
	int filter = 0; //Which Filter To Use
	
	int[] texture = new int[3]; //Storage for 3 textures
        
        Sector sector1;
		
		
        /**
         * renderCanvas(int w, int h)
         *
         * Constructor.
         */
        public renderCanvas(int w, int h)
        {
            super(w, h);
            
            //Registers this canvas to process keyboard events
        	addKeyListener(this);
        }
    
        
        /**
         * void preInit()
         *
         * Called just BEFORE the GL-Context is created.
         */
        public void preInit()
        {
            //We want double buffering
            doubleBuffer = true;
            //But we dont want stereo view
            stereoView = false;
        }
        
        void SetupWorld() {
	    float x, y, z, u, v;
	    int numtriangles;
            
            try {
                String line;
                
                URL world = new URL(getCodeBase() + "data/world.txt");
                
                DataInputStream dis = new DataInputStream(world.openStream());
            
                while ((line = dis.readLine()) != null) {
                    if (line.trim().length() == 0 || line.trim().startsWith("//"))
                        continue;
                    
                    if (line.startsWith("NUMPOLLIES")) {
                        int numTriangles;
                        
                        numTriangles = Integer.parseInt(line.substring(line.indexOf("NUMPOLLIES") + "NUMPOLLIES".length() + 1));
                        sector1 = new Sector(numTriangles);
                        
                        break;
                    }
                }
                
                for (int i = 0; i < sector1.numTriangles; i++) {
                    for (int vert = 0; vert < 3; vert++) {
                        
                        while ((line = dis.readLine()) != null) {
                            if (line.trim().length() == 0 || line.trim().startsWith("//"))
                                continue;
                            
                            break;
                        }
                        
                        if (line != null) {
                            StringTokenizer st = new StringTokenizer(line, " ");
                            
                            sector1.triangles[i].vertex[vert].x = Float.valueOf(st.nextToken()).floatValue();
                            sector1.triangles[i].vertex[vert].y = Float.valueOf(st.nextToken()).floatValue();
                            sector1.triangles[i].vertex[vert].z = Float.valueOf(st.nextToken()).floatValue();
                            sector1.triangles[i].vertex[vert].u = Float.valueOf(st.nextToken()).floatValue();
                            sector1.triangles[i].vertex[vert].v = Float.valueOf(st.nextToken()).floatValue();                            
                        }                        
                    }
                }
                
                dis.close();
                
            } catch (MalformedURLException me) {
                System.out.println("MalformedURLException: " + me);
            } catch (IOException ioe) {
                System.out.println("IOException: " + ioe);
            }
        }

    
    
        /**
         * void LoadGLTextures()
         *
         * Load textures.
         */
        public void LoadGLTextures()
    	{
    		PngTextureLoader texLoader = new PngTextureLoader(gl, glu);
    		texLoader.readTexture(getCodeBase(), "data/crate.png");
    			
    		//Full Brightness, 50% Alpha
    		gl.glColor4f(1.0f, 1.0f, 1.0f, 0.5f);
		
                //Blending Function For Translucency Based On Source Alpha Value
		gl.glBlendFunc(GL_SRC_ALPHA, GL_ONE);
    		
    		if(texLoader.isOk())
    		{			
				//Create Nearest Filtered Texture
				gl.glGenTextures(3, texture);
				gl.glBindTexture(GL_TEXTURE_2D, texture[0]);
			
				gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
				gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
			
				gl.glTexImage2D(GL_TEXTURE_2D,
						 		0,
						 		3,
						 		texLoader.getImageWidth(),
						 		texLoader.getImageHeight(),
						 		0,
						 		GL_RGB,
						 		GL_UNSIGNED_BYTE,
						 		texLoader.getTexture());
						 	
				//Create Linear Filtered Texture
				gl.glBindTexture(GL_TEXTURE_2D, texture[1]);
				gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
				gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
			
				gl.glTexImage2D(GL_TEXTURE_2D,
						 		0,
						 		3,
						 		texLoader.getImageWidth(),
						 		texLoader.getImageHeight(),
						 		0,
						 		GL_RGB,
						 		GL_UNSIGNED_BYTE,
						 		texLoader.getTexture());
						 	
			 
				//Create MipMapped Texture (Only with GL4Java 2.1.2.1 and later!)
				gl.glBindTexture(GL_TEXTURE_2D, texture[2]);
				gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
				gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
			
				glu.gluBuild2DMipmaps(GL_TEXTURE_2D,
								  	  3,
								  	  texLoader.getImageWidth(),
								  	  texLoader.getImageHeight(),
								  	  GL_RGB,
								  	  GL_UNSIGNED_BYTE,
								  	  texLoader.getTexture());
    		}
    	}
    	
        
        /**
         * void init()
         *
         * Called just AFTER the GL-Context is created.
         */
        public void init()
        {
            //Load The Texture(s)
            LoadGLTextures();
	    
            //Enable Texture Mapping
	    gl.glEnable(GL_TEXTURE_2D);
            
            //This Will Clear The Background Color To Black
	    gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);

	    //Enables Clearing Of The Depth Buffer
	    gl.glClearDepth(1.0);
	
            //The Type Of Depth Test To Do
	    gl.glDepthFunc(GL_LESS);
	    //Enables Depth Testing
	    gl.glEnable(GL_DEPTH_TEST);

	    //Enables Smooth Color Shading
	    gl.glShadeModel(GL_SMOOTH);
	    
            //Select The Projection Matrix
	    gl.glMatrixMode(GL_PROJECTION);
	    
            //Reset The Projection Matrix
	    gl.glLoadIdentity();
	
            //Calculate The Aspect Ratio Of The Window
	    glu.gluPerspective(45.0f, (float)getSize().width / (float)getSize().height, 0.1f, 100.0f);
	
            //Select The Modelview Matrix
	    gl.glMatrixMode(GL_MODELVIEW);
			
	    //Lights           
	    gl.glLightfv(GL_LIGHT1, GL_AMBIENT, LightAmbient);
	    gl.glLightfv(GL_LIGHT1, GL_DIFFUSE, LightDiffuse);
	    gl.glLightfv(GL_LIGHT1, GL_POSITION, LightPosition);

	    //Enable light
	    gl.glEnable(GL_LIGHT1);
	    gl.glEnable(GL_LIGHTING);
            
            //Load world
            SetupWorld();
        }
    
    
        /**
         * void destroy()
         *
         * Destroy the canvas.
         */
        public void destroy()
        {
            //Destroy the GLContext
            cvsDispose();
        }
    
		
		/**
		 * void reshape(int width, int height)
		 *
		 * Called after the first paint command.
		 */
        public void reshape(int width, int height)
        {
            //Reset The Current Viewport And Perspective Transformation
	    gl.glViewport(0, 0, width, height);

	    //Select The Projection Matrix
	    gl.glMatrixMode(GL_PROJECTION);
	    
            //Reset The Projection Matrix
	    gl.glLoadIdentity();
	    
            //Calculate The Aspect Ratio Of The Window
	    glu.gluPerspective(45.0f, (float)getSize().width / (float)getSize().height, 0.1f, 100.0f);
	    
            //Select The Modelview Matrix
	    gl.glMatrixMode(GL_MODELVIEW);
        }

        
        /**
         * void display()
         *
         * Draw to the canvas.
         */
        public void display()
        {
            //Ensure GL is initialised correctly
            if (glj.gljMakeCurrent(true) == false)
            	return;
            
            //Clear The Screen And The Depth Buffer
	    gl.glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
            
            //Reset The View
	    gl.glLoadIdentity();
			
	    float x, y, z, u, v;
	    float xtrans = -xpos;
	    float ztrans = -zpos;
	    float ytrans = -walkbias-0.25f;
	    float sceneroty = 360.0f - yrot;

	    gl.glRotatef(lookupdown,1.0f,0,0);
	    gl.glRotatef(sceneroty,0,1.0f,0);
	
	    gl.glTranslatef(xtrans, ytrans, ztrans);
	    gl.glBindTexture(GL_TEXTURE_2D, texture[filter]);
	
            // Process Each Triangle
	    for (int i = 0; i < sector1.numTriangles; i++) {
		gl.glBegin(GL_TRIANGLES);
                    gl.glNormal3f( 0.0f, 0.0f, 1.0f);
                    x = sector1.triangles[i].vertex[0].x;
                    y = sector1.triangles[i].vertex[0].y;
                    z = sector1.triangles[i].vertex[0].z;
                    u = sector1.triangles[i].vertex[0].u;
                    v = sector1.triangles[i].vertex[0].v;
                    gl.glTexCoord2f(u,v); gl.glVertex3f(x,y,z);

                    x = sector1.triangles[i].vertex[1].x;
                    y = sector1.triangles[i].vertex[1].y;
                    z = sector1.triangles[i].vertex[1].z;
                    u = sector1.triangles[i].vertex[1].u;
                    v = sector1.triangles[i].vertex[1].v;
                    gl.glTexCoord2f(u,v); gl.glVertex3f(x,y,z);

                    x = sector1.triangles[i].vertex[2].x;
                    y = sector1.triangles[i].vertex[2].y;
                    z = sector1.triangles[i].vertex[2].z;
                    u = sector1.triangles[i].vertex[2].u;
                    v = sector1.triangles[i].vertex[2].v;
                    gl.glTexCoord2f(u,v); gl.glVertex3f(x,y,z);
                gl.glEnd();
            }
            
            //Swap buffers
            glj.gljSwap();
            glj.gljFree();
        }
        
        
        /**
     	 * void keyTyped(KeyEvent e)
     	 *
     	 * Invoked when a key has been typed. This event occurs when a key press is followed by a key release. 
     	 */
    	public void keyTyped(KeyEvent e)
		{
		}


 		/**
 	 	 * void keyPressed(KeyEvent e)
 	 	 *
 	 	 * Invoked when a key has been pressed.
 	 	 */
 		public void keyPressed(KeyEvent e)
		{
			switch(e.getKeyCode())
			{
				//Switch ON/OFF light when L is pressed
				case KeyEvent.VK_L:
				{
					if(!lp)
					{
						lp = true;
						//Toggle light
						light = !light;
				
						if(!light)
							gl.glDisable(GL_LIGHTING);
						else
							gl.glEnable(GL_LIGHTING);
					}
						
					break;
				}
				
				//Switch filter when F is pressed
				case KeyEvent.VK_F:
				{
					if(!fp)
					{
						fp = true;
						//Change filter
						filter += 1;
						if(filter > 2)
							filter = 0;
					}
					
					break;
				}
				
				//Switch blending when B is pressed
				case KeyEvent.VK_B:
				{
					if(!bp)
					{
						bp = true;
						//Toggle blending
						blend = !blend;
						
						if(blend)
						{
							gl.glEnable(GL_BLEND);			//Turn Blending On
							gl.glDisable(GL_DEPTH_TEST);	//Turn Depth Testing Off
						}
						else
						{
							gl.glDisable(GL_BLEND);			//Turn Blending Off
							gl.glEnable(GL_DEPTH_TEST);		//Turn Depth Testing On
						}
					}
					
					break;
				}
				
				case KeyEvent.VK_PAGE_UP:
				{
					z -= 0.2f;
                                        lookupdown-= 1.0f;
                                        
					break;
				}
				
				case KeyEvent.VK_PAGE_DOWN:
				{
					z += 0.2f;
                                        lookupdown+= 1.0f;
                                        
					break;
				}
				
				case KeyEvent.VK_UP:
				{
					xpos -= (float)Math.sin(heading*piover180) * 0.05f;
					zpos -= (float)Math.cos(heading*piover180) * 0.05f;
					if (walkbiasangle >= 359.0f)
					{
						walkbiasangle = 0.0f;
					}
					else
					{
						walkbiasangle+= 10;
					}
					walkbias = (float)Math.sin(walkbiasangle * piover180)/20.0f;
                                        
                                        break;
				}
				
				case KeyEvent.VK_DOWN:
				{
					xpos += (float)Math.sin(heading*piover180) * 0.05f;
					zpos += (float)Math.cos(heading*piover180) * 0.05f;
					if (walkbiasangle <= 1.0f)
					{
						walkbiasangle = 359.0f;
					}
					else
					{
						walkbiasangle-= 10;
					}
					walkbias = (float)Math.sin(walkbiasangle * piover180)/20.0f;
                                        
                                        break;
				}
					
				//Increase Y rotation speed when user presses RIGHT
				case KeyEvent.VK_RIGHT:
				{
					heading -= 1.0f;
					yrot = heading;
                                        
                                        break;
				}
				
				//Decrease Y rotation speed when user presses LEFT
				case KeyEvent.VK_LEFT:
				{
					heading += 1.0f;	
					yrot = heading;
                                        
					break;
				}
			}
		}


		/**
	 	 * void keyReleased(KeyEvent e)
	 	 *
	 	 * Invoked when a key has been released. 
	 	 */
		public void keyReleased(KeyEvent e)
		{
			switch(e.getKeyCode())
			{
				//Key has been released
				case KeyEvent.VK_L:
				{
					lp = false;
					break;
				}
				
				//Key has been released
				case KeyEvent.VK_F:
				{
					fp = false;					
					break;
				}
				
				//Key has been released
				case KeyEvent.VK_B:
				{
					bp = false;					
					break;
				}
			}
		}
    }
    
    public class Sector	{
        int numTriangles;
	Triangle[] triangles;		
			
	public Sector (int numTriangles) {
	    this.numTriangles = numTriangles;
	    triangles = new Triangle[numTriangles];
            
            for (int i = 0; i < numTriangles; i++)
                triangles[i] = new Triangle();
	}
    }
    
    public class Triangle {
        Vertex[] vertex;
        
        public Triangle () {
            vertex = new Vertex[3];
            
            for (int i = 0; i < 3; i++)
                vertex[i] = new Vertex();
        }
    }
    
    public class Vertex {
        float x, y, z;  // 3D Coordinates
        float u, v;     // Texture Coordinates
        
        public Vertex () {            
        }
    }
}