/*
 * @(#)Cubes.java 01/12/07
 * Copyright (c) 2001 Joachim Diepstraten
 *
 * I grant you ("Licensee") a non-exclusive, royalty free, license to use,
 * modify and redistribute this software in source and binary code form,
 * provided that i) this copyright notice and license appear on all copies of
 * the software;

 * This software is provided "AS IS," without a warranty of any kind. ALL
 * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY
 * IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR
 * NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN AND ITS LICENSORS SHALL NOT BE
 * LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING
 * OR DISTRIBUTING THE SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR ITS
 * LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR DIRECT,
 * INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER
 * CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF THE USE OF
 * OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGES.
 *
 * This software is not designed or intended for use in on-line control of
 * aircraft, air traffic, aircraft navigation or aircraft communications; or in
 * the design, construction, operation or maintenance of any nuclear
 * facility. Licensee represents and warrants that it will not use or
 * redistribute the Software for such purposes.
 */

import java.applet.Applet;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import com.sun.j3d.utils.applet.MainFrame;
import com.sun.j3d.utils.universe.*;
import javax.media.j3d.*;
import javax.vecmath.*;
import java.io.*;
import java.beans.PropertyChangeListener;
import com.sun.j3d.utils.behaviors.vp.*;
import com.sun.j3d.utils.geometry.Box;
import com.sun.j3d.utils.geometry.Sphere;

public class Cubes extends Applet implements PropertyChangeListener {



    private SimpleUniverse u;
    private TransformGroup tg1;
    private TransformGroup tg2;
    private TransformGroup tg3;
    private BranchGroup objRoot;
    private BranchGroup bgEvents;
    private Shape3D boxes[] = new Shape3D[512];

  //  private Sphere boxes[] = new Sphere[512];
    private TransformGroup tgBoxes[] = new TransformGroup[512];
    private Canvas3D c;
    private Texture tex;
    private Texture tex2;
    private TexCoordGeneration genNormalMap;    
    private TextureCubeMap texCmap;

    private float distBuffer[] = new float[128*128];

    private float xcoords[] = new float[128];
    private float ycoords[] = new float[128];

    float mindist = Float.MAX_VALUE;
    float maxdist = Float.MIN_VALUE;
    

/***
 * Initialize CEL Texture parameters 
 */
 
    private void initCelParameters() {
      for (int i=0; i<xcoords.length; i++) {
        xcoords[i] = (float)(Math.random()*128.0f);
        ycoords[i] = (float)(Math.random()*128.0f);
      }  	
      float tempDist = 0;
      for (int x=0; x<128; x++)
        for (int y=0; y<128; y++) {
          tempDist = distToNearestPoint((float)x,(float)y,xcoords,ycoords);
          distBuffer[x+(y << 7)] = tempDist; 	
          if (tempDist < mindist) mindist = tempDist;
          if (tempDist > maxdist) maxdist = tempDist;
      }  	
      
    }

/***
 * Check the distance to the nearest neighbour
 * @param x coordinate of the point to be checked
 * @param y coordinate of the point to be checked
 * @param x coordinate array of all other points
 * @param y coordinate array of all other points
 * @return distance to the nearest neighbour
 */
    
    private float distToNearestPoint(float x, float y, float[] xcoords, float[] ycoords) {
      float mindist = Float.MAX_VALUE;
      float dist = 0;
      for (int i=0; i<xcoords.length; i++) {
        dist = (float)Math.sqrt((xcoords[i]-x)*(xcoords[i]-x)+(ycoords[i]-y)*(ycoords[i]-y));
        if (dist < mindist) mindist = dist;	
      }
      return mindist;	
    }

/***
 * Substitute to Java3D Utility Box Primitive. The reasons for writting
 * my own box routine is to have fake smooth normals and to use only
 * one singular shape instead of 6 for each box side
 * @param xsize of the box
 * @param ysize of the box
 * @param zsize of the box
 * @return Shape3D node for the box
 */

