/*
* Camera.cpp
*
* Copyright (c) 2014-2015 SEACAVE
*
* Author(s):
*
*      cDc <cdc.seacave@gmail.com>
*
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.
*
*
* Additional Terms:
*
*      You are required to preserve legal notices and author attributions in
*      that material or in the Appropriate Legal Notices displayed by works
*      containing it.
*/

#include "Common.h"
#include "Camera.h"

using namespace MVS;


// D E F I N E S ///////////////////////////////////////////////////


// S T R U C T S ///////////////////////////////////////////////////

const Camera Camera::IDENTITY(Matrix3x3::eye(), Point3(REAL(0), REAL(0), REAL(0)));

Camera::Camera(const Matrix3x4& _P, bool bUpdate/*=true*/)
	:
	P(_P)
{
	if (bUpdate)
		DecomposeP();
} // Camera
Camera::Camera(const Matrix3x3& _R, const Point3& _C, bool bUpdate/*=true*/)
	:
	CameraIntern(_R, _C)
{
	if (bUpdate)
		ComposeP_RC();
} // Camera
Camera::Camera(const Matrix3x3& _K, const Matrix3x3& _R, const Point3& _C, bool bUpdate/*=true*/)
	:
	CameraIntern(_K, _R, _C)
{
	if (bUpdate)
		ComposeP();
} // Camera
/*----------------------------------------------------------------*/


Camera& Camera::operator= (const CameraIntern& camera)
{
	K = camera.K;
	R = camera.R;
	C = camera.C;
	return *this;
}
/*----------------------------------------------------------------*/


void Camera::ComposeP_RC()
{
	AssembleProjectionMatrix(R, C, P);
} // ComposeP_RC
/*----------------------------------------------------------------*/

void Camera::ComposeP()
{
	AssembleProjectionMatrix(K, R, C, P);
} // ComposeP
/*----------------------------------------------------------------*/

void Camera::DecomposeP_RC()
{
	DecomposeProjectionMatrix(P, R, C);
} // DecomposeP_RC
/*----------------------------------------------------------------*/

void Camera::DecomposeP()
{
	DecomposeProjectionMatrix(P, K, R, C);
} // DecomposeP
/*----------------------------------------------------------------*/

void Camera::Transform(const Matrix3x3& _R, const Point3& _t, const REAL& _s)
{
	R = R * _R.t();
	C = _R*C*_s+_t;
	ComposeP();
} // Transform
/*----------------------------------------------------------------*/


REAL Camera::PointDepth(const Point3& X) const
{
	return P(2,0)*X.x + P(2,1)*X.y + P(2,2)*X.z + P(2,3);
} // PointDepth
/*----------------------------------------------------------------*/

bool Camera::IsInFront(const Point3& X) const
{
	return (PointDepth(X)>0);
} // IsInFront
/*----------------------------------------------------------------*/

REAL Camera::DistanceSq(const Point3& X) const
{
	const Point3 ray = X-C;
	return (SQUARE(ray.x)+SQUARE(ray.y)+SQUARE(ray.z));
} // Distance
/*----------------------------------------------------------------*/


// decomposition of projection matrix into KR[I|-C]: internal calibration ([3,3]), rotation ([3,3]) and translation ([3,1])
// (comparable with OpenCV: normalized cv::decomposeProjectionMatrix)
void MVS::DecomposeProjectionMatrix(const PMatrix& P, KMatrix& K, RMatrix& R, CMatrix& C)
{
	// extract camera center as the right null vector of P
	const Vec4 hC(P.RightNullVector());
	C = CMatrix(hC[0],hC[1],hC[2]) * INVERT(hC[3]);
	// perform RQ decomposition
	RQDecomp3x3<REAL>(cv::Mat(3,4,cv::DataType<REAL>::type,const_cast<REAL*>(P.val))(cv::Rect(0,0, 3,3)), K, R);
	// normalize calibration matrix
	K *= INVERT(K(2,2));
	// ensure positive focal length
	if (K(0,0) < 0) {
		ASSERT(K(1,1) < 0);
		NEGATE(K(0,0));
		NEGATE(K(1,1));
		NEGATE(K(0,1));
		NEGATE(K(0,2));
		NEGATE(K(1,2));
		(TMatrix<REAL,2,3>&)R *= REAL(-1);
	}
	ASSERT(R.IsValid());
} // DecomposeProjectionMatrix
void MVS::DecomposeProjectionMatrix(const PMatrix& P, RMatrix& R, CMatrix& C)
{
	#ifndef _RELEASE
	KMatrix K;
	DecomposeProjectionMatrix(P, K, R, C);
	ASSERT(K.IsEqual(Matrix3x3::IDENTITY, 1e-5));
	#endif
	// extract camera center as the right null vector of P
	const Vec4 hC(P.RightNullVector());
	C = CMatrix(hC[0],hC[1],hC[2]) * INVERT(hC[3]);
	// get rotation
	const cv::Mat mP(3,4,cv::DataType<REAL>::type,const_cast<REAL*>(P.val));
	mP(cv::Rect(0,0, 3,3)).copyTo(R);
	ASSERT(R.IsValid());
} // DecomposeProjectionMatrix
/*----------------------------------------------------------------*/

