/**
 * @(#) reflect.java
 * @(#) author: Brian Paul (converted to Java by Ron Cemer)
 */

/*
 * Demo of a reflective, texture-mapped surface with OpenGL.
 * Brian Paul   August 14, 1995   This file is in the public domain.
 *
 * Hardware texture mapping is highly recommended!
 *
 * The basic steps are:
 *    1. Render the reflective object (a polygon) from the normal viewpoint,
 *       setting the stencil planes = 1.
 *    2. Render the scene from a special viewpoint:  the viewpoint which
 *       is on the opposite side of the reflective plane.  Only draw where
 *       stencil = 1.  This draws the objects in the reflective surface.
 *    3. Render the scene from the original viewpoint.  This draws the
 *       objects in the normal fashion.  Use blending when drawing
 *       the reflective, textured surface.
 *
 * This is a very crude demo.  It could be much better.
 */
 
/*
 * Dirk Reiners (reiners@igd.fhg.de) made some modifications to this code.
 *
 * August 1996 - A few optimizations by Brian
 */

/*
 * April, 1997 - Added Mark Kilgard's changes.
 */

import java.applet.*;
import java.awt.*;
import java.awt.event.*;
import java.lang.*;
import java.util.*;
import java.io.*;
import java.util.*;

import gl4java.GLContext;
import gl4java.awt.GLAnimCanvas;
import gl4java.applet.SimpleGLAnimApplet1;

public class reflect extends SimpleGLAnimApplet1 
{
        /* Initialize the applet */


	public void init()
	{
	super.init();
        Dimension d = getSize();
        canvas = new reflectCanvas(d.width, d.height);
        add("Center", canvas);
	}


        /* Local GLAnimCanvas extension class */


    private class reflectCanvas extends GLAnimCanvas implements MouseListener, MouseMotionListener
    {
        private static final float M_PI = 3.14159265359f;
        private static final float DEG2RAD = (M_PI/180.0f);
        private static final int MAX_OBJECTS = 2;

        private final float light_pos[] = { 0.0f, 20.0f, 0.0f, 1.0f };

        private int textureid = 0;

        private int table_list;
        private int objects_list[];

        private float spin, xrot, yrot;

        private MatrixFuncs mtxfuncs = null;
        private int prevMouseX, prevMouseY;
        private boolean mouseRButtonDown = false;

        public reflectCanvas(int w, int h)
        {
            super(w, h);
            GLContext.gljNativeDebug = false;
            GLContext.gljClassDebug = false;
            setAnimateFps(60.0f);

            mtxfuncs = new MatrixFuncs();
            objects_list = new int[MAX_OBJECTS];
        }
    
        public void preInit()
        {
            doubleBuffer = true;
            stereoView = false;
	    stencilBits = 3;
        }
    
        public void init()
        {
            System.out.println("init(): " + this);
            reshape(getSize().width, getSize().height);
            
            make_table();
            make_objects();

            int texIdBuf[] = new int[1];
            gl.glGenTextures(1, texIdBuf);
            textureid = texIdBuf[0];

            gl.glBindTexture(GL_TEXTURE_2D,textureid);

            HardCodedTexture texture = new DemoTex1();
            gl.glTexImage2D
                (GL_TEXTURE_2D,
                 0,
                 texture.getComponents(),
                 texture.getWidth(),
                 texture.getHeight(),
                 0,
                 texture.getFormat(),
                 texture.getType(),
                 texture.getBits());

            gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
            gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
            gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
            gl.glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

            gl.glBindTexture(GL_TEXTURE_2D,0);

            spin = 0.0f;
            xrot = 30.0f;
            yrot = 50.0f;

            gl.glShadeModel(GL_FLAT);
   
            gl.glEnable(GL_LIGHT0);
            gl.glEnable(GL_LIGHTING);
            
            gl.glClearColor(0.5f, 0.5f, 0.5f, 1.0f);
            
            gl.glEnable(GL_NORMALIZE);
            
            glj.gljCheckGL();

            addMouseListener(this);
            addMouseMotionListener(this);
        }
    
        public void doCleanup()
        {
            System.out.println("destroy(): " + this);
            removeMouseListener(this);
            removeMouseMotionListener(this);
        }
    
        public void reshape(int width, int height)
        {
            float aspect = (float)width / (float)height;

            gl.glViewport(0,0,width,height);
            gl.glMatrixMode(GL_PROJECTION);
            gl.glLoadIdentity();
            gl.glFrustum(-aspect, aspect, -1.0f, 1.0f, 4.0f, 300.0f);
            gl.glMatrixMode(GL_MODELVIEW);
            gl.glLoadIdentity();
        }

