|
| 1 | +# Representing rotations by quaternions |
| 2 | + |
| 3 | +```@setup quat |
| 4 | +using DeformableBodies |
| 5 | +``` |
| 6 | + |
| 7 | +The most usual ways to represent a rotation in three-dimensional space |
| 8 | +are [rotations matrices](https://en.wikipedia.org/wiki/Rotation_matrix) |
| 9 | +or [Euler angles](https://en.wikipedia.org/wiki/Euler_angles). |
| 10 | +In this package, nevertheless, |
| 11 | +they are represented using unit quaternions. |
| 12 | + |
| 13 | +Quaternions are a four-dimensional non-commutative algebra |
| 14 | +with the property that any rotation may be represented by a quaternion of norm 1. |
| 15 | +Some of their advantages consist in that we only need 4 parameters to represent a quaternion |
| 16 | +(as opposed to the 9 needed by rotation matrices) |
| 17 | +and that they do not suffer from the phenomenon of |
| 18 | +[gimbal lock](https://en.wikipedia.org/wiki/Gimbal_lock) |
| 19 | +(as opposed to Euler angles). |
| 20 | + |
| 21 | +If a quaternion ``q`` represents a rotation matrix ``R``, |
| 22 | +their action on a vector is defined as |
| 23 | +```math |
| 24 | +R v = q v q^{-1}. |
| 25 | +``` |
| 26 | + |
| 27 | +!!! warning |
| 28 | + This package assumes that your coordinate system follows the right-hand rule. |
| 29 | + If, for whatever reason, you want to use a left-handed system, |
| 30 | + ``q`` and ``q^{-1}`` must be transposed on the above formula. |
| 31 | + |
| 32 | +## The `rotate` function |
| 33 | + |
| 34 | +All rotations on `DeformableBodies.jl` are done by the function [`rotate`](@ref). |
| 35 | +Its signature consists of |
| 36 | + |
| 37 | +```julia |
| 38 | +rotate(v::Vector, q::Quaternion, center::Vector) |
| 39 | +``` |
| 40 | + |
| 41 | +This function rotates the vector `v` by the rotation represented by `q` |
| 42 | +in the frame of reference centered on `center`. |
| 43 | + |
| 44 | +If the third argument is left blank, it defaults to the origin. |
| 45 | + |
| 46 | +```@repl quat |
| 47 | +v = [3,0,0] |
| 48 | +q = Quaternion(1,2,3,4) |
| 49 | +rotate(v, q, [0,0,0]) |
| 50 | +rotate(v, q) |
| 51 | +``` |
| 52 | + |
| 53 | +## Representing the identity rotation |
| 54 | +The identity rotation is the matrix ``I`` satisfying ``I v = v`` |
| 55 | +for all ``v``. |
| 56 | +This is represented by the quaternion ``1``, |
| 57 | +which can be gotten using Julia's multiple dispatch via the expression |
| 58 | +`one(Quaternion)`. |
| 59 | + |
| 60 | +```@repl quat |
| 61 | +q = one(Quaternion) |
| 62 | +rotate([1,2,3], q) |
| 63 | +``` |
| 64 | + |
| 65 | +## Composition of rotations |
| 66 | + |
| 67 | +The composition of rotations translates to the quaternion world |
| 68 | +as the product of quaternions. |
| 69 | +Thus, it is the same to rotate a vector by ``q_1`` and then by ``q_2`` |
| 70 | +and to rotate by ``q_2 * q_1``. |
| 71 | + |
| 72 | +```@repl quat |
| 73 | +v = [9, 0, 0] |
| 74 | +q1 = Quaternion(1,2,3,4) |
| 75 | +q2 = Quaternion(4,3,2,1) |
| 76 | +rotate(v, q2*q1) |
| 77 | +rotate(rotate(v, q1), q2) |
| 78 | +``` |
| 79 | + |
| 80 | +!!! note |
| 81 | + Rotations are non-commutative. |
| 82 | + Applying ``q_2`` after ``q_1`` is the same as multiplying |
| 83 | + ``q_2`` to the **left** of ``q_1``. |
| 84 | + |
| 85 | +## Axis-angle representation |
| 86 | + |
| 87 | +An important property of three-dimensional space it that every rotation fixes a line. |
| 88 | +Therefore, they accept an axis-angle representation. |
| 89 | +That is, a rotation ``R`` is defined by a unit vector ``\hat{n}`` and an angle ``\theta`` |
| 90 | +such that ``R`` rotates a vector counterclockwise by an amount of ``\theta`` around the line defined by ``\hat{n}``. |
| 91 | + |
| 92 | +To get the quaternion representing an rotation of `θ` around `n`, |
| 93 | +use the method [`axistoquaternion`](@ref). |
| 94 | +You may pass an unormalized vector and the method will normalize it. |
| 95 | + |
| 96 | +```@repl quat |
| 97 | +q = axistoquaternion(axis = [0,0,2], angle = π/2) |
| 98 | +rotate([1,0,0], q) |
| 99 | +``` |
| 100 | + |
| 101 | +In fact, this combination is so useful that the function `rotate` |
| 102 | +is overloaded to directly convert from an axis-angle pair. |
| 103 | + |
| 104 | +```@repl quat |
| 105 | +rotate([1,0,0], axis=[0,0,1], angle=π/2) |
| 106 | +``` |
| 107 | + |
| 108 | +!!! info |
| 109 | + This version of [`rotate`](@ref) also accepts the optional argument `center`, |
| 110 | + which defaults to the origin. |
| 111 | + ```@repl quat |
| 112 | + rotate([1,0,0], axis=[0,0,1], angle=π/2, center=[0,1,0]) |
| 113 | + ``` |
| 114 | + |
| 115 | +## Converting between matrices and quaternions |
| 116 | + |
| 117 | +Sometimes a rotation may already come represented as a matrix |
| 118 | +or the transition rotations from [`solve!`](@ref) may be needed in matrix form some application. |
| 119 | +To deal with these cases, |
| 120 | +the package provides two auxiliary functions |
| 121 | +[`matrixtoquaternion`](@ref) and [`quaterniontomatrix`](@ref). |
| 122 | + |
| 123 | +To convert a rotation matrix to a unit quaternion, do |
| 124 | +```@repl quat |
| 125 | +R = [cos(π/4) -sin(π/4) 0; |
| 126 | + sin(π/4) cos(π/4) 0; |
| 127 | + 0 0 1] |
| 128 | +R * [sqrt(2), 0, 0] |
| 129 | +q = matrixtoquaternion(R) |
| 130 | +rotate([sqrt(2), 0, 0], q) |
| 131 | +``` |
| 132 | +Some minor differences may occur due to floating-point rounding errors. |
| 133 | + |
| 134 | +!!! danger |
| 135 | + The function [`matrixtoquaternion`](@ref) |
| 136 | + assumes that the input is a _rotation matrix_ |
| 137 | + but, for efficency reasons, no check is done in this regard. |
| 138 | + If you do not make sure beforehand that the matrix is orthogonal, |
| 139 | + bad things may happen. |
| 140 | + |
| 141 | +To convert a quaternion to a matrix simply do |
| 142 | +```@repl quat |
| 143 | +q = Quaternion(1,2,3,4) |
| 144 | +R = quaterniontomatrix(q) |
| 145 | +rotate([3,0,0], q) |
| 146 | +R * [3,0,0] |
| 147 | +``` |
| 148 | + |
| 149 | +The function [`quaterniontomatrix`](@ref) |
| 150 | +works for every quaternion, |
| 151 | +and does not require the input to be a unit quaternion. |
| 152 | + |
| 153 | +!!! note |
| 154 | + There is a **unique** rotation matrix representing a given quaternion |
| 155 | + but there are **two** unit quaternions representing the same matrix. |
| 156 | + |
| 157 | + This means that `quaterniontomatrix ∘ matrixtoquaternion` equals the identity |
| 158 | + (disconsidering floating-point rouding errors) |
| 159 | + but the opposite is not in general true. |
| 160 | + For a simple example. |
| 161 | + ```@repl quat |
| 162 | + q = Quaternion(-1.0) |
| 163 | + (matrixtoquaternion ∘ quaterniontomatrix)(q) |
| 164 | + ``` |
| 165 | + |
| 166 | + Nevertheless, both these quaternions produce the same rotations. |
| 167 | + |
| 168 | +## Rotating a PointMass |
| 169 | + |
| 170 | +All the previous functionalities only require the submodule [`Quaternions`](@ref) |
| 171 | +and work directly with vectors. |
| 172 | +Nevertheless, the models on `DeformableBodies.jl` are constructed with respect |
| 173 | +to the type [`PointMass`](@ref). |
| 174 | +To help with that, |
| 175 | +the function rotate is overloaded to directly rotate the position of a `PointMass` |
| 176 | +without interfering with its mass. |
| 177 | + |
| 178 | +```julia |
| 179 | +rotate(p::PointMass, q::Quaternion, center::Vector) |
| 180 | +rotate(p::PointMass; axis::Vector, angle::Real, center::Vector) |
| 181 | +``` |
| 182 | + |
| 183 | +The usage is identical to the version for vectors including the fact that |
| 184 | +the argument `center` must be a Vector and not another PointMass. |
| 185 | + |
| 186 | +An usual application consists in rotating a body around its center of mass. |
| 187 | +```@repl quat |
| 188 | +body = [ PointMass(rand(), rand(3)) for i in 1:5 ] |
| 189 | +center_of_mass(body) |
| 190 | +q = Quaternion(1,2,3,4) |
| 191 | +rotated = [ rotate(p, q, center_of_mass(body)) for p in body ] |
| 192 | +center_of_mass(rotated) |
| 193 | +``` |
0 commit comments