// assemble projection matrix: P=KR[I|-C]
void MVS::AssembleProjectionMatrix(const KMatrix& K, const RMatrix& R, const CMatrix& C, PMatrix& P)
{
	// compute temporary matrices
	cv::Mat mP(3,4,cv::DataType<REAL>::type,const_cast<REAL*>(P.val));
	cv::Mat M(mP, cv::Rect(0,0, 3,3));
	cv::Mat(K * R).copyTo(M); //3x3
	mP.col(3) = M * cv::Mat(-C); //3x1
} // AssembleProjectionMatrix
void MVS::AssembleProjectionMatrix(const RMatrix& R, const CMatrix& C, PMatrix& P)
{
	Eigen::Map<Matrix3x3::EMat,0,Eigen::Stride<4,0> >(P.val) = (const Matrix3x3::EMat)R;
	Eigen::Map<Point3::EVec,0,Eigen::Stride<0,4> >(P.val+3) = ((const Matrix3x3::EMat)R) * (-((const Point3::EVec)C));
} // AssembleProjectionMatrix
/*----------------------------------------------------------------*/

// compute the focus of attention of a set of cameras; only cameras
// that have the focus of attention in front of them are considered
Point3 MVS::ComputeCamerasFocusPoint(const CameraArr& cameras, const Point3* pInitialFocus)
{
	// set initial focus point
	Point3 focus;
	if (pInitialFocus == NULL) {
		// initialize focus point to the average camera center
		focus = Point3::ZERO;
		for (const Camera& camera: cameras)
			focus += camera.C;
		focus /= cameras.size();
		// compute average distance to the cameras
		REAL avgDist = 0;
		for (const Camera& camera: cameras)
			avgDist += SQRT(camera.DistanceSq(focus));
		avgDist /= cameras.size();
		// move focus point to the average distance in front of first camera
		focus = cameras.front().C + cameras.front().Direction()*avgDist;
	} else
		focus = *pInitialFocus;
	// compute focus point as the point in front of the active cameras,
	// closest to the view direction of each camera
	uint32_t numActive = 0;
	Point3::EVec smp(Point3::EVec::Zero());
	Matrix3x3::EMat smTm(Matrix3x3::EMat::Zero());
	for (const Camera& camera: cameras) {
		if (!camera.IsInFront(focus))
			continue;
		// https://en.wikipedia.org/wiki/Line–line_intersection#In_more_than_two_dimensions
		const Point3::EVec dir = camera.Direction();
		const Matrix3x3::EMat m(Matrix3x3::EMat::Identity() - dir * dir.transpose());
		const Matrix3x3::EMat mTm(m.transpose() * m);
		const Point3::EVec s = mTm * Point3::EVec(camera.C);
		smp += s;
		smTm += mTm;
		++numActive;
	}
	if (numActive >= 2)
		focus = smTm.inverse() * smp;
	return focus;
} // ComputeCamerasFocusPoint
/*----------------------------------------------------------------*/



