
/*
 * Copyright (C) 2002 - 2007 Lehrstuhl Grafische Systeme, BTU Cottbus
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 3
 * of the License, or any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */

package de.grogra.c4d;

import javax.vecmath.AxisAngle4d;
import javax.vecmath.Matrix4d;
import javax.vecmath.Tuple3d;
import javax.vecmath.Vector3d;
import javax.vecmath.Vector3f;

import de.grogra.c4d.model.C4D;
import de.grogra.c4d.model.Graph;
import de.grogra.util.WrapException;
import de.grogra.vecmath.Math2;
import de.grogra.xl.lang.Aggregate;
import de.grogra.xl.util.ObjectList;
import de.grogra.xl.util.Operators;

public class Library
{
	public static void print (String msg)
	{
		C4D.print (msg);
	}

	public static void println (String msg)
	{
		C4D.println (msg);
	}

	public static void println (Object msg)
	{
		C4D.println (String.valueOf(msg));
	}

	public static Graph graph()
	{
		return (Graph) C4D.RUNTIME.currentGraph();
	}

	public static void derive ()
	{
		graph().derive();
	}
	
	public static int irandom (int min, int max)
	{
		return Operators.getRandomGenerator ().nextInt (max + 1 - min) + min;
	}

	public static double random(double min, double max)
	{
		return Operators.getRandomGenerator ().nextDouble () * (max - min) + min;
	}

	public static double normal(double mu, double sigma)
	{
		return mu + sigma * Operators.getRandomGenerator ().nextGaussian ();
	}

	/**
	 * Calculate squared distance between node a an node b.
	 * @param a node a
	 * @param b node b
	 * @return squared distance between a and b
	 */
	public static double distanceSquared (BaseObject a, BaseObject b)
	{
		Matrix4d m = a.getMg();
		double x = m.m03, y = m.m13, z = m.m23;
		m = b.getMg();
		double t;
		return ((t = (x - m.m03)) * t + (t = (y - m.m13)) * t + (t = (z - m.m23))
			* t);
	}

	/**
	 * Computes the global distance between two nodes. The distance is
	 * measured between the points (0, 0, 0) in the local coordinate
	 * systems of the nodes.
	 * 
	 * @param a first node
	 * @param b second node
	 * @return global distance between nodes
	 */
	public static double distance (BaseObject a, BaseObject b)
	{
		return Math.sqrt (distanceSquared (a, b));
	}

	public static final Vector3d X_UP = new Vector3d();
	public static final Vector3d Y_UP = new Vector3d();
	public static final Vector3d Z_UP = new Vector3d();

	public static final Vector3d X_DOWN = new Vector3d();
	public static final Vector3d Y_DOWN = new Vector3d();
	public static final Vector3d Z_DOWN = new Vector3d();

	public static final Matrix4d IDENTITY = new Matrix4d();

	static
	{
		X_UP.x = 1;
		Y_UP.y = 1;
		Z_UP.z = 1;
		X_DOWN.x = -1;
		Y_DOWN.y = -1;
		Z_DOWN.z = -1;
		IDENTITY.setIdentity();
	}

	public static Matrix4d directionalTropism (Matrix4d m, Tuple3d direction, double e)
	{
		Vector3d dir = new Vector3d (direction.z * m.m11 - direction.y * m.m21,
			direction.x * m.m21 - direction.z * m.m01, direction.y * m.m01
				- direction.x * m.m11);
		e *= Math.sqrt (dir.lengthSquared ()
			/ (m.m01 * m.m01 + m.m11 * m.m11 + m.m21 * m.m21));
		if (e * e >= 1e-20)
		{
			Math2.invTransformVector (m, dir);
			Matrix4d result = new Matrix4d();
			result.set(new AxisAngle4d (dir, e));
			return result;
		}
		return IDENTITY;
	}

	public static Matrix4d positionalTropism (Matrix4d m, Tuple3d target, double e)
	{
		double x = target.x - m.m03, y = target.y - m.m13, z = target.z - m.m23;
		double l = x * x + y * y + z * z;
		if (l > 0)
		{
			Vector3d dir = new Vector3d (z * m.m11 - y * m.m21, x * m.m21 - z
				* m.m01, y * m.m01 - x * m.m11);
			e *= Math.sqrt (dir.lengthSquared ()
				/ (l * (m.m01 * m.m01 + m.m11 * m.m11 + m.m21 * m.m21)));
			if (e * e >= 1e-20)
			{
				Math2.invTransformVector (m, dir);
				Matrix4d result = new Matrix4d();
				result.set(new AxisAngle4d (dir, e));
				return result;
			}
		}
		return IDENTITY;
	}

	public static Matrix4d orthogonalTropism (Matrix4d m, Tuple3d direction, double e)
	{
		Vector3d dir = new Vector3d (direction.z * m.m11 - direction.y * m.m21,
			direction.x * m.m21 - direction.z * m.m01, direction.y * m.m01
				- direction.x * m.m11);
		e *= -(m.m01 * direction.x + m.m11 * direction.y + m.m21 * direction.z)
			/ Math.sqrt (m.m01 * m.m01 + m.m11 * m.m11 + m.m21 * m.m21);
		if (e * e >= 1e-20)
		{
			Math2.invTransformVector (m, dir);
			Matrix4d result = new Matrix4d();
			result.set(new AxisAngle4d (dir, e));
			return result;
		}
		return IDENTITY;
	}


