import java.applet.*;
import java.awt.*;
import java.awt.event.*;
import com.sun.j3d.utils.applet.*;
import com.sun.j3d.utils.universe.*;
import javax.media.j3d.*;
import javax.vecmath.*;
import com.sun.j3d.utils.image.*;
import com.sun.j3d.utils.geometry.*;
import com.sun.j3d.utils.behaviors.keyboard.*;
import javax.swing.*;

public class glassUniverse extends Applet implements ActionListener 
{           
    // so all the rays of light start off in the same direction
    // these 2 points serve as a guide
    final Point3d origin=new Point3d(0.0f,0.0f,0.0f);
    final Vector3d rayInitialDirection=new Vector3d(2.0f,0.0f,0.0f);
    
    // so you don't render the scene twice
    boolean firstime=true;
        
        // button to trigger the refraction event
        Button doneButton;
        
        // the scale of the objects
        private final double scale=1;
        
        // a heavyweight Canvas3D so that we can render our 3D graphics 
        private Canvas3D this3Dcanvas;
        
    // Globally used colors
    private Color3f red=new Color3f(0.9f,0.0f,0.0f);
    private Color3f indigo=new Color3f(0.9f,0.0f,0.9f);
        private Color3f black=new Color3f(0.0f,0.0f,0.0f);
        private Color3f white=new Color3f(1.0f,1.0f,1.0f);
        private Color3f lightgrey=new Color3f(0.7f,0.7f,0.7f);
        private Color3f darkgrey=new Color3f(0.2f,0.2f,0.2f);
        private Color3f blue=new Color3f(0.0f,0.0f,0.7f);
        private Color3f green=new Color3f(0.0f,1.0f,0.0f);
        private Color3f pink=new Color3f(1.0f,0.7f,0.7f);
        private Color3f lightblue=new Color3f(0.7f,0.7f,1.0f);
        private Color3f yellow=new Color3f(0.8f,0.8f,0.0f);
        private Color3f orange=new Color3f(0.9f,0.2f,0.0f);
        private Color3f violet=new Color3f(0.8f,0.44f,0.8f);
        
        private Color3f[] rainbowColours={red,orange,yellow,green,blue,indigo,violet};
        // relative indices of refraction going from air to glass
        private static double[] refractiveIndex={1.7,1.6,1.5,1.4,1.3,1.2,1.1};  
        
        // globally used shapes
        myShape[]shapes={
            new Tetrahedron()
            //,new Octahedron()
            //,new convergingLens()
            ,new Cube()
            //,new divergingLens()
            };
            
            
        // the glass shapes' transform groups and transforms
        TransformGroup[] individualObjTransformGrp=new TransformGroup[shapes.length];
        
        // the light rays' group and transform
        TransformGroup lightTransformGrp;
        private Transform3D lightTransform;
        
        TransformGroup objTransGroup=new TransformGroup();
                    
    public glassUniverse() 
    {        
        // set size to maximum
                Dimension screenSize=Toolkit.getDefaultToolkit().getScreenSize();
        this.setLocation(0,0);
                this.setSize(screenSize.width,screenSize.height);
                
                // add instructions
                int controlPanelHeight=150;
                Panel controlPanel=new Panel();
                controlPanel.setSize(screenSize.width,controlPanelHeight);
                Label mouseinstructions=new Label("PUT A GLASS OBJECT IN FRONT OF THE RAY OF LIGHT AND PRESS THE BUTTON.");
                Label keyinstructions=new Label("DRAG the THREE MOUSE BUTTONS (ALT-DRAG works for the 3rd mouse button on a PC) to move the objects.");
                Label lightinstructions=new Label("Use CONTROL, ALT and SHIFT with the ARROW KEYS to navigate.");
                controlPanel.add(mouseinstructions);
                controlPanel.add(keyinstructions);
                controlPanel.add(lightinstructions);
                doneButton=new Button("Refract light");
                doneButton.addActionListener(this);
                controlPanel.add(doneButton);

        // create the glassUniverse Canvas
        this3Dcanvas=new Canvas3D(null);
            this3Dcanvas.setSize(screenSize.width,screenSize.height-controlPanelHeight);
            
        // Layout panels
        setLayout(new FlowLayout());
        controlPanel.setLayout(new GridLayout(4,1));
            add(controlPanel);
            add(this3Dcanvas);

        // Now that we have our Canvas3D, we are ready to build our scene graph 
        VirtualUniverse  thisUniverse = new VirtualUniverse();
        Locale thisLocale = new Locale(thisUniverse);
        // create content branch
        BranchGroup scene=createSceneGraph(this3Dcanvas);
        // Make the universe live by adding the objects to the locale
        thisLocale.addBranchGraph(scene);  
    }
    
    

