Program Listing for File rot_conv.h

Return to documentation for file (include/rot_conv/rot_conv.h)

// Rotations conversion library
// File: rot_conv.h
// Author: Philipp Allgeuer <pallgeuer@ais.uni-bonn.de>

// Ensure header is only included once
#ifndef ROT_CONV_H
#define ROT_CONV_H

// Includes
#include <Eigen/Core>
#include <Eigen/Geometry>
#include <iostream>
#include <cmath>

// Defines
#define ROT_CONV_DEFAULT_TOL 1e-10

// Note: The inline keywords below for the functions that are defined (as well as declared) in this header are
//       required to avoid multiple definition linker errors.
// Note: Where quaternions and rotation matrices are used as inputs, it is implicitly assumed that they are valid
//       (i.e. unit norm q, orthogonal R, det(R) = 1, etc...).
// Note: Where unit vectors (i.e. ZVec, BzG, etc...) are used as inputs, it is implicitly assumed they have unit norm.

// Rotations conversion namespace
namespace rot_conv
{
    // ########################
    // #### Rotation types ####
    // ########################

    // Typedefs
    typedef Eigen::Matrix3d Rotmat;
    typedef Eigen::Quaterniond Quat; // Format: (w,x,y,z)
    typedef Eigen::Vector3d ZVec; // Equivalent to BzG, i.e. the bottom row in RGB
    typedef Eigen::Vector2d Vec2;
    typedef Eigen::Vector3d Vec3;

    // Unit axis enumeration
    enum UnitAxis
    {
        X_AXIS = 0,
        Y_AXIS,
        Z_AXIS,
        AXIS_COUNT
    };

    // Unit axis functions
    inline Vec3 VecUnitX() { return Vec3::UnitX(); }
    inline Vec3 VecUnitY() { return Vec3::UnitY(); }
    inline Vec3 VecUnitZ() { return Vec3::UnitZ(); }
    inline Vec3 VecUnit(UnitAxis axis) { return (axis == X_AXIS ? Vec3::UnitX() : (axis == Y_AXIS ? Vec3::UnitY() : Vec3::UnitZ())); }

    // Helper functions
    namespace internal
    {
        // Picut functions
        inline double picut(double var) { return var + (2.0*M_PI)*std::floor((M_PI - var) / (2.0*M_PI)); }
        inline void picutVar(double& var) { var += (2.0*M_PI)*std::floor((M_PI - var) / (2.0*M_PI)); }
    }

    // Stream insertion operator for the vector types
    inline std::ostream& operator<<(std::ostream& os, const Vec2& v) { return os << "(" << v.x() << ", " << v.y() << ")"; }
    inline std::ostream& operator<<(std::ostream& os, const Vec3& v) { return os << "(" << v.x() << ", " << v.y() << ", " << v.z() << ")"; }

    // Additional quaternion operators
    inline Quat operator+(const Quat& q) { return q; }
    inline Quat operator-(const Quat& q) { return Quat(-q.w(), -q.x(), -q.y(), -q.z()); }
    inline Quat operator+(const Quat& qa, const Quat& qb) { return Quat(qa.w() + qb.w(), qa.x() + qb.x(), qa.y() + qb.y(), qa.z() + qb.z()); }
    inline Quat operator-(const Quat& qa, const Quat& qb) { return Quat(qa.w() - qb.w(), qa.x() - qb.x(), qa.y() - qb.y(), qa.z() - qb.z()); }
    inline Quat& operator+=(Quat& qa, const Quat& qb) { qa.w() += qb.w(); qa.x() += qb.x(); qa.y() += qb.y(); qa.z() += qb.z(); return qa; }
    inline Quat& operator-=(Quat& qa, const Quat& qb) { qa.w() -= qb.w(); qa.x() -= qb.x(); qa.y() -= qb.y(); qa.z() -= qb.z(); return qa; }
    inline Quat operator*(const Quat& q, double s) { return Quat(q.w()*s, q.x()*s, q.y()*s, q.z()*s); }
    inline Quat operator*(double s, const Quat& q) { return Quat(s*q.w(), s*q.x(), s*q.y(), s*q.z()); }
    inline Quat operator/(const Quat& q, double s) { return Quat(q.w()/s, q.x()/s, q.y()/s, q.z()/s); }
    inline Quat& operator*=(Quat& q, double s) { q.w() *= s; q.x() *= s; q.y() *= s; q.z() *= s; return q; }
    inline Quat& operator/=(Quat& q, double s) { q.w() /= s; q.x() /= s; q.y() /= s; q.z() /= s; return q; }

    // Stream insertion operator for the Quat type
    inline std::ostream& operator<<(std::ostream& os, const Quat& q) { return os << "(" << q.w() << ", " << q.x() << ", " << q.y() << ", " << q.z() << ")"; }

    // Euler angles struct with format: ZYX Euler (yaw, pitch, roll)
    struct EulerAngles // Note: POD type
    {
        // Constants
        static inline EulerAngles Identity() { return EulerAngles(0.0, 0.0, 0.0); }

        // Constructors
        EulerAngles() = default;
        explicit EulerAngles(double yaw) : yaw(yaw), pitch(0.0), roll(0.0) {}
        EulerAngles(double yaw, double pitch, double roll) : yaw(yaw), pitch(pitch), roll(roll) {}

        // Set functions
        void set(double yaw, double pitch, double roll) { this->yaw = yaw; this->pitch = pitch; this->roll = roll; }
        void setIdentity() { yaw = pitch = roll = 0.0; }

        // Data members
        double yaw;   // Yaw:    psi  is in (-pi,pi]
        double pitch; // Pitch: theta is in [-pi/2,pi/2]
        double roll;  // Roll:   phi  is in (-pi,pi]
    };

    // Stream insertion operator for the EulerAngles struct
    inline std::ostream& operator<<(std::ostream& os, const EulerAngles& e) { return os << "E(" << e.yaw << ", " << e.pitch << ", " << e.roll << ")"; }

    // Fused angles struct with format: (fusedYaw, fusedPitch, fusedRoll, hemi)
    struct FusedAngles // Note: POD type
    {
        // Constants
        static inline FusedAngles Identity() { return FusedAngles(0.0, 0.0, 0.0, true); }

        // Constructors
        FusedAngles() = default;
        explicit FusedAngles(double fusedYaw) : fusedYaw(fusedYaw), fusedPitch(0.0), fusedRoll(0.0), hemi(true) {}
        explicit FusedAngles(double fusedPitch, double fusedRoll, bool hemi = true) : fusedYaw(0.0), fusedPitch(fusedPitch), fusedRoll(fusedRoll), hemi(hemi) {}
        FusedAngles(double fusedYaw, double fusedPitch, double fusedRoll, bool hemi = true) : fusedYaw(fusedYaw), fusedPitch(fusedPitch), fusedRoll(fusedRoll), hemi(hemi) {}

        // Set functions
        void set(double fusedYaw, double fusedPitch, double fusedRoll, bool hemi = true) { this->fusedYaw = fusedYaw; this->fusedPitch = fusedPitch; this->fusedRoll = fusedRoll; this->hemi = hemi; }
        void setIdentity() { fusedYaw = fusedPitch = fusedRoll = 0.0; hemi = true; }

        // Get functions
        int hemiSign() const { return (hemi ? 1 : -1); }

        // Data members
        double fusedYaw;   // Fused yaw:    psi  = yaw   is in (-pi,pi]
        double fusedPitch; // Fused pitch: theta = pitch is in [-pi/2,pi/2]
        double fusedRoll;  // Fused roll:   phi  = roll  is in [-pi/2,pi/2]
        bool hemi;         // Hemisphere:    h   = hemi  is in {-1,1} (stored as {false,true} respectively)
    };

    // Stream insertion operator for the FusedAngles struct
    inline std::ostream& operator<<(std::ostream& os, const FusedAngles& f) { return os << "F(" << f.fusedYaw << ", " << f.fusedPitch << ", " << f.fusedRoll << ", " << f.hemiSign() << ")"; }

    // Tilt angles struct with format: (fusedYaw, tiltAxisAngle, tiltAngle)
    struct TiltAngles // Note: POD type
    {
        // Constants
        static inline TiltAngles Identity() { return TiltAngles(0.0, 0.0, 0.0); }

        // Constructors
        TiltAngles() = default;
        explicit TiltAngles(double fusedYaw) : fusedYaw(fusedYaw), tiltAxisAngle(0.0), tiltAngle(0.0) {}
        explicit TiltAngles(double tiltAxisAngle, double tiltAngle) : fusedYaw(0.0), tiltAxisAngle(tiltAxisAngle), tiltAngle(tiltAngle) {}
        TiltAngles(double fusedYaw, double tiltAxisAngle, double tiltAngle) : fusedYaw(fusedYaw), tiltAxisAngle(tiltAxisAngle), tiltAngle(tiltAngle) {}

        // Set functions
        void set(double fusedYaw, double tiltAxisAngle, double tiltAngle) { this->fusedYaw = fusedYaw; this->tiltAxisAngle = tiltAxisAngle; this->tiltAngle = tiltAngle; }
        void setIdentity() { fusedYaw = tiltAxisAngle = tiltAngle = 0.0; }

        // Data members
        double fusedYaw;      // Fused yaw:        psi  is in (-pi,pi]
        double tiltAxisAngle; // Tilt axis angle: gamma is in (-pi,pi]
        double tiltAngle;     // Tilt angle:      alpha is in [0,pi]
    };

    // Stream insertion operator for the TiltAngles struct
    inline std::ostream& operator<<(std::ostream& os, const TiltAngles& t) { return os << "T(" << t.fusedYaw << ", " << t.tiltAxisAngle << ", " << t.tiltAngle << ")"; }

    // Forward declarations of tilt phase structs
    struct TiltPhase2D;
    struct TiltPhase3D;

    // Tilt phase 2D struct with format: (px, py)
    struct TiltPhase2D // Note: POD type
    {
        // Constants
        static inline TiltPhase2D Identity() { return TiltPhase2D(0.0, 0.0); }

        // Constructors
        TiltPhase2D() = default;
        TiltPhase2D(double px, double py) : px(px), py(py) {}
        TiltPhase2D(const TiltAngles& t) { setTilt(t); }
        TiltPhase2D(const TiltPhase3D& p);

