import com.hermetica.magician.*;
import com.hermetica.util3d.*;

/** Implements the GLComponentListener for the OpenGL Illumination DemoScene.
 *
 *          R.J.L. August 1998.
 *
 *              Extended to a more complex scene (cube, cylinder, sphere, cone)
 *              July, 1999. RJL.
 */

class DemoScene implements GLEventListener {

    static final double FOVDEGS = 45;   // Default field of view (degrees)
    static final double FOV = FOVDEGS * Math.PI / 180;  // Ibid (radians)

    // Vertex, face and normal data for the cube
    static final double[][] vertices = {{0,0,0}, // left, down, back
       					{1,0,0}, // right, down, back
    					{0,1,0}, // left, up, back
    					{1,1,0}, // right, up, back
    					{0,0,1}, // left, down, front
    					{1,0,1}, // right, down, front
    					{0,1,1}, // left, up, front
    					{1,1,1}};// right,up, front
    static final double[][] normals = {{0,0,-1}, // normal for back face
    					{0,0,1}, // normal for front face
    					{0,1,0}, // normal for top face
        				{0,-1,0},// normal for bottom face
        				{-1,0,0},// normal for left face
        				{1,0,0}};// normal for right face
    // order of vertices important, otherwise backface culling
    // will just do it wrong
    static final int[][] faces = {{0,2,3,1},  // back
    				  {4,5,7,6},  // front
       				  {2,6,7,3},  // top
    				  {4,0,1,5},  // bottom
    				  {6,2,0,4},  // left
	  			      {1,3,7,5}}; // right
    /* The OpenGL state machine */
    private CoreGL gl_ = new CoreGL();
    private CoreGLU glu_ = new CoreGLU();
    GLParameterSource paramSource;
    GLComponent glc;

    /* DemoScene current rotation */
    // not used in Demo
    float[][] rotation = {{1,0,0,0},
    			  {0,1,0,0},
    			  {0,0,1,0},
    			  {0,0,0,1}};

    /** Drawable dimensions */
    int width = 100, height = 100;  // meaningless defaults to keep compiler happy

  public DemoScene(GLParameterSource paramSource) {
        this.paramSource = paramSource;
    }
 
    /** Initialization stuff */
    public void initialize( GLDrawable component ) {
      gl_.glClearColor( 0.0f, 0.0f, 0.0f, 1.0f );
      gl_.glShadeModel( GL.GL_SMOOTH );
      gl_.glEnable(GL.GL_DEPTH_TEST);
    }

    /** Handles viewport resizing */
    public void reshape( GLDrawable component, int x, int y, int width, int height ) {
        /** Setup the viewport */
        gl_.glViewport( component, x, y, width, height );
        this.width = width; this.height = height;
    }