    private Shape3D buildBox(float xsize, float ysize, float zsize) {
      
      Shape3D returnShape = new Shape3D();
      
      QuadArray geometry = new QuadArray(24,QuadArray.COORDINATES|QuadArray.NORMALS);
       
// face 1      
      geometry.setCoordinate(0,new Point3f(-xsize,ysize,zsize));
      geometry.setCoordinate(1,new Point3f(xsize,ysize,zsize));
      geometry.setCoordinate(2,new Point3f(xsize,-ysize,zsize));
      geometry.setCoordinate(3,new Point3f(-xsize,-ysize,zsize));
      geometry.setNormal(0,new Vector3f(-0.33f,-0.33f,0.33f));
      geometry.setNormal(1,new Vector3f(0.33f,-0.33f,0.33f));
      geometry.setNormal(2,new Vector3f(0.33f,0.33f,0.33f));
      geometry.setNormal(3,new Vector3f(-0.33f,0.33f,0.33f));


// face 2
      geometry.setCoordinate(4,new Point3f(-xsize,ysize,-zsize));
      geometry.setCoordinate(5,new Point3f(xsize,ysize,-zsize));
      geometry.setCoordinate(6,new Point3f(xsize,-ysize,-zsize));
      geometry.setCoordinate(7,new Point3f(-xsize,-ysize,-zsize));
      geometry.setNormal(4,new Vector3f(-0.33f,-0.33f,-0.33f));
      geometry.setNormal(5,new Vector3f(0.33f,-0.33f,-0.33f));
      geometry.setNormal(6,new Vector3f(0.33f,0.33f,-0.33f));
      geometry.setNormal(7,new Vector3f(-0.33f,0.33f,-0.33f));

// face 3
      geometry.setCoordinate(8,new Point3f(xsize,ysize,zsize));
      geometry.setCoordinate(9,new Point3f(xsize,ysize,-zsize));
      geometry.setCoordinate(10,new Point3f(xsize,-ysize,-zsize));
      geometry.setCoordinate(11,new Point3f(xsize,-ysize,zsize));
      geometry.setNormal(8,new Vector3f(0.33f,-0.33f,0.33f));
      geometry.setNormal(9,new Vector3f(0.33f,-0.33f,-0.33f));
      geometry.setNormal(10,new Vector3f(0.33f,0.33f,-0.33f));
      geometry.setNormal(11,new Vector3f(0.33f,0.33f,0.33f));

// face 4
      geometry.setCoordinate(12,new Point3f(-xsize,ysize,zsize));
      geometry.setCoordinate(13,new Point3f(-xsize,ysize,-zsize));
      geometry.setCoordinate(14,new Point3f(-xsize,-ysize,-zsize));
      geometry.setCoordinate(15,new Point3f(-xsize,-ysize,zsize));
      geometry.setNormal(12,new Vector3f(-0.33f,-0.33f,0.33f));
      geometry.setNormal(13,new Vector3f(-0.33f,-0.33f,-0.33f));
      geometry.setNormal(14,new Vector3f(-0.33f,0.33f,-0.33f));
      geometry.setNormal(15,new Vector3f(-0.33f,0.33f,0.33f));

// face 5
      geometry.setCoordinate(16,new Point3f(-xsize,-ysize,zsize));
      geometry.setCoordinate(17,new Point3f(xsize,-ysize,zsize));
      geometry.setCoordinate(18,new Point3f(xsize,-ysize,-zsize));
      geometry.setCoordinate(19,new Point3f(-xsize,-ysize,-zsize));
      geometry.setNormal(16,new Vector3f(-0.33f,0.33f,0.33f));
      geometry.setNormal(17,new Vector3f(0.33f,0.33f,0.33f));
      geometry.setNormal(18,new Vector3f(0.33f,0.33f,-0.33f));
      geometry.setNormal(19,new Vector3f(-0.33f,0.33f,-0.33f));

// face 6
      geometry.setCoordinate(20,new Point3f(-xsize,ysize,zsize));
      geometry.setCoordinate(21,new Point3f(xsize,ysize,zsize));
      geometry.setCoordinate(22,new Point3f(xsize,ysize,-zsize));
      geometry.setCoordinate(23,new Point3f(-xsize,ysize,-zsize));
      geometry.setNormal(20,new Vector3f(-0.33f,-0.33f,0.33f));
      geometry.setNormal(21,new Vector3f(0.33f,-0.33f,0.33f));
      geometry.setNormal(22,new Vector3f(0.33f,-0.33f,-0.33f));
      geometry.setNormal(23,new Vector3f(-0.33f,-0.33f,-0.33f));
      
      returnShape.setGeometry(geometry);
      
      return returnShape;
    }

/***
 * Build the CubeEnvironment Textures
 */
    
