/*********************************************/
/* Basic Terrain version 1.2   --29/dec/2001 */
/*********************************************/
 import java.applet.Applet;
 import java.awt.BorderLayout;
 import java.awt.Frame;
 import java.awt.event.*;
 import java.awt.event.WindowAdapter;
 import java.awt.*;
 import javax.swing.*;
 import com.sun.j3d.utils.applet.MainFrame;
 import com.sun.j3d.utils.universe.*;
 import com.sun.j3d.utils.geometry.Sphere;
 import com.sun.j3d.utils.behaviors.keyboard.*;
 import javax.media.j3d.*;
 import javax.vecmath.*;
 import java.awt.event.*;
 import java.util.Enumeration;
 import javax.media.j3d.*;
 import java.lang.*;
 import com.sun.j3d.utils.geometry.ColorCube;

class YRotateBehavior extends Behavior implements ActionListener 
{
    private Transform3D trans;
    private WakeupCriterion criterion;
    private float angle = 0.0f;

    // create a new SimpleBehavior
    public YRotateBehavior(Transform3D t3d) 
    {
        trans = t3d;
    }

    // initialize the behavior to wakeup on a behavior post with the id
    // MouseEvent.MOUSE_CLICKED
    public void initialize() 
    {
        criterion = new WakeupOnBehaviorPost(this,MouseEvent.MOUSE_CLICKED);
        wakeupOn(criterion);
    }

    // processStimulus to rotate the cube
    public void processStimulus(Enumeration criteria) {
        angle += Math.toRadians(10.0);
        trans.rotY(angle);
        transformGroup.setTransform(trans);
        System.out.println("Rotating by "+angle+" radians about Y axis.");
        wakeupOn(criterion);
    }

    // when the mouse is clicked, postId for the behavior
    public void actionPerformed(ActionEvent e) {
        postId(MouseEvent.MOUSE_CLICKED);
    }
}

class XRotateBehavior extends Behavior implements ActionListener 
{
    private TransformGroup transformGroup;
    private Transform3D trans = new Transform3D();
    private WakeupCriterion criterion;
    private float angle = 0.0f;

    // create a new SimpleBehavior
    public XRotateBehavior(TransformGroup tg) 
    {
        transformGroup = tg;
    }

    // initialize the behavior to wakeup on a behavior post with the id
    // MouseEvent.MOUSE_CLICKED
    public void initialize() 
    {
        criterion = new WakeupOnBehaviorPost(this,MouseEvent.MOUSE_CLICKED);
        wakeupOn(criterion);
    }

    // processStimulus to rotate the cube
    public void processStimulus(Enumeration criteria) {
        angle += Math.toRadians(10.0);
        trans.rotX(angle);
        transformGroup.setTransform(trans);
        System.out.println("Rotating by "+angle+" radians about X axis.");
        wakeupOn(criterion);
    }

    // when the mouse is clicked, postId for the behavior
    public void actionPerformed(ActionEvent e) {
        postId(MouseEvent.MOUSE_CLICKED);
    }
}


