/**	
	Company:		Shout Interactive
	Project:		Shout3D 1.0
	Class:			WalkPanel
	Date:			September 15, 1999
	Description:	Class for Walking
	(C) Copyright Shout Interactive, Inc. - 1997/1998/1999 - All rights reserved
 */

package applets;

import java.applet.*;
import java.awt.*;
import java.awt.image.*;
import java.io.*;
import java.util.Date;
import java.net.URL;
import shout3d.core.*;
import shout3d.math.*;
import shout3d.*;

/**
 * Shout3D WalkPanel. This class is meant to provide the user with the ability to \
 * navigate around a 3D world in a similar manner to the VRML "WALK" mode.
 * 
 * @author Jim Stewartson
 * @author Paul Isaacs
 * @author Rory Lane Lutter
 * @author Dave Westwood
 */

public class WalkPanel extends Shout3DPanel implements RenderObserver, DeviceObserver{
	
	// cache the initial camera and the root of the scene
    Viewpoint camera;
    Transform root;
	
	// quaternion for the camera
	Quaternion cameraQuat = new Quaternion();
	
	// variables for camera calculations 
	float cameraHeading = 0;
	float headingDelta = 0;
	float speed = 0;
	float startX = 0;
	float startY = 0;

	// the avatarRadius, avatarHeight, and collideHeight of the avatar
	// can all be set using applet parameters.
	// The camera is always placed at a height of "avatarHeight"
	// Collisions are done in the plane that lies at the level of "collideHeight"
	// The avatarRadius is the closest that the avatar may get to a surface in the scene.
	// The collision algorithm starts with the camera position (which is at a height of avatarHeight) and
	// finds the imaginary collider, which is the point below the camera at the height "collideHeight"
	// Then it attemps to move that collider forward through the scene.  If the proposed motion
	// will move the collider closer than avatarRadius to a surface in the scene, then a collision is
	// declared and the camera position will not be changed.
	public float avatarRadius = 2f;
	public float avatarHeight = 2f;
	public float collideHeight = .25f;
	
	/**
	 * Construct me
	 */
	public WalkPanel(Shout3DApplet applet){
		super(applet);
	}

	/**
	 * Remove observers when done with the panel
	 */
	public void finalize()throws Throwable {
		applet.getDeviceListener().removeDeviceObserver(this, "DeviceInput");
		applet.getRenderer().removeRenderObserver(this);		
		super.finalize();
	}
	
	/**
	 * Overrides Shout3DPanel.customInitialize()
	 */
    public void customInitialize() {
		
		// Read the 3 avatar parameters from applet parameters, if specified:
		String avatarHeightString = applet.getParameter("avatarHeight");
		if (avatarHeightString != null){
			avatarHeight = Float.valueOf(avatarHeightString).floatValue();
		}
		String avatarRadiusString = applet.getParameter("avatarRadius");
		if (avatarRadiusString != null){
			avatarRadius = Float.valueOf(avatarRadiusString).floatValue();
		}
		String collideHeightString = applet.getParameter("collideHeight");
		if (collideHeightString != null){
			collideHeight = Float.valueOf(collideHeightString).floatValue();
		}
	
		// cache the camera and the scene root
		camera = (Viewpoint)(applet.getCurrentBindableNode("Viewpoint"));
        root = (Transform)getScene();
		
		// set the position to your avatar's height
		camera.position.set1Value(1, avatarHeight);
		
		// Set the camera heading to align with that of the active viewpoint.
		Quaternion startCamQuat = new Quaternion();
		startCamQuat.setAxisAngle(camera.orientation.getValue());
		float[] startCamEulers = new float[3];
		startCamQuat.getEulers(startCamEulers);
		cameraHeading = startCamEulers[0];
		
		// register for device events
		getDeviceListener().addDeviceObserver(this, "DeviceInput", null);	
		// register for render events
		getRenderer().addRenderObserver(this, null);
		
	}
	
	MouseInput mi;
	
	public boolean onDeviceInput(DeviceInput di, Object userData){
		if (di instanceof MouseInput){
			mi = (MouseInput)di;
			switch (mi.which){
			case MouseInput.DOWN:
				startX = mi.x;
				startY = mi.y;
				break;
			case MouseInput.DRAG:
				// if the Shift key is down
				if ((mi.modifiers & DeviceInput.SHIFT_MASK) != 0){
					// go fast
					headingDelta = -(mi.x-startX)/200f;				                
					speed = (mi.y-startY)/10f;				      
				}
				else {
					// otherwise go slower
					headingDelta = -(mi.x-startX)/400f;
					speed = (mi.y-startY)/20f;				      
				}
				break;
			case MouseInput.UP:
				// stop
				headingDelta = 0;
				speed = 0;
				break;
				
			}
		}
		// return false, let other entities handle the events too.
		return false;
	}
	