        // Set functions
        void set(double px, double py) { this->px = px; this->py = py; }
        void setTilt(double tiltAxisAngle, double tiltAngle) { px = tiltAngle*cos(tiltAxisAngle); py = tiltAngle*sin(tiltAxisAngle); }
        void setTilt(const TiltAngles& t) { setTilt(t.tiltAxisAngle, t.tiltAngle); }
        void setIdentity() { px = py = 0.0; }

        // Get functions
        void get(double& px, double& py) const { px = this->px; py = this->py; }
        void getTilt(double& tiltAxisAngle, double& tiltAngle) const { tiltAxisAngle = atan2(py, px); tiltAngle = sqrt(px*px + py*py); }
        void getTilt(TiltAngles& t) const { t.fusedYaw = 0.0; getTilt(t.tiltAxisAngle, t.tiltAngle); }
        TiltAngles getTilt() const { TiltAngles t; getTilt(t); return t; }

        // Array subscript operator
        double& operator[](std::size_t idx) { return (idx == 0 ? px : py); }
        const double& operator[](std::size_t idx) const { return (idx == 0 ? px : py); }

        // Assignment operator
        TiltPhase2D& operator=(const TiltPhase3D& p); // Note: Does not touch pz!

        // Data members
        double px; // Phase x: tiltAngle*cos(tiltAxisAngle) in R
        double py; // Phase y: tiltAngle*sin(tiltAxisAngle) in R
    };

    // Stream insertion operator for the TiltPhase2D struct
    inline std::ostream& operator<<(std::ostream& os, const TiltPhase2D& p) { return os << "P(" << p.px << ", " << p.py << ")"; }

    // Additional tilt phase 2D operators
    inline TiltPhase2D operator+(const TiltPhase2D& p) { return p; }
    inline TiltPhase2D operator-(const TiltPhase2D& p) { return TiltPhase2D(-p.px, -p.py); }
    inline TiltPhase2D operator+(const TiltPhase2D& pa, const TiltPhase2D& pb) { return TiltPhase2D(pa.px + pb.px, pa.py + pb.py); }
    inline TiltPhase2D operator-(const TiltPhase2D& pa, const TiltPhase2D& pb) { return TiltPhase2D(pa.px - pb.px, pa.py - pb.py); }
    inline TiltPhase2D& operator+=(TiltPhase2D& pa, const TiltPhase2D& pb) { pa.px += pb.px; pa.py += pb.py; return pa; }
    inline TiltPhase2D& operator-=(TiltPhase2D& pa, const TiltPhase2D& pb) { pa.px -= pb.px; pa.py -= pb.py; return pa; }
    inline TiltPhase2D operator*(const TiltPhase2D& p, double s) { return TiltPhase2D(p.px*s, p.py*s); }
    inline TiltPhase2D operator*(double s, const TiltPhase2D& p) { return TiltPhase2D(s*p.px, s*p.py); }
    inline TiltPhase2D operator/(const TiltPhase2D& p, double s) { return TiltPhase2D(p.px/s, p.py/s); }
    inline TiltPhase2D& operator*=(TiltPhase2D& p, double s) { p.px *= s; p.py *= s; return p; }
    inline TiltPhase2D& operator/=(TiltPhase2D& p, double s) { p.px /= s; p.py /= s; return p; }

    // Tilt phase 3D struct with format: (px, py, pz)
    struct TiltPhase3D // Note: POD type
    {
        // Constants
        static inline TiltPhase3D Identity() { return TiltPhase3D(0.0, 0.0, 0.0); }

        // Constructors
        TiltPhase3D() = default;
        explicit TiltPhase3D(double pz) : px(0.0), py(0.0), pz(pz) {}
        TiltPhase3D(double px, double py, double pz = 0.0) : px(px), py(py), pz(pz) {}
        TiltPhase3D(const TiltAngles& t) { setTilt(t); }
        TiltPhase3D(const TiltPhase2D& p);

        // Set functions
        void set(double px, double py, double pz = 0.0) { this->px = px; this->py = py; this->pz = pz; }
        void setTilt(double tiltAxisAngle, double tiltAngle) { px = tiltAngle*cos(tiltAxisAngle); py = tiltAngle*sin(tiltAxisAngle); } // Note: Does not touch pz!
        void setTilt(double tiltAxisAngle, double tiltAngle, double fusedYaw) { setTilt(tiltAxisAngle, tiltAngle); pz = fusedYaw; }
        void setTilt(const TiltAngles& t) { setTilt(t.tiltAxisAngle, t.tiltAngle, t.fusedYaw); }
        void setIdentity() { px = py = pz = 0.0; }

        // Get functions
        void get(double& px, double& py) const { px = this->px; py = this->py; }
        void get(double& px, double& py, double& pz) const { px = this->px; py = this->py; pz = this->pz; }
        void getTilt(double& tiltAxisAngle, double& tiltAngle) const { tiltAxisAngle = atan2(py, px); tiltAngle = sqrt(px*px + py*py); }
        void getTilt(double& tiltAxisAngle, double& tiltAngle, double& fusedYaw) const { fusedYaw = pz; getTilt(tiltAxisAngle, tiltAngle); }
        void getTilt(TiltAngles& t) const { t.fusedYaw = pz; getTilt(t.tiltAxisAngle, t.tiltAngle); }
        TiltAngles getTilt() const { TiltAngles t; getTilt(t); return t; }

        // Array subscript operator
        double& operator[](std::size_t idx) { return (idx == 0 ? px : (idx == 1 ? py : pz)); }
        const double& operator[](std::size_t idx) const { return (idx == 0 ? px : (idx == 1 ? py : pz)); }

        // Assignment operator
        TiltPhase3D& operator=(const TiltPhase2D& p); // Note: Does not touch pz!

        // Data members
        double px; // Phase x: tiltAngle*cos(tiltAxisAngle) in R
        double py; // Phase y: tiltAngle*sin(tiltAxisAngle) in R
        double pz; // Phase z: fusedYaw in (-pi,pi]
    };

    // Stream insertion operator for the TiltPhase3D struct
    inline std::ostream& operator<<(std::ostream& os, const TiltPhase3D& p) { return os << "P(" << p.px << ", " << p.py << ", " << p.pz << ")"; }

    // Additional tilt phase 3D operators
    inline TiltPhase3D operator+(const TiltPhase3D& p) { return p; }
    inline TiltPhase3D operator-(const TiltPhase3D& p) { return TiltPhase3D(-p.px, -p.py, -p.pz); }
    inline TiltPhase3D operator+(const TiltPhase3D& pa, const TiltPhase3D& pb) { return TiltPhase3D(pa.px + pb.px, pa.py + pb.py, pa.pz + pb.pz); }
    inline TiltPhase3D operator-(const TiltPhase3D& pa, const TiltPhase3D& pb) { return TiltPhase3D(pa.px - pb.px, pa.py - pb.py, pa.pz - pb.pz); }
    inline TiltPhase3D& operator+=(TiltPhase3D& pa, const TiltPhase3D& pb) { pa.px += pb.px; pa.py += pb.py; pa.pz += pb.pz; return pa; }
    inline TiltPhase3D& operator-=(TiltPhase3D& pa, const TiltPhase3D& pb) { pa.px -= pb.px; pa.py -= pb.py; pa.pz -= pb.pz; return pa; }
    inline TiltPhase3D operator*(const TiltPhase3D& p, double s) { return TiltPhase3D(p.px*s, p.py*s, p.pz*s); }
    inline TiltPhase3D operator*(double s, const TiltPhase3D& p) { return TiltPhase3D(s*p.px, s*p.py, s*p.pz); }
    inline TiltPhase3D operator/(const TiltPhase3D& p, double s) { return TiltPhase3D(p.px/s, p.py/s, p.pz/s); }
    inline TiltPhase3D& operator*=(TiltPhase3D& p, double s) { p.px *= s; p.py *= s; p.pz *= s; return p; }
    inline TiltPhase3D& operator/=(TiltPhase3D& p, double s) { p.px /= s; p.py /= s; p.pz /= s; return p; }

    // Tilt phase constructors
    inline TiltPhase2D::TiltPhase2D(const TiltPhase3D& p) : px(p.px), py(p.py) {}
    inline TiltPhase3D::TiltPhase3D(const TiltPhase2D& p) : px(p.px), py(p.py), pz(0.0) {}

    // Tilt phase assignment operators
    inline TiltPhase2D& TiltPhase2D::operator=(const TiltPhase3D& p) { px = p.px; py = p.py; return *this; }
    inline TiltPhase3D& TiltPhase3D::operator=(const TiltPhase2D& p) { px = p.px; py = p.py; return *this; }

    // #################################
    // #### Rotation velocity types ####
    // #################################

    // Typedefs
    typedef Eigen::Vector3d AngVel;

    // Forward declarations of tilt phase velocity structs
    struct TiltPhaseVel2D;
    struct TiltPhaseVel3D;

    // Tilt phase velocity 2D struct with format: <pxVel, pyVel>
    struct TiltPhaseVel2D // Note: POD type
    {
        // Constants
        static inline TiltPhaseVel2D Zero() { return TiltPhaseVel2D(0.0, 0.0); }

        // Constructors
        TiltPhaseVel2D() = default;
        TiltPhaseVel2D(double pxVel, double pyVel) : pxVel(pxVel), pyVel(pyVel) {}
        TiltPhaseVel2D(const TiltPhaseVel3D& pdot);

        // Set functions
        void set(double pxVel, double pyVel) { this->pxVel = pxVel; this->pyVel = pyVel; }
        void setZero() { pxVel = pyVel = 0.0; }

        // Get functions
        void get(double& pxVel, double& pyVel) const { pxVel = this->pxVel; pyVel = this->pyVel; }

        // Array subscript operator
        double& operator[](std::size_t idx) { return (idx == 0 ? pxVel : pyVel); }
        const double& operator[](std::size_t idx) const { return (idx == 0 ? pxVel : pyVel); }

        // Assignment operator
        TiltPhaseVel2D& operator=(const TiltPhaseVel3D& pdot); // Note: Does not touch pzVel!

        // Data members
        double pxVel; // Phase x velocity
        double pyVel; // Phase y velocity
    };

