// 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.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;
import java.util.Locale;

class Renderer {

  private double[] toyPlaneData;
  private GLU glu = new GLU();
  
  public float rot1;
  public float rot2;
  public float rot3;
  
  private void drawTriangle(GL2 gl) {
    gl.glBegin(GL2.GL_TRIANGLES);
    gl.glVertex2f(-0.5f, -0.5f);
    gl.glVertex2f( 0.5f, -0.5f);
    gl.glVertex2f( 0.0f,  0.5f);
    gl.glEnd();
  }

  public void init(GLAutoDrawable d) {
    

    // read vertex data from file
    try {
      int vertSize = 0;
      File file = new File("ToyPlaneData.txt");
      Scanner scanner = new Scanner(file);
      scanner.useLocale(Locale.US);
      if(scanner.hasNextInt()) {
        vertSize  = scanner.nextInt();
        toyPlaneData = new double[vertSize];
      }
      int i = 0;
      while (scanner.hasNextDouble()) {
        double value = scanner.nextDouble();
        toyPlaneData[i] =  value;
        i++;
      }
      scanner.close();
      if(i != vertSize || (vertSize % 3) != 0) toyPlaneData = new double[0];
    } catch (FileNotFoundException e) {
      System.out.println("Can not find vertex data file \"ToyPlaneData.txt\"");
    }
 
    GL2 gl = d.getGL().getGL2(); // get the OpenGL 2 graphics context
    gl.glEnable(GL2.GL_DEPTH_TEST);
  }
  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);
    gl.glMatrixMode(GL2.GL_PROJECTION);
    gl.glLoadIdentity();
    glu.gluPerspective (45.0, (float)w/(float)h, 0.1, 10.0);
  }
  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_MODELVIEW);
    gl.glLoadIdentity();

    // view scene from the side
    gl.glTranslatef(0.0f,0.0f,-3.0f);
    gl.glRotatef( -45.0f, 0.0f, 0.0f, 1.0f);
    gl.glRotatef( -45.0f, 0.0f, 1.0f, 0.0f);
    gl.glRotatef( 135.0f, 1.0f, 0.0f, 0.0f);

    // rotate around z
    gl.glRotatef(rot1, 0.0f, 0.0f, 1.0f);
    gl.glColor3f(0.0f, 0.0f, 1.0f);
    drawCoordinateAxisZ(d);

    // rotate around local y
    gl.glRotatef(rot2, 0.0f, 1.0f, 0.0f);
    gl.glColor3f(0.0f, 1.0f, 0.0f);
    drawCoordinateAxisY(d);

    // rotate around local x
    gl.glRotatef(rot3, 1.0f, 0.0f, 0.0f);
    gl.glColor3f(1.0f, 0.0f, 0.0f);
    drawCoordinateAxisX(d);

    // draw the plane in the local coordinate system
    drawToyPlane(d);
  }
  
  public void dispose(GLAutoDrawable d) {}
  
  private void drawCoordinateAxisZ(GLAutoDrawable d) {
    GL2 gl = d.getGL().getGL2();  // get the OpenGL 2 graphics context
    gl.glBegin(GL2.GL_LINE_LOOP); // circle in x-y plane
    for(int a=0; a<360; a+=10) {
      float angle = (float)Math.PI / 180.0f * a;
      gl.glVertex3f((float)Math.cos(angle), (float)Math.sin(angle), 0.0f);
    }
    gl.glEnd();

    gl.glBegin(GL2.GL_LINES);
    gl.glVertex3f(0.9f, 0.0f, 0.0f); // x-axis
    gl.glVertex3f(1.0f, 0.0f, 0.0f);
    gl.glVertex3f(0.0f, 0.9f, 0.0f); // y-axis
    gl.glVertex3f(0.0f, 1.0f, 0.0f);
    gl.glVertex3f(0.0f, 0.0f,-1.0f); // z-axis
    gl.glVertex3f(0.0f, 0.0f, 1.0f);
    gl. glEnd();

    gl.glBegin(GL2.GL_TRIANGLES); // z-axis tip
    gl.glVertex3f(0.0f,-0.1f, 0.9f);
    gl.glVertex3f(0.0f, 0.0f, 1.0f);
    gl.glVertex3f(0.0f, 0.1f, 0.9f);
    gl.glEnd();
  }

  private void drawCoordinateAxisX(GLAutoDrawable d) {
    GL2 gl = d.getGL().getGL2();  // get the OpenGL 2 graphics context
    gl.glPushMatrix();
    gl.glRotatef(90.0f, 0.0f, 1.0f, 0.0f);
    drawCoordinateAxisZ(d);
    gl.glPopMatrix();
  }

  private void drawCoordinateAxisY(GLAutoDrawable d) {
    GL2 gl = d.getGL().getGL2();  // get the OpenGL 2 graphics context
    gl.glPushMatrix();
    gl.glRotatef(-90.0f, 1.0f, 0.0f, 0.0f);
    drawCoordinateAxisZ(d);
    gl.glPopMatrix();
  }
  

  private void drawToyPlane(GLAutoDrawable d) {
    GL2 gl = d.getGL().getGL2();  // get the OpenGL 2 graphics context
    gl.glColor3f(0.5f, 0.5f, 0.5f);
    gl.glBegin(GL2.GL_TRIANGLES);
    for(int i=0; i <  toyPlaneData.length; i+=3) {
      gl.glVertex3d(toyPlaneData[i], toyPlaneData[i+1], toyPlaneData[i+2]);
    }
    gl.glEnd();
  }
  
  
}

class MyGui extends JFrame implements GLEventListener {

  private Renderer renderer;
  
  public void createGUI() {
    setTitle("Use 1, 2, and 3 keys to rotate (ALT+key rotates in other direction)");
    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 = 2.5f;

        if (event.isAltDown()) offset = -offset;
        switch(event.getKeyCode()) {
          case '1':
            renderer.rot1 += offset;
            redraw = true;
            break;
          case '2':
            renderer.rot2 += offset;
            redraw = true;
            break;
          case '3':
            renderer.rot3 += offset;
            redraw = true;
            break;
          case '0':
            renderer.rot1 = 0.0f;
            renderer.rot2 = 0.0f;
            renderer.rot3 = 0.0f;
            redraw = true;
            break;
        }
        if(redraw) {
          setTitle("Yaw " + renderer.rot1 + ", Pitch " + renderer.rot2 + ", Roll " + renderer.rot3);
        }
      }
    });
    
    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) {
    renderer.dispose(d);
  }

}

public class GimbalLock {
  public static void main(String[] args) {
    System.setProperty("sun.java2d.uiScale", "1.0");
    javax.swing.SwingUtilities.invokeLater(new Runnable() {
      public void run() {
        MyGui myGUI = new MyGui();
        myGUI.createGUI();
      }
    });
  }
}
