// Copyright 2003 David Hilvert <dhilvert@auricle.dyndns.org>,
//                              <dhilvert@ugcs.caltech.edu>

/*  This file is part of the Anti-Lamenessing Engine.

    The Anti-Lamenessing Engine 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 (at your option) any later version.

    The Anti-Lamenessing Engine 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 the Anti-Lamenessing Engine; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

/*
 * d3/et.h: Represent three-dimensional Euclidean transformations.
 */

#ifndef __et_h__
#define __et_h__

#include "point.h"

#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif

/*
 * Structure to describe a three-dimensional euclidean transformation.
 *
 * We consider points to be transformed as homogeneous coordinate vectors
 * multiplied on the right of the transformation matrix.  The transformations
 * are assumed to be small, so xyz Euler angles are used to specify rotation.
 * The order of transformations is (starting with a point representation in the
 * original coordinate system):
 *
 * i)   translation of the point
 * ii)  x-rotation about the origin
 * iii) y-rotation about the origin
 * iv)  z-rotation about the origin
 *
 * For more information on Euler angles, see:
 *
 * http://mathworld.wolfram.com/EulerAngles.html
 *
 * For more information on rotation matrices, see:
 *
 * http://mathworld.wolfram.com/RotationMatrix.html
 *
 * XXX: inconsistently with the 2D class, this class uses radians to represent
 * rotation values.
 *
 */

struct et {
private:
	ale_pos translation[3];
	ale_pos rotation[3];
	mutable ale_pos matrix[4][4];
	mutable ale_pos matrix_inverse[4][4];
	mutable int resultant_memo;
	mutable int resultant_inverse_memo;

	
	/*
	 * Create an identity matrix
	 */
	void mident(ale_pos m1[4][4]) const {
		for (int i = 0; i < 4; i++)
		for (int j = 0; j < 4; j++)
			m1[i][j] = (i == j) ? 1 : 0;
	}

	/*
	 * Create a rotation matrix
	 */
	void mrot(ale_pos m1[4][4], ale_pos angle, int index) const {
		mident(m1);
		m1[(index+1)%3][(index+1)%3] = cos(angle);
		m1[(index+2)%3][(index+2)%3] = cos(angle);
		m1[(index+1)%3][(index+2)%3] = sin(angle);
		m1[(index+2)%3][(index+1)%3] = -sin(angle);
	}

	/*
	 * Multiply matrices M1 and M2; return result M3.
	 */
	void mmult(ale_pos m3[4][4], ale_pos m1[4][4], ale_pos m2[4][4]) const {
		int k;

		for (int i = 0; i < 4; i++)
		for (int j = 0; j < 4; j++)
		for (m3[i][j] = 0, k = 0; k < 4; k++) 
			m3[i][j] += m1[i][k] * m2[k][j];
	}

	/*
	 * Calculate resultant matrix values.
	 */
	void resultant() const {

		/*
		 * If we already know the answers, don't bother calculating 
		 * them again.
		 */

		if (resultant_memo)
			return;

		/*
		 * Multiply matrices.
		 */

		ale_pos m1[4][4], m2[4][4], m3[4][4];

		/*
		 * Translation
		 */

		mident(m1);

		for (int i = 0; i < 3; i++)
			m1[i][3] = translation[i];

		/*
		 * Rotation about x
		 */

		mrot(m2, rotation[0], 0);
		mmult(m3, m2, m1);

		/*
		 * Rotation about y
		 */

		mrot(m1, rotation[1], 1);
		mmult(m2, m1, m3);

		/*
		 * Rotation about z
		 */

		mrot(m3, rotation[2], 2);
		mmult(matrix, m3, m2);

		resultant_memo = 1;
	}

	/*
	 * Calculate the inverse transform matrix values.
	 */
	void resultant_inverse () const {

		/*
		 * If we already know the answers, don't bother calculating 
		 * them again.
		 */

		if (resultant_inverse_memo)
			return;

		/*
		 * The inverse can be explicitly calculated from the rotation
		 * and translation parameters.  We invert the individual
		 * matrices and reverse the order of multiplication.
		 */

		ale_pos m1[4][4], m2[4][4], m3[4][4];

		/*
		 * Translation
		 */

		mident(m1);

		for (int i = 0; i < 3; i++)
			m1[i][3] = -translation[i];

		/*
		 * Rotation about x
		 */

		mrot(m2, -rotation[0], 0);
		mmult(m3, m1, m2);

		/*
		 * Rotation about y
		 */

		mrot(m1, -rotation[1], 1);
		mmult(m2, m3, m1);

		/*
		 * Rotation about z
		 */

		mrot(m3, -rotation[2], 2);
		mmult(matrix_inverse, m2, m3);

		resultant_inverse_memo = 1;
	}

public:	
	/*
	 * Transform point p.
	 */
	struct point transform(struct point p) const {
		struct point result;

		resultant();