namespace MVS {

namespace RECTIFY {
	
// compute the ROIs for the two images based on the corresponding points
void GetImagePairROI(const Point3fArr& points1, const Point3fArr& points2, const Matrix3x3& K1, const Matrix3x3& K2, const Matrix3x3& R1, const Matrix3x3& R2, const Matrix3x3& invK1, const Matrix3x3& invK2, AABB2f& roi1h, AABB2f& roi2h)
{
	ASSERT(!points1.empty() && points1.size() && points2.size());

	// compute rectification homography (from original to rectified image)
	const Matrix3x3 H1(K1 * R1 * invK1);
	const Matrix3x3 H2(K2 * R2 * invK2);

	// determine the ROIs in rectified images
	roi1h.Reset(); roi2h.Reset();
	FOREACH(i, points1) {
		Point2f xh;
		const Point3f& x1 = points1[i];
		ProjectVertex_3x3_2_2(H1.val, x1.ptr(), xh.ptr());
		roi1h.InsertFull(xh);
		const Point3f& x2 = points2[i];
		ProjectVertex_3x3_2_2(H2.val, x2.ptr(), xh.ptr());
		roi2h.InsertFull(xh);
	}
}

void SetCameraMatricesROI(const AABB2f& roi1h, const AABB2f& roi2h, cv::Size& size1, cv::Size& size2, Matrix3x3& K1, Matrix3x3& K2)
{
	// set the new image sizes such that they are equal and contain the entire ROI
	const Point2f size1h(roi1h.GetSize());
	const Point2f size2h(roi2h.GetSize());
	const int maxSize(MAXF(size1.width+size2.width, size1.height+size2.height)/2);
	size1.width = size2.width = MINF(ROUND2INT(MAXF(size1h.x,size2h.x)), maxSize);
	size1.height = size2.height = MINF(ROUND2INT(MAXF(size1h.y,size2h.y)), maxSize);

	// set the new camera matrices such that the ROI is centered
	const Point2f center1h(roi1h.GetCenter());
	const Point2f center2h(roi2h.GetCenter());
	K1(0,2) += size1.width /2-center1h[0];
	K1(1,2) += size1.height/2-center1h[1];
	K2(0,2) += size2.width /2-center2h[0];
	K2(1,2) += size2.height/2-center2h[1];
}

} // namespace RECTIFY

} // namespace MVS

