package vgp.tutor.sizePolygon;

import java.awt.Color;

import jv.geom.PgPolygonSet;
import jv.number.PuDouble;
import jv.number.PuInteger;
import jv.object.PsConfig;
import jv.project.PjProject;
import jv.project.PvCameraIf;
import jv.project.PvDisplayIf;
import jv.vecmath.PdVector;
import jv.vecmath.PiVector;

/**
 * Tutorial project demonstrates how to adjust the individual thickness
 * of polygons.
 * 
 * @author		Konrad Polthier
 * @version		04.11.06, 2.10 revised (kp) Moved to vgp.tutor.<br>
 *					05.07.06, 2.00 created (kp) Copied from previous named PaSizeEdge.<br>
 *					07.02.04, 1.00 created (kp) 
 */
public class PjSizePolygon extends PjProject implements Runnable {
	/** Polygon set whose edges are displayed with varying thickness. */
	protected		PgPolygonSet					m_geom;
	/** Save number of polygons to be able to detect outside changes on geometry. */
	protected		int								m_numPoly;
	/** Rate of change of thickness of each polygon. */
	protected		PdVector							m_delSize;
	/** Thread created in auto-rotate mode. */
	protected		transient Thread				m_thread;
	/** Global speed of movement. */
	protected		PuDouble							m_speed;
	/** Number of horizontal (and vertical) faces. */
	protected		PuInteger						m_discr;