public class BasicTerrain1_2 extends Applet 
{
        private JButton btnRotateY=null;
        private JButton btnRotateX=null;
        public BranchGroup createSceneGraph(SimpleUniverse su) 
        {
         Color3f[][] bitmap = new_RandomBitmap();
         BranchGroup objRoot = new BranchGroup(); 
         BranchGroup objTerrain = new Terrain(bitmap,5,5,5) ; 
	
         //// add the rotation transform to the scene graph
         TransformGroup objTrans = new TransformGroup();
         objTrans.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
         objRoot.addChild(objTrans);
         Transform3D T3DxRotate = new Transform3D();
         Transform3D T3DyRotate = new Transform3D();

	
         objTrans.addChild(objTerrain); /// add the terrain right under the rotate transform.        
         objTrans.addChild(new ColorCube(0.4)); /// just add one more object for fun!

         //////////////////////////////////////////////////////
         //// add the button click behaviors to the scene graph

         BoundingSphere bounds = new BoundingSphere(new Point3d(0.0, 0.0, 0.0),1000.0);
         //////////////////////////////////////////////////////
         ////                   rotation about Y                    /// 	
         //////////////////////////////////////////////////////
         YRotateBehavior yRotateBehavior = new YRotateBehavior(objTrans);
         yRotateBehavior.setSchedulingBounds(bounds);
         objRoot.addChild(yRotateBehavior);
         ///// register the button handler to receive button clicks
         btnRotateY.addActionListener(yRotateBehavior);
         //////////////////////////////////////////////////////
         ////                   rotation about X                    ///
         //////////////////////////////////////////////////////
         XRotateBehavior xRotateBehavior = new XRotateBehavior(objTrans);
         xRotateBehavior.setSchedulingBounds(bounds);
         objRoot.addChild(xRotateBehavior);
         ///// register the button handler to receive button clicks
         btnRotateX.addActionListener(xRotateBehavior);


         //// add the keyboard behavior to the scene graph
         TransformGroup vpTrans = su.getViewingPlatform().getViewPlatformTransform();
         Transform3D T3D= new Transform3D();
         Vector3f translate = new Vector3f( 0.0f, -0.3f, 0.0f); // 3 meter elevation
         T3D.setTranslation(translate); // set as translation
         vpTrans.setTransform(T3D); // used for initial position
         KeyNavigatorBehavior keyNavBeh = new KeyNavigatorBehavior(vpTrans);
         keyNavBeh.setSchedulingBounds(new BoundingSphere(new Point3d(),1000.0));
         objRoot.addChild(keyNavBeh);

         ///// add lights
         Light lgt1,lgt2;               	
         Color3f lColor1   = new Color3f(1.0f, 0.0f, 0.0f);
         Color3f lColor2   = new Color3f(0.0f, 1.0f, 0.0f);
         Point3f lPoint  = new Point3f(1.0f, 0.0f, 0.0f);
         Point3f atten = new Point3f(1.0f, 0.0f, 0.0f);
         Vector3d lPos1 =  new Vector3d(0.0, 0.0, 0.0);
         Vector3d lPos2 = new Vector3d(0.5, 0.8, 2.0);
         Vector3f lDirect1 = new Vector3f(lPos1);
         Vector3f lDirect2 = new Vector3f(lPos2);
         lDirect1.negate();
         lDirect2.negate();

         lgt1 = new DirectionalLight(lColor1, lDirect1);
         lgt2 = new DirectionalLight(lColor2, lDirect2);
         lgt1.setInfluencingBounds(bounds);
         lgt2.setInfluencingBounds(bounds);
         objRoot.addChild(lgt1);
         objRoot.addChild(lgt2);

         return objRoot;
      } // end of createSceneGraph

        public void init() 
        {
         setLayout(new BorderLayout());
         Canvas3D canvas3D = new Canvas3D(SimpleUniverse.getPreferredConfiguration());
         add("Center",canvas3D);

         btnRotateY = new JButton("Rotate about Y.");  
         add("North",btnRotateY);
         btnRotateX = new JButton("Rotate about X.");  
         add("South",btnRotateX);

         SimpleUniverse simpleU = new SimpleUniverse(canvas3D);
         BranchGroup scene = createSceneGraph(simpleU);

         simpleU.getViewingPlatform().setNominalViewingTransform();
         scene.compile();
	
         simpleU.addBranchGraph(scene);
        } // end of init
        Color3f [][] new_RandomBitmap()
        {
         int xmax = (int)(Math.random()*50+1);
         int ymax = (int)(Math.random()*50+1);
         System.out.println("[method new_RandomBitmap()] Ok, pumping in values...xmax="+xmax+";ymax,"+ymax);
         Color3f [][] ret = new Color3f[xmax][ymax];
         for(int i=0;i<xmax; ++i)
         {
          for(int j =0;j<ymax;++j)
          { 	
                float r = (float)Math.random();
                float g = (float)Math.random();
                float b = (float)Math.random();
                ret[i][j] = new Color3f(r,g,b);
          }
         }
         return ret;
        }
        public static void main(String args[])
        {
                new MainFrame(new BasicTerrain1_2(),256,256);
        }
}


