
import java.awt.*;
import java.awt.event.*;

import javax.swing.*;
import com.jogamp.opengl.*;
import com.jogamp.opengl.awt.*;

/**
 * A 2D OpenGL animation that demonstrates the use of glPushMatrix and glPopMatrix 
 * to implement hierarchical modeling.  This program is almost a port of 
 * HierarchicalModeling2D.java, which did more or less the same thing using 
 * Java Graphics2D.
 */
public class CartAndWindmillJogl2D extends JPanel implements GLEventListener {

    public static void main(String[] args) {
        JFrame window = new JFrame("Hierarchical Modeling in 2D With Jogl");
        window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        window.setContentPane(new CartAndWindmillJogl2D());
        window.pack();
        window.setVisible(true);
    }

    private GLJPanel drawable;  // The OpenGL display panel.
    private int frameNumber;    // Current frame number, increases by 1 in each frame.

    /**
     * Constructor creates the GLJPanel that will be used for drawing and adds
     *  it to the main panel.  It also starts a timer to draw the animation.  And
     *  it sets the preferred size to be 700-by-500.
     */
    public CartAndWindmillJogl2D() {
        drawable = new GLJPanel();
        drawable.setPreferredSize(new Dimension(700,500));
        drawable.addGLEventListener(this);
        setLayout(new BorderLayout());
        add(drawable, BorderLayout.CENTER);
        Timer timer = new Timer(30, new ActionListener() {
            public void actionPerformed(ActionEvent evt) {
                frameNumber++;
                drawable.repaint();
            }
        });
        timer.setInitialDelay(1000);
        timer.start();
    }

    /**
     * This method is called when the GLJPanel is created.  It initializes
     * the GL context.  Here, it sets the clear color to be sky blue and
     * it sets the xy-limits for drawing so that x ranges from 0 to 7 and
     * y ranges from -1 to 4.
     */
    public void init(GLAutoDrawable drawable) {
        GL2 gl2 = drawable.getGL().getGL2();
        gl2.glClearColor(0.5f, 0.5f, 1, 1);
            // The next three lines set up the coordinates system.
        gl2.glMatrixMode(GL2.GL_PROJECTION);
        gl2.glLoadIdentity();
        gl2.glOrtho(0, 7, -1, 4, -1, 1);
        gl2.glMatrixMode(GL2.GL_MODELVIEW);
    }

