package vgp.curve.deCasteljau;

import java.awt.Color;

import jv.geom.PgPolygon;
import jv.geom.PgPolygonSet;
import jv.number.PuDouble;
import jv.number.PuInteger;
import jv.number.PuString;
import jv.object.PsConfig;
import jv.object.PsObject;
import jv.project.PgGeometryIf;
import jv.project.PjProject;
import jv.project.PvCameraIf;
import jv.project.PvDisplayIf;
import jv.project.PvGeometryIf;
import jv.vecmath.PdVector;
import jv.vecmath.PiVector;

import jvx.curve.PgBezierCurve;

/**
 * Visualization of the de Casteljau algorithm where a control polygon
 * produces a polynomial Bezier curve. Intermediate steps and evaluation
 * position may be animated.
 * 
 * @author		Konrad Polthier
 * @version		03.11.06, 2.00 revised (kp) Package moved to vgp.curve.deCasteljau from dev.app.deCasteljau.<br>
 *					05.10.04, 1.00 created (kp) 
 */
public class PjDeCasteljau extends PjProject {
	/** Control polygon might later be replaced with control polygon of bezier curve. */
	protected		PgPolygon						m_geom;
	/** Construction edges of de Casteljau algorithm. */
	protected		PgPolygonSet					m_build;
	/** Final Bezier curve determined by the control polygon. */
	protected		PgBezierCurve					m_bezier;
	/** Number of vertices of control polygon. */
	protected		int								m_nov;
	/** Global position in [0,1]. */
	protected		PuDouble							m_position;
	/** Steps of the de Casteljau algorithm. */
	protected		PuInteger						m_depth;

	public PjDeCasteljau() {
		super("DeCasteljau Algorithm");
		m_geom		= new PgPolygon(3);
		m_geom.setParent(this);
		m_geom.setName("Control Polygon");
		m_build		= new PgPolygonSet(3);
		m_build.setName("Construction");
		m_bezier		= new PgBezierCurve(3);
		m_bezier.setName("Bezier Curve");
		m_bezier.setNumVertices(100);
		m_depth		= new PuInteger(PsConfig.getMessage(true, 24000, "Depth"), this);
		m_position	= new PuDouble(PsConfig.getMessage(true, 24000, "Position"), this);
		
		if (getClass() == PjDeCasteljau.class)
			init();
	}
	public void init() {
		super.init();

		m_geom.setGlobalVertexColor(new Color(255, 255, 100));
		m_geom.setGlobalVertexSize(5.);
		m_geom.setGlobalEdgeColor(new Color(0, 0, 120));
		m_geom.setGlobalEdgeSize(3.);

		m_geom.setNumVertices(4);
		m_geom.setVertex(0, -4., -4., 0.);
		m_geom.setVertex(1, -6.,  2., 0.);
		m_geom.setVertex(2,  4.,  3., 0.);
		m_geom.setVertex(3,  1., -3., 0.);		

		m_nov	= m_geom.getNumVertices();

		m_build.setGlobalVertexColor(new Color(192, 192, 192));
		m_build.setGlobalVertexSize(3.);
		m_build.setGlobalPolygonColor(new Color(192, 192, 192));
		m_build.setGlobalPolygonSize(2.);
		m_build.showVertexLabels(true);
		m_build.setEnabledIndexLabels(false);
		m_build.setLabelAttribute(PvGeometryIf.GEOM_ITEM_POINT, 0, 0,
										  PgGeometryIf.LABEL_HEAD, PgGeometryIf.LABEL_TOP,
										  PsConfig.FONT_FIXED);
		
		m_bezier.showVertices(false);
		m_bezier.setGlobalEdgeColor(new Color(200, 100, 50));
		m_bezier.setGlobalEdgeSize(3.);

		m_depth.setDefBounds(0, m_geom.getNumVertices()-1, 1, 1);
		m_depth.setDefValue(m_geom.getNumVertices()-1);
		m_depth.init();
		
		m_position.setDefBounds(0., 1., 0.01, 0.1);
		m_position.setDefValue(0.2);
		m_position.init();

		compute(m_position.getValue());
		m_bezier.computeVertices();
	}
	/**
	 * Add geometries to display and enable xy-projection.
	 */
	public void start() {
		addGeometry(m_geom);
		addGeometry(m_build);
		addGeometry(m_bezier);
		selectGeometry(m_geom);
		PvDisplayIf disp = getDisplay();
		disp.selectCamera(PvCameraIf.CAMERA_ORTHO_XY);
		disp.setMajorMode(PvDisplayIf.MODE_PICK);
		super.start();
	}
	/**
	 * Update the class whenever a child has changed.
	 * Method is usually invoked from the children.
	 */
	public boolean update(Object event) {
		if (event == this) {
			compute(m_position.getValue());
			m_build.update(m_build);
			m_bezier.update(m_bezier);
			return super.update(this);
		} else if (event == m_geom) {
			if (m_nov != m_geom.getNumVertices()) {
				m_nov = m_geom.getNumVertices();
				m_depth.setBounds(0, m_nov-1);
				m_depth.setValue(m_nov-1);
			}
			return update(this);
		} else if (event == m_depth) {
			return update(this);
		} else if (event == m_position) {
			return update(this);
		} else
			return super.update(event);
	}
	/**
	 * Evaluate Bezier curve at given argument value.
	 * @param	t		position in [0,1] where Bezier curve is evaluated.
	 */
	public void compute(double t) {
		int nocv	= m_geom.getNumVertices();
		if (nocv <= 1)
			return;
		int depth	= m_depth.getValue();
		// Hack: cannot render polygon with one vertex:
		int b = nocv-1;
		int a = nocv-depth;
		int nobv = (b + a)*(b-a+1)/2;
		if (depth == nocv-1)
			nobv++;
		m_build.setNumVertices(nobv);
		m_build.setNumPolygons(depth);
		for (int i=0; i<nobv; i++) {
			PdVector v = m_build.getVertex(i);
			v.clearTag(PsObject.IS_SELECTED);
			v.setName("");
		}
		int off	= 0;
		for (int k=0; k<depth; k++) {
			int num	= nocv-1-k;
			// Hack: cannot render polygon with one vertex:
			m_build.setSizeOfPolygon(k, Math.max(2, num));
			PiVector poly	= m_build.getPolygon(k);
			for (int i=0; i<num; i++) {
				PdVector v, w;
				if (off == 0) {
					v = m_geom.getVertex(i);
					w = m_geom.getVertex(i+1);
				} else {
					v = m_build.getVertex(off-(num+1)+i);
					w = m_build.getVertex(off-(num+1)+i+1);
				}
				m_build.getVertex(off+i).blend(1-t, v, t, w);
				poly.setEntry(i, off+i);
			}
			// Hack: cannot render polygon with one vertex:
			if (num == 1) {
				m_build.setVertex(off+1, m_build.getVertex(off));
				m_build.setTagVertex(off+1, PsObject.IS_SELECTED);
				m_build.getVertex(off+1).setName("t = "+PuString.toString((float)t));
				poly.setEntry(1, off+1);
			}
			off += num;
		}
		
		m_bezier.setNumControlPoints(nocv);
		for (int i=0; i<nocv; i++) {
			m_bezier.setControlPoint(i, m_geom.getVertex(i));
		}
	}
	/** Get current position in [0,1] where bezier curve is evaluated. */
	public double getPosition() { return m_position.getValue(); }
	/** Set current position in [0,1] where bezier curve is evaluated. */
	public void setPosition(double time) {
		m_position.setValue(time);
	}
}

