// Copyright (c) 2007, 2008 libmv authors.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.

#ifndef LIBMV_MULTIVIEW_PROJECTION_H_
#define LIBMV_MULTIVIEW_PROJECTION_H_

#include "libmv/numeric/numeric.h"

namespace libmv {

void P_From_KRt(const Mat3& K, const Mat3& R, const Vec3& t, Mat34* P);
void KRt_From_P(const Mat34& P, Mat3* K, Mat3* R, Vec3* t);

// Applies a change of basis to the image coordinates of the projection matrix
// so that the principal point becomes principal_point_new.
void ProjectionShiftPrincipalPoint(const Mat34& P,
                                   const Vec2& principal_point,
                                   const Vec2& principal_point_new,
                                   Mat34* P_new);

// Applies a change of basis to the image coordinates of the projection matrix
// so that the aspect ratio becomes aspect_ratio_new.  This is done by
// stretching the y axis.  The aspect ratio is defined as the quotient between
// the focal length of the y and the x axis.
void ProjectionChangeAspectRatio(const Mat34& P,
                                 const Vec2& principal_point,
                                 double aspect_ratio,
                                 double aspect_ratio_new,
                                 Mat34* P_new);

void HomogeneousToEuclidean(const Mat& H, Mat* X);
void HomogeneousToEuclidean(const Mat3X& h, Mat2X* e);
void HomogeneousToEuclidean(const Mat4X& h, Mat3X* e);
void HomogeneousToEuclidean(const Vec3& H, Vec2* X);
void HomogeneousToEuclidean(const Vec4& H, Vec3* X);
inline Vec2 HomogeneousToEuclidean(const Vec3& h) {
  return h.head<2>() / h(2);
}
inline Vec3 HomogeneousToEuclidean(const Vec4& h) {
  return h.head<3>() / h(3);
}
inline Mat2X HomogeneousToEuclidean(const Mat3X& h) {
  Mat2X e(2, h.cols());
  e.row(0) = h.row(0).array() / h.row(2).array();
  e.row(1) = h.row(1).array() / h.row(2).array();
  return e;
}

void EuclideanToHomogeneous(const Mat& X, Mat* H);
inline Mat3X EuclideanToHomogeneous(const Mat2X& x) {
  Mat3X h(3, x.cols());
  h.block(0, 0, 2, x.cols()) = x;
  h.row(2).setOnes();
  return h;
}
inline void EuclideanToHomogeneous(const Mat2X& x, Mat3X* h) {
  h->resize(3, x.cols());
  h->block(0, 0, 2, x.cols()) = x;
  h->row(2).setOnes();
}
inline Mat4X EuclideanToHomogeneous(const Mat3X& x) {
  Mat4X h(4, x.cols());
  h.block(0, 0, 3, x.cols()) = x;
  h.row(3).setOnes();
  return h;
}
inline void EuclideanToHomogeneous(const Mat3X& x, Mat4X* h) {
  h->resize(4, x.cols());
  h->block(0, 0, 3, x.cols()) = x;
  h->row(3).setOnes();
}
void EuclideanToHomogeneous(const Vec2& X, Vec3* H);
void EuclideanToHomogeneous(const Vec3& X, Vec4* H);
inline Vec3 EuclideanToHomogeneous(const Vec2& x) {
  return Vec3(x(0), x(1), 1);
}
inline Vec4 EuclideanToHomogeneous(const Vec3& x) {
  return Vec4(x(0), x(1), x(2), 1);
}
// Conversion from image coordinates to normalized camera coordinates
void EuclideanToNormalizedCamera(const Mat2X& x, const Mat3& K, Mat2X* n);
void HomogeneousToNormalizedCamera(const Mat3X& x, const Mat3& K, Mat2X* n);

inline Vec2 Project(const Mat34& P, const Vec3& X) {
  Vec4 HX;
  HX << X, 1.0;
  Vec3 hx = P * HX;
  return hx.head<2>() / hx(2);
}

inline void Project(const Mat34& P, const Vec4& X, Vec3* x) {
  *x = P * X;
}

inline void Project(const Mat34& P, const Vec4& X, Vec2* x) {
  Vec3 hx = P * X;
  *x = hx.head<2>() / hx(2);
}

inline void Project(const Mat34& P, const Vec3& X, Vec3* x) {
  Vec4 HX;
  HX << X, 1.0;
  Project(P, HX, x);
}

inline void Project(const Mat34& P, const Vec3& X, Vec2* x) {
  Vec3 hx;
  Project(P, X, &hx);
  *x = hx.head<2>() / hx(2);
}

inline void Project(const Mat34& P, const Mat4X& X, Mat2X* x) {
  x->resize(2, X.cols());
  for (int c = 0; c < X.cols(); ++c) {
    Vec3 hx = P * X.col(c);
    x->col(c) = hx.head<2>() / hx(2);
  }
}

inline Mat2X Project(const Mat34& P, const Mat4X& X) {
  Mat2X x;
  Project(P, X, &x);
  return x;
}

inline void Project(const Mat34& P, const Mat3X& X, Mat2X* x) {
  x->resize(2, X.cols());
  for (int c = 0; c < X.cols(); ++c) {
    Vec4 HX;
    HX << X.col(c), 1.0;
    Vec3 hx = P * HX;
    x->col(c) = hx.head<2>() / hx(2);
  }
}

inline void Project(const Mat34& P, const Mat3X& X, const Vecu& ids, Mat2X* x) {
  x->resize(2, ids.size());
  Vec4 HX;
  Vec3 hx;
  for (int c = 0; c < ids.size(); ++c) {
    HX << X.col(ids[c]), 1.0;
    hx = P * HX;
    x->col(c) = hx.head<2>() / hx(2);
  }
}

inline Mat2X Project(const Mat34& P, const Mat3X& X) {
  Mat2X x(2, X.cols());
  Project(P, X, &x);
  return x;
}

inline Mat2X Project(const Mat34& P, const Mat3X& X, const Vecu& ids) {
  Mat2X x(2, ids.size());
  Project(P, X, ids, &x);
  return x;
}

double Depth(const Mat3& R, const Vec3& t, const Vec3& X);
double Depth(const Mat3& R, const Vec3& t, const Vec4& X);

/**
 * Returns true if the homogenious 3D point X is in front of
 * the camera P.
 */
inline bool isInFrontOfCamera(const Mat34& P, const Vec4& X) {
  double condition_1 = P.row(2).dot(X) * X[3];
  double condition_2 = X[2] * X[3];
  if (condition_1 > 0 && condition_2 > 0) {
    return true;
  } else {
    return false;
  }
}

inline bool isInFrontOfCamera(const Mat34& P, const Vec3& X) {
  Vec4 X_homo;
  X_homo.segment<3>(0) = X;
  X_homo(3) = 1;
  return isInFrontOfCamera(P, X_homo);
}

/**
 * Transforms a 2D point from pixel image coordinates to a 2D point in
 * normalized image coordinates.
 */
inline Vec2 ImageToNormImageCoordinates(Mat3& Kinverse, Vec2& x) {
  Vec3 x_h = Kinverse * EuclideanToHomogeneous(x);
  return HomogeneousToEuclidean(x_h);
}

/// Estimates the root mean square error (2D)
inline double RootMeanSquareError(const Mat2X& x_image,
                                  const Mat4X& X_world,
                                  const Mat34& P) {
  size_t num_points = x_image.cols();
  Mat2X dx = Project(P, X_world) - x_image;
  return dx.norm() / num_points;
}

/// Estimates the root mean square error (2D)
inline double RootMeanSquareError(const Mat2X& x_image,
                                  const Mat3X& X_world,
                                  const Mat3& K,
                                  const Mat3& R,
                                  const Vec3& t) {
  Mat34 P;
  P_From_KRt(K, R, t, &P);
  size_t num_points = x_image.cols();
  Mat2X dx = Project(P, X_world) - x_image;
  return dx.norm() / num_points;
}
}  // namespace libmv

#endif  // LIBMV_MULTIVIEW_PROJECTION_H_
