In a number of my posts I’ve been using properties related to Hopf coordinates and maps without explictly point it out. This is partly because I’m lazy and partly because I don’t know the math very well. Since I’m going to use Hopf coordinates soon I’ll give an informal and inexact overview using my kinda language and notation.

At the same time it makes sense to show some practical implementations of conversion between quaternions and Hopf coordinates and factorizing a quaternion to a rotation pair: torque minimal and a twist.

For this post I’ll be using angle measurements in $\mathbb{R}^3$ instead of my normally preferred in $\mathbb{H}$.


Hopf map


We can represent rotations by unit quaternions which we can associate with the 4D unit sphere $ \left( \mathbb{S}^3 \right) $. The Hopf map $\left(h\right)$ sends a point on $\mathbb{S}^3 $ to a point on the 3D unit sphere $\left(\mathbb{S}^2\right)$.

This section broken down into two parts. The first is just a bullet-point list using geometry and rotations. The second covers the same ground using algebra for brushstrokes of justification.

Rotation brushstrokes

  • We choose a reference direction and the point on the unit sphere in that direction. I’m using $\mathbf{z}=\left(0,0,1\right)$.
  • All directions orthogonal to the reference are in its orthogonal complement. In this case the $\set{\mathbf{xy}}$-plane.
  • Given a 3D rotation represented as unit quaternion $Q$ the rotation can be factored into a pair of rotations. One about the axis in our reference direction $\left(Q_t\right)$, the other about an axis in the orthogonal complement $\left(Q_r\right)$ which compose as $Q=Q_rQ_t$.
  • The Hopf map is simply the point returned by rotating our reference by $Q$ and the result is independent of the $Q_t$ factor.
    • The map sends an infinite number of rotations (all the possible $Q_t$) to each point on the 3D sphere ($p$) and the collection of all them is called a fiber.
    • All rotations about $\mathbf{z}$ are mapped to $\mathbf{z}$. The torque minimal $Q_r$ factor is one.
    • All maximum magnitude rotations are mapped to $-\mathbf{z}$. The torque minimal $Q_t$ factor is one.
    • Generally the value of $h\left(Q\right)=\mathbf{p}$ is uniquely determined by the $Q_r$ factor.

Applying the factors as a global rotation sequence: Rotating $\mathbf{z}$ by any rotation about $\mathbf{z}$ leaves it in place. Then rotating about any axis in the $\set{\mathbf{xy}}$-plane sends it to $\mathbf{p}$.


Algebra brushstrokes


The Hopf map $h: \mathbb{S}^3 \rightarrow \mathbb{S}^2$ can be directly formulated by choosing a reference direction and apply the similarity transform to find the point $\mathbf{p}$. Or more simply: we treat $Q$ as a rotation, use it to rotate $\mathbf{z}$ and the result of the rotation is $\mathbf{p}$:


Given a unit quaternion:


then the Hopf map can be expressed as:


The fiber of $\mathbf{p}$ is the set of all unit quaternions that map to it. Let’s call a rotation about the axis in the reference direction a twist and given the choice of $\mathbf{z}$ we can represent a twist of $\gamma$ as:


where the angle $\gamma$ uniformally parameterizes the 2D unit circle $\left(\mathbb{S}^1\right)$ in the plane spanned by $ \set{ 1,\mathbf{z} } $. Plugging into the map yields:


A straight forward result from our formulation of $h$.

Given some $\mathbf{p}=\left(a,~b,~c\right)$ we can find the torque minimal rotation $\left(Q_r\right)$ that maps (rotates) $\mathbf{z}$ to $\mathbf{p}$:


With the $p$ from $\eqref{Pr}$:


And we can rewrite in terms of a rotation of $\alpha$ about an axis in the $\set{\mathbf{xy}}$-plane parameterized by $\beta$ (awkward choice here is to logically match $\eqref{rotM}$):


So we have a unit quaternion which represents a rotation about an axis in the $\set{\mathbf{xy}}$-plane which we can associate with a point on the 3D unit sphere $\left(\mathbb{S}^2\right)$.


The fiber of $\mathbf{p}$ can then be expressed as the composition of the two rotations $\eqref{rotZ}$ expanded with $\eqref{rotM}$ and $\eqref{rotZp}$:


Simply plugging in the composed rotation into the map gives:


So the result of $h\left(Q\right)$ is determined by the $Q_r$ factor.



Hopf coordinates


Given the above we can represent a rotation a pair of coordinates, one on the unit sphere and another on a unit circle. These coordinates parameterize two rotations which when composed represent the set of all 3D rotations can be parametrized by a triple of angles $\left(\alpha,\beta,\gamma\right)$:

A performance sub-optimal scalar version:

void quat_to_hopf(vec3_t* v, quat_t* q)
{
  float x=q->x, y=q->y, z=q->z, w=q->w;

  // both gamma and alpha can be reduced to single parameter
  // atan by manually performing some argument reduction.
  // Also using a select to logically compute alpha via 
  // asin or acos drops a divide (either way one sqrt is
  // sufficient) by choosing the larger input parameter.
  // Okay beta can be as well...point being the other two
  // are already have less cases to be handled.
  float gamma = 2.f*atan2f(z, w);
  float alpha = 2.f*atan2f(sqrtf(x*x+y*y), sqrtf(w*w+z*z));
  float beta  = atan2f(y*z-w*x, w*y+x*z);

  v->x = alpha;
  v->y = beta;
  v->z = gamma;
}

void hopf_to_quat(quat_t* q, vec3_t* v)
{
  float ha    = 0.5f*v->x;
  float hg    = 0.5f*v->z;
  float b     = v->y;
  float sinHa = sinf(ha);
  float cosHa = cosf(ha);
  
  q->x = sinHa*sinf(hg-b);
  q->y = sinHa*cosf(hg-b);
  q->z = cosHa*sinf(hg);
  q->w = cosHa*cosf(hg);
}



Torque minimal factorization


Given that an input rotation $Q$ can be factor into:


We can solve for $Q_t$ to complete the factorization:


A possible application of such a factorization is to filter and/or cap the twist for a sequence of rotations. Toy scalar code example of the factorization:

typedef struct {
  float mx,my,ms;   // wrt reference direction
  float tz,ts;      // twist
} tmtwist_t;

#define THRESHOLD (ULP1*ULP1)

void quat_to_tmtwist(tmtwist_t* d, quat_t* q)
{
  float x=q->x, y=q->y, z=q->z, w=q->w;
  float t=w*w+z*z;

  // For the full sphere the numerically unstable
  // portion could be improved.
  if (t > THRESHOLD) {
    float s = 1.f/sqrtf(t);
    d->mx = s*(w*x-y*z);
    d->my = s*(w*y+x*z);
    d->ms = s*t;
    d->tz = s*z;
    d->ts = s*w;
  }
  else {
    d->mx = x;
    d->my = y;
    d->ms = 0;
    d->tz = 0;
    d->ts = 1.f;
  }
}

void tmtwist_to_quat(quat_t* q, tmtwist_t* s)
{
  q->x = s->mx*s->ts + s->my*s->tz;
  q->y = s->my*s->ts - s->mx*s->tz;
  q->z = s->ms*s->tz;
  q->w = s->ms*s->ts;
}



References and Footnotes