    private void buildTextures() {

// the following line is maybe one of the most important line
// you have to specifiy the mapping according to the normal
// don't use the TextureCoordinates generated by a primitive
// this will lead to wrong results

        genNormalMap = new TexCoordGeneration(TexCoordGeneration.REFLECTION_MAP,
                                    TexCoordGeneration.TEXTURE_COORDINATE_2);

        texCmap = new TextureCubeMap(Texture.BASE_LEVEL,Texture.RGB,
                                                    128);

// build buffered Images

        BufferedImage bImage1 = new BufferedImage(128,128,BufferedImage.TYPE_INT_RGB);
        BufferedImage bImage2 = new BufferedImage(128,128,BufferedImage.TYPE_INT_RGB);
        BufferedImage bImage3 = new BufferedImage(128,128,BufferedImage.TYPE_INT_RGB);
        BufferedImage bImage4 = new BufferedImage(128,128,BufferedImage.TYPE_INT_RGB);
        BufferedImage bImage5 = new BufferedImage(128,128,BufferedImage.TYPE_INT_RGB);
        BufferedImage bImage6 = new BufferedImage(128,128,BufferedImage.TYPE_INT_RGB);

             
        for (int y=0; y<128; y++)
          for (int x=0; x<128; x++) {            
            int red = (int)(255*((distBuffer[x+(y << 7)]-mindist)/(maxdist-mindist)));                           
            bImage1.setRGB(x,y,(red << 16)+(red << 8));
            bImage2.setRGB(x,y,(red << 16)+red);
            bImage3.setRGB(x,y,(red << 16)+(red << 8)+red);
            bImage4.setRGB(x,y,(red << 15)+(red << 7));
            bImage5.setRGB(x,y,(red << 15)+(red << 8)+(red >> 1));
            bImage6.setRGB(x,y,(red << 8)+red);
          }
     
// build ImageComponents 

        ImageComponent2D imageC1 = new ImageComponent2D(ImageComponent.FORMAT_RGB,bImage1);
        ImageComponent2D imageC2 = new ImageComponent2D(ImageComponent.FORMAT_RGB,bImage2);
        ImageComponent2D imageC3 = new ImageComponent2D(ImageComponent.FORMAT_RGB,bImage3);
        ImageComponent2D imageC4 = new ImageComponent2D(ImageComponent.FORMAT_RGB,bImage4);
        ImageComponent2D imageC5 = new ImageComponent2D(ImageComponent.FORMAT_RGB,bImage5);
        ImageComponent2D imageC6 = new ImageComponent2D(ImageComponent.FORMAT_RGB,bImage6);

// Now set the textures for each cube side

        texCmap.setImage(0,TextureCubeMap.POSITIVE_X,imageC1);
        texCmap.setImage(0,TextureCubeMap.NEGATIVE_X,imageC2);
        texCmap.setImage(0,TextureCubeMap.POSITIVE_Y,imageC3);
        texCmap.setImage(0,TextureCubeMap.NEGATIVE_Y,imageC4);
        texCmap.setImage(0,TextureCubeMap.POSITIVE_Z,imageC5);
        texCmap.setImage(0,TextureCubeMap.NEGATIVE_Z,imageC6);                                                              	
    }

/***
 * Build the scenegraph
 * @return highest BranchGroup node of the scenegraph
 */
    
    public BranchGroup createSceneGraph() {
        // Create the root of the branch graph
        objRoot = new BranchGroup();

        TransformGroup objScale = new TransformGroup();
        Transform3D t3d = new Transform3D();
                	
 
        // Set up the ambient light

        buildTextures();

                Appearance newAppearance2 = new Appearance();
        Material newMaterial2 = new Material(new Color3f(0.0f,0.0f,0.0f),
                                            new Color3f(0.3f,0.3f,0.3f),
                                            new Color3f(0.5f,0.5f,0.5f),
                                            new Color3f(0.4f,0.4f,0.4f),12.0f);
        newAppearance2.setMaterial(newMaterial2);                

        Appearance newAppearance = new Appearance();
        Material newMaterial = new Material(new Color3f(0.0f,0.0f,0.0f),
                                            new Color3f(0.3f,0.3f,0.3f),
                                            new Color3f(0.5f,0.5f,0.5f),
                                            new Color3f(0.4f,0.4f,0.4f),12.0f);
        newAppearance.setMaterial(newMaterial);                
        PolygonAttributes polyAttr = new PolygonAttributes(PolygonAttributes.POLYGON_FILL,
                                                           PolygonAttributes.CULL_NONE,
                                                           0.0f);
        newAppearance.setPolygonAttributes(polyAttr);                                              
        TextureAttributes texAttr = new TextureAttributes();
        TextureUnitState texUnitStates[] = new TextureUnitState[1];
        texUnitStates[0] = new TextureUnitState(texCmap,texAttr,genNormalMap);

        newAppearance.setTextureUnitState(texUnitStates);
        int counter = 0;
        for (int z = 0; z < 8; z++)
          for (int y = 0; y < 8; y++)
            for (int x = 0; x < 8; x++) {
              tgBoxes[counter] = new TransformGroup();
              Transform3D tempTrans = new Transform3D();
              tempTrans.setTranslation(new Vector3f(-4.0f+x,-4.0f+y,-4.0f+z));
              tgBoxes[counter].setTransform(tempTrans);
              boxes[counter] = buildBox(0.5f,0.5f,0.5f);
//              boxes[counter] = new Sphere(0.5f,Sphere.GENERATE_NORMALS,newAppearance);

              boxes[counter].setAppearance(newAppearance);	
              if (x == 1) 
                  boxes[counter].setAppearance(newAppearance2);
              tgBoxes[counter].setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
              tgBoxes[counter].setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);              
              tgBoxes[counter].addChild(boxes[counter]);
              objRoot.addChild(tgBoxes[counter]);	
              counter++;
            }
            
