/* @author Job Zwiers  
 * $Date: 2005/15/12 21:52:17 $    
 * Shows a possible bug in the Jogl implemention of gluBuild2DMipmaps 
 * Usage: 
 * 1) Use the G key to switch between a mipmap created by means of gluBuild2DMipmaps and
 *    an auto-generated mipmap obtained by setting GL_GENERATE_MIPMAP to true.
 * 2) use up/down arrows and PgUp/PgDown to navigate. 
 * 3) use Escape to exit
 * With the GLU based mipmap, the "chromic" texture produces a red "glow" at some areas on the ground, 
 * which is absent when OpenGL generated mipmaps are used. 
 * Other textures have similar problems.
 * For instance, the (512 X 512) "Grass.png" texture shows black areas when using gluBuild2DMipmaps.
 * Using System.setProperty("jogl.glu.nojava", "true"); the red glow on the chromic is gone, 
 * but the visual appearance of the mipmaps is still not identical. 
 * (Especially visible with the chromic texture).
 * This program is based on a small fragment of a C based program called "Sphereworld" 
 * from the OpenGL SuperBible by Richard Wright/Benjamin Lipschak . 
 * This "Sphereworld" is also using gluBuild2DMipmaps, but does not seem to suffer 
 * from the same problem.
 */

import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import javax.swing.*;
import javax.media.opengl.*;
import javax.media.opengl.glu.*;
import java.io.*;
import java.nio.*;
import javax.imageio.*;

public class GluMipmapBug extends JFrame implements GLEventListener, KeyListener {
   static boolean GLUMIPMAP = true;
   //String textureFile = "Grass.png"; // 512 X 512 png
   String textureFile = "chromic.jpg"; // 640 X 480 jpg
   
   GLCanvas glCanvas;
   GLAutoDrawable glDrawable; 
   Thread clock;
   double currentTime, lastTime;
   public boolean[] keyDown = new boolean[256];  
   float[] position = new float[] {0.0f, 0.5f, 0.0f};
   int[] ids = new int[1];
    
   public void loadGLTexture(GL gl) { 
      BufferedImage bufferedImage = null; 
      try { bufferedImage = ImageIO.read( new File( textureFile ));
      } catch (IOException e) { 
         System.out.println("Could not load texture " + textureFile + ": "+ e); 
      }  
      gl.glEnable(GL.GL_TEXTURE_2D); 
      gl.glGenTextures(1, ids, 0);  
      gl.glBindTexture(GL.GL_TEXTURE_2D, ids[0] ); 
        
      gl.glTexEnvi(GL.GL_TEXTURE_ENV, GL.GL_TEXTURE_ENV_MODE, GL.GL_REPLACE);
      gl.glTexParameterf(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_S, GL.GL_REPEAT);
      gl.glTexParameterf(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_T, GL.GL_REPEAT);
        
      gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_LINEAR); 
      gl.glTexParameteri(GL.GL_TEXTURE_2D,GL.GL_TEXTURE_MIN_FILTER,GL.GL_LINEAR_MIPMAP_LINEAR);
        
      byte[] data = ((DataBufferByte) bufferedImage.getRaster().getDataBuffer()).getData();
      ByteBuffer imageBuffer = ByteBuffer.wrap(data); imageBuffer.rewind();
      
