// vechile
// Virtuaalitodellisuustekniikat harjoitustyö
// Janne Löf jlof@mail.student.oulu.fi
// LuTK/TOL


import java.applet.Applet;
import java.awt.*;
import java.awt.event.*;

import javax.swing.*;
import javax.media.j3d.*;
import javax.vecmath.*;

import com.sun.j3d.loaders.lw3d.Lw3dLoader;
import com.sun.j3d.loaders.Loader;
import com.sun.j3d.loaders.Scene;
import com.sun.j3d.utils.applet.MainFrame;
import com.sun.j3d.utils.universe.SimpleUniverse;


public class VirtualWorld extends Applet {

	class Entity {
		TransformGroup tg = null;
		Entity(TransformGroup t) {
			tg = t;
			setReadable();
		}
		
		void setReadable() {
			tg.setCapability(Node.ALLOW_LOCAL_TO_VWORLD_READ);
			tg.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
		}
		void setMovable() {
			tg.setCapability(Node.ALLOW_LOCAL_TO_VWORLD_READ);
			tg.setCapability(TransformGroup.ALLOW_TRANSFORM_READ);
			tg.setCapability(TransformGroup.ALLOW_TRANSFORM_WRITE);
		}

		TransformGroup getTransfromGroup() {
			return tg;
		}

		// Direction vector shows where 'nose' of the entity points to.
		Vector3d getDir() {
			Transform3D t = new Transform3D();
			Matrix3d    m = new Matrix3d();  // rotational component of transform
			tg.getTransform(t);
			t.get(m);
			Vector3d dir = new Vector3d(0,0,1);
			m.transform(dir);
			return dir;
		}

		Point3d getWorldPos() {
			Transform3D t1 = new Transform3D();
			Transform3D t2 = new Transform3D();
			Vector3d    v = new Vector3d();
			tg.getTransform(t2);
			tg.getLocalToVworld(t1);
			t1.mul(t2);
			t1.get(v);
			return new Point3d(v);
		}

		// get center of entity
		Point3d getPos() {
			Transform3D t = new Transform3D();
			Vector3d    v = new Vector3d();
			tg.getTransform(t);
			t.get(v);
			return new Point3d(v);
		}

		void move(Vector3d v) {
			Transform3D t1 = new Transform3D();
			Transform3D t2 = new Transform3D();
			tg.getTransform(t1);
			t2.set(v);
			t1.mul(t2);
			tg.setTransform(t1);
		}

		void rotate(AxisAngle4d aa) {
			Transform3D t1 = new Transform3D();
			Transform3D t2 = new Transform3D();
			tg.getTransform(t1);
			t2.set(aa);
			t1.mul(t2);
			tg.setTransform(t1);
		}

	}

	class Camera extends Entity {

		Camera(TransformGroup t) {
			super(t);
		}
		void lookAt(Entity target) {
			try {
			    // calculate transform for look
			    Transform3D t = new Transform3D();
			    t.lookAt(getWorldPos(), target.getWorldPos(), new Vector3d(0,1,0));
			    t.invert();
			    // set it 
			    TransformGroup vpt = universe.getViewingPlatform().getViewPlatformTransform();
			    vpt.setTransform(t);
			} catch(SingularMatrixException e) {
			    // Eye is right above/below target, so we couldn't invert matrix.
			    // This error can be safely ignored, all that happens is that view stays
			    // in it's old location until target moves to new location
			    System.out.println("Can't invert matrix\n");
			}
		}

	}

	class Vechile extends Entity {
	
		// cockpit view
		Entity target;
		Camera camera;

		Vechile(TransformGroup v, TransformGroup t, TransformGroup c) {
			super(v);
			target = new Entity(t);
			camera = new Camera(c);
			setMovable();
		}

		void moveForward() {
			move(new Vector3d(0,0,1));
		}

		void moveBackward() {
			move(new Vector3d(0,0,-1));
		}

		void turnLeft() {
			rotate( new AxisAngle4d(0,1,0, 5 * Math.PI / 180) );
		}

		void turnRight() {
			rotate( new AxisAngle4d(0,1,0, -5 * Math.PI / 180) );
		}
		
		void up() {
		}

		void down() {
		}

		void look() { // cockpit view
			camera.lookAt(target);
		}
	}

	class Tractor extends Vechile {
		Entity scoop  = null;
		double angle = 0.0;