        Color3f ambientColor = new Color3f(0.1f, 0.1f, 0.1f);

        BoundingSphere bounds =
          new BoundingSphere(new Point3d(0.0,0.0,0.0), 1000.0);

        AmbientLight ambientLightNode = new AmbientLight(ambientColor);
        ambientLightNode.setInfluencingBounds(bounds);
        objRoot.setCapability(BranchGroup.ALLOW_DETACH);
        objRoot.setCapability(Group.ALLOW_CHILDREN_EXTEND);
        objRoot.setCapability(Group.ALLOW_CHILDREN_WRITE);
        objRoot.addChild(ambientLightNode);

        // Set up the directional lights
        Color3f light1Color = new Color3f(1.0f, 1.0f, 0.9f);
        Vector3f light1Direction  = new Vector3f(1.0f, 1.0f, 1.0f);
        Color3f light2Color = new Color3f(1.0f, 1.0f, 1.0f);
        Vector3f light2Direction  = new Vector3f(-1.0f, -1.0f, -1.0f);

        DirectionalLight light1
            = new DirectionalLight(light1Color, light1Direction);
        light1.setInfluencingBounds(bounds);
        objRoot.addChild(light1);

        DirectionalLight light2
            = new DirectionalLight(light2Color, light2Direction);
        light2.setInfluencingBounds(bounds);
        objRoot.addChild(light2);
        objRoot.compile();
        return objRoot;
    }

/***
 * Default constructor
 */
 
    public Cubes() {

        initCelParameters();
	
        setLayout(null);
        GraphicsConfiguration config =
           SimpleUniverse.getPreferredConfiguration();

        c = new Canvas3D(config);
        c.setBounds(0,0,640,480);
        add(c);

        // Create a simple scene and attach it to the virtual universe
        BranchGroup scene = createSceneGraph();
        u = new SimpleUniverse(c,5);
	
        // add mouse behaviors to the ViewingPlatform
        ViewingPlatform viewingPlatform = u.getViewingPlatform();

        // This will move the ViewPlatform back a bit so the
        // objects in the scene can be viewed.
        viewingPlatform.setNominalViewingTransform();
	
        u.getViewer().getView().setBackClipDistance(50.0f);
        tg1 = u.getViewingPlatform().getMultiTransformGroup().getTransformGroup(0);
        tg2 = u.getViewingPlatform().getMultiTransformGroup().getTransformGroup(1);
        tg3 = u.getViewingPlatform().getMultiTransformGroup().getTransformGroup(2);
        Transform3D tempTrans = new Transform3D();
        tempTrans.setTranslation(new Vector3f(0.0f,0.0f,20.0f));
        tg3.setTransform(tempTrans);
        BoundingSphere bounds =
          new BoundingSphere(new Point3d(0.0,0.0,0.0), 1000.0);
        Alpha rotationAlpha = new Alpha(-1, Alpha.INCREASING_ENABLE,
                                          0, 0,
                                          4000, 0, 0,
                                          0, 0, 0);

        Animation anim =
             new Animation(rotationAlpha,tg2,tgBoxes);
        anim.setSchedulingBounds(bounds);
        anim.addPropertyChangeListener(this); 
        bgEvents = new BranchGroup();
        bgEvents.addChild(anim);
        bgEvents.setCapability(BranchGroup.ALLOW_DETACH);
        bgEvents.setCapability(Group.ALLOW_CHILDREN_WRITE);
        objRoot.addChild(bgEvents);

        u.addBranchGraph(scene);
    }


    public void destroy() {
        u.removeAllLocales();
    }


    public static void main(String[] args) {
        new MainFrame(new Cubes(), 640, 480);
    }
    
    public void propertyChange(java.beans.PropertyChangeEvent e) {
    } 
}
