package vgp.tutor.eventCamera;

import java.awt.Color;

import jv.geom.PgElementSet;
import jv.number.PuDouble;
import jv.object.PsObject;
import jv.project.PvDisplayIf;
import jv.project.PvCameraIf;
import jv.project.PvCameraEvent;
import jv.project.PvCameraListenerIf;
import jv.project.PjProject;
import jv.project.PvViewerIf;
import jv.vecmath.PdVector;
import jv.vecmath.PuVectorGeom;

/**
 * Demo project shows how to handle camera events issued from
 * a JavaView display. In this example the events are used to
 * steer the camera in a second window.
 * <p>
 * The camera events from one window are used to steer the
 * camera in the other window such that a stereo effect is produced.
 * Note, the viewing direction of the left camera is slightly rotated
 * around the up-vector of the camera by about 6 degrees.
 * 
 * @see			jv.project.PvCameraEvent
 * @see			jv.project.PvCameraListenerIf
 * @see			jv.project.PvDisplayIf
 * @author		Konrad Polthier
 * @version		14.12.03, 2.00 revised (kp) Full revision.<br>
 *					25.07.00, 1.00 created (kp)
 */
public class PjEventCamera extends PjProject implements PvCameraListenerIf {
	/** Cross eye stereo projection. */
	public		final static int			STEREO_CROSS		= 0;
	/** Parallel eye stereo projection. */
	public		final static int			STEREO_PARALLEL	= 1;
	/** Red green painting of stereo projection. */
	public		final static int			STEREO_REDGREEN	= 2;
	/** Torus geometry added to both displays. */
	protected	PgElementSet				m_geom;
	/** Left display which is steered. */
	protected	PvDisplayIf					m_dispRight;
	/** Right display which issues camera events. */
	protected	PvDisplayIf					m_dispLeft;
	/** Type of stereo projection, see {@link #STEREO_CROSS STEREO_CROSS} and similar. */
	protected	int							m_stereoType;
	/** Angle between left and right camera. */
	protected	PuDouble						m_parallax;
	/** Currently selected display. */
	protected	PvDisplayIf					m_currDisp;

