/**	
	Company:		Shout Interactive
	Project:		Shout3D Core Implementation
	Class:			PyramidTestPanel
	Date:			April 26, 1999
	Description:	Class for panel in which you click-drag to scale some pyramids
	(C) Copyright Shout Interactive, Inc. - 1997/1998/1999 - All rights reserved
 */

package applets;

//Include this in order to be able to work
//with nodes in the custom_nodes package
import custom_nodes.*;

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

/**
 * PyramidTestPanel
 * 
 * Shows how custom nodes can be added to Shout3D
 * and used in applets.
 * 
 * This applet creates three Pyramid nodes (a custom node in the
 * custom_nodes directory) and then changes their fields when
 * the user click-drags them.
 * 
 * 
 * @author Dave Westwood
 * @author Paul Isaacs
 * @author Jim Stewartson
 */

public class PyramidTestPanel extends Shout3DPanel implements DeviceObserver, RenderObserver {
	
	// 3 Pyramids to be displayed and manipulated in the scene.
	Pyramid pyramid0, pyramid1, pyramid2;
	
	// For determining which pyramid is clicked with the mouse.
	Picker myPicker;
    Node[] pathToPick;

	/**
	 * Constructor
	 */
	public PyramidTestPanel(Shout3DApplet applet){
		super(applet);
	}
	
	float[] lavender = { .6f, .6f, 1 };
	float[] seafoam  = { .6f,  1f, .6f };
	float[] gray     = { .6f, .6f, .6f };
	
	float[] pos0 = { -4, 0, 0 };
	float[] pos1 = {  0, 0, 0 };
	float[] pos2 = {  4, 0, 0 };
	 
	float[] rot0 = {  0, 1, 0, .8f };
	float[] rot1 = {  0, 1, 0,  0 };
	float[] rot2 = {  0, 1, 0, -.8f };
	
	/**
	 *
	 * This method is automatically called by the parent class Shout3DPanel
	 * at the correct time during initialize().
	 * 
	 * Subclasses should implement this to perform any custom initialization tasks.
	 */	
    public void customInitialize() {
		
		// Create the three pyramids
		pyramid0 = new Pyramid();	
		pyramid1 = new Pyramid();	
		pyramid2 = new Pyramid();	
		
		// Get a transform for each pyramid, passing in pretty colors
		Transform xf0 = createXf( pyramid0, lavender );
		Transform xf1 = createXf( pyramid1, seafoam );
		Transform xf2 = createXf( pyramid2, gray );
		
		// Position them relative to each other:
		xf0.translation.setValue(pos0);
		xf1.translation.setValue(pos1);
		xf2.translation.setValue(pos2);
		xf0.rotation.setValue(rot0);
		xf1.rotation.setValue(rot1);
		xf2.rotation.setValue(rot2);
		
		// Add to the scene
		Node[] newKids = new Node[3];
		newKids[0] = xf0;
		newKids[1] = xf1;
		newKids[2] = xf2;
		getScene().addChildren(newKids);
		
		// Move the camera back
		Viewpoint myVP = (Viewpoint) getCurrentBindableNode("Viewpoint");
		myVP.position.getValue()[2] = 10;
		//notify
		myVP.position.setValue(myVP.position.getValue());
		
		// Allocate the picker
		myPicker = getNewPicker();
		
		//Watch for mouse events to do the picking.
		this.addDeviceObserver(this, "MouseInput", null);
		//Register to watch rendering. We'll want to change pyramid
		//fields between renders so that field don't change during
		//mid-multithreaded-render
		getRenderer().addRenderObserver(this,null);
		
		//Call the parent class
		super.customInitialize();
	}
	
	/**
	 * Finalize
	 */
	protected void finalize()throws Throwable {
		getRenderer().removeRenderObserver(this);
		this.removeDeviceObserver(this, "MouseInput");
		super.finalize();
	}

	Node    pathTail;
	Pyramid selectedPyramid;
	float   startWidth, startHeight, startDepth;
	int		startCursorX, startCursorY;
	boolean waitingToChangeFields = false;
	float nextWidth  = 1;
	float nextHeight = 1;
	float nextDepth  = 1;
	
	/** 
	 * Called when Mouse input is received.
	 * 
	 * On mouse DOWN, checks for selection of one of the pyramids.
	 * Remembers which pyramid, and starting cursor location.
	 * 
	 * On mouse DRAG, scales the selected pyramid.
	 * Horizontal motion scales width,depth.
	 * Vertical motion scales height.
	 *  
	 */
	public boolean onDeviceInput(DeviceInput di, Object userData){
		//No need to check type of deviceInput, only registered for Mouse Input.
		MouseInput mi = (MouseInput) di;
		if (mi.which == MouseInput.DOWN){

			// Perform a pick and see if one of the pyramids was picked.
		    //
			selectedPyramid = null;
			pathToPick = myPicker.pickClosest(mi.x,mi.y);
		    if (pathToPick!=null && pathToPick.length > 0){
				pathTail = pathToPick[pathToPick.length-1];
				if (pathTail instanceof Pyramid){
					selectedPyramid = (Pyramid) pathTail;
					
					startCursorX = mi.x;
					startCursorY = mi.y;
					startWidth  = selectedPyramid.width.getValue();
					startHeight = selectedPyramid.height.getValue();
					startDepth  = selectedPyramid.depth.getValue();

					//This input was used
					return true;
				}
            }
        }
		else if (mi.which == MouseInput.DRAG){
			// Based on mouse motion, change fields of currentPyramid.
			
			// Width and depth change equally with horizontal motion,
			// Height changes with vertical motion.
			// Change by 1 unit every 100 pixels
			nextWidth  = startWidth  + (float)(mi.x-startCursorX)/100;
			nextHeight = startHeight - (float)(mi.y-startCursorY)/100;
			nextDepth  = startDepth  + (float)(mi.x-startCursorX)/100;
			
			// Clamp values to be > 0.1
			if (nextWidth  < 0.1) nextWidth  = 0.1f;
			if (nextHeight < 0.1) nextHeight = 0.1f;
			if (nextDepth  < 0.1) nextDepth  = 0.1f;
			
			// Don't set the field values now.
			// Let them be set during onPreRender so that
			// the field change occurs between renders.
			waitingToChangeFields = true;
			
			// Used the input
			return true;
		}
		//Did not care about this input
		return false;
    }
	
	/**
	 * Changing the fields should be done between renders.
	 * This is because rendering happens in a different thread,
	 * and updating the fields during a render can cause unpleasant
	 * flashing.
	 */
	public void onPreRender(Renderer r, Object userData){
		if (selectedPyramid != null && waitingToChangeFields){
			selectedPyramid.width.setValue(nextWidth);
			selectedPyramid.height.setValue(nextHeight);
			selectedPyramid.depth.setValue(nextDepth);
			waitingToChangeFields = false;
		}
	}
    
	Transform createXf( Pyramid pyr, float[/*3*/] color ) {
		Material newMat = new Material();
		newMat.diffuseColor.setValue(color);

		Appearance newApp = new Appearance();
		newApp.material.setValue(newMat);
		
		shout3d.core.Shape newShape = new shout3d.core.Shape();
		newShape.geometry.setValue(pyr);
		newShape.appearance.setValue(newApp);

		Transform newXf = new Transform();
		Node[] kids = new Node[1];
		kids[0] = newShape;
		newXf.addChildren(kids);
		return newXf;
	}
}