    public BranchGroup createSceneGraph(Canvas3D canvas) 
    {
        // Create the root of the branch graph
        BranchGroup rootBranch=new BranchGroup();
    
        // attach the scene graph's ViewPlatform to a Canvas3D for rendering
        View thisView = new View();
        thisView.addCanvas3D(this3Dcanvas);
        // use the default PhysicalBody and PhysicalEnvironment
        PhysicalBody thisBod = new PhysicalBody();
        thisBod.setLeftEyePosition(new Point3d(-.006,0.0,0.0));// default is(-0.033, 0.0, 0.0)
        thisBod.setRightEyePosition(new Point3d(+.006,0.0,0.0));
        thisView.setPhysicalBody(thisBod);
        thisView.setPhysicalEnvironment(new PhysicalEnvironment());
        thisView.setMonoscopicViewPolicy(View.LEFT_EYE_VIEW);
        
        // construct a View and connect it to our view branch's ViewPlatform    
        // insert the platform into the transform group,
        // and the transform group into the branch group
        BranchGroup viewBranch=new BranchGroup();
        TransformGroup viewTransformGrp=new TransformGroup();
        Transform3D viewTransform=new Transform3D();
        Vector3d moveBack=new Vector3d(5,0,20);
            viewTransform.set(moveBack);
        viewTransformGrp.setTransform(viewTransform);       
        ViewPlatform thisViewPlatform=new ViewPlatform();
        viewTransformGrp.addChild(thisViewPlatform);
        viewBranch.addChild(viewTransformGrp);
        rootBranch.addChild(viewBranch);
        // attach view to the view platform  
        thisView.attachViewPlatform(thisViewPlatform);
        
        // add the transform group node for the glass objects
            rootBranch.addChild(objTransGroup);
            
            // Create a bounds for the background and behaviours
            BoundingSphere bounds=new BoundingSphere(new Point3d
            (0.0,0.0,0.0), // centre
            1000.0); // extent
            
            // Set up the patterned background from an image file
            /*
            TextureLoader bgTexture=new TextureLoader("Clouds.gif",this);
            Background bg=new Background(bgTexture.getImage());
            bg.setApplicationBounds(bounds);
            rootBranch.addChild(bg);
            */
            
            // Set up the plain white background
            Background thisBackground=new Background(white);
            thisBackground.setApplicationBounds(bounds);
            rootBranch.addChild(thisBackground);

            // Set up the global lights
            Vector3f lightDirection3DVector=new Vector3f(-1.0f,-1.0f,-1.0f);
            AmbientLight thisAmbientLight=new AmbientLight(darkgrey);
            thisAmbientLight.setInfluencingBounds(bounds);
            DirectionalLight thisDirectionalLight=
            new DirectionalLight(lightgrey,lightDirection3DVector);
            thisDirectionalLight.setInfluencingBounds(bounds);
            rootBranch.addChild(thisAmbientLight);
            rootBranch.addChild(thisDirectionalLight);
            
            // Set up colouring attributes
            // A ColoringAttributes node component controls shading model (flat or Gouraud) 
            //ColoringAttributes theseColouringAttributes=new ColoringAttributes();
            //theseColouringAttributes.setColor(pink);
            Appearance thisAppearance=new Appearance();
        //thisAppearance.setColoringAttributes(theseColouringAttributes);       
                    
            // Set up the transparency amount (0.0=opaque, 1.0=invisible) 
                TransparencyAttributes ta=new TransparencyAttributes();
                ta.setTransparencyMode(ta.FASTEST);
                ta.setTransparency(0.6f);
                thisAppearance.setTransparencyAttributes(ta);
                
                // Set up the lit solid texture map from an image file
                /*TextureLoader tex=new TextureLoader("Silver.gif",this);
                thisAppearance.setTexture(tex.getTexture());
                TextureAttributes theseTextureAttributes=new TextureAttributes();
                theseTextureAttributes.setTextureMode(TextureAttributes.MODULATE);
                thisAppearance.setTextureAttributes(theseTextureAttributes);*/
                
                // A Material node component controls: 
        // Ambient, emissive, diffuse, and specular color, shininess factor
        Material thisMaterial=new Material();
        thisMaterial.setAmbientColor(lightgrey);
        thisMaterial.setDiffuseColor(lightblue);
        thisMaterial.setEmissiveColor(darkgrey);
        thisMaterial.setSpecularColor(white);
        thisMaterial.setShininess(70.0f);
                thisAppearance.setMaterial(thisMaterial);

            // Enable the TRANSFORM_READ/WRITE capabilities so that the behaviour 
            // code can modify it at runtime
            objTransGroup.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
            objTransGroup.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
            objTransGroup.setCapability(Group.ALLOW_CHILDREN_EXTEND);
                    
            // create the key behaviour so you can navigate
            KeyBehavior keyNavigationBehaviour=new KeyBehavior(objTransGroup);
                keyNavigationBehaviour.setSchedulingBounds(new BoundingSphere(new Point3d(),1000.0));
                objTransGroup.addChild(keyNavigationBehaviour);
                // create the mouse pick behaviour so you can drag and drop objects
                PickRotateBehavior pickRotateBehaviour=new PickRotateBehavior(rootBranch,canvas,bounds,PickObject.USE_BOUNDS);
        objTransGroup.addChild(pickRotateBehaviour);
        PickZoomBehavior pickZoomBehaviour=new PickZoomBehavior(rootBranch,canvas,bounds,PickObject.USE_BOUNDS);
        objTransGroup.addChild(pickZoomBehaviour);
        PickTranslateBehavior pickTranslateBehaviour=new PickTranslateBehavior(rootBranch,canvas,bounds,PickObject.USE_BOUNDS);
        objTransGroup.addChild(pickTranslateBehaviour);
        
            // add the glass object nodes
            for(int j=0;j<shapes.length;j++) 
            {
                // an arbitrary equation that separates the objects nicely
                    double xpos=(double)(j+1)*scale*4;
                    objTransGroup.addChild(createObject(thisAppearance,xpos,0.0,j));
            }

        // add the light rays' transform group and transform
            lightTransformGrp=new TransformGroup();
            // set the light ray to the left of the glass objects a bit
            Vector3d thisVector3d=new Vector3d(-0.5,0.0,0.0);
            Transform3D translateLightTransform=new Transform3D();
            translateLightTransform.set(thisVector3d);
            // rotate the light ray so it's pointing at the glass objects
            Transform3D rotateLightTransform=new Transform3D();
            rotateLightTransform.rotZ(-Math.PI/2);
            // multiply these two transforms together
            lightTransform=new Transform3D();
            lightTransform.mul(translateLightTransform,rotateLightTransform);
            // set the scale
            lightTransform.setScale(scale);
            // set the group's transform
            lightTransformGrp.setTransform(lightTransform);
            // Enable the capabilities so that our behaviour code can modify it at runtime
            lightTransformGrp.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
        lightTransformGrp.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
        
        // add the arrow of light
        // set up arrow lengths
        float arrowDiameter=0.2f;
        double arrowLength=rayInitialDirection.length();
        float arrowHeadLength=5.0f*arrowDiameter;
        float arrowHeadDiameter=2.0f*arrowDiameter;
        float cylinderLength=(float)arrowLength-arrowHeadLength;
        // Set up arrow appearance
        Appearance arrowAppearance=new Appearance();
        ColoringAttributes arrowColouringAttributes;
        arrowColouringAttributes=new ColoringAttributes();
        arrowColouringAttributes.setColor(yellow);
        arrowAppearance.setColoringAttributes(arrowColouringAttributes);
            TransparencyAttributes arrowTransparency=new TransparencyAttributes();
            arrowTransparency.setTransparencyMode(arrowTransparency.FASTEST);
            arrowTransparency.setTransparency(0.6f);
            arrowAppearance.setTransparencyAttributes(arrowTransparency);
        Material arrowMaterial=new Material();
        arrowMaterial.setAmbientColor(pink);
        arrowMaterial.setDiffuseColor(orange);
        arrowMaterial.setEmissiveColor(yellow);
        arrowMaterial.setSpecularColor(white);
        arrowMaterial.setShininess(0.0f);
            arrowAppearance.setMaterial(arrowMaterial);
        // the parts of the arrow
        Node arrowCylinder=new Cylinder(arrowDiameter,cylinderLength,arrowAppearance);
        Node ArrowHeadCone=new Cone(arrowHeadDiameter,arrowHeadLength,1,arrowAppearance);
        // put these 2 pieces together
        Transform3D arrowHeadTransform=new Transform3D();
        arrowHeadTransform.set(new Vector3f(0.0f,cylinderLength/2.0f+0.5f*arrowHeadLength,0.0f));
        TransformGroup arrowHeadTransformGroup=new TransformGroup(arrowHeadTransform);
        arrowHeadTransformGroup.addChild(ArrowHeadCone);
        lightTransformGrp.addChild(arrowHeadTransformGroup);
        lightTransformGrp.addChild(arrowCylinder);
            
            // add the arrow/light group to the picture
            objTransGroup.addChild(lightTransformGrp);

        // Let Java 3D perform optimisations on this scene graph.
        rootBranch.compile();

            return rootBranch;
    }