	public PjEventCamera() {
		super("Camera Events");
		m_geom		= new PgElementSet(3);
		m_parallax	= new PuDouble("Parallax", this);
		if (getClass() == PjEventCamera.class)
		  init();
	}
	public void init() {
		super.init();
		m_geom.setName("Torus");
		m_geom.computeTorus(10, 10, 2., 1.);
		m_geom.setGlobalVertexSize(4.);
		m_geom.setGlobalEdgeSize(4.);
		m_geom.showVertices(true);
		m_geom.showElements(false);
		m_geom.makeVertexColorsFromZHue();
		m_geom.showVertexColors(false);
		m_geom.showEdgeColors(true);
		m_geom.showEdgeColorFromElements(false);
		// Mark a vertex to help focussing the eyes.
		PdVector v = m_geom.getVertex(74);
		if (v != null)
			v.setTag(PsObject.IS_SELECTED);
		
		m_stereoType	= STEREO_CROSS;
		m_parallax.setDefBounds(0., 30., 0.5, 2.);
		m_parallax.setDefValue(6.);
		m_parallax.init();
		
		m_currDisp		= m_dispRight;
	}
	public void start() {
		// Add geometry to default window, ie. the right window.
		addGeometry(m_geom);
		selectGeometry(m_geom);

		getLeftDisplay().addGeometry(m_geom);
		getRightDisplay().addGeometry(m_geom);
		super.start();
	}
	public void reset() {
		init();
		if (!m_dispRight.containsGeometry(m_geom))
			m_dispRight.addGeometry(m_geom);
		if (!m_dispLeft.containsGeometry(m_geom))
			m_dispLeft.addGeometry(m_geom);
		m_geom.update(m_geom);
		update(this);
		updateDisplay(m_currDisp);
	}
	public boolean update(Object event) {
		if (event == m_geom) {
			return true;
		} else if (event == m_parallax) {
			updateDisplay(m_currDisp);
			return true;
		}
		return super.update(event);
	}
	public PvDisplayIf getRightDisplay() {
		if (m_dispRight != null)
			return m_dispRight;
		// Get viewer and ask for another display
		PvViewerIf viewer = getViewer();
		// Create right window and add a clone of the torus geometry.
		m_dispRight = viewer.newDisplay("Right Display", false);
		m_dispRight.setBackgroundColor(Color.white);
		m_dispRight.showEdgeAura(true);
		m_dispRight.addCameraListener(this);
		return m_dispRight;
	}
	public PvDisplayIf getLeftDisplay() {
		if (m_dispLeft != null)
			return m_dispLeft;
		// Get viewer and ask for another display
		PvViewerIf viewer = getViewer();
		// Create left window and add a clone of the torus geometry.
		m_dispLeft = viewer.newDisplay("Left Display", false);
		m_dispLeft.setBackgroundColor(Color.white);
		m_dispLeft.showEdgeAura(true);
		m_dispLeft.addCameraListener(this);
		return m_dispLeft;
	}
	/** 
	 * Get type of stereo projection.
	 * @return		stereo projection type, see {@link #STEREO_CROSS STEREO_CROSS} and similar.
	 */
	public int getStereoType() { return m_stereoType; }
	/** 
	 * Set type of stereo projection.
	 * @param		type		stereo projection type, see {@link #STEREO_CROSS STEREO_CROSS} and similar.
	 */
	public void setStereoType(int type) { m_stereoType = type; }
	/**
	 * Get camera events resulting from picking the mouse.
	 * Use information about camera in one display to
	 * adjust the camera in the other window. But previously
	 * rotate the camera position around the line given by
	 * interest+t*upVector by -6 degrees.
	 */
	public void pickCamera(PvCameraEvent pos) {
		if (m_bUpdating)
			return;
		// Do nothing if not both display have been created yet
		if (m_dispLeft==null || m_dispRight==null)
			return;

		// Find out which camera has issued the event, and then steer the other.
		PvDisplayIf dispSrc		= pos.getSource();
		updateDisplay(dispSrc);
	}

	protected void updateDisplay(PvDisplayIf dispSrc) {
		if (dispSrc == null)
			return;
		m_currDisp					= dispSrc;
		PvCameraIf camSrc			= dispSrc.getCamera();
		
		PdVector interest			= camSrc.getInterest();
		PdVector upVector			= camSrc.getUpVector();
		PdVector position			= camSrc.getPosition();
		double scale				= camSrc.getScale();
		
		PvDisplayIf dispSteer	= null;
		PvCameraIf	camSteer		= null;
		double angle				= m_parallax.getValue()*Math.PI/180;
		double flag					= ((m_stereoType==STEREO_CROSS)?-1:1);
		if (dispSrc == m_dispRight) {
			camSteer		= m_dispLeft.getCamera();
			dispSteer	= m_dispLeft;
			angle			= -flag*angle;
		} else {
			camSteer		= m_dispRight.getCamera();
			dispSteer	= m_dispRight;
			angle			= flag*angle;
		}
		PdVector pRot = (PdVector)position.clone();
		PuVectorGeom.rotatePointAroundLine(pRot, position, interest, upVector, angle);

		camSteer.setScale(scale);
		camSteer.setFullPosition(interest, pRot, upVector);
		
		// Update the camera in the steered display
		// but block this current method to avoid an infinite loop
		// since the steered camera will also issue a camera event.
		m_bUpdating = true;
		dispSteer.update(camSteer);
		m_bUpdating = false;
	}
	boolean m_bUpdating = false;
	/**
	 * Get camera events resulting from dragging the mouse.
	 * This method just calls the method pickCamera which does all the work.
	 */
	public void dragCamera(PvCameraEvent pos) {
		pickCamera(pos);
	}
}

