Program Listing for File quaternion.hpp

Return to documentation for file (include/nanoeigenpy/geometry/quaternion.hpp)

#pragma once

#include "detail/rotation-base.hpp"
#include "nanoeigenpy/utils/helpers.hpp"
#include <nanobind/nanobind.h>
#include <nanobind/operators.h>

namespace nanoeigenpy {
namespace nb = nanobind;

template <typename Quaternion>
struct QuaternionVisitor : nb::def_visitor<QuaternionVisitor<Quaternion>> {
  using Class = Quaternion;
  using QuaternionBase = Eigen::QuaternionBase<Quaternion>;
  static_assert(std::is_base_of_v<QuaternionBase, Quaternion>);
  using Scalar = typename QuaternionBase::Scalar;
  using Vector3 = typename QuaternionBase::Vector3;
  using Vector4 = typename QuaternionBase::Coefficients;
  using Matrix3 = typename QuaternionBase::Matrix3;
  using Coefficients = typename QuaternionBase::Coefficients;
  using AngleAxisType = typename QuaternionBase::AngleAxisType;

 public:
  template <typename... Ts>
  void execute(nb::class_<Class, Ts...>& cl) {
    using namespace nb::literals;
    // Inits
    cl.def(nb::init<>(), "Default constructor")
        .def(nb::init<Scalar, Scalar, Scalar, Scalar>(), "x"_a, "y"_a, "z"_a,
             "w"_a,
             "Initialize from coefficients.\n\n"
             "... note:: The order of coefficients is *w*, *x*, *y*, *z*. "
             "The [] operator numbers them differently, 0...4 for *x* *y* *z* "
             "*w*!")
        .def(nb::init<const AngleAxisType&>(), "aa"_a,
             "Initialize from an angle axis.\n"
             "\taa: angle axis object.")
        .def(nb::init<const Eigen::Ref<const Matrix3>>(), "R"_a,
             "Initialize from rotation matrix.\n"
             "\tR : a rotation matrix 3x3.")
        .def(nb::init<const Quaternion&>(), "other"_a,
             "Copy constructor.\n"
             "\tquat: a quaternion.")
        .def(
            "__init__",
            [](Quaternion* self, const Eigen::Ref<const Vector4>& v) {
              new (self) Quaternion(v[3], v[0], v[1], v[2]);
            },
            "v"_a,
            "Initialize from a 4D vector (xyzw).\n"
            "\tv : a 4D vector representing quaternion coefficients in the "
            "order xyzw.")
        .def(
            "__init__",
            [](Quaternion* self, const Eigen::Ref<const Eigen::Vector3d>& u,
               const Eigen::Ref<const Eigen::Vector3d>& v) {
              new (self) Quaternion();
              self->setFromTwoVectors(u, v);
            },
            "u"_a, "v"_a, "Initialize from two vectors u and v.")

        .def_prop_rw("x", &QuaternionVisitor::getCoeff<0>,
                     &QuaternionVisitor::setCoeff<0>, "The x coefficient.")
        .def_prop_rw("y", &QuaternionVisitor::getCoeff<1>,
                     &QuaternionVisitor::setCoeff<1>, "The y coefficient.")
        .def_prop_rw("z", &QuaternionVisitor::getCoeff<2>,
                     &QuaternionVisitor::setCoeff<2>, "The z coefficient.")
        .def_prop_rw("w", &QuaternionVisitor::getCoeff<3>,
                     &QuaternionVisitor::setCoeff<3>, "The w coefficient.")

        .def(
            "isApprox",
            [](const Quaternion& self, const Quaternion& other) -> bool {
              return self.isApprox(other,
                                   Eigen::NumTraits<Scalar>::dummy_precision());
            },
            "other"_a,
            "Returns true if *this is approximately equal to other using "
            "default precision.")
        .def(
            "isApprox",
            [](const Quaternion& self, const Quaternion& other,
               const Scalar& prec) -> bool {
              return self.isApprox(other, prec);
            },
            "other"_a, "prec"_a,
            "Returns true if *this is approximately equal to other, "
            "within the precision determined by prec.")

        // Methods
        .def(
            "coeffs",
            [](const Quaternion& self) -> const Vector4& {
              return self.coeffs();
            },
            "Returns a vector of the coefficients (x,y,z,w)",
            nb::rv_policy::reference_internal)
        .def(RotationBaseVisitor<Quaternion, 3>())
        .def("setFromTwoVectors", &setFromTwoVectors, "a"_a, "b"_a,
             nb::rv_policy::reference)
        .def("conjugate", &Quaternion::conjugate,
             "Returns the conjugated quaternion.\n"
             "The conjugate of a quaternion represents the opposite rotation.")
        .def("setIdentity", &Quaternion::setIdentity,
             "Set *this to the identity rotation.", nb::rv_policy::reference)
        .def("norm", &Quaternion::norm,
             "Returns the norm of the quaternion's coefficients.")
        .def("normalize", &Quaternion::normalize,
             "Normalizes the quaternion *this.", nb::rv_policy::reference)
        .def("normalized", &normalized, nb::rv_policy::take_ownership,
             "Returns a normalized copy of *this.")
        .def("squaredNorm", &Quaternion::squaredNorm,
             "Returns the squared norm of the quaternion's coefficients.")
        .def("dot", &Quaternion::template dot<Quaternion>, "other"_a,
             "Returns the dot product of *this with an other Quaternion.\n"
             "Geometrically speaking, the dot product of two unit quaternions "
             "corresponds to the cosine of half the angle between the two "
             "rotations.")
        .def("_transformVector", &Quaternion::_transformVector, "vector"_a,
             "Rotation of a vector by a quaternion.")
        .def(
            "vec", [](const Quaternion& self) -> Vector3 { return self.vec(); },
            "Returns a vector expression of the imaginary part (x,y,z).")
        .def("angularDistance",
             &Quaternion::template angularDistance<Quaternion>,
             "Returns the angle (in radian) between two rotations.")
        .def(
            "slerp",
            [](const Quaternion& self, const Scalar t, const Quaternion& other)
                -> Quaternion { return self.slerp(t, other); },
            "t"_a, "other"_a,
            "Returns the spherical linear interpolation between the two "
            "quaternions *this and other at the parameter t in [0;1].")

        // Operators
        .def(nb::self * nb::self)
        .def(nb::self *= nb::self, nb::rv_policy::none)
        .def(nb::self * Vector3())
        .def(nb::self == nb::self)
        .def("__eq__",
             [](const Quaternion& u, const Quaternion& v) -> bool {
               return u.coeffs() == v.coeffs();
             })
        .def("__ne__",
             [](const Quaternion& u, const Quaternion& v) -> bool {
               return u.coeffs() != v.coeffs();
             })
        .def("__abs__", &Quaternion::norm)
        .def("__len__", &QuaternionVisitor::__len__)
        .def("__setitem__", &QuaternionVisitor::__setitem__)
        .def("__getitem__", &QuaternionVisitor::__getitem__)
        .def("assign", &assign<Quaternion>,
             "Set *this from an quaternion quat and returns a reference to "
             "*this.",
             nb::rv_policy::reference)
        .def(
            "assign",
            [](Quaternion* self, const AngleAxisType& aa) -> Quaternion& {
              return (*self = aa);
            },
            "aa"_a, nb::rv_policy::reference,
            "Set *this from an angle-axis and return a reference to self.")
        .def("__str__", &print)
        .def("__repr__", &print)

        .def_static("FromTwoVectors", &FromTwoVectors, "a"_a, "b"_a,
                    "Returns the quaternion which transforms a into b through "
                    "a rotation.",
                    nb::rv_policy::take_ownership)
        .def_static("Identity", &Identity,
                    "Returns a quaternion representing an identity rotation.",
                    nb::rv_policy::take_ownership);
  }