    private Group createObject(Appearance thisAppearance,double xpos,double ypos,int index) 
    {
            // Create a transform
            Transform3D objectTransform=new Transform3D();
            objectTransform.set(scale,new Vector3d(xpos,ypos,0.0));
            // attach the transform to the group
        individualObjTransformGrp[index]=new TransformGroup(objectTransform);
            // Enable the capabilities so that our behaviour code can modify it at runtime
        individualObjTransformGrp[index].setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
        individualObjTransformGrp[index].setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
        individualObjTransformGrp[index].setCapability(TransformGroup.ENABLE_PICK_REPORTING);
            // Create a shapes leaf node and set the appearance
            shapes[index].setAppearance(thisAppearance);
            individualObjTransformGrp[index].addChild(shapes[index]);

            return individualObjTransformGrp[index];
    }

public void actionPerformed(ActionEvent evt) 
    {
        if(firstime)
        {
            firstime=false;
            /*
            * To add a shapes to a live scene, create a BranchGroup, add a
            * transform group, add your shapes and then add this BranchGroup 
            * to the live BranchGroup
            * (root BranchGroup)
            *
            * rootBranchGroup (live)
            *   |
            * addBranchGroup (not live until added)
            *   |
            * TransformGroup
            *   |
            * shapes
            */
            
            // the branch group that is added when the button is pressed to show refraction
                BranchGroup refractionBranch=new BranchGroup();
                
            // number of rays I'll show; you can change this number
            int noOfRays=3;
            
            // comment this line back in if you want to see all the colours
            //for(int colourIndex=0;colourIndex<rainbowColours.length;colourIndex++)
            int colourIndex=0;
            {
                // add refracted beam's transform
                    TransformGroup refractionTransformGrp[]=new TransformGroup[noOfRays];
                    Transform3D[] refractionTransform=new Transform3D[noOfRays];
                    for(int i=0;i<noOfRays;i++)
                    {
                        refractionTransformGrp[i]=new TransformGroup();
                        refractionTransform[i]=new Transform3D();
                        // Enable the capabilities so that our behaviour code can modify it at runtime
                        refractionTransformGrp[i].setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
                    refractionTransformGrp[i].setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
                }
                
                // initial start point and direction of light ray
                Point3d start=new Point3d(origin);
                Vector3d dir=new Vector3d(rayInitialDirection);
                
                // the transforms on the shapes
                Transform3D[] ShapeTransforms=new Transform3D[noOfRays];
                for(int i=0;i<shapes.length;i++)ShapeTransforms[i]=getShapeTransform(i);
                
                // the light beams
                lightBeam[] thisLightBeam=new lightBeam[noOfRays];
                
                // to hold the data about the refraction
                double[] refractionVariables=new double[10];
                
                // to hold the refractive index
                double refractIndex=refractiveIndex[colourIndex];
                
                // the rotation and translation transforms for the beams
                Transform3D translateTransform=new Transform3D();
                Transform3D rotateXTransform=new Transform3D();
                Transform3D rotateYTransform=new Transform3D();
                Transform3D rotateZTransform=new Transform3D();

                for(int i=0;i<noOfRays;i++)
                {
                    calculateRefraction(start,dir,shapes,ShapeTransforms,refractiveIndex[colourIndex],refractionVariables);
                    
                    // the rotation and translation transforms for the next beam                
                    refractionTransform[i].mul(rotateXTransform);
                    refractionTransform[i].mul(rotateYTransform);
                    refractionTransform[i].mul(rotateZTransform);
                    refractionTransform[i].mul(translateTransform);
                    
                    // add beam of light
                    thisLightBeam[i]=new lightBeam(rainbowColours[colourIndex]
                        ,refractionVariables[0]);
                    refractionTransformGrp[i].setTransform(refractionTransform[i]);
                    refractionTransformGrp[i].addChild(thisLightBeam[i]);
                    refractionBranch.addChild(refractionTransformGrp[i]);
                    
                    // the point of intersection
                    Point3d intersectionPoint=new Point3d
                    (refractionVariables[3],refractionVariables[4],refractionVariables[5]);
                    // the new start and direction of light ray
                    start.set(intersectionPoint);
                    
                    // the direction of the refracted ray
                    Vector3d refractedDirection=new Vector3d
                    (refractionVariables[6],refractionVariables[7],refractionVariables[8]);
                    refractedDirection.scale(refractionVariables[0]);
                    dir.set(refractedDirection);
                    
                    translateTransform.set(new Vector3d(refractionVariables[3]
                        ,refractionVariables[4],refractionVariables[5]));
                    rotateXTransform.rotX(refractionVariables[6]);
                    rotateYTransform.rotY(refractionVariables[7]);
                    rotateZTransform.rotZ(refractionVariables[8]);
                }
            }
              
            objTransGroup.addChild(refractionBranch); // now live
            
               
            }
        }
            
