#include "MathUtil.h"
#include <time.h>
#define _USE_MATH_DEFINES
#include <math.h>


int cMathUtil::Clamp(int val, int min, int max)
{
	return std::max(min, std::min(val, max));
}

void cMathUtil::Clamp(const Eigen::VectorXd& min, const Eigen::VectorXd& max, Eigen::VectorXd& out_vec)
{
	out_vec = out_vec.cwiseMin(max).cwiseMax(min);
}

double cMathUtil::Clamp(double val, double min, double max)
{
	return std::max(min, std::min(val, max));
}

double cMathUtil::Saturate(double val)
{
	return Clamp(val, 0.0, 1.0);
}

double cMathUtil::Lerp(double t, double val0, double val1)
{
	return (1 - t) * val0 + t * val1;
}

double cMathUtil::NormalizeAngle(double theta)
{
	// normalizes theta to be between [-pi, pi]
	double norm_theta = fmod(theta, 2 * M_PI);
	if (norm_theta > M_PI)
	{
		norm_theta = -2 * M_PI + norm_theta;
	}
	else if (norm_theta < -M_PI)
	{
		norm_theta = 2 * M_PI + norm_theta;
	}
	return norm_theta;
}


double cMathUtil::SmoothStep(double t)
{
	double val = t * t * t * (t * (t * 6 - 15) + 10);
	return val;
}


tMatrix cMathUtil::TranslateMat(const tVector& trans)
{
	tMatrix mat = tMatrix::Identity();
	mat(0, 3) = trans[0];
	mat(1, 3) = trans[1];
	mat(2, 3) = trans[2];
	return mat;
}

tMatrix cMathUtil::ScaleMat(double scale)
{
	return ScaleMat(tVector::Ones() * scale);
}

tMatrix cMathUtil::ScaleMat(const tVector& scale)
{
	tMatrix mat = tMatrix::Identity();
	mat(0, 0) = scale[0];
	mat(1, 1) = scale[1];
	mat(2, 2) = scale[2];
	return mat;
}

tMatrix cMathUtil::RotateMat(const tVector& euler)
{
	double x = euler[0];
	double y = euler[1];
	double z = euler[2];

	double x_s = std::sin(x);
	double x_c = std::cos(x);
	double y_s = std::sin(y);
	double y_c = std::cos(y);
	double z_s = std::sin(z);
	double z_c = std::cos(z);

	tMatrix mat = tMatrix::Identity();
	mat(0, 0) = y_c * z_c;
	mat(1, 0) = y_c * z_s;
	mat(2, 0) = -y_s;

	mat(0, 1) = x_s * y_s * z_c - x_c * z_s;
	mat(1, 1) = x_s * y_s * z_s + x_c * z_c;
	mat(2, 1) = x_s * y_c;

	mat(0, 2) = x_c * y_s * z_c + x_s * z_s;
	mat(1, 2) = x_c * y_s * z_s - x_s * z_c;
	mat(2, 2) = x_c * y_c;

	return mat;
}

tMatrix cMathUtil::RotateMat(const tVector& axis, double theta)
{
	assert(std::abs(axis.squaredNorm() - 1) < 0.0001);
	
	double c = std::cos(theta);
	double s = std::sin(theta);
	double x = axis[0];
	double y = axis[1];
	double z = axis[2];

	tMatrix mat;
	mat <<	c + x * x * (1 - c),		x * y * (1 - c) - z * s,	x * z * (1 - c) + y * s,	0,
			y * x * (1 - c) + z * s,	c + y * y * (1 - c),		y * z * (1 - c) - x * s,	0,
			z * x * (1 - c) - y * s,	z * y * (1 - c) + x * s,	c + z * z * (1 - c),		0,
			0,							0,							0,							1;

	return mat;
}