		for (int i = 0; i < 3; i++) {
			for (int k = 0; k < 3; k++)
				result[i] += matrix[i][k] * p[k];
			result[i] += matrix[i][3];
		}

		return result;
	}

	/*
	 * operator() is the transformation operator.
	 */
	struct point operator()(struct point p) const {
		return transform(p);
	}

	/*
	 * Map point p using the inverse of the transform.
	 */
	struct point inverse_transform(struct point p) const {
		struct point result;

		resultant_inverse();

		for (int i = 0; i < 3; i++) {
			for (int k = 0; k < 3; k++)
				result[i] += matrix_inverse[i][k] * p[k];
			result[i] += matrix_inverse[i][3];
		}

		return result;
	}

	/*
	 * Default constructor
	 *
	 * Returns the identity transformation.
	 *
	 * Note: identity() depends on this.
	 */
	et() {
		resultant_memo = 0;
		resultant_inverse_memo = 0;

		translation[0] = 0;
		translation[1] = 0;
		translation[2] = 0;

		rotation[0] = 0;
		rotation[1] = 0;
		rotation[2] = 0;
	}
	
	et(ale_pos x, ale_pos y, ale_pos z, ale_pos P, ale_pos Y, ale_pos R) {
		resultant_memo = 0;
		resultant_inverse_memo = 0;

		translation[0] = x;
		translation[1] = y;
		translation[2] = z;

		rotation[0] = P;
		rotation[1] = Y;
		rotation[2] = R;
	}
	
	/*
	 * Return identity transformation.
	 */
	static struct et identity() {
		struct et r;

		return r;
	}

	/*
	 * Set Euclidean parameters (x, y, z, P, Y, R).
	 */
	void set(double values[6]) {
		for (int i = 0; i < 3; i++) 
			translation[i] = values[i];
		for (int i = 0; i < 3; i++)
			rotation[i] = values[i + 3];
	}

	/*
	 * Adjust translation in the indicated manner.
	 */
	void modify_translation(int i1, ale_pos diff) {
		assert (i1 >= 0);
		assert (i1 < 3);

		resultant_memo = 0;
		resultant_inverse_memo = 0;

		translation[i1] += diff;
	}

	void set_translation(int i1, ale_pos new_value) {
		assert (i1 >= 0);
		assert (i1 < 3);

		resultant_memo = 0;
		resultant_inverse_memo = 0;

		translation[i1] = new_value;
	}

	/*
	 * Adjust rotation in the indicated manner.
	 */
	void modify_rotation(int i1, ale_pos diff) {
		assert (i1 >= 0);
		assert (i1 < 3);

		resultant_memo = 0;
		resultant_inverse_memo = 0;

		rotation[i1] += diff;
	}

	void set_rotation(int i1, ale_pos new_value) {
		assert (i1 >= 0);
		assert (i1 < 3);

		resultant_memo = 0;
		resultant_inverse_memo = 0;

		rotation[i1] = new_value;
	}

	ale_pos get_rotation(int i1) {
		assert (i1 >= 0);
		assert (i1 < 3);

		return rotation[i1];
	}

	ale_pos get_translation(int i1) {
		assert (i1 >= 0);
		assert (i1 < 3);

		return translation[i1];
	}


	void debug_output() {
		fprintf(stderr, "[et.do t=[%f %f %f] r=[%f %f %f]\n"
				"       m=[[%f %f %f %f] [%f %f %f %f] [%f %f %f %f] [%f %f %f %f]]\n"
				"       mi=[[%f %f %f %f] [%f %f %f %f] [%f %f %f %f] [%f %f %f %f]]\n"
				"       rm=%d rim=%d]\n",
			(double) translation[0], (double) translation[1], (double) translation[2],
			(double) rotation[0], (double) rotation[1], (double) rotation[2],
			(double) matrix[0][0], (double) matrix[0][1], (double) matrix[0][2], (double) matrix[0][3],
			(double) matrix[1][0], (double) matrix[1][1], (double) matrix[1][2], (double) matrix[1][3],
			(double) matrix[2][0], (double) matrix[2][1], (double) matrix[2][2], (double) matrix[2][3],
			(double) matrix[3][0], (double) matrix[3][1], (double) matrix[3][2], (double) matrix[3][3],
			(double) matrix_inverse[0][0], (double) matrix_inverse[0][1], (double) matrix_inverse[0][2], (double) matrix_inverse[0][3],
			(double) matrix_inverse[1][0], (double) matrix_inverse[1][1], (double) matrix_inverse[1][2], (double) matrix_inverse[1][3],
			(double) matrix_inverse[2][0], (double) matrix_inverse[2][1], (double) matrix_inverse[2][2], (double) matrix_inverse[2][3],
			(double) matrix_inverse[3][0], (double) matrix_inverse[3][1], (double) matrix_inverse[3][2], (double) matrix_inverse[3][3],
			resultant_memo, resultant_inverse_memo);
	}
};

#endif