    public static void main(String[] args)
    // allows glassWorld to be run as an application
    {
            new MainFrame(new glassUniverse(),1014,700);
    }
        
    // returns true if this ray intersects with the shape
    // has a problem (i.e. returns false) if the ray hits an edge
    // should build in a ray intersect line method
    // parameters: the light ray start and finish and the shape index
    private boolean doesRayIntersectShape
    (Vector3d rayStart,Vector3d rayFinish,myShape[] shapes,int shapeIndex)
    {       
        // set up the ray
        rayFinish.sub(rayStart);
        PickRay ray=new PickRay(new Point3d(rayStart),new Vector3d(rayFinish));
        
        // get the real vertex coordinates of the shape
        myShape thisShape=shapes[shapeIndex];
        Point3f[] faceCoords=new Point3f[thisShape.getNumberTriangles()*3];
        Point3d[]faceCoordinates=new Point3d[faceCoords.length];
        for(int i=0;i<thisShape.getNumberTriangles()*3;i++)
        {
            faceCoords[i]=new Point3f(thisShape.getTriangleCoordinates(i));
        }
        for(int i=0;i<faceCoords.length;i++)
        {
            getShapeTransform(shapeIndex).transform(faceCoords[i]); 
            faceCoordinates[i]=new Point3d(faceCoords[i]);
        }
        
        // see if the ray intersects any of the faces
        Intersect thisIntersect=new Intersect();
        boolean toReturn=false;
        double[]dist=new double[3];
        for(int i=0;i<faceCoordinates.length;i+=3)
        {
            if(thisIntersect.rayAndTriangle(ray,faceCoordinates,i,dist))toReturn=true;
        }
        return toReturn;
    }
        