		Tractor(TransformGroup v, TransformGroup t, TransformGroup c, TransformGroup s) {
			super(v,t,c);
			scoop = new Entity(s);
		}

		void up() {
			if (angle > -30) {
				angle -= 2;
				scoop.rotate( new AxisAngle4d(1,0,0, -2 * Math.PI / 180) );
			}
		}

		void down() {
			if (angle < 16) {
				angle += 2;
				scoop.rotate( new AxisAngle4d(1,0,0,  2 * Math.PI / 180) );
			}
		}
	}

	class Blimp extends Vechile {

		Blimp(TransformGroup v, TransformGroup t, TransformGroup c) {
			super(v,t,c);
		}

		void up() {
			move(new Vector3d(0,1,0));
		}

		void down() {
			if (getPos().y > 10)
				move(new Vector3d(0,-1,0));
		}
	}

	SimpleUniverse universe = null;

	Tractor tractor  = null;
	Blimp   blimp    = null;

	Camera globalCamera[];

	Vechile vechile  = null;
	Camera camera    = null;

	public VirtualWorld() {

		// load the scene file
		Lw3dLoader lwloader = new Lw3dLoader(Loader.LOAD_ALL);
		Scene loaderScene = null;
		try {
			loaderScene = lwloader.load("VirtualWorld.lws");
		} catch (Exception e) {
			System.err.println("Exception: " + e);
			System.exit(1);
		}

		// get vechiles
		// tractor is confusingly called vechile in LighWave scene files,
		// first plan was to have just tractor, blimp was added later.
		tractor = new Tractor(lwloader.getObject("vechile.lwo"),
		                      lwloader.getObject("VechileInsideTarget"),
		                      lwloader.getObject("VechileInsideCamera"),
		                      lwloader.getObject("scoop.lwo"));

		blimp = new Blimp(lwloader.getObject("blimp.lwo"),
		                  lwloader.getObject("BlimpInsideTarget"),
		                  lwloader.getObject("BlimpInsideCamera"));

		// get cameras
		globalCamera = new Camera[5];
		globalCamera[0] = new Camera(lwloader.getObject("GlobalCamera1"));
		globalCamera[1] = new Camera(lwloader.getObject("GlobalCamera2"));
		globalCamera[2] = new Camera(lwloader.getObject("GlobalCamera3"));
		globalCamera[3] = new Camera(lwloader.getObject("GlobalCamera4"));
		globalCamera[4] = new Camera(lwloader.getObject("GlobalCamera5"));

		vechile = tractor;
		camera  = globalCamera[4];

		// Construct the applet canvas
		setLayout(new BorderLayout());
		GraphicsConfiguration config = SimpleUniverse.getPreferredConfiguration();
		Canvas3D canvas = new Canvas3D(config);
		add("Center", canvas);

		// Create a basic universe setup and the root of our scene
		universe = new SimpleUniverse(canvas);

		// Change the back clip distance
		universe.getViewer().getView().setBackClipDistance(50000f);
		//
		universe.getViewingPlatform().setNominalViewingTransform();

		// Make the scene graph live by inserting the root into the universe
		universe.addBranchGraph(loaderScene.getSceneGroup());

		// set camera
		camera.lookAt(vechile);
		// keyboard events
		canvas.addKeyListener(new KeyAdapter() {
			public void keyPressed(KeyEvent e) {

				switch (e.getKeyChar()) {
				case 'w': System.out.println("Cool"); vechile.moveForward(); break;
				case 's': vechile.moveBackward(); break;
				case 'a': vechile.turnLeft(); break;
				case 'd': vechile.turnRight(); break;
				case 'q': vechile.up(); break;
				case 'e': vechile.down(); break;

				case 'x':
					if (vechile == tractor)
						vechile = blimp;
					else
						vechile = tractor;
					break;

				case '1': camera = globalCamera[0]; break;
				case '2': camera = globalCamera[1]; break;
				case '3': camera = globalCamera[2]; break;
				case '4': camera = globalCamera[3]; break;
				case '5': camera = globalCamera[4]; break;
				case '0': camera = null; break;

				}
				
				if (camera != null)
					camera.lookAt(vechile);
				else {
					vechile.look();
				}
			}
		});
	} // VirtualWorld()


	// main method...
	public static void main(String args[]) {
		new MainFrame(new VirtualWorld(), 400, 300);
	}
}