    // Stream insertion operator for the TiltPhaseVel2D struct
    inline std::ostream& operator<<(std::ostream& os, const TiltPhaseVel2D& pdot) { return os << "P<" << pdot.pxVel << ", " << pdot.pyVel << ">"; }

    // Additional tilt phase velocity 2D operators
    inline TiltPhaseVel2D operator+(const TiltPhaseVel2D& pdot) { return pdot; }
    inline TiltPhaseVel2D operator-(const TiltPhaseVel2D& pdot) { return TiltPhaseVel2D(-pdot.pxVel, -pdot.pyVel); }
    inline TiltPhaseVel2D operator+(const TiltPhaseVel2D& pdota, const TiltPhaseVel2D& pdotb) { return TiltPhaseVel2D(pdota.pxVel + pdotb.pxVel, pdota.pyVel + pdotb.pyVel); }
    inline TiltPhaseVel2D operator-(const TiltPhaseVel2D& pdota, const TiltPhaseVel2D& pdotb) { return TiltPhaseVel2D(pdota.pxVel - pdotb.pxVel, pdota.pyVel - pdotb.pyVel); }
    inline TiltPhaseVel2D& operator+=(TiltPhaseVel2D& pdota, const TiltPhaseVel2D& pdotb) { pdota.pxVel += pdotb.pxVel; pdota.pyVel += pdotb.pyVel; return pdota; }
    inline TiltPhaseVel2D& operator-=(TiltPhaseVel2D& pdota, const TiltPhaseVel2D& pdotb) { pdota.pxVel -= pdotb.pxVel; pdota.pyVel -= pdotb.pyVel; return pdota; }
    inline TiltPhaseVel2D operator*(const TiltPhaseVel2D& pdot, double s) { return TiltPhaseVel2D(pdot.pxVel*s, pdot.pyVel*s); }
    inline TiltPhaseVel2D operator*(double s, const TiltPhaseVel2D& pdot) { return TiltPhaseVel2D(s*pdot.pxVel, s*pdot.pyVel); }
    inline TiltPhaseVel2D operator/(const TiltPhaseVel2D& pdot, double s) { return TiltPhaseVel2D(pdot.pxVel/s, pdot.pyVel/s); }
    inline TiltPhaseVel2D& operator*=(TiltPhaseVel2D& pdot, double s) { pdot.pxVel *= s; pdot.pyVel *= s; return pdot; }
    inline TiltPhaseVel2D& operator/=(TiltPhaseVel2D& pdot, double s) { pdot.pxVel /= s; pdot.pyVel /= s; return pdot; }

    // Finite difference operations
    inline void TiltPhaseDiff(const TiltPhase2D& pa, const TiltPhase2D& pb, double dt, TiltPhaseVel2D& pdot) { pdot.pxVel = (pb.px - pa.px)/dt; pdot.pyVel = (pb.py - pa.py)/dt; } // Calculates (pb - pa)/dt
    inline TiltPhaseVel2D TiltPhaseDiff(const TiltPhase2D& pa, const TiltPhase2D& pb, double dt) { TiltPhaseVel2D pdot; TiltPhaseDiff(pa, pb, dt, pdot); return pdot; }

    // Tilt phase velocity 3D struct with format: <pxVel, pyVel, pzVel>
    struct TiltPhaseVel3D // Note: POD type
    {
        // Constants
        static inline TiltPhaseVel3D Zero() { return TiltPhaseVel3D(0.0, 0.0, 0.0); }

        // Constructors
        TiltPhaseVel3D() = default;
        explicit TiltPhaseVel3D(double pzVel) : pxVel(0.0), pyVel(0.0), pzVel(pzVel) {}
        TiltPhaseVel3D(double pxVel, double pyVel, double pzVel = 0.0) : pxVel(pxVel), pyVel(pyVel), pzVel(pzVel) {}
        TiltPhaseVel3D(const TiltPhaseVel2D& pdot);

        // Set functions
        void set(double pxVel, double pyVel, double pzVel = 0.0) { this->pxVel = pxVel; this->pyVel = pyVel; this->pzVel = pzVel; }
        void setZero() { pxVel = pyVel = pzVel = 0.0; }

        // Get functions
        void get(double& pxVel, double& pyVel) const { pxVel = this->pxVel; pyVel = this->pyVel; }
        void get(double& pxVel, double& pyVel, double& pzVel) const { pxVel = this->pxVel; pyVel = this->pyVel; pzVel = this->pzVel; }

        // Array subscript operator
        double& operator[](std::size_t idx) { return (idx == 0 ? pxVel : (idx == 1 ? pyVel : pzVel)); }
        const double& operator[](std::size_t idx) const { return (idx == 0 ? pxVel : (idx == 1 ? pyVel : pzVel)); }

        // Assignment operator
        TiltPhaseVel3D& operator=(const TiltPhaseVel2D& pdot); // Note: Does not touch pzVel!

        // Data members
        double pxVel; // Phase x velocity
        double pyVel; // Phase y velocity
        double pzVel; // Phase z velocity
    };

    // Stream insertion operator for the TiltPhaseVel3D struct
    inline std::ostream& operator<<(std::ostream& os, const TiltPhaseVel3D& pdot) { return os << "P<" << pdot.pxVel << ", " << pdot.pyVel << ", " << pdot.pzVel << ">"; }

    // Additional tilt phase velocity 3D operators
    inline TiltPhaseVel3D operator+(const TiltPhaseVel3D& pdot) { return pdot; }
    inline TiltPhaseVel3D operator-(const TiltPhaseVel3D& pdot) { return TiltPhaseVel3D(-pdot.pxVel, -pdot.pyVel, -pdot.pzVel); }
    inline TiltPhaseVel3D operator+(const TiltPhaseVel3D& pdota, const TiltPhaseVel3D& pdotb) { return TiltPhaseVel3D(pdota.pxVel + pdotb.pxVel, pdota.pyVel + pdotb.pyVel, pdota.pzVel + pdotb.pzVel); }
    inline TiltPhaseVel3D operator-(const TiltPhaseVel3D& pdota, const TiltPhaseVel3D& pdotb) { return TiltPhaseVel3D(pdota.pxVel - pdotb.pxVel, pdota.pyVel - pdotb.pyVel, pdota.pzVel - pdotb.pzVel); }
    inline TiltPhaseVel3D& operator+=(TiltPhaseVel3D& pdota, const TiltPhaseVel3D& pdotb) { pdota.pxVel += pdotb.pxVel; pdota.pyVel += pdotb.pyVel; pdota.pzVel += pdotb.pzVel; return pdota; }
    inline TiltPhaseVel3D& operator-=(TiltPhaseVel3D& pdota, const TiltPhaseVel3D& pdotb) { pdota.pxVel -= pdotb.pxVel; pdota.pyVel -= pdotb.pyVel; pdota.pzVel -= pdotb.pzVel; return pdota; }
    inline TiltPhaseVel3D operator*(const TiltPhaseVel3D& pdot, double s) { return TiltPhaseVel3D(pdot.pxVel*s, pdot.pyVel*s, pdot.pzVel*s); }
    inline TiltPhaseVel3D operator*(double s, const TiltPhaseVel3D& pdot) { return TiltPhaseVel3D(s*pdot.pxVel, s*pdot.pyVel, s*pdot.pzVel); }
    inline TiltPhaseVel3D operator/(const TiltPhaseVel3D& pdot, double s) { return TiltPhaseVel3D(pdot.pxVel/s, pdot.pyVel/s, pdot.pzVel/s); }
    inline TiltPhaseVel3D& operator*=(TiltPhaseVel3D& pdot, double s) { pdot.pxVel *= s; pdot.pyVel *= s; pdot.pzVel *= s; return pdot; }
    inline TiltPhaseVel3D& operator/=(TiltPhaseVel3D& pdot, double s) { pdot.pxVel /= s; pdot.pyVel /= s; pdot.pzVel /= s; return pdot; }

    // Finite difference operations
    inline void TiltPhaseDiff(const TiltPhase3D& pa, const TiltPhase3D& pb, double dt, TiltPhaseVel3D& pdot) { pdot.pxVel = (pb.px - pa.px)/dt; pdot.pyVel = (pb.py - pa.py)/dt; pdot.pzVel = (pb.pz - pa.pz)/dt; } // Calculates (pb - pa)/dt
    inline TiltPhaseVel3D TiltPhaseDiff(const TiltPhase3D& pa, const TiltPhase3D& pb, double dt) { TiltPhaseVel3D pdot; TiltPhaseDiff(pa, pb, dt, pdot); return pdot; }

    // Tilt phase velocity constructors
    inline TiltPhaseVel2D::TiltPhaseVel2D(const TiltPhaseVel3D& pdot) : pxVel(pdot.pxVel), pyVel(pdot.pyVel) {}
    inline TiltPhaseVel3D::TiltPhaseVel3D(const TiltPhaseVel2D& pdot) : pxVel(pdot.pxVel), pyVel(pdot.pyVel), pzVel(0.0) {}

    // Tilt phase velocity assignment operators
    inline TiltPhaseVel2D& TiltPhaseVel2D::operator=(const TiltPhaseVel3D& pdot) { pxVel = pdot.pxVel; pyVel = pdot.pyVel; return *this; }
    inline TiltPhaseVel3D& TiltPhaseVel3D::operator=(const TiltPhaseVel2D& pdot) { pxVel = pdot.pxVel; pyVel = pdot.pyVel; return *this; }

    // ################################
    // #### Rotation normalisation ####
    // ################################

    // Normalise: Rotation matrix
    void NormaliseRotmat(Rotmat& R);
    inline Rotmat NormalisedRotmat(const Rotmat& R) { Rotmat Rout = R; NormaliseRotmat(Rout); return Rout; }

    // Normalise: Quaternion
    inline double QuatNorm(const Quat& q) { return sqrt(q.w()*q.w() + q.x()*q.x() + q.y()*q.y() + q.z()*q.z()); }
    inline double QuatNormSq(const Quat& q) { return q.w()*q.w() + q.x()*q.x() + q.y()*q.y() + q.z()*q.z(); }
    void NormaliseQuat(Quat& q, double normTol = 0.0);
    inline Quat NormalisedQuat(const Quat& q, double normTol = 0.0) { Quat qout = q; NormaliseQuat(qout, normTol); return qout; }

