// This code example is created for educational purpose
// by Thorsten Thormaehlen (contact: www.thormae.de).
// It is distributed without any warranty.

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import com.jogamp.opengl.*;
import com.jogamp.opengl.awt.GLCanvas;
import com.jogamp.opengl.util.FPSAnimator;
import com.jogamp.opengl.GL.*;  
import com.jogamp.opengl.GL2.*; 
import com.jogamp.opengl.glu.GLU;
import java.util.Random;

class Renderer {

  private GLU glu = new GLU();
  private int width = 0;
  private int height = 0;
  private float[] randVals = new float[1000];
  
  public float t = 1.0f;
  public float d0 = 3.0f;
  
  public void init(GLAutoDrawable d) {
    GL2 gl = d.getGL().getGL2(); // get the OpenGL 2 graphics context
    gl.glEnable(GL2.GL_DEPTH_TEST);
    
    Random generator = new Random();
    // create random values between -1.0 and 1.0
    for(int r=0; r < randVals.length; r++) {
      double rval = generator.nextDouble();
      randVals[r] = ((float)(2.0 * rval - 1.0));
    }
    
  }
  public void resize(GLAutoDrawable d, int w, int h) {
    GL2 gl = d.getGL().getGL2(); // get the OpenGL 2 graphics context
    // ignore this for now
    gl.glViewport(0, 0, w, h);
    width = w;
    height = h;
  }
  public void display(GLAutoDrawable d) {
    GL2 gl = d.getGL().getGL2();  // get the OpenGL 2 graphics context
    gl.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
    gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);

    gl.glMatrixMode(GL2.GL_PROJECTION);
    gl.glLoadIdentity();
    glu.gluPerspective (dollyZoomFovy(), (float)width/(float)height, 0.1, 50.0);

    gl.glMatrixMode(GL2.GL_MODELVIEW);
    gl.glLoadIdentity();

    // translate camera by 3 units
    gl.glTranslatef(0.0f, 0.0f, -t*d0);

    // draw a cube in the local coordinate system
    drawCube(d);
    // draw random lines
    drawRandomLines(d);
  }

  public float dollyZoomFovy() {
    double fovyInit = 60.0; // initial field of view
    double theta = fovyInit / 180.0 * Math.PI; // degree to rad
    double f = 1.0f / Math.tan(theta/2.0f);
    double fNew = f * (d0*t-1) / (d0-1);
    double thetaNew = Math.atan(1.0f / fNew) * 2.0;
    double val = 180.0 * thetaNew / Math.PI; //rad to degree
    return (float) val;
  }
  
  private void drawCube(GLAutoDrawable d ) {
    GL2 gl = d.getGL().getGL2();  // get the OpenGL 2 graphics context
    
    gl.glColor3f(1.0f, 1.0f, 1.0f);
    gl.glLineWidth(3.0f);
    gl.glBegin(GL2.GL_LINE_LOOP);
    gl.glVertex3f(-1.0f, 1.0f, 1.0f);
    gl.glVertex3f( 1.0f, 1.0f, 1.0f);
    gl.glVertex3f( 1.0f,-1.0f, 1.0f);
    gl.glVertex3f(-1.0f,-1.0f, 1.0f);
    gl.glEnd();
    gl.glBegin(GL2.GL_LINE_LOOP);
    gl.glVertex3f(-1.0f, 1.0f,-1.0f);
    gl.glVertex3f( 1.0f, 1.0f,-1.0f);
    gl.glVertex3f( 1.0f,-1.0f,-1.0f);
    gl.glVertex3f(-1.0f,-1.0f,-1.0f);
    gl.glEnd();

    gl.glBegin(GL2.GL_LINE_LOOP);
    gl.glVertex3f( 1.0f, 1.0f,-1.0f);
    gl. glVertex3f( 1.0f, 1.0f, 1.0f);
    gl.glVertex3f( 1.0f,-1.0f, 1.0f);
    gl.glVertex3f( 1.0f,-1.0f,-1.0f);
    gl.glEnd();

    gl.glBegin(GL2.GL_LINE_LOOP);
    gl.glVertex3f(-1.0f, 1.0f,-1.0f);
    gl.glVertex3f(-1.0f, 1.0f, 1.0f);
    gl.glVertex3f(-1.0f,-1.0f, 1.0f);
    gl.glVertex3f(-1.0f,-1.0f,-1.0f);
    gl.glEnd();
    gl.glLineWidth(1.0f);
  }
  
  private void drawRandomLines(GLAutoDrawable d) {
    GL2 gl = d.getGL().getGL2();  // get the OpenGL 2 graphics context
    
    if(randVals.length % 5 != 0) return;
    int i = 0;
    while(i < randVals.length) {
      gl.glColor3d(Math.abs(randVals[i++]), Math.abs(randVals[i++]), Math.abs(randVals[i++]));
      float x = randVals[i++];
      float y = randVals[i++];
      gl.glBegin(GL2.GL_LINES);
      gl.glVertex3f(x, y, -1.0f);
      gl.glVertex3f(x, y,  1.0f);
      gl.glEnd();
    }
  }
  
  
}

class MyGui extends JFrame implements GLEventListener {

  private Renderer renderer;
  
  public void createGUI() {
    setTitle("Use the 1 key to perform a dolly zoom");
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
   
    GLProfile glp = GLProfile.getDefault();
    GLCapabilities caps = new GLCapabilities(glp);
    GLCanvas canvas = new GLCanvas(caps);
    setSize(320, 320);
    
    getContentPane().add(canvas);
    final FPSAnimator ani = new FPSAnimator(canvas, 60, true);
    canvas.addGLEventListener(this);
    setVisible(true);
    renderer = new Renderer();
    
    canvas.addKeyListener(new KeyAdapter() {
      public void keyPressed(KeyEvent event) {
        boolean redraw = false;
        float offset = 0.02f;

        if (event.isAltDown()) offset = -offset;
        switch(event.getKeyCode()) {
          case '1':
            renderer.t += offset;
            if(renderer.t <  1.0f) renderer.t = 4.0f;
            if(renderer.t >  4.0f) renderer.t = 1.0f;
            redraw = true;
            break;
          case '0':
            renderer.t = 1.0f;
            redraw = true;
            break;
        }
        if(redraw) {
          float distance = renderer.t*renderer.d0;
          float fovy = renderer.dollyZoomFovy();
          setTitle("Distance: " + distance + " FieldofView: " + fovy);
        }
      }
    });
    
    ani.start();
  }

  @Override
  public void init(GLAutoDrawable d) { 
    renderer.init(d); 
  }

  @Override
  public void reshape(GLAutoDrawable d, int x, int y, int width, int height) {
    renderer.resize(d, width, height);
  }

  @Override
  public void display(GLAutoDrawable d) {
    renderer.display(d);
  }

  @Override
  public void dispose(GLAutoDrawable d) { 
  }

}

public class DollyZoom {
  public static void main(String[] args) {
    javax.swing.SwingUtilities.invokeLater(new Runnable() {
      public void run() {
        MyGui myGUI = new MyGui();
        myGUI.createGUI();
      }
    });
  }
}