    // parameters: the light ray start and finish, the shapes and their transforms,
    // the light's relative refractive index
    // the refractionVariables array should be allocated by the user. 
    // make sure the shapes are closed and the refractionVariables array has a length of 9
    
    // refractionVariables will contain:
    // at index 0, the distance from ray start to the first intersection
    // at index 1, air to glass=0, glass to air=1
    // at index 2, a flag telling you whether it's refraction(0) or reflection(1)
    // at indices 3,4,5 the components representing the point of intersection
    // at indices 6,7,8 the normalised components of the direction of the reflected/refracted ray
    public static void calculateRefraction
    (Point3d rayStart,Vector3d direction,myShape[] shapes,Transform3D[] ShapeTransforms,
    double refractiveIndex,double[] refractionVariables)
     {
        boolean totalInternalReflection;
        Vector3d dirOfRefractedRay=new Vector3d(); // direction of refracted/reflected ray
        Intersect thisIntersect=new Intersect();
        
        // set up the ray
        PickRay ray=new PickRay(rayStart,direction);
        int count=0;
        
        // to hold all the distances from ray start to intersection
        double[] intersectDist=new double[shapes.length*2];
        // to hold all the angles of incidence
        boolean[] isAirToGlass=new boolean[shapes.length*2];
        // to hold the refracted directions
        Vector3d[] refractedDirections=new Vector3d[shapes.length*2];
        // to hold all the normals
        Vector3d[] normals=new Vector3d[shapes.length*2];
        // to hold all the totalInternalReflections
        boolean[] totalInternalReflections=new boolean[shapes.length*2];
        for(int i=0;i<shapes.length*2;i++)
        {
            isAirToGlass[i]=true;
            refractedDirections[i]=new Vector3d();
            normals[i]=new Vector3d();
        }
        
        for(int shapeIndex=0;shapeIndex<shapes.length;shapeIndex++)
        {
            // get the real vertex coordinates of the shape after they've been placed
            // in space with the shape transform
            myShape thisShape=shapes[shapeIndex];
            Point3f[] faceCoords=new Point3f[thisShape.getNumberTriangles()*3];
            Point3d[]faceCoordinates=new Point3d[faceCoords.length];
            for(int i=0;i<thisShape.getNumberTriangles()*3;i++)
            {
                faceCoords[i]=new Point3f(thisShape.getTriangleCoordinates(i));
            }
            for(int i=0;i<faceCoords.length;i++)
            {
                ShapeTransforms[shapeIndex].transform(faceCoords[i]); 
                faceCoordinates[i]=new Point3d(faceCoords[i]);
            }
                        
            // the distance to intersection
            double[]dist=new double[3];
            double[] distance=new double[2];
            // the direction of refraction
            Vector3d[] refractedDir={new Vector3d(),new Vector3d()};
            Vector3d[] norm={new Vector3d(),new Vector3d()};
            boolean[] reflect={false,false};
            
            int countFacesIntersected=0;

            // to calculate the shape face normals
            Vector3d AB=new Vector3d();
            Vector3d AC=new Vector3d();
            Vector3d normal=new Vector3d();
            
            // the cosine of the angle of incidence
            double cosine;
            
            // air to glass or glass to air
            boolean[] airOrGlass=new boolean[2];
            double actualRefractiveIndex;
            
            // for each shape face
            for(int i=0;i<faceCoordinates.length;i+=3)
            {
                if(thisIntersect.rayAndTriangle(ray,faceCoordinates,i,dist))
                {
                    // the distance to intersection
                    distance[countFacesIntersected]=dist[0];
                    
                    AB.sub(faceCoordinates[i+1],faceCoordinates[i]);
                    AC.sub(faceCoordinates[i+2],faceCoordinates[i]);
                    normal.cross(AB,AC);
                    normal.normalize(); // the normal
                    
                    norm[countFacesIntersected]=normal;
                                        
                    // check if light is going from air to glass or glass to air
                    // check if rayStart point is on positive side of this plane
                    
                    boolean airGlassOrder=true;
                    airOrGlass[countFacesIntersected]=airGlassOrder;
                    if(airGlassOrder)actualRefractiveIndex=refractiveIndex;
                    else actualRefractiveIndex=1/refractiveIndex;
                    
                    Vector3d incidentDirection=new Vector3d(direction);
                    incidentDirection.normalize(); // the vector of ray incidence
                                                                                
                    // method taken from:
                    // http://www.research.microsoft.com/~hollasch/cgindex/render/refraction.txt
                    Vector3d temp=new Vector3d();
                    double c1=incidentDirection.dot(normal);
                    double c2=1-actualRefractiveIndex*actualRefractiveIndex*(1-c1*c1);
                    if (c2<0) totalInternalReflection=true;
                    else totalInternalReflection=false;
                    reflect[countFacesIntersected]=totalInternalReflection;
                    
                    if(!totalInternalReflection)
                    {
                        incidentDirection.scale(actualRefractiveIndex);
                        dirOfRefractedRay.set(incidentDirection);
                        temp.set(normal);
                        temp.scale(actualRefractiveIndex*c1-Math.sqrt(c2));
                        dirOfRefractedRay.add(temp);   
                    }
                    else
                    {
                        temp.set(incidentDirection);
                        double t=-2*temp.dot(normal);
                        temp.set(normal);
                        temp.scale(t);
                        temp.add(incidentDirection);
                        dirOfRefractedRay.set(temp);
                    }
                    
                    dirOfRefractedRay.normalize();
                    refractedDir[countFacesIntersected]=dirOfRefractedRay;
// JOptionPane.showMessageDialog(null,"refractedDir["+countFacesIntersected+"]:"+refractedDir[countFacesIntersected].x+","+refractedDir[countFacesIntersected].y+","+refractedDir[countFacesIntersected].z);                                     
                    countFacesIntersected++;
                }
            }
            
            // copy the data for the 2 points of intersection with this shape
            intersectDist[count]=distance[0];
            refractedDirections[count]=refractedDir[0];
JOptionPane.showMessageDialog(null,"distance"+intersectDist[count]);
JOptionPane.showMessageDialog(null,"refractedDirections["+count+"]:"+refractedDirections[count].x+","+refractedDirections[count].y+","+refractedDirections[count].z);     
            normals[count]=norm[0];
            totalInternalReflections[count]=reflect[0];
            isAirToGlass[count++]=airOrGlass[0];
            intersectDist[count]=distance[1];
            refractedDirections[count]=refractedDir[1];
JOptionPane.showMessageDialog(null,"distance"+intersectDist[count]);
JOptionPane.showMessageDialog(null,"refractedDirections["+count+"]:"+refractedDirections[count].x+","+refractedDirections[count].y+","+refractedDirections[count].z);                                
            normals[count]=norm[1];
            totalInternalReflections[count]=reflect[1];
            isAirToGlass[count++]=airOrGlass[1];
        }// end of for each shape

        // get the index of the shortest distance, i.e. to the first intersection
        double shortestDistance=1000000000;
        int indexOfShortest=-1;
        for(int i=0;i<shapes.length*2;i++)
        {
            if(intersectDist[i]==0)intersectDist[i]=1000000000;
            if(intersectDist[i]<=shortestDistance)
            {
                shortestDistance=intersectDist[i];
                indexOfShortest=i;
            }
        }
        
        // from air to glass or glass to air
        boolean airToGlass=isAirToGlass[indexOfShortest];
        if(airToGlass)refractionVariables[1]=0;
        else refractionVariables[1]=1;
        
        // distance to intersection
        refractionVariables[0]=shortestDistance;
        
        // normal to intersected face
        Vector3d normalToIntersectedFace=normals[indexOfShortest];
        
        // reflection or refraction
        boolean didItReflect=totalInternalReflections[indexOfShortest];
        if (didItReflect) refractionVariables[2]=1;
        else refractionVariables[2]=0;
        
        // calculate the point of intersection
        direction.normalize();
        // incident direction now going up to intersection
        direction.scale(refractionVariables[0]); 
        Point3d intersection=new Point3d();
        intersection.set(rayStart);
        intersection.add(direction);
        refractionVariables[3]=intersection.x;
        refractionVariables[4]=intersection.y;
        refractionVariables[5]=intersection.z;
         
        // get the direction of the refracted ray
        refractionVariables[6]=refractedDirections[indexOfShortest].x;
        refractionVariables[7]=refractedDirections[indexOfShortest].y;
        refractionVariables[8]=refractedDirections[indexOfShortest].z;
        
        // check
// JOptionPane.showMessageDialog(null,"distance:"+shortestDistance);
normalToIntersectedFace.normalize();
// JOptionPane.showMessageDialog(null,"normal:"+normalToIntersectedFace.x+","+normalToIntersectedFace.y+","+normalToIntersectedFace.z);
// JOptionPane.showMessageDialog(null,"directionOfRefractedRay:"+refractedDirections[indexOfShortest].x+","+refractedDirections[indexOfShortest].y+","+refractedDirections[indexOfShortest].z);
// JOptionPane.showMessageDialog(null,"intersection:"+intersection.x+","+intersection.y+","+intersection.z);
// JOptionPane.showMessageDialog(null,"totalInternalReflection:"+didItReflect);  
// JOptionPane.showMessageDialog(null,"airToGlass:"+airToGlass); 
     }
                     
    // returns the transform of the shapes
    // parameter: the index of the shape in the shapes array
    Transform3D getShapeTransform(int shapeIndex)
    {
        Transform3D shapeTransform=new Transform3D();
        individualObjTransformGrp[shapeIndex].getTransform(shapeTransform);
        return shapeTransform;
    }    
}