    // Normalise: Vector
    inline double VecNorm(const Vec3& v) { return sqrt(v.x()*v.x() + v.y()*v.y() + v.z()*v.z()); }
    inline double VecNormSq(const Vec3& v) { return v.x()*v.x() + v.y()*v.y() + v.z()*v.z(); }
    void NormaliseVec(Vec3& v, double normTol = 0.0, const Vec3& vdef = VecUnitZ());
    inline Vec3 NormalisedVec(const Vec3& v, double normTol = 0.0, const Vec3& vdef = VecUnitZ()) { Vec3 vout = v; NormaliseVec(vout, normTol, vdef); return vout; }

    // ##########################
    // #### Random rotations ####
    // ##########################

    // Random: Angle
    inline double RandAng(double maxAbs = M_PI) { return maxAbs*((2.0*std::rand()) / RAND_MAX - 1.0); } // Range [-maxAbs,maxAbs]
    inline double RandAng(double min, double max) { return min + ((max - min) * std::rand()) / RAND_MAX; } // Range [min,max]

    // Random: Vector
    Vec3 RandVec(double maxNorm);
    Vec3 RandUnitVec();

    // Random: Rotation matrix
    void RandRotmat(Rotmat& R);
    inline Rotmat RandRotmat() { Rotmat R; RandRotmat(R); return R; }

    // Random: Quaternion
    void RandQuat(Quat& q);
    inline Quat RandQuat() { Quat q; RandQuat(q); return q; }

    // Random: Euler angles
    void RandEuler(EulerAngles& e);
    inline EulerAngles RandEuler() { EulerAngles e; RandEuler(e); return e; }

    // Random: Fused angles
    void RandFused(FusedAngles& f);
    inline FusedAngles RandFused() { FusedAngles f; RandFused(f); return f; }

    // Random: Tilt angles
    void RandTilt(TiltAngles& t);
    inline TiltAngles RandTilt() { TiltAngles t; RandTilt(t); return t; }

    // ##########################################
    // #### Rotation checking and validation ####
    // ##########################################

    // Check and validate: Rotation matrix
    bool ValidateRotmat(Rotmat& R, double tol = ROT_CONV_DEFAULT_TOL);
    inline bool CheckRotmat(const Rotmat& R, double tol = ROT_CONV_DEFAULT_TOL) { Rotmat RR = R; return ValidateRotmat(RR, tol); }
    inline bool CheckRotmat(const Rotmat& R, Rotmat& Rout, double tol = ROT_CONV_DEFAULT_TOL) { Rout = R; return ValidateRotmat(Rout, tol); }

    // Check and validate: Quaternion
    bool ValidateQuat(Quat& q, double tol = ROT_CONV_DEFAULT_TOL, bool unique = false);
    inline bool CheckQuat(const Quat& q, double tol = ROT_CONV_DEFAULT_TOL, bool unique = false) { Quat qq = q; return ValidateQuat(qq, tol, unique); }
    inline bool CheckQuat(const Quat& q, Quat& qout, double tol = ROT_CONV_DEFAULT_TOL, bool unique = false) { qout = q; return ValidateQuat(qout, tol, unique); }

    // Check and validate: Euler angles
    bool ValidateEuler(EulerAngles& e, double tol = ROT_CONV_DEFAULT_TOL, bool unique = false);
    inline bool CheckEuler(const EulerAngles& e, double tol = ROT_CONV_DEFAULT_TOL, bool unique = false) { EulerAngles ee = e; return ValidateEuler(ee, tol, unique); }
    inline bool CheckEuler(const EulerAngles& e, EulerAngles& eout, double tol = ROT_CONV_DEFAULT_TOL, bool unique = false) { eout = e; return ValidateEuler(eout, tol, unique); }

    // Check and validate: Fused angles
    bool ValidateFused(FusedAngles& f, double tol = ROT_CONV_DEFAULT_TOL, bool unique = false);
    inline bool CheckFused(const FusedAngles& f, double tol = ROT_CONV_DEFAULT_TOL, bool unique = false) { FusedAngles ff = f; return ValidateFused(ff, tol, unique); }
    inline bool CheckFused(const FusedAngles& f, FusedAngles& fout, double tol = ROT_CONV_DEFAULT_TOL, bool unique = false) { fout = f; return ValidateFused(fout, tol, unique); }

    // Check and validate: Tilt angles
    bool ValidateTilt(TiltAngles& t, double tol = ROT_CONV_DEFAULT_TOL, bool unique = false);
    inline bool CheckTilt(const TiltAngles& t, double tol = ROT_CONV_DEFAULT_TOL, bool unique = false) { TiltAngles tt = t; return ValidateTilt(tt, tol, unique); }
    inline bool CheckTilt(const TiltAngles& t, TiltAngles& tout, double tol = ROT_CONV_DEFAULT_TOL, bool unique = false) { tout = t; return ValidateTilt(tout, tol, unique); }

    // ###########################
    // #### Rotation equality ####
    // ###########################

    // Check equality: Rotation matrix
    bool RotmatEqual(const Rotmat& Ra, const Rotmat& Rb, double tol = ROT_CONV_DEFAULT_TOL);
    bool RotmatEqualExact(const Rotmat& Ra, const Rotmat& Rb, double tol = ROT_CONV_DEFAULT_TOL);

    // Check equality: Quaternion
    bool QuatEqual(const Quat& qa, const Quat& qb, double tol = ROT_CONV_DEFAULT_TOL);
    bool QuatEqualExact(const Quat& qa, const Quat& qb, double tol = ROT_CONV_DEFAULT_TOL);

    // Check equality: Euler angles
    bool EulerEqual(const EulerAngles& ea, const EulerAngles& eb, double tol = ROT_CONV_DEFAULT_TOL);
    bool EulerEqualExact(const EulerAngles& ea, const EulerAngles& eb, double tol = ROT_CONV_DEFAULT_TOL);

    // Check equality: Fused angles
    bool FusedEqual(const FusedAngles& fa, const FusedAngles& fb, double tol = ROT_CONV_DEFAULT_TOL);
    bool FusedEqualExact(const FusedAngles& fa, const FusedAngles& fb, double tol = ROT_CONV_DEFAULT_TOL);

    // Check equality: Tilt angles
    bool TiltEqual(const TiltAngles& ta, const TiltAngles& tb, double tol = ROT_CONV_DEFAULT_TOL);
    bool TiltEqualExact(const TiltAngles& ta, const TiltAngles& tb, double tol = ROT_CONV_DEFAULT_TOL);

    // ##############################
    // #### Rotation composition ####
    // ##############################

    // Helper functions
    namespace internal
    {
        // Compose: Rotation matrix
        inline void ComposeRotmatHelper(Rotmat&) {}
        template<typename... Types> void ComposeRotmatHelper(Rotmat& Rout, const Rotmat& R, Types&&... args);

        // Compose: Quaternion
        inline void ComposeQuatHelper(Quat&) {}
        template<typename... Types> void ComposeQuatHelper(Quat& qout, const Quat& q, Types&&... args);

        // Compose: Euler angles
        inline void ComposeEulerHelper(Rotmat&) {}
        template<typename... Types> void ComposeEulerHelper(Rotmat& Rout, const EulerAngles& e, Types&&... args);

        // Compose: Fused angles
        inline void ComposeFusedHelper(Rotmat&) {}
        template<typename... Types> void ComposeFusedHelper(Rotmat& Rout, const FusedAngles& f, Types&&... args);

        // Compose: Tilt angles
        inline void ComposeTiltHelper(Rotmat&) {}
        template<typename... Types> void ComposeTiltHelper(Rotmat& Rout, const TiltAngles& t, Types&&... args);
    }

    // Compose: Rotation matrix (alternative to operator*)
    template<typename... Types> Rotmat ComposeRotmat(const Rotmat& R, Types&&... args);
    template<typename... Types> inline Rotmat RotmatMult(Types&&... args) { return ComposeRotmat(std::forward<Types>(args)...); } // Function alias for convenience

    // Compose: Quaternion (alternative to operator*)
    template<typename... Types> Quat ComposeQuat(const Quat& q, Types&&... args);
    template<typename... Types> inline Quat QuatMult(Types&&... args) { return ComposeQuat(std::forward<Types>(args)...); } // Function alias for convenience

    // Compose: Euler angles
    template<typename... Types> EulerAngles ComposeEuler(const EulerAngles& e, Types&&... args);

    // Compose: Fused angles
    template<typename... Types> FusedAngles ComposeFused(const FusedAngles& f, Types&&... args);

    // Compose: Tilt angles
    template<typename... Types> TiltAngles ComposeTilt(const TiltAngles& t, Types&&... args);

    // #########################
    // #### Yaw of rotation ####
    // #########################

    // Yaw of: Rotation matrix
    double EYawOfRotmat(const Rotmat& R);
    double FYawOfRotmat(const Rotmat& R);

    // Yaw of: Quaternion
    double EYawOfQuat(const Quat& q);
    double FYawOfQuat(const Quat& q);

    // Yaw of: Euler angles
    inline double EYawOfEuler(const EulerAngles& e) { return e.yaw; }
    double FYawOfEuler(const EulerAngles& e);

    // Yaw of: Fused angles
    double EYawOfFused(const FusedAngles& f);
    inline double FYawOfFused(const FusedAngles& f) { return f.fusedYaw; }

    // Yaw of: Tilt angles
    double EYawOfTilt(const TiltAngles& t);
    inline double FYawOfTilt(const TiltAngles& t) { return t.fusedYaw; }

    // ##################################
    // #### Remove yaw from rotation ####
    // ##################################

    // Remove yaw from: Rotation matrix
    void RotmatNoEYaw(const Rotmat& R, Rotmat& Rout);
    void RotmatNoFYaw(const Rotmat& R, Rotmat& Rout);
    inline Rotmat RotmatNoEYaw(const Rotmat& R) { Rotmat Rout; RotmatNoEYaw(R, Rout); return Rout; }
    inline Rotmat RotmatNoFYaw(const Rotmat& R) { Rotmat Rout; RotmatNoFYaw(R, Rout); return Rout; }

