
/**
 * Matrix3D.
 *
 * Specialized matrix class, for 3D coordinates,
 * and transformation/translation/rotation.
 * 
 * Since trig functions are computationally intensive, a way
 * to optimize the calculations are to use trig tables.
 
 * Copyright (C) 1999  Shazron Abdullah
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library 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
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * @author Shazron Abdullah
 */
public class Matrix3D extends Matrix
{
	public Matrix3D( long x, long y, long z )
	{
		super(1,4); 
		setX( x );
		setY( y );
		setZ( z );
		setValue(0,3,1);
	}
	
	public final long getX() { return getValue( 0, 0 ); }
	public final long getY() { return getValue( 0, 1 ); }
	public final long getZ() { return getValue( 0, 2 ); }
	public final void setX( long x ) { setValue( 0, 0, x ); }
	public final void setY( long y ) { setValue( 0, 1, y ); }
	public final void setZ( long z ) { setValue( 0, 2, z ); }
	
	public long[] getVector()
	{
		long[] v = { getX(), getY(), getZ() };
		return v;
	}
	
	public void setVector( long[] v )
	{
		setX( v[0] );
		setY( v[1] );
		setZ( v[2] );
		setValue(0,3,1);
	}
	
	public void translate( long sx, long sy, long sz )
	{
		copyData( mult( data, getTranslationTransform( sx, sy, sz ) ) );
	}
	
	public void scale( long sx, long sy, long sz )
	{
		copyData( mult( data, getScalingTransform( sx, sy, sz ) ) );
	}
	
	public void rotateX( double deg )
	{
		copyData( mult( data, getRotateXTransform( deg ) ) );
	}

	public void rotateY( double deg )
	{
		copyData( mult( data, getRotateYTransform( deg ) ) );
	}

	public void rotateZ( double deg )
	{
		copyData( mult( data, getRotateZTransform( deg ) ) );
	}
	
	public void reflect( boolean x, boolean y, boolean z )
	{
		copyData( mult( data, getReflectTransform( x, y, z ) ) );
	}
	
	public void shearX( long value ) 
	{
		copyData( mult( data, getShearXTransform( value ) ) );
	}
	
	public void shearY( long value ) 
	{
		copyData( mult( data, getShearYTransform( value ) ) );
	}
	
	public void shearZ( long a, long b ) 
	{
		copyData( mult( data, getShearZTransform( a, b ) ) );
	}
	
	public double getLength()
	{
		return getLength( getVector() );
	}
	
	public long[] getNormalized()
	{
		long[] v = getVector();
		double len = getLength();
		for ( int i=0; i < v.length; i++ ) {
			v[i] = v[i] / (long)len;
		}
		return v;
	}
	
	/* *************************************************************
	 *
	 * Static functions.
	 *
	 ***************************************************************/

	public static long[][] getTranslationTransform( long sx, long sy, long sz )
	{
		long[][] lm = { {  1,  0,  0, 0 },
						{  0,  1,  0, 0 },
						{  0,  0,  1, 0 },
						{ sx, sy, sz, 1 }
					  };
		return lm;
	}

	public static long[][] getScalingTransform( long sx, long sy, long sz )
	{
		long[][] lm = { { sx, 0, 0, 0 },
			     	  	{  0,sy, 0, 0 },
			     		{  0, 0,sz, 0 },
			     		{  0, 0, 0, 1 }
			   		  };
		return lm;
	}

	public static long[][] getRotateXTransform( double deg )
	{
		long cos_deg = (long)Math.cos(deg);
		long sin_deg = (long)Math.sin(deg);
		
		long[][] lm = { { 1,           0,       0, 0 },
			      		{ 0,     cos_deg, sin_deg, 0 },
			      		{ 0,  sin_deg*-1, cos_deg, 0 },
			      		{ 0,           0,       0, 1 }
			    	  };
		return lm;
	}

	public static long[][] getRotateYTransform( double deg )
	{
		long cos_deg = (long)Math.cos(deg);
		long sin_deg = (long)Math.sin(deg);
		
		long[][] lm = { { cos_deg, 0, sin_deg*-1, 0 },
			     		{       0, 1,          0, 0 },
			     		{ sin_deg, 0,    cos_deg, 0 },
			     		{       0, 0,          0, 1 }
			   		  };
		return lm;
	}

	public static long[][] getRotateZTransform( double deg )
	{
		long cos_deg = (long)Math.cos(deg);
		long sin_deg = (long)Math.sin(deg);
		
		long[][] lm = { {    cos_deg, sin_deg, 0, 0 },
					    { sin_deg*-1, cos_deg, 0, 0 },
					    {          0,       0, 1, 0 },
					    {          0,       0, 0, 1 }
					  };
		return lm;
	}

	/**
	 * @param x set to true to reflect along X-axis
	 * @param y set to true to reflect along Y-axis
	 * @param z set to true to reflect along Z-axis
	 */
	public static long[][] getReflectTransform( boolean x, boolean y, boolean z )
	{
		// yes, the order below is correct
		long[][] lm = { { (y ? -1 : 1),            0,            0, 0 },
					    {            0, (x ? -1 : 1),            0, 0 },
					    {            0,            0, (z ? -1 : 1), 0 },
					    {            0,            0,            0, 1 }
					  };
		return lm;
	}
	
	/**
	 */
	public static long[][] getShearXTransform( long value )
	{
		long[][] lm = { {     1, 0, 0, 0 },
					    { value, 1, 0, 0 },
					    {     0, 0, 1, 0 },
					    {     0, 0, 0, 1 }
					  };
		return lm;
	}
	
	/**
	 */
	public static long[][] getShearYTransform( long value )
	{
		long[][] lm = { { 1, value, 0, 0 },
					    { 0,     1, 0, 0 },
					    { 0,     0, 1, 0 },
					    { 0,     0, 0, 1 }
					  };
		return lm;
	}

	/**
	 */
	public static long[][] getShearZTransform( long a, long b )
	{
		long[][] lm = { { 1, 0, 0, 0 },
					    { 0, 1, 0, 0 },
					    { a, b, 1, 0 },
					    { 0, 0, 0, 1 }
					  };
		return lm;
	}
	
	/**
	 * Calculate the cross product of two vectors (array of values).
	 * 
	 * Both vectors must be of at least length 3 (discards anything after).
	 */
	public static long[] crossProduct( long[] v, long[] w )
	{
		if ( v.length < 3 || w.length < 3) throw new CrossProductException();
		
		// v = (x1, y1, z1) w = (x2, y2, z2),
		// v x w = (y1 * z2 - z1 * y2, z1 * x2 - x1 * z2, x1 * y2 - y1 * x2 ). 
		
		long new_x = v[1] * w[2] - v[2] * w[1];
		long new_y = v[2] * w[0] - v[0] * w[2];
		long new_z = v[0] * w[1] - v[1] * w[0];
		long[] n = { new_x, new_y, new_z };
		
		return n;
	}
	
	/**
	 * Calculate the angle between two vectors.
	 * 
	 * Both vectors must be of at least length 3 (discards anything after).
	 */
	public static double angleBetween( long[] v, long[] w )
	{
		if ( v.length < 3 || w.length < 3) throw new CrossProductException();
		
		// theta = acos( v.w / ||v|| ||w||
		
		double len_v = getLength(v);
		double len_w = getLength(w);
		long dp = dotProduct( v, w );
		
		return Math.acos( dp / ( len_v * len_w ) );
	}	
	
	public static double getLength( long[] v )
	{
		return Math.sqrt( dotProduct( v, v ) );
	}
	
	public static class CrossProductException extends RuntimeException { };
}