 private:
  static Quaternion* normalized(const Quaternion& self) {
    return new Quaternion(self.normalized());
  }

  static Quaternion& setFromTwoVectors(Quaternion& self,
                                       Eigen::Ref<const Vector3> a,
                                       Eigen::Ref<const Vector3> b) {
    return self.setFromTwoVectors(a, b);
  }

  template <int i>
  static Scalar getCoeff(Quaternion& self) {
    return self.coeffs()[i];
  }

  template <int i>
  static void setCoeff(Quaternion& self, Scalar value) {
    self.coeffs()[i] = value;
  }

  static int __len__() { return 4; }

  static Scalar __getitem__(const Quaternion& self, int idx) {
    if ((idx < 0) || (idx >= 4))
      throw nb::index_error("Index out of range [0, 3]");
    return self.coeffs()[idx];
  }

  static void __setitem__(Quaternion& self, int idx, const Scalar value) {
    if ((idx < 0) || (idx >= 4))
      throw nb::index_error("Index out of range [0, 3]");
    self.coeffs()[idx] = value;
  }

  template <typename OtherQuat>
  static Quaternion& assign(Quaternion& self, const OtherQuat& quat) {
    return self = quat;
  }

  static Quaternion* FromTwoVectors(const Eigen::Ref<const Vector3>& u,
                                    const Eigen::Ref<const Vector3>& v) {
    Quaternion* q = new Quaternion;
    q->setFromTwoVectors(u, v);
    return q;
  }

  static Quaternion* Identity() {
    Quaternion* q = new Quaternion;
    q->setIdentity();
    return q;
  }

  static std::string print(const Quaternion& self) {
    std::stringstream ss;
    ss << "(x,y,z,w) = " << self.coeffs().transpose() << std::endl;
    return ss.str();
  }

  template <typename Scalar>
  bool isApprox(
      const Quaternion& self, const Quaternion& other,
      const Scalar& prec = Eigen::NumTraits<Scalar>::dummy_precision()) {
    return self.isApprox(other, prec);
  }

 public:
  static void expose(nb::module_& m, const char* name) {
    if (check_registration_alias<Quaternion>(m)) {
      return;
    }
    nb::class_<Quaternion>(m, name).def(QuaternionVisitor());
  }
};

template <typename Scalar>
void exposeQuaternion(nb::module_& m, const char* name) {
  using Quaternion = Eigen::Quaternion<Scalar>;
  QuaternionVisitor<Quaternion>::expose(m, name);
}

}  // namespace nanoeigenpy