    /** Renders a DemoScene */
    public void display( GLDrawable component ) {

        gl_.glClear( GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT );
        gl_.glShadeModel(paramSource.getShadingModel());

        // Set up projection and viewing (with model rotatable by mouse)
        gl_.glMatrixMode( GL.GL_PROJECTION );
        gl_.glLoadIdentity();
        double zoom = paramSource.getZoomFactor();
        double fovActual = 2 * Math.atan(Math.tan(FOV / 2) / zoom) * 180.0 / Math.PI;
        glu_.gluPerspective( (float) fovActual, (float) width / height, 0.5f, 20.0f );
        gl_.glMatrixMode( GL.GL_MODELVIEW );

        gl_.glLoadIdentity();
        glu_.gluLookAt( 0.0f, 0.5f, 8.0f, 0.0f, 0.5f, 0.0f, 0.0f, 1.0f, 0.0f );
        gl_.glMultMatrixf(rotation);

        // Set up lighting

        float intens = paramSource.getLightIntensity();
        float ambient = paramSource.getAmbientLightLevel();
        float[] lightColour = {intens, intens, intens, 1.0f};
        float[] ambientColour = {ambient, ambient, ambient, 1.0f};
        float[] black = {0,0,0,1};
        gl_.glLightModeli(GL.GL_LIGHT_MODEL_TWO_SIDE, GL.GL_TRUE);
        gl_.glLightModelfv(GL.GL_LIGHT_MODEL_AMBIENT, ambientColour);
        gl_.glLightfv(GL.GL_LIGHT0, GL.GL_AMBIENT, black);
        gl_.glLightfv(GL.GL_LIGHT0, GL.GL_DIFFUSE, lightColour);
        gl_.glLightfv(GL.GL_LIGHT0, GL.GL_SPECULAR, lightColour);
        gl_.glLightfv(GL.GL_LIGHT0, GL.GL_POSITION, paramSource.getLightPosition());
        gl_.glEnable(GL.GL_LIGHT_MODEL_LOCAL_VIEWER);
        gl_.glEnable(GL.GL_LIGHTING);
        gl_.glEnable(GL.GL_LIGHT0);
        gl_.glEnable(GL.GL_NORMALIZE);

        // Set up scene, starting with "ground" polygon.
        float[] groundColour = {0.2f, 0.4f, 0.2f, 1.0f};
        float[] groundSpecular = {0,0,0,1.0f};
        gl_.glMaterialfv(GL.GL_FRONT, GL.GL_AMBIENT_AND_DIFFUSE, groundColour);
        gl_.glMaterialfv(GL.GL_FRONT, GL.GL_SPECULAR, groundSpecular);
        gl_.glMaterialfv(GL.GL_FRONT, GL.GL_SHININESS, groundSpecular);
           
        float[][] groundSq = {{-5,0,5}, {5,0,5},{5,0,-5},{-5,0,-5}};
        float[] vertical = {0f, 1f, 0f};
        gl_.glBegin(GL.GL_POLYGON);
        gl_.glNormal3fv(vertical);
        for (int i=0;i<4;i++) {
	    gl_.glVertex3fv(groundSq[i]);
   	}
        gl_.glEnd();
       
        float[] lightPos=paramSource.getLightPosition();
	int tess = paramSource.getNumTessellations();

	// Set shadows
	float[] shadowMatrix={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
        shadowMatrix[0] = lightPos[1];
	shadowMatrix[4] = -lightPos[0];
	// shadowMatrix[5] = lightPos[1]-lightPos[1];   
	shadowMatrix[6] = -lightPos[2];
	shadowMatrix[7] = -1;
	shadowMatrix[10] = lightPos[1];
	shadowMatrix[15] = lightPos[1];
	gl_.glDisable(GL.GL_DEPTH_TEST);
	gl_.glDisable(GL.GL_LIGHTING);
	gl_.glColor4fv(black);

	// shadows:
	gl_.glPushMatrix();
	// sphere
        gl_.glMultMatrixf(shadowMatrix);
        gl_.glTranslatef(1,1,1);
	glu_.gluSphere(glu_.gluNewQuadric(), 0.4, tess, tess);
		
	// cube
	gl_.glMultMatrixf(shadowMatrix);
        gl_.glTranslatef(-2,0,0);
        drawCube(0.8);
   	
	// cylinder
	gl_.glMultMatrixf(shadowMatrix);
        gl_.glTranslatef(2,0,-2);
        gl_.glPushMatrix();
	glu_.gluCylinder(glu_.gluNewQuadric(), 0.4, 0.4, 0.8, tess, 1);
       	gl_.glPushMatrix();
        gl_.glRotated(180, 0, 1, 0);				// Flip this one
        glu_.gluDisk(glu_.gluNewQuadric(), 0, 0.4, tess, 1);	// base of cylinder
	gl_.glPopMatrix();
        gl_.glTranslatef(0, 0, 0.8f);
        glu_.gluDisk(glu_.gluNewQuadric(), 0, 0.4, tess, 1); 	// top of cylinder
        gl_.glPopMatrix();    
        
        // cone
	gl_.glMultMatrixf(shadowMatrix);
	gl_.glTranslatef(-2,0,0);
        gl_.glPushMatrix();
	glu_.gluCylinder(glu_.gluNewQuadric(), 0.4, 0, 0.8, tess, 5);
	gl_.glRotated(180, 0, 1, 0);				// Flip disk to point outwards
        glu_.gluDisk(glu_.gluNewQuadric(), 0, 0.4, tess, 1);	// base of cone
        gl_.glPopMatrix();
        
        gl_.glPopMatrix();
	gl_.glEnable(GL.GL_DEPTH_TEST);
	gl_.glEnable(GL.GL_LIGHTING);


	// Settings for the material
        float[] matAmbient = paramSource.getMaterialAmbient();
        float[] matDiffuse = paramSource.getMaterialDiffuse();
        float[] matSpecular = paramSource.getMaterialSpecular();
        float[] matShininess = {paramSource.getShininess()};
        gl_.glMaterialfv(GL.GL_FRONT, GL.GL_AMBIENT, matAmbient);
        gl_.glMaterialfv(GL.GL_FRONT, GL.GL_DIFFUSE, matDiffuse);
        gl_.glMaterialfv(GL.GL_FRONT, GL.GL_SPECULAR, matSpecular);
        gl_.glMaterialfv(GL.GL_FRONT, GL.GL_SHININESS, matShininess);
	
	// Now the sphere, the cube, the cylinder and the cone
        gl_.glTranslatef(1,1,1);
        glu_.gluSphere(glu_.gluNewQuadric(), 0.4, tess, tess);
	
	gl_.glTranslatef(-2,0,0);
        drawCube(0.8);
   
        gl_.glTranslatef(2,0,-2);
        gl_.glPushMatrix();
        glu_.gluCylinder(glu_.gluNewQuadric(), 0.4, 0.4, 0.8, tess, 1);
      	gl_.glPushMatrix();
        gl_.glRotated(180, 0, 1, 0);
        glu_.gluDisk(glu_.gluNewQuadric(), 0, 0.4, tess, 1);
	gl_.glPopMatrix();
        gl_.glTranslatef(0, 0, 0.8f);
        glu_.gluDisk(glu_.gluNewQuadric(), 0, 0.4, tess, 1); 
        gl_.glPopMatrix();

        gl_.glTranslatef(-2,0,0);
        gl_.glPushMatrix();
	glu_.gluCylinder(glu_.gluNewQuadric(), 0.4, 0, 0.8, tess, 5);
	gl_.glRotated(180, 0, 1, 0);
        glu_.gluDisk(glu_.gluNewQuadric(), 0, 0.4, tess, 1);
        gl_.glPopMatrix();
}

	

    /** Returns a GL pipeline from the listener */
    public GL getGL() {
        return gl_;
    }
    // The following method is not part of the GLComponentListener interface,
    // but is specific to the DemoScene.

    /** Set rotation of colour cube. Called by virtual trackball handler */
    public void setRotation(float[][] rotationMatrix) {
        rotation = rotationMatrix;
    }
    
    // Draw cube of given size, centred at origin.
    // Cube material is whatever is set up at the time. 
    private void drawCube(double size) {
        gl_.glPushMatrix();
        gl_.glScaled(size, size, size);
        gl_.glTranslatef(-0.5f,-0.5f,-0.5f);
        for (int i=0; i < 6; i++) {
            gl_.glBegin(GL.GL_POLYGON);
            gl_.glNormal3dv(normals[i]);
            for (int j=0; j<faces[i].length; j++)
                gl_.glVertex3dv(vertices[faces[i][j]]);
            gl_.glEnd();
        }
        gl_.glPopMatrix();
    }

}