package vgp.tutor.vectorField;

import java.awt.Color;
import jv.geom.PgPointSet;
import jv.geom.PgVectorField;
import jv.number.PuDouble;
import jv.object.PsPanel;
import jv.project.PvCameraIf;
import jv.project.PvDisplayIf;
import jv.project.PjProject;
import jv.vecmath.PdVector;
import jv.vecmath.PiVector;
import jvx.surface.PgDomain;
import jvx.surface.PgDomainDescr;

/**
 * Tutorial project on vector fields.
 * Sample vector field is gradient or rotation of a potential on a square grid. User
 * may interactively modify potential by marking or unmarking vertices.
 *
 * @see			jv.geom.PgVectorField
 * @author		Konrad Polthier
 * @version		11.11.02, 2.01 revised (ep) Corrected update handling.<br>
 *					19.10.01, 2.00 created (ep) Major revision.<br>
 *					18.09.99, 1.00 created (kp)
 */
public class PjVectorField extends PjProject {
	/** Vectorfield is gradient of potential. */
	public		static final int		GRADIENT = 0;
	/** Vectorfield is J*gradient of potential. */
	public		static final int		ROTATION = 1;
	
	/** Base geometry is a rectangular grid which carries the vector field. */
	protected	PgDomain					m_geom;
	/** Vector field, associated with base geometry. */
	protected	PgVectorField			m_vf;
	/** Types of singularities. Possible values are GRADIENT, ROTATION etc. */
	protected	PiVector					m_fieldType;
	/** Current selection of type of singularity. */
	protected	int						m_fieldTypeSelection = GRADIENT;
	/** Ratio of decay of singularities. */
	protected	PdVector					m_radiusForEach;

	/** Singularities of potential. */
	protected	PgPointSet				m_center;
	/** Radius of influence of each potential. */
	protected	PuDouble					m_radius;
	
	public PjVectorField() {
		super("Potential");

		// create a vectorfield!
		{
			m_vf = new PgVectorField(2);
			m_vf.setName("Vector Field");
		}

		// Create empty surface in 3-dimensional space, and add vector field
		m_geom = new PgDomain(2);
		m_geom.setName("Domain with Vectorfield");
		m_geom.setParent(this);

		// Create a point set that contains the singular points of the vector field, just
		// for later calculation and interaction with the user
		m_center = new PgPointSet(2);
		m_center.setName("Singularities");
		//This makes it possible to act on events like 'drag' and 'add vertex', see method 'update' below
		m_center.setParent(this);
		
		// Save the properties of the singularities
		m_fieldType = new PiVector();
		m_radiusForEach = new PdVector();
		
		//See vgp.tutor.slider for detail on the use of sliders like this one
		m_radius = new PuDouble("Ratio of Decay", this);
		
		if (getClass() == PjVectorField.class) {
			init();
		}
	}

	public void init() {
		super.init();
		//Set the properties of the slider that enables the user to select the decay value of last added singularity
		//See vgp.tutor.slider for detail on the use of sliders like this one
		m_radius.setDefBounds(0.001, 5., 0.01, 0.1);
		m_radius.setDefValue(.5);
		m_radius.init();

		//Create a rectangular domain and set the element set to it
		PgDomainDescr m_descr = m_geom.getDescr();
		m_descr.setMaxSize(-10., -10., 10., 10.);
		m_descr.setSize( -5., -5., 5., 5.);
		m_descr.setDiscrBounds(2, 2, 50, 50);
		m_descr.setDiscr(20, 20);
		m_geom.compute();
		
		addSingularity(0., 0., GRADIENT, 0.6);
		addSingularity(-2., 2., ROTATION, 0.4);
	}
	