    // Remove yaw from: Quaternion
    void QuatNoEYaw(const Quat& q, Quat& qout);
    void QuatNoFYaw(const Quat& q, Quat& qout);
    inline Quat QuatNoEYaw(const Quat& q) { Quat qout; QuatNoEYaw(q, qout); return qout; }
    inline Quat QuatNoFYaw(const Quat& q) { Quat qout; QuatNoFYaw(q, qout); return qout; }

    // Remove yaw from: Euler angles
    inline void EulerNoEYaw(const EulerAngles& e, EulerAngles& eout) { eout.set(0.0, e.pitch, e.roll); }
    void EulerNoFYaw(const EulerAngles& e, EulerAngles& eout);
    inline EulerAngles EulerNoEYaw(const EulerAngles& e) { return EulerAngles(0.0, e.pitch, e.roll); }
    inline EulerAngles EulerNoFYaw(const EulerAngles& e) { EulerAngles eout; EulerNoFYaw(e, eout); return eout; }

    // Remove yaw from: Fused angles
    void FusedNoEYaw(const FusedAngles& f, FusedAngles& fout);
    inline void FusedNoFYaw(const FusedAngles& f, FusedAngles& fout) { fout.set(0.0, f.fusedPitch, f.fusedRoll, f.hemi); }
    inline FusedAngles FusedNoEYaw(const FusedAngles& f) { FusedAngles fout; FusedNoEYaw(f, fout); return fout; }
    inline FusedAngles FusedNoFYaw(const FusedAngles& f) { return FusedAngles(f.fusedPitch, f.fusedRoll, f.hemi); }

    // Remove yaw from: Tilt angles
    void TiltNoEYaw(const TiltAngles& t, TiltAngles& tout);
    inline void TiltNoFYaw(const TiltAngles& t, TiltAngles& tout) { tout.set(0.0, t.tiltAxisAngle, t.tiltAngle); }
    inline TiltAngles TiltNoEYaw(const TiltAngles& t) { TiltAngles tout; TiltNoEYaw(t, tout); return tout; }
    inline TiltAngles TiltNoFYaw(const TiltAngles& t) { return TiltAngles(t.tiltAxisAngle, t.tiltAngle); }

    // #################################
    // #### Rotation with given yaw ####
    // #################################

    // Rotation with given yaw: Rotation matrix
    void RotmatWithEYaw(const Rotmat& R, double eulerYaw, Rotmat& Rout);
    void RotmatWithFYaw(const Rotmat& R, double fusedYaw, Rotmat& Rout);
    inline Rotmat RotmatWithEYaw(const Rotmat& R, double eulerYaw) { Rotmat Rout; RotmatWithEYaw(R, eulerYaw, Rout); return Rout; }
    inline Rotmat RotmatWithFYaw(const Rotmat& R, double fusedYaw) { Rotmat Rout; RotmatWithFYaw(R, fusedYaw, Rout); return Rout; }

    // Rotation with given yaw: Quaternion
    void QuatWithEYaw(const Quat& q, double eulerYaw, Quat& qout);
    void QuatWithFYaw(const Quat& q, double fusedYaw, Quat& qout);
    inline Quat QuatWithEYaw(const Quat& q, double eulerYaw) { Quat qout; QuatWithEYaw(q, eulerYaw, qout); return qout; }
    inline Quat QuatWithFYaw(const Quat& q, double fusedYaw) { Quat qout; QuatWithFYaw(q, fusedYaw, qout); return qout; }

    // Rotation with given yaw: Euler angles
    inline void EulerWithEYaw(const EulerAngles& e, double eulerYaw, EulerAngles& eout) { eout.set(eulerYaw, e.pitch, e.roll); }
    void EulerWithFYaw(const EulerAngles& e, double fusedYaw, EulerAngles& eout);
    inline EulerAngles EulerWithEYaw(const EulerAngles& e, double eulerYaw) { return EulerAngles(eulerYaw, e.pitch, e.roll); }
    inline EulerAngles EulerWithFYaw(const EulerAngles& e, double fusedYaw) { EulerAngles eout; EulerWithFYaw(e, fusedYaw, eout); return eout; }

    // Rotation with given yaw: Fused angles
    void FusedWithEYaw(const FusedAngles& f, double eulerYaw, FusedAngles& fout);
    inline void FusedWithFYaw(const FusedAngles& f, double fusedYaw, FusedAngles& fout) { fout.set(fusedYaw, f.fusedPitch, f.fusedRoll, f.hemi); }
    inline FusedAngles FusedWithEYaw(const FusedAngles& f, double eulerYaw) { FusedAngles fout; FusedWithEYaw(f, eulerYaw, fout); return fout; }
    inline FusedAngles FusedWithFYaw(const FusedAngles& f, double fusedYaw) { return FusedAngles(fusedYaw, f.fusedPitch, f.fusedRoll, f.hemi); }

    // Rotation with given yaw: Tilt angles
    void TiltWithEYaw(const TiltAngles& t, double eulerYaw, TiltAngles& tout);
    inline void TiltWithFYaw(const TiltAngles& t, double fusedYaw, TiltAngles& tout) { tout.set(fusedYaw, t.tiltAxisAngle, t.tiltAngle); }
    inline TiltAngles TiltWithEYaw(const TiltAngles& t, double eulerYaw) { TiltAngles tout; TiltWithEYaw(t, eulerYaw, tout); return tout; }
    inline TiltAngles TiltWithFYaw(const TiltAngles& t, double fusedYaw) { return TiltAngles(fusedYaw, t.tiltAxisAngle, t.tiltAngle); }

    // ###########################
    // #### Rotation inverses ####
    // ###########################

    // Inverse: Rotation matrix
    void RotmatInv(const Rotmat& R, Rotmat& Rinv);
    inline Rotmat RotmatInv(const Rotmat& R) { Rotmat Rinv; RotmatInv(R, Rinv); return Rinv; }

    // Inverse: Quaternion
    void QuatInv(const Quat& q, Quat& qinv);
    inline Quat QuatInv(const Quat& q) { Quat qinv; QuatInv(q, qinv); return qinv; }

    // Inverse: Euler angles
    void EulerInv(const EulerAngles& e, EulerAngles& einv);
    inline EulerAngles EulerInv(const EulerAngles& e) { EulerAngles einv; EulerInv(e, einv); return einv; }

    // Inverse: Fused angles
    void FusedInv(const FusedAngles& f, FusedAngles& finv);
    inline FusedAngles FusedInv(const FusedAngles& f) { FusedAngles finv; FusedInv(f, finv); return finv; }

    // Inverse: Tilt angles
    void TiltInv(const TiltAngles& t, TiltAngles& tinv);
    inline TiltAngles TiltInv(const TiltAngles& t) { TiltAngles tinv; TiltInv(t, tinv); return tinv; }

    // ##########################
    // #### Vector rotations ####
    // ##########################

    // Rotate vector by: Rotation matrix
    Vec3 RotmatRotVec(const Rotmat& R, const Vec3& v);
    void RotmatRotVecInPlace(const Rotmat& R, Vec3& v);
    Vec3 RotmatRotVecPureZ(const Rotmat& R, double vz);

    // Rotate vector by: Quaternion
    Vec3 QuatRotVec(const Quat& q, const Vec3& v);
    void QuatRotVecInPlace(const Quat& q, Vec3& v);
    Vec3 QuatRotVecPureZ(const Quat& q, double vz);

    // Rotate vector by: Euler angles
    Vec3 EulerRotVec(const EulerAngles& e, const Vec3& v);
    void EulerRotVecInPlace(const EulerAngles& e, Vec3& v);
    Vec3 EulerRotVecPureZ(const EulerAngles& e, double vz);

    // Rotate vector by: Fused angles
    Vec3 FusedRotVec(const FusedAngles& f, const Vec3& v);
    void FusedRotVecInPlace(const FusedAngles& f, Vec3& v);
    Vec3 FusedRotVecPureZ(const FusedAngles& f, double vz);

    // Rotate vector by: Tilt angles
    Vec3 TiltRotVec(const TiltAngles& t, const Vec3& v);
    void TiltRotVecInPlace(const TiltAngles& t, Vec3& v);
    Vec3 TiltRotVecPureZ(const TiltAngles& t, double vz);

    // #########################################
    // #### Rotation about global unit axis ####
    // #########################################

    // Rotate about global unit axis: Rotation matrix
    void RotmatRotGlobalX(const Rotmat& R, double angle, Rotmat& Rout);
    void RotmatRotGlobalY(const Rotmat& R, double angle, Rotmat& Rout);
    void RotmatRotGlobalZ(const Rotmat& R, double angle, Rotmat& Rout);
    inline Rotmat RotmatRotGlobalX(const Rotmat& R, double angle) { Rotmat Rout; RotmatRotGlobalX(R, angle, Rout); return Rout; }
    inline Rotmat RotmatRotGlobalY(const Rotmat& R, double angle) { Rotmat Rout; RotmatRotGlobalY(R, angle, Rout); return Rout; }
    inline Rotmat RotmatRotGlobalZ(const Rotmat& R, double angle) { Rotmat Rout; RotmatRotGlobalZ(R, angle, Rout); return Rout; }

    // Rotate about global unit axis: Quaternion
    void QuatRotGlobalX(const Quat& q, double angle, Quat& qout);
    void QuatRotGlobalY(const Quat& q, double angle, Quat& qout);
    void QuatRotGlobalZ(const Quat& q, double angle, Quat& qout);
    inline Quat QuatRotGlobalX(const Quat& q, double angle) { Quat qout; QuatRotGlobalX(q, angle, qout); return qout; }
    inline Quat QuatRotGlobalY(const Quat& q, double angle) { Quat qout; QuatRotGlobalY(q, angle, qout); return qout; }
    inline Quat QuatRotGlobalZ(const Quat& q, double angle) { Quat qout; QuatRotGlobalZ(q, angle, qout); return qout; }