tMatrix cMathUtil::RotateMat(const tQuaternion& q)
{
	tMatrix mat = tMatrix::Identity();

	double sqw = q.w() * q.w();
	double sqx = q.x()*  q.x();
	double sqy = q.y() * q.y();
	double sqz = q.z() * q.z();
	double invs = 1 / (sqx + sqy + sqz + sqw);

	mat(0, 0) = (sqx - sqy - sqz + sqw) * invs;
	mat(1, 1) = (-sqx + sqy - sqz + sqw) * invs;
	mat(2, 2) = (-sqx - sqy + sqz + sqw) * invs;

	double tmp1 = q.x()*q.y();
	double tmp2 = q.z()*q.w();
	mat(1, 0) = 2.0 * (tmp1 + tmp2) * invs;
	mat(0, 1) = 2.0 * (tmp1 - tmp2) * invs;

	tmp1 = q.x()*q.z();
	tmp2 = q.y()*q.w();
	mat(2, 0) = 2.0 * (tmp1 - tmp2) * invs;
	mat(0, 2) = 2.0 * (tmp1 + tmp2) * invs;

	tmp1 = q.y()*q.z();
	tmp2 = q.x()*q.w();
	mat(2, 1) = 2.0 * (tmp1 + tmp2) * invs;
	mat(1, 2) = 2.0 * (tmp1 - tmp2) * invs;

	return mat;
}

tMatrix cMathUtil::CrossMat(const tVector& a)
{
	tMatrix m;
	m << 0,		-a[2],	a[1],	0,
		 a[2],	0,		-a[0],	0,
		 -a[1],	a[0],	0,		0,
		 0,		0,		0,		1;
	return m;
}

tMatrix cMathUtil::InvRigidMat(const tMatrix& mat)
{
	tMatrix inv_mat = tMatrix::Zero();
	inv_mat.block(0, 0, 3, 3) = mat.block(0, 0, 3, 3).transpose();
	inv_mat.col(3) = -inv_mat * mat.col(3);
	inv_mat(3, 3) = 1;
	return inv_mat;
}

tVector cMathUtil::GetRigidTrans(const tMatrix& mat)
{
	return tVector(mat(0, 3), mat(1, 3), mat(2, 3), 0);
}

tVector cMathUtil::InvEuler(const tVector& euler)
{
	tMatrix inv_mat = cMathUtil::RotateMat(tVector(1, 0, 0, 0), -euler[0])
					* cMathUtil::RotateMat(tVector(0, 1, 0, 0), -euler[1])
					* cMathUtil::RotateMat(tVector(0, 0, 1, 0), -euler[2]);
	tVector inv_euler = cMathUtil::RotMatToEuler(inv_mat);
	return inv_euler;
}

void cMathUtil::RotMatToAxisAngle(const tMatrix& mat, tVector& out_axis, double& out_theta)
{
	double c = (mat(0, 0) + mat(1, 1) + mat(2, 2) - 1) * 0.5;
	c = cMathUtil::Clamp(c, -1.0, 1.0);

	out_theta = std::acos(c);
	if (std::abs(out_theta) < 0.00001)
	{
		out_axis = tVector(0, 0, 1, 0);
	}
	else
	{
		double m21 = mat(2, 1) - mat(1, 2);
		double m02 = mat(0, 2) - mat(2, 0);
		double m10 = mat(1, 0) - mat(0, 1);
		double denom = std::sqrt(m21 * m21 + m02 * m02 + m10 * m10);
		out_axis[0] = m21 / denom;
		out_axis[1] = m02 / denom;
		out_axis[2] = m10 / denom;
		out_axis[3] = 0;
	}
}

tVector cMathUtil::RotMatToEuler(const tMatrix& mat)
{
	tVector euler;
	euler[0] = std::atan2(mat(2, 1), mat(2, 2));
	euler[1] = std::atan2(-mat(2, 0), std::sqrt(mat(2, 1) * mat(2, 1) + mat(2, 2) * mat(2, 2)));
	euler[2] = std::atan2(mat(1, 0), mat(0, 0)); 
	euler[3] = 0;
	return euler;
}