// Compute stereo rectification homographies that transform two images,
// such that corresponding pixels in one image lie on the same scan-line in the other image;
// note: this function assumes that the two cameras are already undistorted
REAL Camera::StereoRectify(const cv::Size& size1, const Camera& camera1, const cv::Size& size2, const Camera& camera2, Matrix3x3& R1, Matrix3x3& R2, Matrix3x3& K1, Matrix3x3& K2)
{
	// compute relative pose
	RMatrix poseR;
	CMatrix poseC;
	ComputeRelativePose(camera1.R, camera1.C, camera2.R, camera2.C, poseR, poseC);

	// compute the average rotation between the first and the second camera
	const Point3 r(poseR.GetRotationAxisAngle());
	reinterpret_cast<RMatrix&>(R2).SetFromAxisAngle(r*REAL(-0.5));
	R1 = R2.t();

	// compute the translation, such that it coincides with the X-axis
	int idx(0);
	Point3 t(R2 * (poseR*(-poseC)));
	#if 0
	const Point3 unitX(t.x<0?-1:1, 0, 0);
	const Point3 axis(t.cross(unitX));
	const REAL normAxis(norm(axis));
	if (normAxis > EPSILONTOLERANCE<REAL>()) {
		ASSERT(t.dot(unitX) >= 0);
		const REAL angle(ACOS(ComputeAngle(t.ptr(), unitX.ptr())));
		const RMatrix Rx(axis*angle/normAxis);
	#else
	const REAL nt(norm(t));
	//idx = (ABS(t.x) > ABS(t.y) ? 0 : 1);
	Point3 axis(Point3(0,0,1).cross(t));
	const REAL na(norm(axis));
	if (na < EPSILONTOLERANCE<REAL>())
		return 0;
	{
		const Point3 w(normalized(t.cross(axis)));
		RMatrix Rx;
		for (int i = 0; i < 3; ++i) {
			Rx(idx,i) = -t[i]/nt;
			Rx(idx^1,i) = -axis[i]/na;
			Rx(2,i) = w[i]*(1-2*idx); // if idx == 1 -> opposite direction
		}
	#endif
		R1 = Rx * R1;
		R2 = Rx * R2;
		t = R2 * (poseR*(-poseC));
	}
	ASSERT(ISEQUAL(-t.x, norm(camera2.C-camera1.C)) && ISZERO(t.y) && ISZERO(t.z));

	// determine the intrinsic calibration matrix;
	// vertical focal length must be the same for both images to keep the epipolar constraint
	K1 = Matrix3x3::IDENTITY;
	K1(0,0) = (camera1.K(0,0)+camera2.K(0,0))/2;
	K1(1,1) = (camera1.K(1,1)+camera2.K(1,1))/2;
	K2 = K1;
	// set ROI to contain the whole transformed image
	for (int k = 0; k < 2; k++) {
		const Matrix3x3 H(k == 0 ? K1*R1*camera1.GetInvK() : K2*R2*camera2.GetInvK());
		Point2f ptMin(std::numeric_limits<float>::max());
		for (int i = 0; i < 4; i++) {
			const Point2f pt(
				(float)((i%2)*size1.width),
				(float)((i<2?0:1)*size1.height)
			);
			Point2f ptH;
			ProjectVertex_3x3_2_2(H.val, pt.ptr(), ptH.ptr());
			ptMin.x = MINF(ptMin.x,ptH.x);
			ptMin.y = MINF(ptMin.y,ptH.y);
		}
		Matrix3x3& KNew(k == 0 ? K1 : K2);
		KNew(0,2) = -ptMin.x;
		KNew(1,2) = -ptMin.y;
	}
	K1(idx^1,2) = K2(idx^1,2) = MAXF(K1(idx^1,2), K2(idx^1,2));
	return t.x;
} // StereoRectify

// see: "A compact algorithm for rectification of stereo pairs", A. Fusiello, E. Trucco, and A. Verri, 2000
REAL Camera::StereoRectifyFusiello(const cv::Size& size1, const Camera& camera1, const cv::Size& size2, const Camera& camera2, Matrix3x3& R1, Matrix3x3& R2, Matrix3x3& K1, Matrix3x3& K2)
{
	// compute relative pose
	RMatrix poseR;
	CMatrix poseC;
	ComputeRelativePose(camera1.R, camera1.C, camera2.R, camera2.C, poseR, poseC);

	// new x axis (baseline, from C1 to C2)
	const Point3 v1(camera2.C-camera1.C);
	// new y axes (orthogonal to old z and new x)
	const Point3 v2(camera1.Direction().cross(v1));
	// new z axes (no choice, orthogonal to baseline and y)
	const Point3 v3(v1.cross(v2));

	// new extrinsic (translation unchanged)
	RMatrix R;
	R.SetFromRowVectors(normalized(v1), normalized(v2), normalized(v3));

	// new intrinsic (arbitrary)
	K1 = camera1.K; K1(0,1) = 0;
	K2 = camera2.K; K2(0,1) = 0;
	K1(1,1) = K2(1,1) = (camera1.K(1,1)+camera2.K(1,1))/2;

	// new rotations
	R1 = R*camera1.R.t();
	R2 = R*camera2.R.t();

	#if 0
	// new projection matrices
	PMatrix P1, P2;
	AssembleProjectionMatrix(K1, R, camera1.C, P1);
	AssembleProjectionMatrix(K2, R, camera2.C, P2);

	// rectifying image transformation
	#if 0
	const Matrix3x3 H1((PMatrix::EMat(P1).leftCols<3>()*(PMatrix::EMat(camera1.P).leftCols<3>().inverse())).eval());
	const Matrix3x3 H2((PMatrix::EMat(P2).leftCols<3>()*(PMatrix::EMat(camera2.P).leftCols<3>().inverse())).eval());
	#else
	const Matrix3x3 H1(K1*R*camera1.R.t()*camera1.GetInvK());
	const Matrix3x3 H2(K2*R*camera2.R.t()*camera2.GetInvK());
	#endif
	#endif

	const Point3 t(R2 * (poseR*(-poseC)));
	ASSERT(ISEQUAL(-t.x, norm(v1)) && ISZERO(t.y) && ISZERO(t.z));
	return t.x;
} // StereoRectifyFusiello

// adjust rectified camera matrices such that the entire area common to both source images is contained in the rectified images;
// as long as the focal-length on x and the skewness are equal in both cameras
// the point-scale is also equal in both images;
// note however that the surface-scale can still be different (most likely is)
// as it depends on its (unknown) normal too;
//  - points1 and points2: contain the pairs of corresponding pairs of image projections and their depth
//  - size1 and size2: input the size of the source images, output the size of the rectified images (rectified image sizes are equal)
void Camera::SetStereoRectificationROI(const Point3fArr& points1, cv::Size& size1, const Camera& camera1, const Point3fArr& points2, cv::Size& size2, const Camera& camera2, const Matrix3x3& R1, const Matrix3x3& R2, Matrix3x3& K1, Matrix3x3& K2)
{
	ASSERT(!points1.empty() && points1.size() && points2.size());

	#if 1
	// ignore skewness
	K1(0,1) = K2(0,1) = 0;
	#else
	// set same skewness
	K1(0,1) = K2(0,1) = (K1(0,1)+K2(0,1))/2;
	#endif

	// set same focal-length on x too
	K1(0,0) = K2(0,0) = (K1(0,0)+K2(0,0))/2;
	ASSERT(ISEQUAL(K1(1,1), K2(1,1)));

	// determine the ROIs in rectified images
	AABB2f roi1h, roi2h;
	RECTIFY::GetImagePairROI(points1, points2, K1, K2, R1, R2, camera1.GetInvK(), camera2.GetInvK(), roi1h, roi2h);

	// set the new camera matrices such that the ROI is centered
	RECTIFY::SetCameraMatricesROI(roi1h, roi2h, size1, size2, K1, K2);
} // SetStereoRectificationROI
/*----------------------------------------------------------------*/