      // This is the section which makes the difference:
      if (GLUMIPMAP) {   
         GLU glu = new GLU();   
         glu.gluBuild2DMipmaps(GL.GL_TEXTURE_2D, GL.GL_RGB8, 
                               bufferedImage.getWidth(), bufferedImage.getHeight(), 
                               GL.GL_RGB, GL.GL_UNSIGNED_BYTE, imageBuffer);    
      } else {   
         gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_GENERATE_MIPMAP, GL.GL_TRUE);       
         gl.glTexImage2D(GL.GL_TEXTURE_2D, 0, GL.GL_RGB8, 
                         bufferedImage.getWidth(), bufferedImage.getHeight(), 
                         0, GL.GL_RGB, GL.GL_UNSIGNED_BYTE, imageBuffer);                                                    
      }
   }
   
   public void renderGround(GL gl) {
      int fExtent = 20;
      int fStep = 1;
      float s = 0.0f;
      float t = 0.0f;
      float texStep = 1.0f / (20.0f * 0.075f);
      for(int iStrip = -fExtent; iStrip <= fExtent; iStrip += fStep) {
          t = 0.0f;
          gl.glBegin(GL.GL_TRIANGLE_STRIP);   
              for(int iRun = fExtent; iRun >= -fExtent; iRun -= fStep) {
                  gl.glTexCoord2f(s, t);
                  gl.glNormal3f(0.0f, 1.0f, 0.0f);  
                  gl.glVertex3f(iStrip, 0.0f, iRun);
                  gl.glTexCoord2f(s + texStep, t);
                  gl.glNormal3f(0.0f, 1.0f, 0.0f);   
                  gl.glVertex3f((iStrip + fStep), 0.0f, (float)iRun);
                  t += texStep;
               }
          gl.glEnd();
          s += texStep;
       }
   }
      
   public GluMipmapBug() {
      super("Textures..."); 
      setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      setLocation(100, 100); setSize(800, 600);    
      GLCanvas glCanvas = new GLCanvas(new GLCapabilities());
      glCanvas.setBackground(new Color(0.25f, 0.25f, 0.25f, 1.0f));
      add(glCanvas);
      glCanvas.addKeyListener(this);  
      glCanvas.addGLEventListener(this);
      setVisible(true); 
      glCanvas.requestFocusInWindow();
   }
 
   public void init(final GLAutoDrawable glDrawable) {
      this.glDrawable = glDrawable;
      final GL gl = glDrawable.getGL();
      gl.glClearColor(0.25f, 0.25f, 0.25f, 1.0f);
      gl.glMatrixMode(GL.GL_MODELVIEW);
 
      loadGLTexture(gl);
      clock = new Thread(){
         public void run() {                
            currentTime = (double) (System.nanoTime()/1E9);
            while (true) {  
               lastTime = currentTime;
               currentTime =  (double) (System.nanoTime()/1E9);
               GluMipmapBug.this.glDrawable.display(); 
               yield(); 
            }
          }  
      };
      clock.start();
   }
 
   public void display(GLAutoDrawable glDrawable) {     
      final GL gl = glDrawable.getGL();
      gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
      gl.glMatrixMode(GL.GL_MODELVIEW);   
      gl.glLoadIdentity(); 
      float delta = (float) (currentTime-lastTime);
      if (keyDown[KeyEvent.VK_UP])    position[2]  -= delta * 20.0;
      if (keyDown[KeyEvent.VK_DOWN])  position[2]  += delta * 20.0;
      if (keyDown[KeyEvent.VK_PAGE_UP])   position[1]  += delta * 5.0;
      if (keyDown[KeyEvent.VK_PAGE_DOWN]) position[1]  -= delta * 5.0;
      gl.glTranslatef(-position[0],-position[1], -position[2]);
      
      if (keyDown[KeyEvent.VK_G]) {
           GLUMIPMAP = ! GLUMIPMAP;
           loadGLTexture(gl);
      }
      renderGround(gl);     
   }
   
   public void displayChanged(GLAutoDrawable glDrawable, boolean modeChanged, boolean deviceChanged){}

   public void reshape(GLAutoDrawable glDrawable, int x, int y, int width, int height) {
      final GL gl = glDrawable.getGL();
      if(height <= 0)  height = 1;
      gl.glViewport(0, 0, width, height);     
      gl.glMatrixMode(GL.GL_PROJECTION);        
      gl.glLoadIdentity();       
      new GLU().gluPerspective(35.0f,(float)width/(float)height, 1.0f, 1000.0f);        
      gl.glMatrixMode(GL.GL_MODELVIEW);    
      gl.glLoadIdentity();         
   }
      
   public void keyPressed(KeyEvent evt) {  
      keyDown[evt.getKeyCode()] = true;
      if (evt.getKeyCode() == KeyEvent.VK_ESCAPE)  System.exit(0);
   }    
   public void keyReleased(KeyEvent evt) { keyDown[evt.getKeyCode()] = false; } 
   public void keyTyped(KeyEvent evt) {}

   public static void main(String[] arg) {
      System.setProperty("sun.java2d.noddraw", "true"); 
      // enabling jogl.glu.nojava also gets rid of the bug, although the glu-based mipmaps
      // are still visually different from the ones generated by OpenGL.
      //System.setProperty("jogl.glu.nojava", "true");
      new GluMipmapBug();
   }   
}