	public void start() {
		//Link the vector field with the element set and the other way round!
		{
			m_geom.addVectorField(m_vf);
			m_vf.setGeometry(m_geom);
			m_vf.setGlobalVectorColor(Color.black);
			computeVectorfield();
		}

		//Set visual properties of vector field!
		{
			m_geom.showVectorArrows(true); //add an arrow point to each vector
		}
		
		//Set visual properties of element set
		m_geom.showEdges(false);
		m_geom.showElements(false);
		m_geom.update(null);
		
		//Put element set and singularity point set into display
		addGeometry(m_geom);
		addGeometry(m_center);
		
		//This enables the user to drag singularities and add further singularities
		selectGeometry(m_center);
		
		//Switch to 2D camera
		PvDisplayIf disp = getDisplay();
		disp.selectCamera(PvCameraIf.CAMERA_ORTHO_XY);
		disp.setBackgroundColor(Color.white);
		
		super.start();
	}
	/**
	 * Update the class whenever a child has changed.
	 * Method is usually invoked from the children.
	 */
	public boolean update(Object event) {
		if (event == null) {
			super.update(null);
		} else if (event == m_geom) { //the domain changed -> recalculate vectors
			computeVectorfield();
			return true;
		} else if (event == m_radius) { //change the last added singularity's property
			if (m_radiusForEach.getSize() > 0) {
				m_radiusForEach.setEntry(m_radiusForEach.getSize()-1, m_radius.getValue());
				computeVectorfield();
				m_geom.update(null);
			}
			return true;
		} else if (event == getInfoPanel()) {
			computeVectorfield();
			m_geom.update(null);
			return true;
		} else if (event == m_center) { //user either dragged a vertex or added new vertex/vertices
			if (m_center.getNumVertices() > m_radiusForEach.getSize()) {
				int begin = m_radiusForEach.getSize();
				int end = m_center.getNumVertices();
				m_radiusForEach.setSize(end);
				m_fieldType.setSize(end);
				for (int i = begin; i < end; i++) {
					m_radiusForEach.setEntry(i, m_radius.getValue());
					m_fieldType.setEntry(i, m_fieldTypeSelection);
				}
			}
			else {
				int newsize = m_center.getNumVertices();
				m_radiusForEach.setSize(newsize);
				m_fieldType.setSize(newsize);
			}
			computeVectorfield();
			m_vf.update(null);
			m_geom.update(null);
			return super.update(null);
		}
		return super.update(event);
	}
	
	
	public void computeVectorfield() {
		int numVectors	= m_geom.getNumVertices();
		m_vf.setNumVectors(numVectors);
		
		PdVector [] vector	= m_vf.getVectors();
		PdVector [] vertex	= m_vf.getVertices();
		
		double f, dfx, dfy;
		int i, j;
		PdVector p;
		int numsing = m_center.getNumVertices();
		for (i=0; i<numVectors; i++) {
			f = 0.;
			dfx = dfy = 0.;
			//calculate vector
			for (j = 0; j < numsing ; j++) {
				p = m_center.getVertex(j);
				f = Math.exp(-2.*PdVector.dist(p, vertex[i])*m_radiusForEach.getEntry(j));
				if (m_fieldType.getEntry(j) == GRADIENT) {
					dfx += -2.*(vertex[i].m_data[0]-p.m_data[0])*f;
					dfy += -2.*(vertex[i].m_data[1]-p.m_data[1])*f;
				}
				else {
					dfx -= -2.*(vertex[i].m_data[1]-p.m_data[1])*f;
					dfy += -2.*(vertex[i].m_data[0]-p.m_data[0])*f;
				}
			}
			//set vector
			vector[i].set(dfx, dfy);
		}
		if (m_vf.hasInspector(PsPanel.INFO) &&
			 m_vf.getInspector(PsPanel.INFO).isShowing())
			m_vf.getInspector(PsPanel.INFO).update(m_vf);
	}
	
	protected void setFieldType(int type) {
		m_fieldTypeSelection = type;
	}
	
	protected int getFieldType() {
		return m_fieldTypeSelection;
	}
	
	protected void addSingularity(double x, double y, int type, double decay) {
		int numVertices = m_center.getNumVertices()+1;
		m_center.setNumVertices(numVertices);
		m_center.setVertex(numVertices-1, new PdVector(x, y));
		m_radiusForEach.setSize(numVertices);
		m_fieldType.setSize(numVertices);
		m_radiusForEach.setEntry(numVertices-1, decay);
		m_fieldType.setEntry(numVertices-1, type);
	}
}