    // Rotate about global unit axis: Euler angles
    void EulerRotGlobalX(const EulerAngles& e, double angle, EulerAngles& eout);
    void EulerRotGlobalY(const EulerAngles& e, double angle, EulerAngles& eout);
    inline void EulerRotGlobalZ(const EulerAngles& e, double angle, EulerAngles& eout) { eout.set(internal::picut(e.yaw + angle), e.pitch, e.roll); }
    inline EulerAngles EulerRotGlobalX(const EulerAngles& e, double angle) { EulerAngles eout; EulerRotGlobalX(e, angle, eout); return eout; }
    inline EulerAngles EulerRotGlobalY(const EulerAngles& e, double angle) { EulerAngles eout; EulerRotGlobalY(e, angle, eout); return eout; }
    inline EulerAngles EulerRotGlobalZ(const EulerAngles& e, double angle) { EulerAngles eout; EulerRotGlobalZ(e, angle, eout); return eout; }

    // Rotate about global unit axis: Fused angles
    void FusedRotGlobalX(const FusedAngles& f, double angle, FusedAngles& fout);
    void FusedRotGlobalY(const FusedAngles& f, double angle, FusedAngles& fout);
    inline void FusedRotGlobalZ(const FusedAngles& f, double angle, FusedAngles& fout) { fout.set(internal::picut(f.fusedYaw + angle), f.fusedPitch, f.fusedRoll, f.hemi); }
    inline FusedAngles FusedRotGlobalX(const FusedAngles& f, double angle) { FusedAngles fout; FusedRotGlobalX(f, angle, fout); return fout; }
    inline FusedAngles FusedRotGlobalY(const FusedAngles& f, double angle) { FusedAngles fout; FusedRotGlobalY(f, angle, fout); return fout; }
    inline FusedAngles FusedRotGlobalZ(const FusedAngles& f, double angle) { FusedAngles fout; FusedRotGlobalZ(f, angle, fout); return fout; }

    // Rotate about global unit axis: Tilt angles
    void TiltRotGlobalX(const TiltAngles& t, double angle, TiltAngles& tout);
    void TiltRotGlobalY(const TiltAngles& t, double angle, TiltAngles& tout);
    inline void TiltRotGlobalZ(const TiltAngles& t, double angle, TiltAngles& tout) { tout.set(internal::picut(t.fusedYaw + angle), t.tiltAxisAngle, t.tiltAngle); }
    inline TiltAngles TiltRotGlobalX(const TiltAngles& t, double angle) { TiltAngles tout; TiltRotGlobalX(t, angle, tout); return tout; }
    inline TiltAngles TiltRotGlobalY(const TiltAngles& t, double angle) { TiltAngles tout; TiltRotGlobalY(t, angle, tout); return tout; }
    inline TiltAngles TiltRotGlobalZ(const TiltAngles& t, double angle) { TiltAngles tout; TiltRotGlobalZ(t, angle, tout); return tout; }

    // #####################################
    // #### Conversions from axis angle ####
    // #####################################

    // Conversion: Axis angle --> Rotation matrix
    void RotmatFromAxis(UnitAxis axis, double angle, Rotmat& R);
    void RotmatFromAxis(const Vec3& axis, double angle, Rotmat& R);
    inline Rotmat RotmatFromAxis(UnitAxis axis, double angle) { Rotmat R; RotmatFromAxis(axis, angle, R); return R; }
    inline Rotmat RotmatFromAxis(const Vec3& axis, double angle) { Rotmat R; RotmatFromAxis(axis, angle, R); return R; }

    // Conversion: Axis angle --> Quaternion
    void QuatFromAxis(UnitAxis axis, double angle, Quat& q);
    void QuatFromAxis(const Vec3& axis, double angle, Quat& q);
    inline Quat QuatFromAxis(UnitAxis axis, double angle) { Quat q; QuatFromAxis(axis, angle, q); return q; }
    inline Quat QuatFromAxis(const Vec3& axis, double angle) { Quat q; QuatFromAxis(axis, angle, q); return q; }

    // Conversion: Axis angle --> Euler angles
    void EulerFromAxis(UnitAxis axis, double angle, EulerAngles& e);
    void EulerFromAxis(const Vec3& axis, double angle, EulerAngles& e);
    inline EulerAngles EulerFromAxis(UnitAxis axis, double angle) { EulerAngles e; EulerFromAxis(axis, angle, e); return e; }
    inline EulerAngles EulerFromAxis(const Vec3& axis, double angle) { EulerAngles e; EulerFromAxis(axis, angle, e); return e; }

    // Conversion: Axis angle --> Fused angles
    void FusedFromAxis(UnitAxis axis, double angle, FusedAngles& f);
    void FusedFromAxis(const Vec3& axis, double angle, FusedAngles& f);
    inline FusedAngles FusedFromAxis(UnitAxis axis, double angle) { FusedAngles f; FusedFromAxis(axis, angle, f); return f; }
    inline FusedAngles FusedFromAxis(const Vec3& axis, double angle) { FusedAngles f; FusedFromAxis(axis, angle, f); return f; }

    // Conversion: Axis angle --> Tilt angles
    void TiltFromAxis(UnitAxis axis, double angle, TiltAngles& t);
    void TiltFromAxis(const Vec3& axis, double angle, TiltAngles& t);
    inline TiltAngles TiltFromAxis(UnitAxis axis, double angle) { TiltAngles t; TiltFromAxis(axis, angle, t); return t; }
    inline TiltAngles TiltFromAxis(const Vec3& axis, double angle) { TiltAngles t; TiltFromAxis(axis, angle, t); return t; }

    // Conversion: Axis angle --> Z vector
    void ZVecFromAxis(UnitAxis axis, double angle, ZVec& z);
    void ZVecFromAxis(const Vec3& axis, double angle, ZVec& z);
    inline ZVec ZVecFromAxis(UnitAxis axis, double angle) { ZVec z; ZVecFromAxis(axis, angle, z); return z; }
    inline ZVec ZVecFromAxis(const Vec3& axis, double angle) { ZVec z; ZVecFromAxis(axis, angle, z); return z; }

    // ############################################
    // #### Conversions from rotation matrices ####
    // ############################################

    // Conversion: Rotation matrix --> Axes
    inline void AxisXFromRotmat(const Rotmat& R, Vec3& axis) { axis = R.col(0); }
    inline void AxisYFromRotmat(const Rotmat& R, Vec3& axis) { axis = R.col(1); }
    inline void AxisZFromRotmat(const Rotmat& R, Vec3& axis) { axis = R.col(2); }
    inline Vec3 AxisXFromRotmat(const Rotmat& R) { return R.col(0); }
    inline Vec3 AxisYFromRotmat(const Rotmat& R) { return R.col(1); }
    inline Vec3 AxisZFromRotmat(const Rotmat& R) { return R.col(2); }

    // Conversion: Rotation matrix --> Quaternion
    void QuatFromRotmat(const Rotmat& R, Quat& q);
    inline Quat QuatFromRotmat(const Rotmat& R) { Quat q; QuatFromRotmat(R, q); return q; }

    // Conversion: Rotation matrix --> Euler angles
    inline void EulerFromRotmat(const Rotmat& R, double& yaw) { yaw = EYawOfRotmat(R); }
    void EulerFromRotmat(const Rotmat& R, double& yaw, double& pitch, double& roll);
    inline void EulerFromRotmat(const Rotmat& R, EulerAngles& e) { EulerFromRotmat(R, e.yaw, e.pitch, e.roll); }
    inline EulerAngles EulerFromRotmat(const Rotmat& R) { EulerAngles e; EulerFromRotmat(R, e.yaw, e.pitch, e.roll); return e; }

    // Conversion: Rotation matrix --> Fused angles
    inline void FusedFromRotmat(const Rotmat& R, double& fusedYaw) { fusedYaw = FYawOfRotmat(R); }
    void FusedFromRotmat(const Rotmat& R, double& fusedPitch, double& fusedRoll);
    void FusedFromRotmat(const Rotmat& R, double& fusedYaw, double& fusedPitch, double& fusedRoll);
    void FusedFromRotmat(const Rotmat& R, double& fusedYaw, double& fusedPitch, double& fusedRoll, bool& hemi);
    inline void FusedFromRotmat(const Rotmat& R, FusedAngles& f) { FusedFromRotmat(R, f.fusedYaw, f.fusedPitch, f.fusedRoll, f.hemi); }
    inline FusedAngles FusedFromRotmat(const Rotmat& R) { FusedAngles f; FusedFromRotmat(R, f.fusedYaw, f.fusedPitch, f.fusedRoll, f.hemi); return f; }

    // Conversion: Rotation matrix --> Tilt angles
    inline void TiltFromRotmat(const Rotmat& R, double& fusedYaw) { fusedYaw = FYawOfRotmat(R); }
    void TiltFromRotmat(const Rotmat& R, double& tiltAxisAngle, double& tiltAngle);
    void TiltFromRotmat(const Rotmat& R, double& fusedYaw, double& tiltAxisAngle, double& tiltAngle);
    inline void TiltFromRotmat(const Rotmat& R, TiltAngles& t) { TiltFromRotmat(R, t.fusedYaw, t.tiltAxisAngle, t.tiltAngle); }
    inline TiltAngles TiltFromRotmat(const Rotmat& R) { TiltAngles t; TiltFromRotmat(R, t.fusedYaw, t.tiltAxisAngle, t.tiltAngle); return t; }

    // Conversion: Rotation matrix --> Z vector
    inline void ZVecFromRotmat(const Rotmat& R, ZVec& z) { z = R.row(2); }
    inline ZVec ZVecFromRotmat(const Rotmat& R) { return R.row(2); }

    // ######################################
    // #### Conversions from quaternions ####
    // ######################################

    // Conversion: Quaternion --> Axes
    void AxisXFromQuat(const Quat& q, Vec3& axis);
    void AxisYFromQuat(const Quat& q, Vec3& axis);
    void AxisZFromQuat(const Quat& q, Vec3& axis);
    inline Vec3 AxisXFromQuat(const Quat& q) { Vec3 axis; AxisXFromQuat(q, axis); return axis; }
    inline Vec3 AxisYFromQuat(const Quat& q) { Vec3 axis; AxisYFromQuat(q, axis); return axis; }
    inline Vec3 AxisZFromQuat(const Quat& q) { Vec3 axis; AxisZFromQuat(q, axis); return axis; }