        public void display()
        {
            if (glj.gljMakeCurrent() == false) return;

            spin += 0.8f;
            yrot += 1.2f;
            if (yrot >= 360.0f) yrot -= 360.0f;

            float dist = 20.0f;
            float eyex, eyey, eyez;

            gl.glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);

            eyex = dist * (float)Math.cos(yrot*DEG2RAD) * (float)Math.cos(xrot*DEG2RAD);
            eyez = dist * (float)Math.sin(yrot*DEG2RAD) * (float)Math.cos(xrot*DEG2RAD);
            eyey = dist * (float)Math.sin(xrot*DEG2RAD);

            /* view from top */
            gl.glPushMatrix();

            glu.gluLookAt(eyex,eyey,eyez,0.0f,0.0f,0.0f,0.0f,1.0f,0.0f);
            
            gl.glLightfv(GL_LIGHT0, GL_POSITION, light_pos);
             
            /* draw table into stencil planes (non-textured for speed) */
            gl.glEnable(GL_STENCIL_TEST);
            gl.glDisable(GL_DEPTH_TEST);
            gl.glStencilFunc(GL_ALWAYS, 1, 0xffffffff);
            gl.glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
            gl.glColorMask(false, false, false, false);
            draw_table();
            gl.glColorMask(true, true, true, true);
            
            gl.glEnable(GL_DEPTH_TEST);

            /* render view from below (reflected viewport) */
            /* only draw where stencil==1 */
            if (eyey > 0.0f)
            {
                gl.glPushMatrix();
                
                gl.glStencilFunc(GL_EQUAL, 1, 0xffffffff);  /* draw if ==1 */
                gl.glStencilOp(GL_KEEP, GL_KEEP, GL_KEEP);
                gl.glScalef(1.0f, -1.0f, 1.0f);
                
                /* Reposition light in reflected space. */
                gl.glLightfv(GL_LIGHT0, GL_POSITION, light_pos);
                
                draw_objects(eyex, eyey, eyez);
                gl.glPopMatrix();
                
                /* Restore light's original unreflected position. */
                gl.glLightfv(GL_LIGHT0, GL_POSITION, light_pos);
            }

            /* draw table into color planes (textured this time) */
            gl.glDisable(GL_STENCIL_TEST);
            
            gl.glEnable(GL_BLEND);
            gl.glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

            gl.glBindTexture(GL_TEXTURE_2D,textureid);
            gl.glEnable(GL_TEXTURE_2D);
            draw_table();
            gl.glDisable(GL_TEXTURE_2D);
            gl.glBindTexture(GL_TEXTURE_2D,0);
            gl.glDisable(GL_BLEND);

            /* view from top */
            gl.glPushMatrix();
            
            draw_objects(eyex, eyey, eyez);

            gl.glPopMatrix();
            
            gl.glPopMatrix();

            glj.gljSwap();
            glj.gljCheckGL();
            glj.gljFree();
    
            //if (!isSuspended()) repaint();  // Animate at full speed.
        }

        private void make_table()
        {
            float table_mat[] = { 1.0f, 1.0f, 1.0f, 0.6f };
            float gray[] = { 0.4f, 0.4f, 0.4f, 1.0f };
            
            table_list = gl.glGenLists(1);
            gl.glNewList(table_list,GL_COMPILE);
            
            /* load table's texture */
            gl.glMaterialfv(GL_FRONT,GL_AMBIENT_AND_DIFFUSE,table_mat);
            gl.glMaterialfv(GL_FRONT,GL_DIFFUSE,table_mat);
            gl.glMaterialfv(GL_FRONT,GL_AMBIENT,gray);
            
            /* draw textured square for the table */
            gl.glPushMatrix();
            gl.glScalef(4.0f, 4.0f, 4.0f);
            gl.glBegin(GL_POLYGON);
            gl.glNormal3f(0.0f, 1.0f, 0.0f);
            gl.glTexCoord2f(0.0f, 0.0f);   gl.glVertex3f(-1.0f, 0.0f,  1.0f);
            gl.glTexCoord2f(1.0f, 0.0f);   gl.glVertex3f( 1.0f, 0.0f,  1.0f);
            gl.glTexCoord2f(1.0f, 1.0f);   gl.glVertex3f( 1.0f, 0.0f, -1.0f);
            gl.glTexCoord2f(0.0f, 1.0f);   gl.glVertex3f(-1.0f, 0.0f, -1.0f);
            gl.glEnd();
            gl.glPopMatrix();
            
            gl.glEndList();
        }