    /**
     * This method is called when the GLJPanel needs to be redrawn.
     * It draws the current frame of the animation.
     */
    public void display(GLAutoDrawable drawable) {
        GL2 gl2 = drawable.getGL().getGL2();
        gl2.glClear(GL2.GL_COLOR_BUFFER_BIT); // Fills the scene with blue.
        gl2.glLoadIdentity();

        /* Draw three green triangles to form a ridge of hills in the background */

        gl2.glColor3f(0, 0.6f, 0.2f);
        gl2.glBegin(GL2.GL_POLYGON);
        gl2.glVertex2f(-3,-1);
        gl2.glVertex2f(1.5f,1.65f);
        gl2.glVertex2f(5,-1);
        gl2.glEnd();
        gl2.glBegin(GL2.GL_POLYGON);
        gl2.glVertex2f(-3,-1);
        gl2.glVertex2f(3,2.1f);
        gl2.glVertex2f(7,-1);
        gl2.glEnd();
        gl2.glBegin(GL2.GL_POLYGON);
        gl2.glVertex2f(0,-1);
        gl2.glVertex2f(6,1.2f);
        gl2.glVertex2f(20,-1);
        gl2.glEnd();

        /* Draw a bluish-gray rectangle to represent the road. */

        gl2.glColor3f(0.4f, 0.4f, 0.5f);
        gl2.glBegin(GL2.GL_POLYGON);
        gl2.glVertex2f(0,-0.4f);
        gl2.glVertex2f(7,-0.4f);
        gl2.glVertex2f(7,0.4f);
        gl2.glVertex2f(0,0.4f);
        gl2.glEnd();

        /* Draw a white line to represent the stripe down the middle
         * of the road. */

        gl2.glLineWidth(6);  // Set the line width to be 6 pixels.
        gl2.glColor3f(1,1,1);
        gl2.glBegin(GL2.GL_LINES);
        gl2.glVertex2f(0,0);
        gl2.glVertex2f(7,0);
        gl2.glEnd();
        gl2.glLineWidth(1);  // Reset the line width to be 1 pixel.

        /* Draw the sun.  The drawSun method draws the sun centered at (0,0).  A 2D translation
         * is applied to move the center of the sun to (5,3.3).   A rotation makes it rotate*/

        gl2.glPushMatrix();
        gl2.glTranslated(5.8,3,0);
        gl2.glRotated(-frameNumber*0.7,0,0,1);
        drawSun(gl2);
        gl2.glPopMatrix();

        /* Draw three windmills.  The drawWindmill method draws the windmill with its base 
         * at (0,0), and the top of the pole at (0,3).  Each windmill is first scaled to change
         * its size and then translated to move its base to a different paint.  In the animation,
         * the vanes of the windmill rotate.  That rotation is done with a transform inside the
         * drawWindmill method. */

        gl2.glPushMatrix();
        gl2.glTranslated(0.75,1,0);
        gl2.glScaled(0.6,0.6,1);
        drawWindmill(gl2);
        gl2.glPopMatrix();

        gl2.glPushMatrix();
        gl2.glTranslated(2.2,1.6,0);
        gl2.glScaled(0.4,0.4,1);
        drawWindmill(gl2);
        gl2.glPopMatrix();

        gl2.glPushMatrix();
        gl2.glTranslated(3.7,0.8,0);
        gl2.glScaled(0.7,0.7,1);
        drawWindmill(gl2);
        gl2.glPopMatrix();

        /* Draw the cart.  The drawCart method draws the cart with the center of its base at
         * (0,0).  The body of the cart is 5 units long and 2 units high.  A scale is first
         * applied to the cart to make its size more reasonable for the picture.  Then a
         * translation is applied to move the cart horizontally.  The amount of the translation
         * depends on the frame number, which makes the cart move from left to right across
         * the screen as the animation progresses.  The cart animation repeats every 300 
         * frames.  At the beginning of the animation, the cart is off the left edge of the
         * screen. */

        gl2.glPushMatrix();
        gl2.glTranslated(-3 + 13*(frameNumber % 300) / 300.0, 0, 0);
        gl2.glScaled(0.3,0.3,1);
        drawCart(gl2);
        gl2.glPopMatrix();

    }

    /**
     * Draw a sun with radius 0.5 centered at (0,0).  There are also 13 rays which
     * extend outside from the sun for another 0.25 units.
     */
    private void drawSun(GL2 gl2) {
        gl2.glColor3f(1,1,0);
        for (int i = 0; i < 13; i++) { // Draw 13 rays, with different rotations.
            gl2.glRotatef( 360f / 13, 0, 0, 1 ); // Note that the rotations accumulate!
            gl2.glBegin(GL2.GL_LINES);
            gl2.glVertex2f(0, 0);
            gl2.glVertex2f(0.75f, 0);
            gl2.glEnd();
        }
        drawDisk(gl2, 0.5);
        gl2.glColor3f(0,0,0);
    }

    /**
     * Draw a 32-sided regular polygon as an approximation for a circular disk.
     * (This is necessary since OpenGL has no commands for drawing ovals, circles,
     * or curves.)  The disk is centered at (0,0) with a radius given by the 
     * parameter.
     */
    private void drawDisk(GL2 gl2, double radius) {
        gl2.glBegin(GL2.GL_POLYGON);
        for (int d = 0; d < 32; d++) {
            double angle = 2*Math.PI/32 * d;
            gl2.glVertex2d( radius*Math.cos(angle), radius*Math.sin(angle));
        }
        gl2.glEnd();
    }