tQuaternion cMathUtil::RotMatToQuaternion(const tMatrix& mat)
{
	double tr = mat(0, 0) + mat(1, 1) + mat(2, 2);
	tQuaternion q;

	if (tr > 0) {
		double S = sqrt(tr + 1.0) * 2; // S=4*qw 
		q.w() = 0.25 * S;
		q.x() = (mat(2, 1) - mat(1, 2)) / S;
		q.y() = (mat(0, 2) - mat(2, 0)) / S;
		q.z() = (mat(1, 0) - mat(0, 1)) / S;
	}
	else if ((mat(0, 0) > mat(1, 1) && (mat(0, 0) > mat(2, 2)))) {
		double S = sqrt(1.0 + mat(0, 0) - mat(1, 1) - mat(2, 2)) * 2; // S=4*qx 
		q.w() = (mat(2, 1) - mat(1, 2)) / S;
		q.x() = 0.25 * S;
		q.y() = (mat(0, 1) + mat(1, 0)) / S;
		q.z() = (mat(0, 2) + mat(2, 0)) / S;
	}
	else if (mat(1, 1) > mat(2, 2)) {
		double S = sqrt(1.0 + mat(1, 1) - mat(0, 0) - mat(2, 2)) * 2; // S=4*qy
		q.w() = (mat(0, 2) - mat(2, 0)) / S;
		q.x() = (mat(0, 1) + mat(1, 0)) / S;
		q.y() = 0.25 * S;
		q.z() = (mat(1, 2) + mat(2, 1)) / S;
	}
	else {
		double S = sqrt(1.0 + mat(2, 2) - mat(0, 0) - mat(1, 1)) * 2; // S=4*qz
		q.w() = (mat(1, 0) - mat(0, 1)) / S;
		q.x() = (mat(0, 2) + mat(2, 0)) / S;
		q.y() = (mat(1, 2) + mat(2, 1)) / S;
		q.z() = 0.25 * S;
	}

	return q;
}

void cMathUtil::EulerToAxisAngle(const tVector& euler, tVector& out_axis, double& out_theta)
{
	double x = euler[0];
	double y = euler[1];
	double z = euler[2];

	double x_s = std::sin(x);
	double x_c = std::cos(x);
	double y_s = std::sin(y);
	double y_c = std::cos(y);
	double z_s = std::sin(z);
	double z_c = std::cos(z);

	double c = (y_c * z_c + x_s * y_s * z_s + x_c * z_c + x_c * y_c - 1) * 0.5;
	c = Clamp(c, -1.0, 1.0);

	out_theta = std::acos(c);
	if (std::abs(out_theta) < 0.00001)
	{
		out_axis = tVector(0, 0, 1, 0);
	}
	else
	{
		double m21 = x_s * y_c - x_c * y_s * z_s + x_s * z_c;
		double m02 = x_c * y_s * z_c + x_s * z_s + y_s;
		double m10 = y_c * z_s - x_s * y_s * z_c + x_c * z_s;
		double denom = std::sqrt(m21 * m21 + m02 * m02 + m10 * m10);
		out_axis[0] = m21 / denom;
		out_axis[1] = m02 / denom;
		out_axis[2] = m10 / denom;
		out_axis[3] = 0;
	}
}

tVector cMathUtil::AxisAngleToEuler(const tVector& axis, double theta)
{
	tQuaternion q = AxisAngleToQuaternion(axis, theta);
	return QuaternionToEuler(q);
}

tMatrix cMathUtil::DirToRotMat(const tVector& dir, const tVector& up)
{
	tVector x = up.cross3(dir);
	double x_norm = x.norm();
	if (x_norm == 0)
	{
		x_norm = 1;
		x = (dir.dot(up) >= 0) ? tVector(1, 0, 0, 0) : tVector(-1, 0, 0, 0);
	}
	x /= x_norm;

	tVector y = dir.cross3(x).normalized();
	tVector z = dir;

	tMatrix mat = tMatrix::Identity();
	mat.block(0, 0, 3, 1) = x.segment(0, 3);
	mat.block(0, 1, 3, 1) = y.segment(0, 3);
	mat.block(0, 2, 3, 1) = z.segment(0, 3);

	return mat;
}

void cMathUtil::DeltaRot(const tVector& axis0, double theta0, const tVector& axis1, double theta1,
							tVector& out_axis, double& out_theta)
{
	tMatrix R0 = RotateMat(axis0, theta0);
	tMatrix R1 = RotateMat(axis1, theta1);
	tMatrix M = DeltaRot(R0, R1);
	RotMatToAxisAngle(M, out_axis, out_theta);
}

tMatrix cMathUtil::DeltaRot(const tMatrix& R0, const tMatrix& R1)
{
	return R1 * R0.transpose();
}

tQuaternion cMathUtil::EulerToQuaternion(const tVector& euler)
{
	tVector axis;
	double theta;
	EulerToAxisAngle(euler, axis, theta);
	return AxisAngleToQuaternion(axis, theta);
}