class Terrain extends BranchGroup
{
        int xmid,ymid;
        float scaleX,scaleY,scaleZ;
        Color3f bitmap[][];
        public Terrain(Color3f _bitmap[][],float scalex,float scaley,float scalez)
        {
         bitmap=_bitmap;
         ymid = bitmap.length/2; // number of rows / 2
         xmid = bitmap[0].length/2; // number of cols /2 must be uniform
         System.out.println("[class Terrain] xmid:"+xmid);
         System.out.println("[class Terrain] ymid:"+ymid);
         scaleX = scalex;scaleY=scaley;scaleZ=scalez;
         System.out.print("[class Terrain] Tiling with traingles ");
         for(int xi=0;xi<bitmap.length-1;++xi)
         {
          for(int yi=1;yi<bitmap[0].length;++yi)
          {	
                Point3f triangle1[] = new Point3f[3];
                triangle1[0] = new_vertex(xi,yi);
                triangle1[1] = new_vertex(xi,yi-1);
                triangle1[2] = new_vertex(xi+1,yi);
                addChild(new Triangle(triangle1,
                                            new Color3f(0.0f, 0.0f, 0.0f),
                                            new Color3f(1.0f, 1.0f, 1.0f),
                                            average_color1(xi,yi) ));                           	
                //System.out.println("[class Terrain] Adding traingle .. @("+xi+","+yi+")-("+xi+","+(yi-1)+")-"+(xi+1)+","+yi+");");
                Point3f triangle2[] = new Point3f[3];
                triangle2[0] = new_vertex(xi+1,yi);
                triangle2[1] = new_vertex(xi,yi-1);
                triangle2[2] = new_vertex(xi+1,yi-1);
                addChild(new Triangle(triangle2,
                                            new Color3f(0.0f, 0.0f, 0.0f),
                                            new Color3f(1.0f, 1.0f, 1.0f),
                                            average_color2(xi,yi) ));                           	
                // to do : call setNormals()
          }
        if ((xi % (bitmap.length/10+1)) ==0) System.out.print(".");
         }
        }

        private float height(Color3f color)
        {       return sqrt(color.x*color.x+color.y*color.y+color.z*color.z)*scaleZ; 
        }

        private Color3f average_color1(int xi,int yi) 
        { return new Color3f(0.6f, 0.9f, 0.6f);
/*              Color3f c = new Color3f(bitmap[xi][yi]);
                c.add(bitmap[xi][yi-1]);
                c.add(bitmap[xi+1][yi]);
                c.scale(1.0f/3.0f); // average :)
                return c;*/
        }

        private Color3f average_color2(int xi,int yi) 
        { return new Color3f(0.6f, 0.9f, 0.6f);
/*              Color3f c = new Color3f(bitmap[xi+1][yi]);
                c.add(bitmap[xi][yi-1]);
                c.add(bitmap[xi+1][yi-1]);
                c.scale(1.0f/3.0f); // average :)
                return c;*/
        }

        private Point3f new_vertex(int xi,int yi)
        {
                float x1 = (xi-xmid)*scaleX;
                float y1 = (yi-ymid)*scaleY;
                float z1 = height(bitmap[xi][yi]);
                return new Point3f(x1,y1,z1);
        }
        private float sqrt(float x) 
        { return (float)Math.sqrt((double)x); }
         
}
  
class Triangle extends Shape3D
{
        private TriangleArray tGeometry;
        private Appearance tAppearance;
        Color3f eColor    = null;
        Color3f sColor    = null; 
        Color3f objColor  = null; 
        Point3f _vertex[];

        Triangle (Point3f vertex[],Color3f ecolor,Color3f scolor,Color3f objcolor)
        { 
         eColor = ecolor;
         sColor = scolor;
         objColor = objcolor;
         _vertex = vertex;

         tGeometry = createGeometry();
         setNormals(new Vector3f(0.0f,0.0f,0.0f));

         tAppearance = createAppearance();
         this.setGeometry(tGeometry);
         this.setAppearance(tAppearance);
        }
        private TriangleArray createGeometry()
        {
         TriangleArray ta = new TriangleArray(6,
                                                         TriangleArray.COORDINATES|
                                                         TriangleArray.NORMALS);
         ta.setCoordinates(0,_vertex);
         return ta;
        }
        private void setNormals(Vector3f d)
        {
         /// normals face the point represented by vector d.
         Vector3f np[] = operatorVector3fArr (_vertex);
         for(int i=0;i<3;++i) np[i].sub(d);
         tGeometry.setNormals(0,np);	
        }
        private Appearance createAppearance()
        {
                Material m = new Material(objColor,eColor,objColor,sColor,100.0f);
                Appearance a = new Appearance();
                m.setLightingEnable(true);
                a.setMaterial(m);
        /*      PolygonAttributes pa = new PolygonAttributes();
                pa.setCullFace( PolygonAttributes.CULL_NONE );
                a.setPolygonAttributes( pa );*/
                return a;
        }
        static Vector3f[] operatorVector3fArr(Point3f p[])
        {
         Vector3f v[] = new Vector3f[p.length];
         for(int i=0;i<v.length;++i)
         {v[i] = new Vector3f(p[i].x,p[i].y,p[i].z);}
         return v;
        }
}