    /**
     * Draw a windmill, consisting of a pole and three vanes.  The pole extends from the
     * point (0,0) to (0,3).  The vanes radiate out from (0,3).  A rotation that depends
     * on the frame number is applied to the whole set of vanes, which causes the windmill
     * to rotate as the animation proceeds.  Note that this method changes the current
     * transform in the GL context gl!  The caller of this subroutine should take care
     * to save and restore the original transform, if necessary.
     */
    private void drawWindmill(GL2 gl2) {
        gl2.glColor3f(0.8f, 0.8f, 0.9f);
        gl2.glBegin(GL2.GL_POLYGON);
        gl2.glVertex2f(-0.05f, 0);
        gl2.glVertex2f(0.05f, 0);
        gl2.glVertex2f(0.05f, 3);
        gl2.glVertex2f(-0.05f, 3);
        gl2.glEnd();
        gl2.glTranslatef(0, 3, 0);
        gl2.glRotated(frameNumber * (180.0/46), 0, 0, 1);
        gl2.glColor3f(0.4f, 0.4f, 0.8f);
        for (int i = 0; i < 3; i++) {
            gl2.glRotated(120, 0, 0, 1);  // Note: These rotations accumulate.
            gl2.glBegin(GL2.GL_POLYGON);
            gl2.glVertex2f(0,0);
            gl2.glVertex2f(0.5f, 0.1f);
            gl2.glVertex2f(1.5f,0);
            gl2.glVertex2f(0.5f, -0.1f);
            gl2.glEnd();
        }
    }

    /**
     * Draw a cart consisting of a rectangular body and two wheels.  The wheels
     * are drawn by the drawWheel() method; a different translation is applied to each
     * wheel to move them into position under the body.  The body of the cart
     * is a red rectangle with corner at (0,-2.5), width 5, and height 2.  The
     * center of the bottom of the rectangle is at (0,0).
     */
    private void drawCart(GL2 gl2) {
        gl2.glPushMatrix();
        gl2.glTranslatef(-1.5f, -0.1f, 0);
        gl2.glScalef(0.8f,0.8f,1);
        drawWheel(gl2);
        gl2.glPopMatrix();
        gl2.glPushMatrix();
        gl2.glTranslatef(1.5f, -0.1f, 0);
        gl2.glScalef(0.8f,0.8f,1);
        drawWheel(gl2);
        gl2.glPopMatrix();
        gl2.glColor3f(1,0,0);
        gl2.glBegin(GL2.GL_POLYGON);
        gl2.glVertex2f(-2.5f,0);
        gl2.glVertex2f(2.5f,0);
        gl2.glVertex2f(2.5f,2);
        gl2.glVertex2f(-2.5f,2);
        gl2.glEnd();
    }

    /**
     * Draw a wheel, centered at (0,0) and with radius 1. The wheel has 15 spokes
     * that rotate in a clockwise direction as the animation proceeds.
     */
    private void drawWheel(GL2 gl2) {
        gl2.glColor3f(0,0,0);
        drawDisk(gl2,1);
        gl2.glColor3f(0.75f, 0.75f, 0.75f);
        drawDisk(gl2, 0.8);
        gl2.glColor3f(0,0,0);
        drawDisk(gl2, 0.2);
        gl2.glRotatef(frameNumber*20,0,0,1);
        gl2.glBegin(GL2.GL_LINES);
        for (int i = 0; i < 15; i++) {
            gl2.glVertex2f(0,0);
            gl2.glVertex2d(Math.cos(i*2*Math.PI/15), Math.sin(i*2*Math.PI/15));
        }
        gl2.glEnd();
    }

    public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height) {
    }

    public void displayChanged(GLAutoDrawable drawable, boolean modeChanged, boolean deviceChanged) {
    }

    public void dispose(GLAutoDrawable arg0) {
    }
}