tVector cMathUtil::QuaternionToEuler(const tQuaternion& q)
{
	double sinr = 2.0 * (q.w() * q.x() + q.y() * q.z());
	double cosr = 1.0 - 2.0 * (q.x() * q.x() + q.y() * q.y());
	double x = std::atan2(sinr, cosr);

	double sinp = 2.0 * (q.w() * q.y() - q.z() * q.x());
	double y = 0;
	if (fabs(sinp) >= 1)
	{
		y = copysign(M_PI / 2, sinp); // use 90 degrees if out of range
	}
	else
	{
		y = asin(sinp);
	}

	double siny = 2.0 * (q.w() * q.z() + q.x() * q.y());
	double cosy = 1.0 - 2.0 * (q.y() * q.y() + q.z() * q.z());
	double z = std::atan2(siny, cosy);

	return tVector(x, y, z, 0);
}

tQuaternion cMathUtil::AxisAngleToQuaternion(const tVector& axis, double theta)
{
	// axis must be normalized
	double c = std::cos(theta / 2);
	double s = std::sin(theta / 2);
	tQuaternion q;
	q.w() = c;
	q.x() = s * axis[0];
	q.y() = s * axis[1];
	q.z() = s * axis[2];
	return q;
}

void cMathUtil::QuaternionToAxisAngle(const tQuaternion& q, tVector& out_axis, double& out_theta)
{
	out_theta = 0;
	out_axis = tVector(0, 0, 1, 0);

	tQuaternion q1 = q;
	if (q1.w() > 1)
	{
		q1.normalize();
	}

	double sin_theta = std::sqrt(1 - q1.w() * q1.w());
	if (sin_theta > 0.000001)
	{
		out_theta = 2 * std::acos(q1.w());
		out_theta = cMathUtil::NormalizeAngle(out_theta);
		out_axis = tVector(q1.x(), q1.y(), q1.z(), 0) / sin_theta;
	}
}

tMatrix cMathUtil::BuildQuaternionDiffMat(const tQuaternion& q)
{
	tMatrix mat;
	mat << -0.5 * q.x(), -0.5 * q.y(), -0.5 * q.z(), 0,
		0.5 * q.w(), -0.5 * q.z(), 0.5 * q.y(), 0,
		0.5 * q.z(), 0.5 * q.w(), -0.5 * q.x(), 0,
		-0.5 * q.y(), 0.5 * q.x(), 0.5 * q.w(), 0;
	return mat;
}

tVector cMathUtil::CalcQuaternionVel(const tQuaternion& q0, const tQuaternion& q1, double dt)
{
	tQuaternion q_diff = cMathUtil::QuatDiff(q0, q1);
	tVector axis;
	double theta;
	QuaternionToAxisAngle(q_diff, axis, theta);
	return (theta / dt) * axis;
}

tVector cMathUtil::CalcQuaternionVelRel(const tQuaternion& q0, const tQuaternion& q1, double dt)
{
	// calculate relative rotational velocity in the coordinate frame of q0
	tQuaternion q_diff = q0.conjugate() * q1;
	tVector axis;
	double theta;
	QuaternionToAxisAngle(q_diff, axis, theta);
	return (theta / dt) * axis;
}

tQuaternion cMathUtil::VecToQuat(const tVector& v)
{
	return tQuaternion(v[0], v[1], v[2], v[3]);
}

tVector cMathUtil::QuatToVec(const tQuaternion& q)
{
	return tVector(q.w(), q.x(), q.y(), q.z());
}

tQuaternion cMathUtil::QuatDiff(const tQuaternion& q0, const tQuaternion& q1)
{
	return q1 * q0.conjugate();
}

double cMathUtil::QuatDiffTheta(const tQuaternion& q0, const tQuaternion& q1)
{
	tQuaternion dq = QuatDiff(q0, q1);
	return QuatTheta(dq);
}

double cMathUtil::QuatTheta(const tQuaternion& dq)
{
	double theta = 0;
	tQuaternion q1 = dq;
	if (q1.w() > 1)
	{
		q1.normalize();
	}

	double sin_theta = std::sqrt(1 - q1.w() * q1.w());
	if (sin_theta > 0.0001)
	{
		theta = 2 * std::acos(q1.w());
		theta = cMathUtil::NormalizeAngle(theta);
	}
	return theta;
}