	public static int select (Aggregate a, BaseObject value)
	{
		if (a.initialize())
		{
			a.aval1 = new ObjectList<BaseObject>();
		}
		if (a.isFinished())
		{
			ObjectList<BaseObject> list = (ObjectList<BaseObject>) a.aval1;
			a.ival = list.size();
			if (a.ival > 0)
			{
				list.get(0).select(Constants.SELECTION_NEW);
				for (int i = 1; i < a.ival; i++)
				{
					list.get(i).select(Constants.SELECTION_ADD);
				}
			}
		}
		else if (value != null)
		{
			((ObjectList<BaseObject>) a.aval1).add(value);
		}
		return 0;
	}

	public static BaseDocument document()
	{
		return C4D.getCurrentDocument();
	}

	/**
	 * This aggregate method computes the mean of a sequence of
	 * <code>Tuple3d</code>'s.
	 * 
	 * @param a aggregate instance (provided by the XL compiler)
	 * @param value a value of the sequence of values
	 */
	public static void mean (Aggregate a, Tuple3d value)
	{
		if (a.initialize ())
		{
			try
			{
				a.aval = a.getType ().newInstance ();
			}
			catch (Exception e)
			{
				throw new WrapException (e);
			}
			((Tuple3d) a.aval).set (0, 0, 0);
			a.ival = 0;
		}
		if (a.isFinished ())
		{
			if (a.ival > 1)
			{
				((Tuple3d) a.aval).scale (1d / a.ival);
			}
		}
		else
		{
			((Tuple3d) a.aval).add (value);
			a.ival++;
		}
	}

	public static void sum (Aggregate a, Tuple3d value)
	{
		if (a.initialize ())
		{
			try
			{
				a.aval = a.getType ().newInstance ();
			}
			catch (Exception e)
			{
				throw new WrapException (e);
			}
			((Tuple3d) a.aval).set (0, 0, 0);
		}
		if (!a.isFinished ())
		{
			((Tuple3d) a.aval).add (value);
		}
	}

	/**
	 * This aggregate method computes the mean of a sequence of
	 * <code>Matrix4d</code>'s.
	 * 
	 * @param a aggregate instance (provided by the XL compiler)
	 * @param value a value of the sequence of values
	 */
	public static void mean (Aggregate a, Matrix4d value)
	{
		if (a.initialize ())
		{
			try
			{
				a.aval = a.getType ().newInstance ();
			}
			catch (Exception e)
			{
				throw new WrapException (e);
			}
			((Matrix4d) a.aval).setZero();
			a.ival = 0;
		}
		if (a.isFinished ())
		{
			if (a.ival > 1)
			{
				((Matrix4d) a.aval).mul (1d / a.ival);
			}
		}
		else
		{
			((Matrix4d) a.aval).add (value);
			a.ival++;
		}
	}

	public static Vector3d operator$index(Matrix4d m, int column)
	{
		Vector3d v = new Vector3d();
		switch (column)
		{
			case 0:
				v.x = m.m00;
				v.y = m.m10;
				v.z = m.m20;
				break;
			case 1:
				v.x = m.m01;
				v.y = m.m11;
				v.z = m.m21;
				break;
			case 2:
				v.x = m.m02;
				v.y = m.m12;
				v.z = m.m22;
				break;
			case 3:
				v.x = m.m03;
				v.y = m.m13;
				v.z = m.m23;
				break;
			default:
				throw new ArrayIndexOutOfBoundsException("column must be 0 to 3 and is " + column);
		}
		return v;
	}

	public static Matrix4d getOrthogonalBasis (Tuple3d in)
	{
		Matrix4d out = new Matrix4d();
		double s = 1 / Math.sqrt (in.x * in.x + in.y * in.y + in.z * in.z);
		out.m01 = in.x * s;
		out.m11 = in.y * s;
		out.m21 = in.z * s;
		if (Math.abs (in.x) > Math.abs (in.y))
		{
			double f = 1 / Math.sqrt (in.x * in.x + in.z * in.z);
			out.m02 = in.z * f;
			out.m12 = 0;
			out.m22 = -in.x * f;
		}
		else
		{
			double f = 1 / Math.sqrt (in.y * in.y + in.z * in.z);
			out.m02 = 0;
			out.m12 = in.z * f;
			out.m22 = -in.y * f;
		}
		out.m00 = out.m11 * out.m22 - out.m21 * out.m12;
		out.m10 = out.m21 * out.m02 - out.m01 * out.m22;
		out.m20 = out.m01 * out.m12 - out.m11 * out.m02;
		out.m33 = 1;
		return out;
	}

	public static double noise(double x, double y, double z)
	{
		return Math2.noise((float) x, (float) y, (float) z);
	}

	public static double noise(Tuple3d t)
	{
		return Math2.noise((float) t.x, (float) t.y, (float) t.z);
	}

	public static Vector3d dNoise(double x, double y, double z)
	{
		Vector3f v = new Vector3f();
		Math2.dNoise(v, (float) x, (float) y, (float) z);
		return new Vector3d(v);
	}

	public static Vector3d dNoise(Tuple3d t)
	{
		return dNoise(t.x, t.y, t.z);
	}
}