    // Conversion: Quaternion --> Rotation matrix
    void RotmatFromQuat(const Quat& q, Rotmat& R);
    inline Rotmat RotmatFromQuat(const Quat& q) { Rotmat R; RotmatFromQuat(q, R); return R; }

    // Conversion: Quaternion --> Euler angles
    inline void EulerFromQuat(const Quat& q, double& yaw) { yaw = EYawOfQuat(q); }
    void EulerFromQuat(const Quat& q, double& yaw, double& pitch, double& roll);
    inline void EulerFromQuat(const Quat& q, EulerAngles& e) { EulerFromQuat(q, e.yaw, e.pitch, e.roll); }
    inline EulerAngles EulerFromQuat(const Quat& q) { EulerAngles e; EulerFromQuat(q, e.yaw, e.pitch, e.roll); return e; }

    // Conversion: Quaternion --> Fused angles
    inline void FusedFromQuat(const Quat& q, double& fusedYaw) { fusedYaw = FYawOfQuat(q); }
    void FusedFromQuat(const Quat& q, double& fusedPitch, double& fusedRoll);
    void FusedFromQuat(const Quat& q, double& fusedYaw, double& fusedPitch, double& fusedRoll);
    void FusedFromQuat(const Quat& q, double& fusedYaw, double& fusedPitch, double& fusedRoll, bool& hemi);
    inline void FusedFromQuat(const Quat& q, FusedAngles& f) { FusedFromQuat(q, f.fusedYaw, f.fusedPitch, f.fusedRoll, f.hemi); }
    inline FusedAngles FusedFromQuat(const Quat& q) { FusedAngles f; FusedFromQuat(q, f.fusedYaw, f.fusedPitch, f.fusedRoll, f.hemi); return f; }

    // Conversion: Quaternion --> Tilt angles
    inline void TiltFromQuat(const Quat& q, double& fusedYaw) { fusedYaw = FYawOfQuat(q); }
    void TiltFromQuat(const Quat& q, double& tiltAxisAngle, double& tiltAngle);
    void TiltFromQuat(const Quat& q, double& fusedYaw, double& tiltAxisAngle, double& tiltAngle);
    inline void TiltFromQuat(const Quat& q, TiltAngles& t) { TiltFromQuat(q, t.fusedYaw, t.tiltAxisAngle, t.tiltAngle); }
    inline TiltAngles TiltFromQuat(const Quat& q) { TiltAngles t; TiltFromQuat(q, t.fusedYaw, t.tiltAxisAngle, t.tiltAngle); return t; }

    // Conversion: Quaternion --> Z vector
    void ZVecFromQuat(const Quat& q, ZVec& z);
    inline ZVec ZVecFromQuat(const Quat& q) { ZVec z; ZVecFromQuat(q, z); return z; }

    // #######################################
    // #### Conversions from Euler angles ####
    // #######################################

    // Conversion: Euler angles --> Rotation matrix
    inline Rotmat RotmatFromEuler(double yaw) { return RotmatFromAxis(Z_AXIS, yaw); }
    Rotmat RotmatFromEuler(double yaw, double pitch, double roll);
    inline Rotmat RotmatFromEuler(const EulerAngles& e) { return RotmatFromEuler(e.yaw, e.pitch, e.roll); }

    // Conversion: Euler angles --> Quaternion
    inline Quat QuatFromEuler(double yaw) { return QuatFromAxis(Z_AXIS, yaw); }
    Quat QuatFromEuler(double yaw, double pitch, double roll);
    inline Quat QuatFromEuler(const EulerAngles& e) { return QuatFromEuler(e.yaw, e.pitch, e.roll); }

    // Conversion: Euler angles --> Fused angles
    inline FusedAngles FusedFromEuler(double yaw) { return FusedAngles(yaw); }
    FusedAngles FusedFromEuler(double yaw, double pitch, double roll);
    inline FusedAngles FusedFromEuler(const EulerAngles& e) { return FusedFromEuler(e.yaw, e.pitch, e.roll); }

    // Conversion: Euler angles --> Tilt angles
    inline TiltAngles TiltFromEuler(double yaw) { return TiltAngles(yaw); }
    TiltAngles TiltFromEuler(double yaw, double pitch, double roll);
    inline TiltAngles TiltFromEuler(const EulerAngles& e) { return TiltFromEuler(e.yaw, e.pitch, e.roll); }

    // Conversion: Euler angles --> Z vector
    ZVec ZVecFromEuler(double pitch, double roll);
    inline ZVec ZVecFromEuler(const EulerAngles& e) { return ZVecFromEuler(e.pitch, e.roll); }

    // #######################################
    // #### Conversions from fused angles ####
    // #######################################

    // Conversion: Fused angles --> Rotation matrix
    inline Rotmat RotmatFromFused(double fusedYaw) { return RotmatFromAxis(Z_AXIS, fusedYaw); }
    Rotmat RotmatFromFused(double fusedPitch, double fusedRoll);
    Rotmat RotmatFromFused(double fusedYaw, double fusedPitch, double fusedRoll, bool hemi = true);
    inline Rotmat RotmatFromFused(const FusedAngles& f) { return RotmatFromFused(f.fusedYaw, f.fusedPitch, f.fusedRoll, f.hemi); }

    // Conversion: Fused angles --> Quaternion
    inline Quat QuatFromFused(double fusedYaw) { return QuatFromAxis(Z_AXIS, fusedYaw); }
    Quat QuatFromFused(double fusedPitch, double fusedRoll);
    Quat QuatFromFused(double fusedYaw, double fusedPitch, double fusedRoll, bool hemi = true);
    inline Quat QuatFromFused(const FusedAngles& f) { return QuatFromFused(f.fusedYaw, f.fusedPitch, f.fusedRoll, f.hemi); }

    // Conversion: Fused angles --> Euler angles
    inline EulerAngles EulerFromFused(double fusedYaw) { return EulerAngles(fusedYaw); }
    EulerAngles EulerFromFused(double fusedPitch, double fusedRoll);
    EulerAngles EulerFromFused(double fusedYaw, double fusedPitch, double fusedRoll, bool hemi = true);
    inline EulerAngles EulerFromFused(const FusedAngles& f) { return EulerFromFused(f.fusedYaw, f.fusedPitch, f.fusedRoll, f.hemi); }

    // Conversion: Fused angles --> Tilt angles
    inline TiltAngles TiltFromFused(double fusedYaw) { return TiltAngles(fusedYaw); }
    TiltAngles TiltFromFused(double fusedPitch, double fusedRoll);
    TiltAngles TiltFromFused(double fusedYaw, double fusedPitch, double fusedRoll, bool hemi = true);
    inline TiltAngles TiltFromFused(const FusedAngles& f) { return TiltFromFused(f.fusedYaw, f.fusedPitch, f.fusedRoll, f.hemi); }
    double TiltAngleFromFused(double fusedPitch, double fusedRoll);

    // Conversion: Fused angles --> Z vector
    ZVec ZVecFromFused(double fusedPitch, double fusedRoll, bool hemi = true);
    inline ZVec ZVecFromFused(const FusedAngles& f) { return ZVecFromFused(f.fusedPitch, f.fusedRoll, f.hemi); }

    // ######################################
    // #### Conversions from tilt angles ####
    // ######################################

    // Conversion: Tilt angles --> Rotation matrix
    inline Rotmat RotmatFromTilt(double fusedYaw) { return RotmatFromAxis(Z_AXIS, fusedYaw); }
    Rotmat RotmatFromTilt(double tiltAxisAngle, double tiltAngle);
    Rotmat RotmatFromTilt(double fusedYaw, double tiltAxisAngle, double tiltAngle);
    inline Rotmat RotmatFromTilt(const TiltAngles& t) { return RotmatFromTilt(t.fusedYaw, t.tiltAxisAngle, t.tiltAngle); }

    // Conversion: Tilt angles --> Quaternion
    inline Quat QuatFromTilt(double fusedYaw) { return QuatFromAxis(Z_AXIS, fusedYaw); }
    Quat QuatFromTilt(double tiltAxisAngle, double tiltAngle);
    Quat QuatFromTilt(double fusedYaw, double tiltAxisAngle, double tiltAngle);
    inline Quat QuatFromTilt(const TiltAngles& t) { return QuatFromTilt(t.fusedYaw, t.tiltAxisAngle, t.tiltAngle); }

    // Conversion: Tilt angles --> Euler angles
    inline EulerAngles EulerFromTilt(double fusedYaw) { return EulerAngles(fusedYaw); }
    EulerAngles EulerFromTilt(double tiltAxisAngle, double tiltAngle);
    EulerAngles EulerFromTilt(double fusedYaw, double tiltAxisAngle, double tiltAngle);
    inline EulerAngles EulerFromTilt(const TiltAngles& t) { return EulerFromTilt(t.fusedYaw, t.tiltAxisAngle, t.tiltAngle); }

    // Conversion: Tilt angles --> Fused angles
    inline FusedAngles FusedFromTilt(double fusedYaw) { return FusedAngles(fusedYaw); }
    FusedAngles FusedFromTilt(double tiltAxisAngle, double tiltAngle);
    FusedAngles FusedFromTilt(double fusedYaw, double tiltAxisAngle, double tiltAngle);
    inline FusedAngles FusedFromTilt(const TiltAngles& t) { return FusedFromTilt(t.fusedYaw, t.tiltAxisAngle, t.tiltAngle); }

    // Conversion: Tilt angles --> Z vector
    ZVec ZVecFromTilt(double tiltAxisAngle, double tiltAngle);
    inline ZVec ZVecFromTilt(const TiltAngles& t) { return ZVecFromTilt(t.tiltAxisAngle, t.tiltAngle); }

    // ####################################
    // #### Conversions from Z vectors ####
    // ####################################

    // Conversion: Z vector --> Rotation matrix (zero fused yaw)
    void RotmatFromZVec(const ZVec& z, Rotmat& R);
    inline Rotmat RotmatFromZVec(const ZVec& z) { Rotmat R; RotmatFromZVec(z, R); return R; }

    // Conversion: Z vector --> Quaternion (zero fused yaw)
    void QuatFromZVec(const ZVec& z, Quat& q);
    inline Quat QuatFromZVec(const ZVec& z) { Quat q; QuatFromZVec(z, q); return q; }