tQuaternion cMathUtil::VecDiffQuat(const tVector& v0, const tVector& v1)
{
	return tQuaternion::FromTwoVectors(v0.segment(0, 3), v1.segment(0, 3));
}

tVector cMathUtil::QuatRotVec(const tQuaternion& q, const tVector& dir)
{
	tVector rot_dir = tVector::Zero();
	rot_dir.segment(0, 3)  = q * dir.segment(0, 3);
	return rot_dir;
}

tQuaternion cMathUtil::MirrorQuaternion(const tQuaternion& q, eAxis axis)
{
	tQuaternion mirror_q;
	mirror_q.w() = q.w();
	mirror_q.x() = (axis == eAxisX) ? q.x() : -q.x();
	mirror_q.y() = (axis == eAxisY) ? q.y() : -q.y();
	mirror_q.z() = (axis == eAxisZ) ? q.z() : -q.z();
	return mirror_q;
}

double cMathUtil::Sign(double val)
{
	return SignAux<double>(val);
}

int cMathUtil::Sign(int val)
{
	return SignAux<int>(val);
}

double cMathUtil::AddAverage(double avg0, int count0, double avg1, int count1)
{
	double total = count0 + count1;
	return (count0 / total) * avg0 + (count1 / total) * avg1;
}

tVector cMathUtil::AddAverage(const tVector& avg0, int count0, const tVector& avg1, int count1)
{
	double total = count0 + count1;
	return (count0 / total) * avg0 + (count1 / total) * avg1 ;
}

void cMathUtil::AddAverage(const Eigen::VectorXd& avg0, int count0, const Eigen::VectorXd& avg1, int count1, Eigen::VectorXd& out_result)
{
	double total = count0 + count1;
	out_result = (count0 / total) * avg0 + (count1 / total) * avg1;
}

void cMathUtil::CalcSoftmax(const Eigen::VectorXd& vals, double temp, Eigen::VectorXd& out_prob)
{
	assert(out_prob.size() == vals.size());
	int num_vals = static_cast<int>(vals.size());
	double sum = 0;
	double max_val = vals.maxCoeff();
	for (int i = 0; i < num_vals; ++i)
	{
		double val = vals[i];
		val = std::exp((val - max_val) / temp);
		out_prob[i] = val;
		sum += val;
	}

	out_prob /= sum;
}

double cMathUtil::EvalGaussian(const Eigen::VectorXd& mean, const Eigen::VectorXd& covar, const Eigen::VectorXd& sample)
{
	assert(mean.size() == covar.size());
	assert(sample.size() == covar.size());

	Eigen::VectorXd diff = sample - mean;
	double exp_val = diff.dot(diff.cwiseQuotient(covar));
	double likelihood = std::exp(-0.5 * exp_val);

	double partition = CalcGaussianPartition(covar);
	likelihood /= partition;
	return likelihood;
}

double cMathUtil::EvalGaussian(double mean, double covar, double sample)
{
	double diff = sample - mean;
	double exp_val = diff * diff / covar;
	double norm = 1 / std::sqrt(2 * M_PI * covar);
	double likelihood = norm * std::exp(-0.5 * exp_val);
	return likelihood;
}

double cMathUtil::CalcGaussianPartition(const Eigen::VectorXd& covar)
{
	int data_size = static_cast<int>(covar.size());
	double det = covar.prod();
	double partition = std::sqrt(std::pow(2 * M_PI, data_size) * det);
	return partition;
}

double cMathUtil::EvalGaussianLogp(const Eigen::VectorXd& mean, const Eigen::VectorXd& covar, const Eigen::VectorXd& sample)
{
	int data_size = static_cast<int>(covar.size());

	Eigen::VectorXd diff = sample - mean;
	double logp = -0.5 * diff.dot(diff.cwiseQuotient(covar));
	double det = covar.prod();
	logp += -0.5 * (data_size * std::log(2 * M_PI) + std::log(det));

	return logp;
}

double cMathUtil::EvalGaussianLogp(double mean, double covar, double sample)
{
	double diff = sample - mean;
	double logp = -0.5 * diff * diff / covar;
	logp += -0.5 * (std::log(2 * M_PI) + std::log(covar));
	return logp;
}

