/*
 * JaamSim Discrete Event Simulation
 * Copyright (C) 2012 Ausenco Engineering Canada Inc.
 *
 * 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
 * (at your option) 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.
 */
package com.jaamsim.math;

/**
 * The Transform class represents a linear transform consisting of a rotation, scale and translation
 * in 3-space. Internally it stores all 3 as discrete transforms but allows for arbitrary transforms to be chained,
 * collapsed and inverted. The primary limitation is it only allows for uniform (isotropic) scaling
 * @author Matt Chudleigh
 *
 */
public class Transform {

private final Quaternion _rot;
private final Vec3d _trans;
private double _scale;
private final Mat4d _mat4d = new Mat4d();
private boolean _matrixDirty;

public static final Transform ident = new Transform(); // Static Identity transform

public Transform() {
	_trans = new Vec3d();
	_rot = new Quaternion(); // Identity
	_scale = 1;
	_matrixDirty = false;
}

public Transform(Transform t) {
	_trans = new Vec3d(t._trans);
	_rot = new Quaternion(t._rot);
	_scale = t._scale;
	_matrixDirty = true;
}

public Transform(Vec3d trans, Quaternion rot, double scale)
{
	if (trans == null)
		_trans = new Vec3d();
	else
		_trans = new Vec3d(trans);

	if (rot == null)
		_rot = new Quaternion();
	else
		_rot = new Quaternion(rot);
	_scale = scale;
	_matrixDirty = true;
}

public Transform(Vec3d trans) {
	this(trans, null, 1.0d);
}

public void copyFrom(Transform t) {
	_trans.set3(t._trans);
	_rot.set(t._rot);
	_scale = t._scale;

	if (!t._matrixDirty) {
		_mat4d.set4(t._mat4d);
	}
	_matrixDirty = t._matrixDirty;
}

public void setTrans(Vec3d trans) {
	if (_trans.equals3(trans)) {
		return;
	}
	_trans.set3(trans);
	_matrixDirty = true;
}

public void setRot(Quaternion q) {
	if (_rot.equals(q)) {
		return;
	}
	_rot.set(q);
	_matrixDirty = true;
}

public void setScale(double s) {
	if (MathUtils.near(_scale, s)) {
		return;
	}
	_scale = s;
	_matrixDirty = true;
}

public void getRot(Quaternion out) {
	out.set(_rot);
}

public Quaternion getRotRef() {
	return _rot;
}

public Vec3d getTransRef() {
	return _trans;
}

public double getScale() {
	return _scale;
}

public void addTrans(Vec3d trans) {
	_trans.add3(trans);
	_matrixDirty = true;
}

/**
 * Calculated the 4x4 matrix corresponding to this transform
 * @param out - the 4x4 matrix
 */
public void getMat4d(Mat4d out) {
	if (_matrixDirty) {
		updateMatrix();
	}

	out.set4(_mat4d);

}

public Mat4d getMat4dRef() {
	if (_matrixDirty) {
		updateMatrix();
	}

	return _mat4d;
}


private void updateMatrix() {
	assert(!Double.isNaN(_trans.x));
	assert(!Double.isNaN(_trans.y));
	assert(!Double.isNaN(_trans.z));

	_mat4d.setRot4(_rot);
	_mat4d.setTranslate3(_trans);
	_mat4d.scale3(_scale);

	_matrixDirty = false;

}

/**
 * Populates a transform that is the merging of this and 'rhs'
 * The matrix from this new transform will be the same as if the matrices of both transforms
 * were multiplied (see the unit tests)
 * @param rhs - the right hand matrix to merge with
 * @param out
 */
public void merge(Transform a, Transform b) {
	Vec3d temp = new Vec3d(a._trans);

	Mat4d rotTemp = new Mat4d();
	rotTemp.setRot3(a._rot);

	_trans.mult3(rotTemp, b._trans);
	_trans.scale3(a._scale);

	_trans.add3(temp);
	_matrixDirty = true;

	_rot.mult(a._rot, b._rot);

	_scale = a._scale * b._scale;

}

/**
 * Apply this transform to a vector
 * @param vect - the vector to transform
 * @param out - the transformed vector
 */
public void apply(Vec4d vect, Vec4d out) {
	if (_matrixDirty) {
		updateMatrix();
	}

	out.mult4(_mat4d, vect);
}

public void multAndTrans(Vec3d vect, Vec3d out) {
	if (_matrixDirty) {
		updateMatrix();
	}

	out.multAndTrans3(_mat4d, vect);
}

/**
 * Returns a transform that is the inverse of this transform, merging the two in any order will
 * result in the identity transform
 * @param out
 */
public void inverse(Transform out) {
	out._scale = 1/_scale;
	out._rot.conjugate(_rot);

	out._trans.set3(_trans);
	out._trans.scale3(-out._scale);

	Mat4d rotTemp = new Mat4d();
	rotTemp.setRot3(out._rot);
	out._trans.mult3(rotTemp, out._trans);

	out._matrixDirty = true;
}

@Override
public boolean equals(Object o) {
	if (!(o instanceof Transform)) return false;
	Transform t = (Transform)o;

	return _trans.equals3(t._trans) && _rot.equals(t._rot) && MathUtils.near(_scale, t._scale);
}

public boolean near(Transform t) {
	return _trans.near3(t._trans) && _rot.equals(t._rot) && MathUtils.near(_scale, t._scale);
}

@Override
public int hashCode() {
	assert false : "hashCode not designed";
	return 42; // any arbitrary constant will do
}

@Override
public String toString()
{
	return "T: " + _trans.toString() + " R: " + _rot.toString() + " S: " + Double.toString(_scale);
}

} // class

@@ -2572,90 +2572,8 @@
 %0A%7D%0A%0A
-public void addTrans(Vec3d trans) %7B%0A%09_trans.add3(trans);%0A%09_matrixDirty = true;%0A%7D%0A%0A
 /**%0A

remove unused Transform method addTrans