    // Conversion: Z vector --> Euler angles (zero Euler yaw)
    void EulerFromZVec(const ZVec& z, double& pitch, double& roll);
    inline void EulerFromZVec(const ZVec& z, EulerAngles& e) { EulerFromZVec(z, e.pitch, e.roll); e.yaw = 0.0; }
    inline EulerAngles EulerFromZVec(const ZVec& z) { EulerAngles e; EulerFromZVec(z, e.pitch, e.roll); e.yaw = 0.0; return e; }

    // Conversion: Z vector --> Fused angles (zero fused yaw)
    void FusedFromZVec(const ZVec& z, double& fusedPitch, double& fusedRoll);
    void FusedFromZVec(const ZVec& z, double& fusedPitch, double& fusedRoll, bool& hemi);
    inline void FusedFromZVec(const ZVec& z, FusedAngles& f) { FusedFromZVec(z, f.fusedPitch, f.fusedRoll, f.hemi); f.fusedYaw = 0.0; }
    inline FusedAngles FusedFromZVec(const ZVec& z) { FusedAngles f; FusedFromZVec(z, f.fusedPitch, f.fusedRoll, f.hemi); f.fusedYaw = 0.0; return f; }

    // Conversion: Z vector --> Tilt angles (zero fused yaw)
    void TiltFromZVec(const ZVec& z, double& tiltAxisAngle, double& tiltAngle);
    inline void TiltFromZVec(const ZVec& z, TiltAngles& t) { TiltFromZVec(z, t.tiltAxisAngle, t.tiltAngle); t.fusedYaw = 0.0; }
    inline TiltAngles TiltFromZVec(const ZVec& z) { TiltAngles t; TiltFromZVec(z, t.tiltAxisAngle, t.tiltAngle); t.fusedYaw = 0.0; return t; }

    // #########################################
    // #### Conversions from yaw and z-axis ####
    // #########################################

    // Conversion: Yaw and z-axis --> Rotation matrix
    void RotmatFromFYawBzG(double fusedYaw, const Vec3& BzG, Rotmat& RGB);
    void RotmatFromFYawGzB(double fusedYaw, const Vec3& GzB, Rotmat& RGB);
    inline Rotmat RotmatFromFYawBzG(double fusedYaw, const Vec3& BzG) { Rotmat RGB; RotmatFromFYawBzG(fusedYaw, BzG, RGB); return RGB; }
    inline Rotmat RotmatFromFYawGzB(double fusedYaw, const Vec3& GzB) { Rotmat RGB; RotmatFromFYawGzB(fusedYaw, GzB, RGB); return RGB; }

    // Conversion: Yaw and z-axis --> Quaternion
    void QuatFromFYawBzG(double fusedYaw, const Vec3& BzG, Quat& qGB);
    void QuatFromFYawGzB(double fusedYaw, const Vec3& GzB, Quat& qGB);
    inline Quat QuatFromFYawBzG(double fusedYaw, const Vec3& BzG) { Quat qGB; QuatFromFYawBzG(fusedYaw, BzG, qGB); return qGB; }
    inline Quat QuatFromFYawGzB(double fusedYaw, const Vec3& GzB) { Quat qGB; QuatFromFYawGzB(fusedYaw, GzB, qGB); return qGB; }

    // Conversion: Yaw and z-axis --> Euler angles
    void EulerFromFYawBzG(double fusedYaw, const Vec3& BzG, EulerAngles& eGB);
    void EulerFromFYawGzB(double fusedYaw, const Vec3& GzB, EulerAngles& eGB);
    inline EulerAngles EulerFromFYawBzG(double fusedYaw, const Vec3& BzG) { EulerAngles eGB; EulerFromFYawBzG(fusedYaw, BzG, eGB); return eGB; }
    inline EulerAngles EulerFromFYawGzB(double fusedYaw, const Vec3& GzB) { EulerAngles eGB; EulerFromFYawGzB(fusedYaw, GzB, eGB); return eGB; }

    // Conversion: Yaw and z-axis --> Fused angles
    void FusedFromFYawBzG(double fusedYaw, const Vec3& BzG, FusedAngles& fGB);
    void FusedFromFYawGzB(double fusedYaw, const Vec3& GzB, FusedAngles& fGB);
    inline FusedAngles FusedFromFYawBzG(double fusedYaw, const Vec3& BzG) { FusedAngles fGB; FusedFromFYawBzG(fusedYaw, BzG, fGB); return fGB; }
    inline FusedAngles FusedFromFYawGzB(double fusedYaw, const Vec3& GzB) { FusedAngles fGB; FusedFromFYawGzB(fusedYaw, GzB, fGB); return fGB; }

    // Conversion: Yaw and z-axis --> Tilt angles
    void TiltFromFYawBzG(double fusedYaw, const Vec3& BzG, TiltAngles& tGB);
    void TiltFromFYawGzB(double fusedYaw, const Vec3& GzB, TiltAngles& tGB);
    inline TiltAngles TiltFromFYawBzG(double fusedYaw, const Vec3& BzG) { TiltAngles tGB; TiltFromFYawBzG(fusedYaw, BzG, tGB); return tGB; }
    inline TiltAngles TiltFromFYawGzB(double fusedYaw, const Vec3& GzB) { TiltAngles tGB; TiltFromFYawGzB(fusedYaw, GzB, tGB); return tGB; }

    // ########################################
    // #### Spherical Linear Interpolation ####
    // ########################################

    // Slerp: Quaternion
    Quat QuatSlerp(const Quat& q0, const Quat& q1, double u);
    Quat QuatSlerp(const Quat& q, double u);

    // Slerp: Unit vector
    Vec3 VecSlerp(const Vec3& v0, const Vec3& v1, double u);

    // ##############################
    // #### Tilt phase functions ####
    // ##############################

    // Helper functions
    namespace internal
    {
        // Sum of tilt phases
        inline void TiltPhaseSumHelper(TiltPhase2D&) {}
        inline void TiltPhaseSumHelper(TiltPhase3D&) {}
        template<typename... Types> void TiltPhaseSumHelper(TiltPhase2D& pout, const TiltPhase2D& p, Types&&... args);
        template<typename... Types> void TiltPhaseSumHelper(TiltPhase3D& pout, const TiltPhase3D& p, Types&&... args);

        // Sum of tilt phase velocities
        inline void TiltPhaseVelSumHelper(TiltPhaseVel2D&) {}
        inline void TiltPhaseVelSumHelper(TiltPhaseVel3D&) {}
        template<typename... Types> void TiltPhaseVelSumHelper(TiltPhaseVel2D& pdotout, const TiltPhaseVel2D& pdot, Types&&... args);
        template<typename... Types> void TiltPhaseVelSumHelper(TiltPhaseVel3D& pdotout, const TiltPhaseVel3D& pdot, Types&&... args);
    }

    // Sum of tilt phases (alternative to operator+)
    template<typename... Types> TiltPhase2D TiltPhaseSum(const TiltPhase2D& p, Types&&... args);
    template<typename... Types> TiltPhase3D TiltPhaseSum(const TiltPhase3D& p, Types&&... args);

    // Mean of tilt phases
    template<typename... Types> TiltPhase2D TiltPhaseMean(const TiltPhase2D& p, Types&&... args);
    template<typename... Types> TiltPhase3D TiltPhaseMean(const TiltPhase3D& p, Types&&... args);

    // Sum of tilt phase velocities (alternative to operator+)
    template<typename... Types> TiltPhaseVel2D TiltPhaseVelSum(const TiltPhaseVel2D& pdot, Types&&... args);
    template<typename... Types> TiltPhaseVel3D TiltPhaseVelSum(const TiltPhaseVel3D& pdot, Types&&... args);

    // Conversion: Tilt phase velocity --> Angular velocity
    void AngVelFromTiltPhaseVel(const TiltPhaseVel2D& pdot, const TiltAngles& t, AngVel& angVel);
    void AngVelFromTiltPhaseVel(const TiltPhaseVel3D& pdot, const TiltAngles& t, AngVel& angVel);
    inline AngVel AngVelFromTiltPhaseVel(const TiltPhaseVel2D& pdot, const TiltAngles& t) { AngVel angVel; AngVelFromTiltPhaseVel(pdot, t, angVel); return angVel; }
    inline AngVel AngVelFromTiltPhaseVel(const TiltPhaseVel3D& pdot, const TiltAngles& t) { AngVel angVel; AngVelFromTiltPhaseVel(pdot, t, angVel); return angVel; }

    // #######################
    // #### Miscellaneous ####
    // #######################

    // Conversion: Split yaw and tilt --> Quaternion
    Quat QuatHFromFYawGTiltH(const Quat& qGH, double fusedYawG, const Quat& qH);
    inline Quat QuatHFromFYawGTiltH(const Quat& qGH, double fusedYawG, const TiltAngles& tH) { Quat qH = QuatFromTilt(tH.tiltAxisAngle, tH.tiltAngle); return QuatHFromFYawGTiltH(qGH, fusedYawG, qH); }
    inline Quat QuatHFromFYawGTiltH(const Quat& qGH, double fusedYawG, double tiltAxisAngleH, double tiltAngleH) { Quat qH = QuatFromTilt(tiltAxisAngleH, tiltAngleH); return QuatHFromFYawGTiltH(qGH, fusedYawG, qH); }
    inline Quat QuatGFromFYawGTiltH(const Quat& qGH, double fusedYawG, const Quat& qH) { return qGH * QuatHFromFYawGTiltH(qGH, fusedYawG, qH); }
    inline Quat QuatGFromFYawGTiltH(const Quat& qGH, double fusedYawG, const TiltAngles& tH) { Quat qH = QuatFromTilt(tH.tiltAxisAngle, tH.tiltAngle); return qGH * QuatHFromFYawGTiltH(qGH, fusedYawG, qH); }
    inline Quat QuatGFromFYawGTiltH(const Quat& qGH, double fusedYawG, double tiltAxisAngleH, double tiltAngleH) { Quat qH = QuatFromTilt(tiltAxisAngleH, tiltAngleH); return qGH * QuatHFromFYawGTiltH(qGH, fusedYawG, qH); }
}

// Include implementations that should occur in the header
#include <rot_conv/rot_conv_impl.h>

#endif
// EOF