double cMathUtil::Sigmoid(double x)
{
	return Sigmoid(x, 1, 0);
}

double cMathUtil::Sigmoid(double x, double gamma, double bias)
{
	double exp = -gamma * (x + bias);
	double val = 1 / (1 + std::exp(exp));
	return val;
}


tVector cMathUtil::CalcBarycentric(const tVector& p, const tVector& a, const tVector& b, const tVector& c)
{
	tVector v0 = b - a;
	tVector v1 = c - a;
	tVector v2 = p - a;

	double d00 = v0.dot(v0);
	double d01 = v0.dot(v1);
	double d11 = v1.dot(v1);
	double d20 = v2.dot(v0);
	double d21 = v2.dot(v1);
	double denom = d00 * d11 - d01 * d01;
	double v = (d11 * d20 - d01 * d21) / denom;
	double w = (d00 * d21 - d01 * d20) / denom;
	double u = 1.0f - v - w;

	return tVector(u, v, w, 0);
}

bool cMathUtil::ContainsAABB(const tVector& pt, const tVector& aabb_min, const tVector& aabb_max)
{
	bool contains = pt[0] >= aabb_min[0] && pt[1] >= aabb_min[1] && pt[2] >= aabb_min[2]
					&& pt[0] <= aabb_max[0] && pt[1] <= aabb_max[1] && pt[2] <= aabb_max[2];
	return contains;
}

bool cMathUtil::ContainsAABB(const tVector& aabb_min0, const tVector& aabb_max0, const tVector& aabb_min1, const tVector& aabb_max1)
{
	return ContainsAABB(aabb_min0, aabb_min1, aabb_max1) && ContainsAABB(aabb_max0, aabb_min1, aabb_max1);
}

bool cMathUtil::ContainsAABBXZ(const tVector& pt, const tVector& aabb_min, const tVector& aabb_max)
{
	bool contains = pt[0] >= aabb_min[0] && pt[2] >= aabb_min[2]
		&& pt[0] <= aabb_max[0] && pt[2] <= aabb_max[2];
	return contains;
}

bool cMathUtil::ContainsAABBXZ(const tVector& aabb_min0, const tVector& aabb_max0, const tVector& aabb_min1, const tVector& aabb_max1)
{
	return ContainsAABBXZ(aabb_min0, aabb_min1, aabb_max1) && ContainsAABBXZ(aabb_max0, aabb_min1, aabb_max1);
}

void cMathUtil::CalcAABBIntersection(const tVector& aabb_min0, const tVector& aabb_max0, const tVector& aabb_min1, const tVector& aabb_max1,
									tVector& out_min, tVector& out_max)
{
	out_min = aabb_min0.cwiseMax(aabb_min1);
	out_max = aabb_max0.cwiseMin(aabb_max1);
	if (out_min[0] > out_max[0])
	{
		out_min[0] = 0;
		out_max[0] = 0;
	}
	if (out_min[1] > out_max[1])
	{
		out_min[1] = 0;
		out_max[1] = 0;
	}
	if (out_min[2] > out_max[2])
	{
		out_min[2] = 0;
		out_max[2] = 0;
	}
}

void cMathUtil::CalcAABBUnion(const tVector& aabb_min0, const tVector& aabb_max0, const tVector& aabb_min1, const tVector& aabb_max1,
								tVector& out_min, tVector& out_max)
{
	out_min = aabb_min0.cwiseMin(aabb_min1);
	out_max = aabb_max0.cwiseMax(aabb_max1);
}

bool cMathUtil::IntersectAABB(const tVector& aabb_min0, const tVector& aabb_max0, const tVector& aabb_min1, const tVector& aabb_max1)
{
	tVector center0 = 0.5 * (aabb_max0 + aabb_min0);
	tVector center1 = 0.5 * (aabb_max1 + aabb_min1);
	tVector size0 = aabb_max0 - aabb_min0;
	tVector size1 = aabb_max1 - aabb_min1;
	tVector test_len = 0.5 * (size0 + size1);
	tVector delta = center1 - center0;
	bool overlap = (std::abs(delta[0]) <= test_len[0]) && (std::abs(delta[1]) <= test_len[1]) && (std::abs(delta[2]) <= test_len[2]);
	return overlap;
}