	/**
	 * Do camera stuff before rendering
	 */
	public void onPreRender(Renderer r, Object userData){
		
		// adjust the camera heading according to user input
		cameraHeading += headingDelta/getFramesPerSecond();
		// set the camera orientation to be an axis/angle rotation equivalent to
		// the new heading.
		cameraQuat.setEulers(cameraHeading, 0, 0);
		float[] tempOr = new float[4];
		cameraQuat.getAxisAngle(tempOr);
		camera.orientation.setValue(tempOr);
		
		// Determine the proposed camera motion according to user input
		float[] cameraMotion = new float[3];
		cameraMotion[2] = (float)(speed/getFramesPerSecond());
		cameraQuat.xform(cameraMotion);

		// Collisions are done at the level of an imaginary collider located below
		// the camera at a level of collideHeight.  Calculate the old collider location
		// and the proposed new collider location.
		float[] oldColliderLoc = new float[3];
		oldColliderLoc[0] = camera.position.getValue()[0];
		oldColliderLoc[1] = camera.position.getValue()[1] - avatarHeight + collideHeight;
		oldColliderLoc[2] = camera.position.getValue()[2];
		
		float[] newColliderLoc = new float[3];
		newColliderLoc[0] = oldColliderLoc[0] + cameraMotion[0];
		newColliderLoc[1] = oldColliderLoc[1] + cameraMotion[1];
		newColliderLoc[2] = oldColliderLoc[2] + cameraMotion[2];

		// check for collision in moving knee from old to new location
		if (!checkCollision(oldColliderLoc, newColliderLoc)){
			// If no collision, move camera by the given amount.
			camera.position.set1Value(0, camera.position.getValue()[0] + cameraMotion[0]);
			camera.position.set1Value(1, camera.position.getValue()[1] + cameraMotion[1]);
			camera.position.set1Value(2, camera.position.getValue()[2] + cameraMotion[2]);
		}
	}

	// cache a picker
	Picker picker;
	
	/**
	 * Check to see if the motion has resulted in a collision
	 * 
	 * @return whether there is a collision
	 */
	
	boolean checkCollision(float[] oldPos, float[] newPos){
		if (picker == null) {
			// Get a new picker 
			picker = getNewPicker();
			// set the picker to provide the hit point
			picker.setPickInfo(Picker.POINT, true);
			// set the picker's scene to be while scene
			picker.setScene(getScene());
		}
		// This returns the path to the geometry that has the closest intersection 
		// to oldPos that lies along the directed ray from oldPos to newPos.
		// If there is no geometry that intersects that ray, results will be null.
		Node[] results = picker.pickClosestFromTo(oldPos, newPos);
		
		if (results == null) {
			// Nothing in the way, no collision.
			return false;
		}
		else {
			// Retrieve the point of intersection from the picker.
			float[] intersection = picker.getPickInfo(Picker.POINT);
			
			// Find the distance between oldPos/newPos and oldPos/intersection
			float oldToNewDist   = getDistance(oldPos, newPos);
			float oldToInterDist = getDistance(oldPos, intersection);
			
			// If newPos is further from oldPos than the intersection, you'd collide with
			// the geometry in travelling from oldPos to newPos, so return true
			if (oldToNewDist >= oldToInterDist)
				return true;
			
			// If the difference in the distances is less than avatarRadius, the newPos is too
			// close to the intersection for the avatar to fit.  That counts as a collision, return true
			if ((oldToInterDist - oldToNewDist) < avatarRadius)
				return true;
		}
		// There's an intersection point but it is not located where it would cause a collision.
		return false;
	}
	
	// Distance function
	float getDistance(float[] from, float[] to){
		float x = from[0] - to[0];
		float y = from[1] - to[1];
		float z = from[2] - to[2];
		float dist = (float)Math.sqrt(x*x + y*y + z*z);
		//System.out.println(dist);
		return dist;
	}
	
	public void onPostRender(Renderer r, Object userData){
	}

}
