gtsam/gtsam/geometry/Unit3.cpp

307 lines
9.7 KiB
C++

/* ----------------------------------------------------------------------------
* Atlanta, Georgia 30332-0415
* All Rights Reserved
* GTSAM Copyright 2010, Georgia Tech Research Corporation,
* Authors: Frank Dellaert, et al. (see THANKS for the full author list)
* See LICENSE for the license information
* -------------------------------------------------------------------------- */
/*
* @file Unit3.h
* @date Feb 02, 2011
* @author Can Erdogan
* @author Frank Dellaert
* @author Alex Trevor
* @author Zhaoyang Lv
* @brief The Unit3 class - basically a point on a unit sphere
*/
#include <gtsam/geometry/Unit3.h>
#include <gtsam/geometry/Point2.h>
#include <gtsam/config.h> // for GTSAM_USE_TBB
#include <iostream>
#include <limits>
#include <cmath>
#include <vector>
using namespace std;
namespace gtsam {
/* ************************************************************************* */
Unit3::Unit3(const Vector3& p) : p_(p.normalized()) {}
Unit3::Unit3(double x, double y, double z) : p_(x, y, z) { p_.normalize(); }
Unit3::Unit3(const Point2& p, double f) : p_(p.x(), p.y(), f) {
p_.normalize();
}
/* ************************************************************************* */
Unit3 Unit3::FromPoint3(const Point3& point, OptionalJacobian<2, 3> H) {
// 3*3 Derivative of representation with respect to point is 3*3:
Matrix3 D_p_point;
Unit3 direction;
direction.p_ = normalize(point, H ? &D_p_point : 0);
if (H)
*H << direction.basis().transpose() * D_p_point;
return direction;
}
/* ************************************************************************* */
Unit3 Unit3::Random(std::mt19937& rng) {
// http://mathworld.wolfram.com/SpherePointPicking.html
// Adapted from implementation in boost, but using std <random>
std::uniform_real_distribution<double> uniform(-1.0, 1.0);
double sqsum;
double x, y;
do {
x = uniform(rng);
y = uniform(rng);
sqsum = x * x + y * y;
} while (sqsum > 1);
const double mult = 2 * sqrt(1 - sqsum);
return Unit3(x * mult, y * mult, 2 * sqsum - 1);
}
/* ************************************************************************* */
// Get the axis of rotation with the minimum projected length of the point
static Point3 CalculateBestAxis(const Point3& n) {
double mx = std::abs(n.x()), my = std::abs(n.y()), mz = std::abs(n.z());
if ((mx <= my) && (mx <= mz)) {
return Point3(1.0, 0.0, 0.0);
} else if ((my <= mx) && (my <= mz)) {
return Point3(0.0, 1.0, 0.0);
} else {
return Point3(0, 0, 1);
}
}
/* ************************************************************************* */
const Matrix32& Unit3::basis(OptionalJacobian<6, 2> H) const {
#ifdef GTSAM_USE_TBB
// NOTE(hayk): At some point it seemed like this reproducably resulted in
// deadlock. However, I don't know why and I can no longer reproduce it.
// It either was a red herring or there is still a latent bug left to debug.
std::unique_lock<std::mutex> lock(B_mutex_);
#endif
const bool cachedBasis = static_cast<bool>(B_);
const bool cachedJacobian = static_cast<bool>(H_B_);
if (H) {
if (!cachedJacobian) {
// Compute Jacobian. Recomputes B_
Matrix32 B;
Matrix62 jacobian;
Matrix33 H_B1_n, H_b1_B1, H_b2_n, H_b2_b1;
// Choose the direction of the first basis vector b1 in the tangent plane
// by crossing n with the chosen axis.
const Point3 n(p_), axis = CalculateBestAxis(n);
const Point3 B1 = gtsam::cross(n, axis, &H_B1_n);
// Normalize result to get a unit vector: b1 = B1 / |B1|.
B.col(0) = normalize(B1, &H_b1_B1);
// Get the second basis vector b2, which is orthogonal to n and b1.
B.col(1) = gtsam::cross(n, B.col(0), &H_b2_n, &H_b2_b1);
// Chain rule tomfoolery to compute the jacobian.
const Matrix32& H_n_p = B;
jacobian.block<3, 2>(0, 0) = H_b1_B1 * H_B1_n * H_n_p;
auto H_b1_p = jacobian.block<3, 2>(0, 0);
jacobian.block<3, 2>(3, 0) = H_b2_n * H_n_p + H_b2_b1 * H_b1_p;
// Cache the result and jacobian
H_B_ = (jacobian);
B_ = (B);
}
// Return cached jacobian, possibly computed just above
*H = *H_B_;
}
if (!cachedBasis) {
// Same calculation as above, without derivatives.
// Done after H block, as that possibly computes B_ for the first time
Matrix32 B;
const Point3 n(p_), axis = CalculateBestAxis(n);
const Point3 B1 = gtsam::cross(n, axis);
B.col(0) = normalize(B1);
B.col(1) = gtsam::cross(n, B.col(0));
B_ = (B);
}
return *B_;
}
/* ************************************************************************* */
Point3 Unit3::point3(OptionalJacobian<3, 2> H) const {
if (H)
*H = basis();
return Point3(p_);
}
/* ************************************************************************* */
Vector3 Unit3::unitVector(OptionalJacobian<3, 2> H) const {
if (H)
*H = basis();
return p_;
}
/* ************************************************************************* */
std::ostream& operator<<(std::ostream& os, const Unit3& pair) {
os << pair.p_ << endl;
return os;
}
/* ************************************************************************* */
void Unit3::print(const std::string& s) const {
cout << s << ":" << p_ << endl;
}
/* ************************************************************************* */
Matrix3 Unit3::skew() const {
return skewSymmetric(p_.x(), p_.y(), p_.z());
}
/* ************************************************************************* */
double Unit3::dot(const Unit3& q, OptionalJacobian<1, 2> H_p,
OptionalJacobian<1, 2> H_q) const {
// Get the unit vectors of each, and the derivative.
Matrix32 H_pn_p;
Point3 pn = point3(H_p ? &H_pn_p : nullptr);
Matrix32 H_qn_q;
const Point3 qn = q.point3(H_q ? &H_qn_q : nullptr);
// Compute the dot product of the Point3s.
Matrix13 H_dot_pn, H_dot_qn;
double d = gtsam::dot(pn, qn, H_p ? &H_dot_pn : nullptr, H_q ? &H_dot_qn : nullptr);
if (H_p) {
(*H_p) << H_dot_pn * H_pn_p;
}
if (H_q) {
(*H_q) = H_dot_qn * H_qn_q;
}
return d;
}
/* ************************************************************************* */
Vector2 Unit3::error(const Unit3& q, OptionalJacobian<2, 2> H_q) const {
// 2D error is equal to B'*q, as B is 3x2 matrix and q is 3x1
const Vector2 xi = basis().transpose() * q.p_;
if (H_q) {
*H_q = basis().transpose() * q.basis();
}
return xi;
}
/* ************************************************************************* */
Vector2 Unit3::errorVector(const Unit3& q, OptionalJacobian<2, 2> H_p,
OptionalJacobian<2, 2> H_q) const {
// Get the point3 of this, and the derivative.
Matrix32 H_qn_q;
const Point3 qn = q.point3(H_q ? &H_qn_q : nullptr);
// 2D error here is projecting q into the tangent plane of this (p).
Matrix62 H_B_p;
Matrix23 Bt = basis(H_p ? &H_B_p : nullptr).transpose();
Vector2 xi = Bt * qn;
if (H_p) {
// Derivatives of each basis vector.
const Matrix32& H_b1_p = H_B_p.block<3, 2>(0, 0);
const Matrix32& H_b2_p = H_B_p.block<3, 2>(3, 0);
// Derivatives of the two entries of xi wrt the basis vectors.
const Matrix13 H_xi1_b1 = qn.transpose();
const Matrix13 H_xi2_b2 = qn.transpose();
// Assemble dxi/dp = dxi/dB * dB/dp.
const Matrix12 H_xi1_p = H_xi1_b1 * H_b1_p;
const Matrix12 H_xi2_p = H_xi2_b2 * H_b2_p;
*H_p << H_xi1_p, H_xi2_p;
}
if (H_q) {
// dxi/dq is given by dxi/dqu * dqu/dq, where qu is the unit vector of q.
const Matrix23 H_xi_qu = Bt;
*H_q = H_xi_qu * H_qn_q;
}
return xi;
}
/* ************************************************************************* */
double Unit3::distance(const Unit3& q, OptionalJacobian<1, 2> H) const {
Matrix2 H_xi_q;
const Vector2 xi = error(q, H ? &H_xi_q : nullptr);
const double theta = xi.norm();
if (H)
*H = (xi.transpose() / theta) * H_xi_q;
return theta;
}
/* ************************************************************************* */
Unit3 Unit3::retract(const Vector2& v, OptionalJacobian<2,2> H) const {
// Compute the 3D xi_hat vector
const Vector3 xi_hat = basis() * v;
const double theta = xi_hat.norm();
const double c = std::cos(theta);
// Treat case of very small v differently.
Matrix23 H_from_point;
if (theta < std::numeric_limits<double>::epsilon()) {
const Unit3 exp_p_xi_hat = Unit3::FromPoint3(c * p_ + xi_hat,
H? &H_from_point : nullptr);
if (H) { // Jacobian
*H = H_from_point *
(-p_ * xi_hat.transpose() + Matrix33::Identity()) * basis();
}
return exp_p_xi_hat;
}
const double st = std::sin(theta) / theta;
const Unit3 exp_p_xi_hat = Unit3::FromPoint3(c * p_ + xi_hat * st,
H? &H_from_point : nullptr);
if (H) { // Jacobian
*H = H_from_point *
(p_ * -st * xi_hat.transpose() + st * Matrix33::Identity() +
xi_hat * ((c - st) / std::pow(theta, 2)) * xi_hat.transpose()) * basis();
}
return exp_p_xi_hat;
}
/* ************************************************************************* */
Vector2 Unit3::localCoordinates(const Unit3& other) const {
const double x = p_.dot(other.p_);
// Crucial quantity here is y = theta/sin(theta) with theta=acos(x)
// Now, y = acos(x) / sin(acos(x)) = acos(x)/sqrt(1-x^2)
// We treat the special case 1 and -1 below
const double x2 = x * x;
const double z = 1 - x2;
double y;
if (z < std::numeric_limits<double>::epsilon()) {
if (x > 0) // first order expansion at x=1
y = 1.0 - (x - 1.0) / 3.0;
else // cop out
return Vector2(M_PI, 0.0);
} else {
// no special case
y = acos(x) / sqrt(z);
}
return basis().transpose() * y * (other.p_ - x * p_);
}
/* ************************************************************************* */
} // namespace gtsam