bool cMathUtil::IntersectAABBXZ(const tVector& aabb_min0, const tVector& aabb_max0, const tVector& aabb_min1, const tVector& aabb_max1)
{
	tVector center0 = 0.5 * (aabb_max0 + aabb_min0);
	tVector center1 = 0.5 * (aabb_max1 + aabb_min1);
	tVector size0 = aabb_max0 - aabb_min0;
	tVector size1 = aabb_max1 - aabb_min1;
	tVector test_len = 0.5 * (size0 + size1);
	tVector delta = center1 - center0;
	bool overlap = (std::abs(delta[0]) <= test_len[0]) && (std::abs(delta[2]) <= test_len[2]);
	return overlap;
}

bool cMathUtil::CheckNextInterval(double delta, double curr_val, double int_size)
{
	double pad = 0.001 * delta;
	int curr_count = static_cast<int>(std::floor((curr_val + pad) / int_size));
	int prev_count = static_cast<int>(std::floor((curr_val + pad - delta) / int_size));
	bool new_action = (curr_count != prev_count);
	return new_action;
}



void cMathUtil::QuatSwingTwistDecomposition(const tQuaternion& q, const tVector& dir, tQuaternion& out_swing, tQuaternion& out_twist)
{
	assert(std::abs(dir.norm() - 1) < 0.000001);
	assert(std::abs(q.norm() - 1) < 0.000001);

	tVector q_axis = tVector(q.x(), q.y(), q.z(), 0);
	double p = q_axis.dot(dir);
	tVector twist_axis = p * dir;
	out_twist = tQuaternion(q.w(), twist_axis[0], twist_axis[1], twist_axis[2]);
	out_twist.normalize();
	out_swing = q * out_twist.conjugate();
}

tQuaternion cMathUtil::ProjectQuat(const tQuaternion& q, const tVector& dir)
{
	assert(std::abs(dir.norm() - 1) < 0.00001);
	tVector ref_axis = tVector::Zero();
	int min_idx = 0;
	dir.cwiseAbs().minCoeff(&min_idx);
	ref_axis[min_idx] = 1;

	tVector rot_dir0 = dir.cross3(ref_axis);
	tVector rot_dir1 = cMathUtil::QuatRotVec(q, rot_dir0);
	rot_dir1 -= rot_dir1.dot(dir) * dir;

	double dir1_norm = rot_dir1.norm();
	tQuaternion p_rot = tQuaternion::Identity();
	if (dir1_norm > 0.0001)
	{
		rot_dir1 /= dir1_norm;
		p_rot = cMathUtil::VecDiffQuat(rot_dir0, rot_dir1);
	}
	return p_rot;
}

void cMathUtil::ButterworthFilter(double dt, double cutoff, Eigen::VectorXd& out_x)
{
	double sampling_rate = 1 / dt;
	int n = static_cast<int>(out_x.size());

	double wc = std::tan(cutoff * M_PI / sampling_rate);
	double k1 = std::sqrt(2) * wc;
	double k2 = wc * wc;
	double a = k2 / (1 + k1 + k2);
	double b = 2 * a;
	double c = a;
	double k3 = b / k2;
	double d = -2 * a + k3;
	double e = 1 - (2 * a) - k3;

	double xm2 = out_x[0];
	double xm1 = out_x[0];
	double ym2 = out_x[0];
	double ym1 = out_x[0];

	for (int s = 0; s < n; ++s) 
	{
		double x = out_x[s];
		double y = a * x + b * xm1 + c * xm2 + d * ym1 + e * ym2;

		out_x[s] = y;
		xm2 = xm1;
		xm1 = x;
		ym2 = ym1;
		ym1 = y;
	}

	double yp2 = out_x[n - 1];
	double yp1 = out_x[n - 1];
	double zp2 = out_x[n - 1];
	double zp1 = out_x[n - 1];

	for (int t = n - 1; t >= 0; --t) 
	{
		double y = out_x[t];
		double z = a * y + b * yp1 + c * yp2 + d * zp1 + e * zp2;

		out_x[t] = z;
		yp2 = yp1;
		yp1 = y;
		zp2 = zp1;
		zp1 = z;
	}
}