        private void make_objects()
        {
           long q;
        
           float cyan[]  = { 0.0f, 1.0f, 1.0f, 1.0f };
           float green[] = { 0.2f, 1.0f, 0.2f, 1.0f };
           float black[] = { 0.0f, 0.0f, 0.0f, 0.0f };
        
           q = glu.gluNewQuadric();
           glu.gluQuadricDrawStyle(q, GLU_FILL);
           glu.gluQuadricNormals(q, GLU_SMOOTH);
        
           objects_list[0] = gl.glGenLists(1);
           gl.glNewList(objects_list[0], GL_COMPILE);
           gl.glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, cyan );
           gl.glMaterialfv(GL_FRONT, GL_EMISSION, black );
           glu.gluCylinder(q, 0.5f, 0.5f,  1.0f, 15, 10);
           gl.glEndList();
        
           objects_list[1] = gl.glGenLists(1);
           gl.glNewList(objects_list[1], GL_COMPILE);
           gl.glMaterialfv(GL_FRONT, GL_AMBIENT_AND_DIFFUSE, green);
           gl.glMaterialfv(GL_FRONT, GL_EMISSION, black);
           glu.gluCylinder(q, 1.5, 0.0f, 2.5f, 15, 10);
           gl.glEndList();
        }


        private void draw_objects(float eyex, float eyey, float eyez)
        {
            gl.glPushMatrix();
            gl.glTranslatef(1.0f, 1.5f, 0.0f);
            gl.glRotatef(spin, 1.0f, 0.5f, 0.0f);
            gl.glRotatef(0.5f*spin, 0.0f, 0.5f, 1.0f);
            gl.glCallList(objects_list[0]);
            gl.glPopMatrix();
            
            gl.glPushMatrix();
            gl.glTranslatef(-1.0f, 0.85f+3.0f*(float)Math.abs((float)Math.cos(0.01f*spin)),0.0f);
            gl.glRotatef(0.5f*spin, 0.0f, 0.5f, 1.0f);
            gl.glRotatef(spin, 1.0f, 0.5f, 0.0f);
            gl.glScalef(0.5f, 0.5f, 0.5f);
            gl.glCallList(objects_list[1]);
            gl.glPopMatrix();
        }

        private void draw_table()
        {
            gl.glCallList(table_list);
        }

        float[] MatrixTransform(float m[], float vx, float vy, float vz, float vw)
        {
            float v[] = new float[4];
            float nx, ny, nz, nw, ninvw;
    
            nx = ((vx*m[0])+(vy*m[4])+(vz*m[ 8])+(vw*m[12]));
            ny = ((vx*m[1])+(vy*m[5])+(vz*m[ 9])+(vw*m[13]));
            nz = ((vx*m[2])+(vy*m[6])+(vz*m[10])+(vw*m[14]));
            nw = ((vx*m[3])+(vy*m[7])+(vz*m[11])+(vw*m[15]));
            if ( (nw != 0.0f) && (nw != 1.0f) )
            {
                ninvw = 1.0f/nw;
                nx *= ninvw;
                ny *= ninvw;
                nz *= ninvw;
            }
            v[0] = nx;
            v[1] = ny;
            v[2] = nz;
            v[3] = 1.0f;
            return(v);
        }

        // Methods required for the implementation of MouseListener
        public void mouseEntered( MouseEvent evt )
        {
        }
    
        public void mouseExited( MouseEvent evt )
        {
        }
    
        public void mousePressed( MouseEvent evt )
        {
            prevMouseX = evt.getX();
            prevMouseY = evt.getY();
            if ((evt.getModifiers() & evt.BUTTON3_MASK) != 0)
            {
                mouseRButtonDown = true;
                evt.consume();
            }
        }
    
        public void mouseReleased( MouseEvent evt )
        {
            if ((evt.getModifiers() & evt.BUTTON3_MASK) != 0)
            {
                mouseRButtonDown = false;
                evt.consume();
            }
        }
    
        public void mouseClicked( MouseEvent evt )
        {
        }
    
        // Methods required for the implementation of MouseMotionListener
        public void mouseDragged( MouseEvent evt )
        {
            int x = evt.getX();
            int y = evt.getY();
            float thetaX = (float)(x-prevMouseX)*(360.0f/(float)getSize().width);
            float thetaY = (float)(prevMouseY-y)*(360.0f/(float)getSize().height);
            float mtxbuf[] = new float[16];
            float mtxbuf2[] = new float[16];
    
            prevMouseX = x;
            prevMouseY = y;
            if ( (thetaX != 0.0f) || (thetaY != 0.0f) )
            {
// This is a special hack for this applet only, due to
// the fact that we are not using a rotation matrix!!!
                yrot += thetaX;
                while (yrot < 0.0f) yrot += 360.0f;
                while (yrot >= 360.0f) yrot -= 360.0f;

                xrot -= thetaY;
                if (xrot > 90.0f)
                    xrot = 90.0f;
                else if (xrot < -90.0f)
                    xrot = -90.0f;
            }
            evt.consume();
        }
    
        public void mouseMoved( MouseEvent evt )
        {
        }
    }
}