	public PjSizePolygon() {
		super("Size of Polygon Edges");
		m_geom		= new PgPolygonSet(3);
		m_geom.setParent(this);

		m_delSize	= new PdVector();
		m_discr		= new PuInteger(PsConfig.getMessage(true, 84000, "Discretization"), this);
		m_speed		= new PuDouble(PsConfig.getMessage(true, 84000, "Speed"), this);
		
		if (getClass() == PjSizePolygon.class) {
			init();
		}
	}
	public void init() {
		super.init();

		m_geom.setGlobalPolygonSize(15.);
		// m_geom.showPolygonEndArrow(true);
		m_geom.showVertices(false);
		m_geom.showPolygonColors(true);
		m_geom.showPolygonSizes(true);
		
		m_discr.setDefBounds(2, 20, 1, 5);
		m_discr.setDefValue(8);
		m_discr.init();
		
		m_speed.setDefBounds(0., 1., 0.02, 0.1);
		m_speed.setDefValue(0.1);
		m_speed.init();
		
		compute();
	}
	public void start() {
		addGeometry(m_geom);
		selectGeometry(m_geom);
		m_geom.update(m_geom);
		PvDisplayIf disp = getDisplay();
		disp.selectCamera(PvCameraIf.CAMERA_ORTHO_XY);
		disp.showEdgeAura(true);
		disp.setEnabledZBuffer(true);
		super.start();
	}
	/**
	 * Create polygons with initial thickness and the rate of chance.
	 */
	public void compute() {
		int discr	= m_discr.getValue();
		int uDiscr	= m_discr.getValue();
		int vDiscr	= m_discr.getValue();
		if (discr<2 || uDiscr<2 || vDiscr<2)
			return;
		m_geom.setNumVertices((uDiscr+vDiscr)*(discr+2));
		m_geom.setNumPolygons(uDiscr+vDiscr);

		// Bounding box of the polygon set
		double uSize = 10.;
		double vSize = 10.;
		double wSize = 3.;
		PdVector [] vertex			= m_geom.getVertices();
		// Determines the index of the currently computed vertex
		int ind = 0;
		// Compute vertical lines in y-direction
		double uFac = uSize/(-1.+uDiscr);
		double vFac = vSize/(-1.+discr);
		for (int i=0; i<uDiscr; i++) {
			double u = uFac*i;
			PiVector line = m_geom.getPolygon(i);
			line.setSize(discr+2);
			for (int j=0; j<discr+2; j++) {
				double v = vFac*(j-1);
				vertex[ind].m_data[0] = u;
				vertex[ind].m_data[1] = v;
				vertex[ind].m_data[2] = 0.;
				
				line.m_data[j] = ind;
				ind++;
			}
		}
		// Compute horizontal lines in x-direction
		uFac = uSize/(-1.+discr);
		vFac = vSize/(-1.+vDiscr);
		for (int i=0; i<vDiscr; i++) {
			double v = vFac*i;
			PiVector line = m_geom.getPolygon(uDiscr+i);
			line.setSize(discr+2);
			for (int j=0; j<discr+2; j++) {
				double u = uFac*(j-1);
				vertex[ind].m_data[0] = u;
				vertex[ind].m_data[1] = v;
				// Move vertex either above or below the vertical lines
				// to avoid rendering artifacts.
				if (Math.random() < 0.5)
					vertex[ind].m_data[2] = -wSize;
				else
					vertex[ind].m_data[2] = wSize;
				
				line.m_data[j] = ind;
				ind++;
			}
		}
	
		int nop				= m_geom.getNumPolygons();
		// Compute vector (-1 or +1) for each polygon which determines
		// how the polygon size changes in each time step.
		m_delSize.setSize(nop);
		for (int i=0; i<nop; i++) {
			if (Math.random() > .5)
				m_delSize.setEntry(i, 1.);
			else
				m_delSize.setEntry(i, -1.);
		}

		// Assign a random initial size of each polygon
		for (int i=0; i<nop; i++) {
			m_geom.setPolygonSize(i, Math.random());
		}
		m_numPoly	= nop;
		// Compute polygon colors based on the current size
		computeColorsFromSize();
	}
	/**
	 * Compute polygon colors based on the current size.
	 */
	private void computeColorsFromSize() {
		int nop				= m_geom.getNumPolygons();
		m_geom.assurePolygonColors();
		Color [] color			= m_geom.getPolygonColors();
		for (int i=0; i<nop; i++) {
			double size = m_geom.getPolygonSize(i);
			float col	= (float)(size);
			color[i]		= new Color(Color.HSBtoRGB(0.83333f*(1.f-col), 1.f, 1.f));
		}
	}
	/**
	 * Change size of each vertex slightly along its element vector.
	 * If an element reaches the boundary then do a reflection like billard.
	 */
	public void changePolygonSize(double speed) {
		int nop				= m_geom.getNumPolygons();
		if (nop != m_numPoly)
			return;
		for (int i=0; i<nop; i++) {
			double size = m_geom.getPolygonSize(i);
			double incr = m_delSize.getEntry(i)*speed;
			if (incr>0 && size+incr > 1. ||
				 incr<0 && size+incr < 0.) {
				incr = -incr;
				m_delSize.setEntry(i, -m_delSize.getEntry(i));
			}
			m_geom.setPolygonSize(i, size+incr);
		}
		computeColorsFromSize();
	}
	/**
	 * Update the class whenever a child has changed.
	 * Method is usually invoked from the children.
	 */
	public boolean update(Object event) {
		if (event == this) {
			m_geom.update(m_geom);
		} else if (event == m_geom) {
			if (m_numPoly != m_geom.getNumPolygons()) {
				compute();
			}
			return true;
		} else if (event == m_discr) {
			compute();
			m_geom.update(m_geom);
			return true;
		} else if (event == m_speed) {
			return true;
		}
		return super.update(event);
	}
	/** Start animation by creating a new thread */
	public void startAnim() {
		if (m_thread != null)
			return;
		m_thread = new Thread(this, PsConfig.getProgram()+": "+"SizePolygons");
		m_thread.setPriority(Thread.NORM_PRIORITY);
		m_thread.start();
	}
	
	/** Stop animation by removing the thread. */
	public void stopAnim() {
		m_thread = null;
	}
	/** Do the animation by incrementing the polygon thickness. */
	public void run() {
		while(m_thread != null) {
			changePolygonSize(m_speed.getValue());
			m_geom.update(m_geom);
			try {
				Thread.sleep(30);
			} catch(InterruptedException e) {}
		}
	}
}
