From ccd3a830a3e74ae78afbd4dc95a43b4377e1c459 Mon Sep 17 00:00:00 2001 From: hyrodium Date: Thu, 1 Dec 2022 20:41:11 +0900 Subject: [PATCH 01/16] remove rotation-realted functions --- src/Quaternion.jl | 89 ----------------------------------------------- 1 file changed, 89 deletions(-) diff --git a/src/Quaternion.jl b/src/Quaternion.jl index d7b97338..18f472a6 100644 --- a/src/Quaternion.jl +++ b/src/Quaternion.jl @@ -199,20 +199,6 @@ Base.:(==)(q::Quaternion, w::Quaternion) = (q.s == w.s) & (q.v1 == w.v1) & (q.v2 angleaxis(q::Quaternion) = angle(q), axis(q) -function Base.angle(q::Quaternion) - Base.depwarn("`Base.angle(::Quaternion)` is deprecated. Please consider using Rotations package instead.", :angle) - 2 * atan(abs_imag(q), real(q)) -end - -function axis(q::Quaternion) - Base.depwarn("`axis(::Quaternion)` is deprecated. Please consider using Rotations package instead.", :axis) - q = sign(q) - s = sin(angle(q) / 2) - abs(s) > 0 ? - [q.v1, q.v2, q.v3] / s : - [1.0, 0.0, 0.0] -end - """ extend_analytic(f, q::Quaternion) @@ -311,81 +297,6 @@ function Base.randn(rng::AbstractRNG, ::Type{Quaternion{T}}) where {T<:AbstractF ) end -## Rotations - -function qrotation(axis::AbstractVector{T}, theta) where {T <: Real} - Base.depwarn("`qrotation(::AbstractVector)` is deprecated. Please consider using Rotations package instead.", :qrotation) - if length(axis) != 3 - error("Must be a 3-vector") - end - normaxis = norm(axis) - if iszero(normaxis) - normaxis = oneunit(normaxis) - theta = zero(theta) - end - s,c = sincos(theta / 2) - scaleby = s / normaxis - Quaternion(c, scaleby * axis[1], scaleby * axis[2], scaleby * axis[3]) -end - -# Variant of the above where norm(rotvec) encodes theta -function qrotation(rotvec::AbstractVector{T}) where {T <: Real} - Base.depwarn("`qrotation(::AbstractVector)` is deprecated. Please consider using Rotations package instead.", :qrotation) - if length(rotvec) != 3 - error("Must be a 3-vector") - end - theta = norm(rotvec) - s,c = sincos(theta / 2) - scaleby = s / (iszero(theta) ? one(theta) : theta) - Quaternion(c, scaleby * rotvec[1], scaleby * rotvec[2], scaleby * rotvec[3]) -end - -function qrotation(dcm::AbstractMatrix{T}) where {T<:Real} - Base.depwarn("`qrotation(::AbstractMatrix)` is deprecated. Please consider using Rotations package instead.", :qrotation) - # See https://arxiv.org/pdf/math/0701759.pdf - a2 = 1 + dcm[1,1] + dcm[2,2] + dcm[3,3] - b2 = 1 + dcm[1,1] - dcm[2,2] - dcm[3,3] - c2 = 1 - dcm[1,1] + dcm[2,2] - dcm[3,3] - d2 = 1 - dcm[1,1] - dcm[2,2] + dcm[3,3] - - if a2 ≥ max(b2, c2, d2) - a = sqrt(a2)/2 - b,c,d = (dcm[3,2]-dcm[2,3])/4a, (dcm[1,3]-dcm[3,1])/4a, (dcm[2,1]-dcm[1,2])/4a - elseif b2 ≥ max(c2, d2) - b = sqrt(b2)/2 - a,c,d = (dcm[3,2]-dcm[2,3])/4b, (dcm[2,1]+dcm[1,2])/4b, (dcm[1,3]+dcm[3,1])/4b - elseif c2 ≥ d2 - c = sqrt(c2)/2 - a,b,d = (dcm[1,3]-dcm[3,1])/4c, (dcm[2,1]+dcm[1,2])/4c, (dcm[3,2]+dcm[2,3])/4c - else - d = sqrt(d2)/2 - a,b,c = (dcm[2,1]-dcm[1,2])/4d, (dcm[1,3]+dcm[3,1])/4d, (dcm[3,2]+dcm[2,3])/4d - end - if a > 0 - return Quaternion(a,b,c,d) - else - return Quaternion(-a,-b,-c,-d) - end -end - -function qrotation(dcm::AbstractMatrix{T}, qa::Quaternion) where {T<:Real} - Base.depwarn("`qrotation(::AbstractMatrix, ::Quaternion)` is deprecated. Please consider using Rotations package instead.", :qrotation) - q = qrotation(dcm) - abs(q-qa) < abs(q+qa) ? q : -q -end - -rotationmatrix(q::Quaternion) = rotationmatrix_normalized(sign(q)) - -function rotationmatrix_normalized(q::Quaternion) - Base.depwarn("`rotationmatrix_normalized(::Quaternion)` is deprecated. Please consider using Rotations package instead.", :rotationmatrix_normalized) - sx, sy, sz = 2q.s * q.v1, 2q.s * q.v2, 2q.s * q.v3 - xx, xy, xz = 2q.v1^2, 2q.v1 * q.v2, 2q.v1 * q.v3 - yy, yz, zz = 2q.v2^2, 2q.v2 * q.v3, 2q.v3^2 - [1 - (yy + zz) xy - sz xz + sy; - xy + sz 1 - (xx + zz) yz - sx; - xz - sy yz + sx 1 - (xx + yy)] -end - """ slerp(qa::Quaternion, qb::Quaternion, t::Real) From 05914a90776c36b10dcbdb1de17ac779e218098d Mon Sep 17 00:00:00 2001 From: hyrodium Date: Thu, 1 Dec 2022 21:17:51 +0900 Subject: [PATCH 02/16] remove exports for rotations --- src/Quaternions.jl | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Quaternions.jl b/src/Quaternions.jl index ae0a4cb3..8de5bec2 100644 --- a/src/Quaternions.jl +++ b/src/Quaternions.jl @@ -13,12 +13,7 @@ module Quaternions QuaternionF64 export quat export imag_part - export angleaxis - export angle - export axis export quatrand export nquatrand - export qrotation - export rotationmatrix export slerp end From f10387117bc8547bb37042fb8250ef476d95d90d Mon Sep 17 00:00:00 2001 From: hyrodium Date: Thu, 1 Dec 2022 21:18:10 +0900 Subject: [PATCH 03/16] fix tests for rotations --- test/Quaternion.jl | 158 ++++++++++++--------------------------------- 1 file changed, 40 insertions(+), 118 deletions(-) diff --git a/test/Quaternion.jl b/test/Quaternion.jl index 1f761340..2e133e8f 100644 --- a/test/Quaternion.jl +++ b/test/Quaternion.jl @@ -452,101 +452,23 @@ Base.:(/)(a::MyReal, b::Real) = a.val / b @inferred(sign(Quaternion(1, 2, 3, 4))) end - @testset "rotations" begin - @testset "qrotation" begin - @test qrotation([0, 0, 0], 1.0) == Quaternion(1.0) # a zero axis should act like zero rotation - @test qrotation([1, 0, 0], 0.0) == Quaternion(1.0) - @test qrotation([0, 0, 0]) == Quaternion(1.0) - qx = qrotation(view([1, 0, 0], :), pi / 4) - @test qx * qx ≈ qrotation([1, 0, 0], pi / 2) - @test qx^2 ≈ qrotation([1, 0, 0], pi / 2) - - # Regression test for - # https://github.com/JuliaGeometry/Quaternions.jl/issues/8#issuecomment-610640094 - # this used to throw an error - @testset "qrotation can handle arbitrary reals" begin - @test qrotation([1, 0, 0], MyReal(1.5)) == qrotation([1, 0, 0], 1.5) - end - end - - @testset "rotationmatrix" begin - theta = pi / 8 - qx = qrotation([1, 0, 0], theta) - c = cos(theta) - s = sin(theta) - Rx = [1 0 0; 0 c -s; 0 s c] - @test rotationmatrix(qx) ≈ Rx - theta = pi / 6 - qy = qrotation([0, 1, 0], theta) - c = cos(theta) - s = sin(theta) - Ry = [c 0 s; 0 1 0; -s 0 c] - @test rotationmatrix(qy) ≈ Ry - theta = 4pi / 3 - qz = qrotation([0, 0, 1], theta) - c = cos(theta) - s = sin(theta) - Rz = [c -s 0; s c 0; 0 0 1] - @test rotationmatrix(qz) ≈ Rz - - @test rotationmatrix(qx * qy * qz) ≈ Rx * Ry * Rz - @test rotationmatrix(qy * qx * qz) ≈ Ry * Rx * Rz - @test rotationmatrix(qz * qx * qy) ≈ Rz * Rx * Ry - - for _ in 1:100 - q1 = nquatrand() - q2 = qrotation(rotationmatrix(q1), q1) - q3 = qrotation(rotationmatrix(q1)) - @test q1 ≈ q2 - @test q2 === q3 || q2 === -q3 - @test real(q3) ≥ 0 - @test abs(q2) ≈ 1 - @test abs(q3) ≈ 1 - end + @testset "slerp" begin + function qrotation(axis, theta) + s, c = sincos(theta / 2) + axis = normalize(axis) + return Quaternion(c, s*axis[1], s*axis[2], s*axis[3]) end - - @testset "angle/axis/angleaxis" begin - @test_throws ErrorException qrotation([0, 1], 0.1) - @test_throws ErrorException qrotation([0, 1, 0, 0], 0.1) - @test_throws ErrorException qrotation([0, 1]) - @test_throws ErrorException qrotation([0, 1, 0, 0]) - @test angle(qrotation([1, 0, 0], 0)) ≈ 0 - @test angle(qrotation([0, 1, 0], pi / 4)) ≈ pi / 4 - @test angle(qrotation([0, 0, 1], pi / 2)) ≈ pi / 2 - - @testset "numerical stability of angle" begin - ax = randn(3) - for θ in [1e-9, π - 1e-9] - q = qrotation(ax, θ) - @test angle(q) ≈ θ - end - end - - @testset "qrotation and angleaxis inverse" begin + @testset "q1=1" begin + a = quat(1, 0, 0, 0.0) + b = quat(0, 0, 0, 1.0) + @test slerp(a, b, 0.0) ≈ a + @test slerp(a, b, 1.0) ≈ b + @test slerp(a, b, 0.5) ≈ qrotation([0, 0, 1], deg2rad(90)) + @test abs(slerp(a, b, 0.0)) ≈ 1 + @test abs(slerp(a, b, 1.0)) ≈ 1 + @test abs(slerp(a, b, 0.5)) ≈ 1 + @testset "scale $scale" for scale in (1, 1e-5, 1e-10) for _ in 1:100 - ax = randn(3) - ax = ax / norm(ax) - Θ = π * rand() - q = qrotation(ax, Θ) - @test angle(q) ≈ Θ - @test axis(q) ≈ ax - @test angleaxis(q)[1] ≈ Θ - @test angleaxis(q)[2] ≈ ax - end - end - end - - @testset "slerp" begin - @testset "q1=1" begin - a = quat(1, 0, 0, 0.0) - b = quat(0, 0, 0, 1.0) - @test slerp(a, b, 0.0) ≈ a - @test slerp(a, b, 1.0) ≈ b - @test slerp(a, b, 0.5) ≈ qrotation([0, 0, 1], deg2rad(90)) - @test abs(slerp(a, b, 0.0)) ≈ 1 - @test abs(slerp(a, b, 1.0)) ≈ 1 - @test abs(slerp(a, b, 0.5)) ≈ 1 - for _ in 1:100, scale in (1, 1e-5, 1e-10) q1 = quat(1, 0, 0, 0.0) θ = rand() * π * scale ax = randn(3) @@ -563,36 +485,36 @@ Base.:(/)(a::MyReal, b::Real) = a.val / b @test slerp(q1, qsmall, 0.5) ≈ sign((q1 + qsmall) / 2) end end + end - @testset "conjugate invariance" begin - for _ in 1:100 - q, q1, q2 = randn(QuaternionF64, 3) - ⊗(s, t) = s * t * inv(s) - t = rand() - @test q ⊗ slerp(q1, q2, t) ≈ slerp(q ⊗ q1, q ⊗ q2, t) - end + @testset "conjugate invariance" begin + for _ in 1:100 + q, q1, q2 = randn(QuaternionF64, 3) + ⊗(s, t) = s * t * inv(s) + t = rand() + @test q ⊗ slerp(q1, q2, t) ≈ slerp(q ⊗ q1, q ⊗ q2, t) end + end - @testset "type promotion" begin - @test slerp(quat(1),quat(1),1) isa Quaternion{Float64} - @test slerp(quat(1),quat(1),big(1)) isa Quaternion{BigFloat} - @test slerp(quat(1),quat(1),Float32(1)) isa Quaternion{Float32} - @test slerp(quat(1),quat(Float32(1)),Float32(1)) isa Quaternion{Float32} - @test slerp(quat(Float64(1)),quat(Float32(1)),Float32(1)) isa Quaternion{Float64} - end + @testset "type promotion" begin + @test slerp(quat(1),quat(1),1) isa Quaternion{Float64} + @test slerp(quat(1),quat(1),big(1)) isa Quaternion{BigFloat} + @test slerp(quat(1),quat(1),Float32(1)) isa Quaternion{Float32} + @test slerp(quat(1),quat(Float32(1)),Float32(1)) isa Quaternion{Float32} + @test slerp(quat(Float64(1)),quat(Float32(1)),Float32(1)) isa Quaternion{Float64} + end - @testset "DomainError" begin - @test_throws DomainError slerp(quat(1),quat(0),1) - @test_throws DomainError slerp(quat(0),quat(1),0) - end + @testset "DomainError" begin + @test_throws DomainError slerp(quat(1),quat(0),1) + @test_throws DomainError slerp(quat(0),quat(1),0) + end - @testset "Normalizing input quaternions" begin - for _ in 1:100 - q1 = randn(QuaternionF64) - q2 = randn(QuaternionF64) - t = rand() - @test slerp(sign(q1),sign(q2),t) ≈ slerp(q1,q2,t) - end + @testset "Normalizing input quaternions" begin + for _ in 1:100 + q1 = randn(QuaternionF64) + q2 = randn(QuaternionF64) + t = rand() + @test slerp(sign(q1),sign(q2),t) ≈ slerp(q1,q2,t) end end end From 15f31373b11bf7aa1d2654939bc0628cb74a7d39 Mon Sep 17 00:00:00 2001 From: hyrodium Date: Thu, 1 Dec 2022 23:13:55 +0900 Subject: [PATCH 04/16] add an example page for rotation with quaternion --- docs/make.jl | 5 +- docs/src/examples/rotations.md | 141 +++++++++++++++++++++++++++++++++ 2 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 docs/src/examples/rotations.md diff --git a/docs/make.jl b/docs/make.jl index b32dfa71..31e20f24 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -15,7 +15,10 @@ makedocs(; pages=[ "Home" => "index.md", "APIs" => "api.md", - "Examples" => ["examples/dual_quaternions.md"], + "Examples" => [ + "examples/rotations.md", + "examples/dual_quaternions.md" + ], ], ) diff --git a/docs/src/examples/rotations.md b/docs/src/examples/rotations.md new file mode 100644 index 00000000..5739f0f4 --- /dev/null +++ b/docs/src/examples/rotations.md @@ -0,0 +1,141 @@ +# Rotations with quaternions + +One of the most useful application of quaternions is representation of 3D-rotations. +See also [Rotations.jl documentation](https://juliageometry.github.io/Rotations.jl/stable/3d_quaternion/) + +```@example rotation +using Quaternions +using LinearAlgebra +``` + +## Basics +A 3D rotation can be represented by a [unit quaternion (versor)](https://en.wikipedia.org/wiki/Versor). +For example, 90° rotation around y-axis is ``q = 1/\sqrt{2} + 0i + j/\sqrt{2} + 0k``. +Rotations with quaternions have the following properties: + +* An additive inverse of a unit quaternion represents the same rotation. +* Unit quaternion is more efficient to represent rotation than rotation matrix. + * High computation accuracy + * High computation performance, both in time and memory +* A conjugate of a unit quaternion represents the inverse rotation. + * The quaternion has unit length, so conjugate and multiplicative inverse is the same. +* The set of unit quaternion ``\left\{w + ix + jy + kz \in \mathbb{H} \ | \ x, y, z \in \mathbb{R} \right\} \simeq S^3`` is isomorphic to ``SU(2)``. + * These groups are homomorphic to ``SO(3)``. + * These groups are double covering of ``SO(3)``. + +## Rotation around a vector +A ``\theta`` rotation around a unit vector ``v = (v_x, v_y, v_z)`` can be obtained as +```math +q = \cos(\theta/2) + \sin(\theta/2)(iv_x + jv_y + kv_z). +``` + +```@example rotation +function quat_from_axisangle(axis, theta) + if length(axis) != 3 + error("Must be a 3-vector") + end + s, c = sincos(theta / 2) + axis = normalize(axis) + return Quaternion(c, s*axis[1], s*axis[2], s*axis[3]) +end +nothing # hide +``` + +```@repl rotation +q1 = quat_from_axisangle([0,1,0], deg2rad(90)) # 90° rotation around y-axis +q2 = quat_from_axisangle([1,1,1], deg2rad(120)) +q3 = -q2 # additive inverse quaternion represents the same rotation +``` + +## Rotate a vector with a quaternion +A vector ``v = (v_x, v_y, v_z)`` can be rotated by a unit quaternion ``q``. +The rotated vector ``v' = (v_x', v_y', v_z')`` can be obtained as +```math +\begin{aligned} +q_v &= iv_x + jv_y + kv_z \\ +q_v' &= q q_v \bar{q} = 0 + iv_x + jv_y + kv_z \\ +v' &= (v_x', v_y', v_z'). +\end{aligned} +``` + +```@example rotation +function rotate_vector(q::Quaternion, vector) + if length(vector) != 3 + error("Must be a 3-vector") + end + q_v = Quaternion(0, vector[1], vector[2], vector[3]) + q_v′ = q*q_v*conj(q) + return [imag_part(q_v′)...] +end +nothing # hide +``` + +```@repl rotation +rotate_vector(q1, [1,2,3]) +rotate_vector(q2, [1,2,3]) +rotate_vector(q3, [1,2,3]) # Same as q2 +``` + +## Convert a quaternion to a rotation matrix +A unit quaternion can be converted to a rotation matrix. + +```@example rotation +function rotmatrix_from_quat(q::Quaternion) + sx, sy, sz = 2q.s * q.v1, 2q.s * q.v2, 2q.s * q.v3 + xx, xy, xz = 2q.v1^2, 2q.v1 * q.v2, 2q.v1 * q.v3 + yy, yz, zz = 2q.v2^2, 2q.v2 * q.v3, 2q.v3^2 + r = [1 - (yy + zz) xy - sz xz + sy; + xy + sz 1 - (xx + zz) yz - sx; + xz - sy yz + sx 1 - (xx + yy)] + return r +end +nothing # hide +``` + +```@repl rotation +m1 = rotmatrix_from_quat(q1) +m2 = rotmatrix_from_quat(q2) +m3 = rotmatrix_from_quat(q3) # Same as q2 +``` + +This function does not return [`StaticMatrix`](https://juliaarrays.github.io/StaticArrays.jl/dev/pages/api/#StaticArraysCore.StaticArray), so the implementation is not much effective. +If you need more performance, please consider using [Rotations.jl](https://github.com/JuliaGeometry/Rotations.jl). + +## Convert a rotation matrix to a quaternion +A rotation matrix can be converted to a unit quaternion. +The following implementation is based on [https://arxiv.org/pdf/math/0701759.pdf](https://arxiv.org/pdf/math/0701759.pdf). +Note that the following mapping ``SO(3) \to SU(2)`` is not surjective. + +```@example rotation +function quat_from_rotmatrix(dcm::AbstractMatrix{T}) where {T<:Real} + a2 = 1 + dcm[1,1] + dcm[2,2] + dcm[3,3] + a = sqrt(a2)/2 + b,c,d = (dcm[3,2]-dcm[2,3])/4a, (dcm[1,3]-dcm[3,1])/4a, (dcm[2,1]-dcm[1,2])/4a + return Quaternion(a,b,c,d) +end +nothing # hide +``` + +```@repl rotation +quat_from_rotmatrix(m1) +quat_from_rotmatrix(m2) +quat_from_rotmatrix(m3) +quat_from_rotmatrix(m1) ≈ q1 +quat_from_rotmatrix(m2) ≈ q2 +quat_from_rotmatrix(m3) ≈ q3 # q2 == -q3 +``` + +## Interpolate two rotations (slerp) +Slerp (spherical linear interpolation) is a method to interpolate between two unit quaternions. +This function [`slerp`](@ref) equates antipodal points, and interpolates the shortest path. +Therefore, the output `slerp(q1, q2, 1)` may be different from `q2`. (`slerp(q1, q2, 0)` is always equal to `q1`.) + +```@repl rotation +slerp(q1, q2, 0) ≈ q1 +slerp(q1, q2, 1) ≈ q2 +slerp(q1, q3, 1) ≈ q3 +slerp(q1, q3, 1) ≈ -q3 +r = slerp(q1, q2, 1/2) +abs(q1-r) ≈ abs(q2-r) # Same distance +abs(r) # Interpolates on the unit sphere S³ +``` From 821fac03ab4df92ac9ef78e874425df0d28004a1 Mon Sep 17 00:00:00 2001 From: hyrodium Date: Thu, 1 Dec 2022 23:14:26 +0900 Subject: [PATCH 05/16] replace rotationmatrix with rotmatrix_from_quat --- docs/src/examples/dual_quaternions.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/docs/src/examples/dual_quaternions.md b/docs/src/examples/dual_quaternions.md index 1759d7dc..48e7ed58 100644 --- a/docs/src/examples/dual_quaternions.md +++ b/docs/src/examples/dual_quaternions.md @@ -88,8 +88,18 @@ function transform(d::DualQuaternion, p::AbstractVector) return pnew end +function rotmatrix_from_quat(q::Quaternion) + sx, sy, sz = 2q.s * q.v1, 2q.s * q.v2, 2q.s * q.v3 + xx, xy, xz = 2q.v1^2, 2q.v1 * q.v2, 2q.v1 * q.v3 + yy, yz, zz = 2q.v2^2, 2q.v2 * q.v3, 2q.v3^2 + r = [1 - (yy + zz) xy - sz xz + sy; + xy + sz 1 - (xx + zz) yz - sx; + xz - sy yz + sx 1 - (xx + yy)] + return r +end + function transformationmatrix(d::DualQuaternion) - R = rotationmatrix(rotation_part(d)) + R = rotmatrix_from_quat(rotation_part(d)) t = translation(d; first=false) T = similar(R, 4, 4) T[1:3, 1:3] .= R From b01af90afbcfa7e2127eb81b8d4e922b7f2becaf Mon Sep 17 00:00:00 2001 From: Yuto Horikawa Date: Mon, 5 Dec 2022 23:52:05 +0900 Subject: [PATCH 06/16] Apply suggestion (fix english and LaTeX) Co-authored-by: Seth Axen --- docs/src/examples/rotations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/examples/rotations.md b/docs/src/examples/rotations.md index 5739f0f4..0bbc7d84 100644 --- a/docs/src/examples/rotations.md +++ b/docs/src/examples/rotations.md @@ -10,7 +10,7 @@ using LinearAlgebra ## Basics A 3D rotation can be represented by a [unit quaternion (versor)](https://en.wikipedia.org/wiki/Versor). -For example, 90° rotation around y-axis is ``q = 1/\sqrt{2} + 0i + j/\sqrt{2} + 0k``. +For example, a 90° rotation around the ``y``-axis is ``q = \frac{1}{\sqrt{2}} + 0i + \frac{1}{\sqrt{2}} j + 0k``. Rotations with quaternions have the following properties: * An additive inverse of a unit quaternion represents the same rotation. From 503013c62a392c1d54953da23e57c2e3648a5b08 Mon Sep 17 00:00:00 2001 From: Yuto Horikawa Date: Mon, 5 Dec 2022 23:52:36 +0900 Subject: [PATCH 07/16] Apply suggestion (fix english) Co-authored-by: Seth Axen --- docs/src/examples/rotations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/examples/rotations.md b/docs/src/examples/rotations.md index 0bbc7d84..5b6e7b31 100644 --- a/docs/src/examples/rotations.md +++ b/docs/src/examples/rotations.md @@ -14,7 +14,7 @@ For example, a 90° rotation around the ``y``-axis is ``q = \frac{1}{\sqrt{2}} + Rotations with quaternions have the following properties: * An additive inverse of a unit quaternion represents the same rotation. -* Unit quaternion is more efficient to represent rotation than rotation matrix. +* A unit quaternion is more efficient for representing a rotation than a rotation matrix. * High computation accuracy * High computation performance, both in time and memory * A conjugate of a unit quaternion represents the inverse rotation. From 4054efcc5bc44dd03b372c06074956de268dce6f Mon Sep 17 00:00:00 2001 From: hyrodium Date: Mon, 5 Dec 2022 23:59:30 +0900 Subject: [PATCH 08/16] apply english suggestion from code review --- docs/src/examples/rotations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/examples/rotations.md b/docs/src/examples/rotations.md index 5b6e7b31..50522b26 100644 --- a/docs/src/examples/rotations.md +++ b/docs/src/examples/rotations.md @@ -13,7 +13,7 @@ A 3D rotation can be represented by a [unit quaternion (versor)](https://en.wiki For example, a 90° rotation around the ``y``-axis is ``q = \frac{1}{\sqrt{2}} + 0i + \frac{1}{\sqrt{2}} j + 0k``. Rotations with quaternions have the following properties: -* An additive inverse of a unit quaternion represents the same rotation. +* The negative of a unit quaternion represents the same rotation. * A unit quaternion is more efficient for representing a rotation than a rotation matrix. * High computation accuracy * High computation performance, both in time and memory From 0502337c7a0b84bc24d68294133065e0d4dcc2c1 Mon Sep 17 00:00:00 2001 From: hyrodium Date: Tue, 6 Dec 2022 00:07:57 +0900 Subject: [PATCH 09/16] update the documentation for basic rotation with unit quaternion --- docs/src/examples/rotations.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/src/examples/rotations.md b/docs/src/examples/rotations.md index 50522b26..13fae890 100644 --- a/docs/src/examples/rotations.md +++ b/docs/src/examples/rotations.md @@ -13,11 +13,10 @@ A 3D rotation can be represented by a [unit quaternion (versor)](https://en.wiki For example, a 90° rotation around the ``y``-axis is ``q = \frac{1}{\sqrt{2}} + 0i + \frac{1}{\sqrt{2}} j + 0k``. Rotations with quaternions have the following properties: +* A unit quaternion (4 real numbers) is more efficient for representing a rotation than a rotation matrix (9 real numbers). + * This results in higher computational performance in terms of time, memory usage, and accuracy. * The negative of a unit quaternion represents the same rotation. -* A unit quaternion is more efficient for representing a rotation than a rotation matrix. - * High computation accuracy - * High computation performance, both in time and memory -* A conjugate of a unit quaternion represents the inverse rotation. +* The conjugate of a unit quaternion represents the inverse rotation. * The quaternion has unit length, so conjugate and multiplicative inverse is the same. * The set of unit quaternion ``\left\{w + ix + jy + kz \in \mathbb{H} \ | \ x, y, z \in \mathbb{R} \right\} \simeq S^3`` is isomorphic to ``SU(2)``. * These groups are homomorphic to ``SO(3)``. From 5a60395dcd564e0cabc239f77f8742cf8db402c6 Mon Sep 17 00:00:00 2001 From: hyrodium Date: Tue, 6 Dec 2022 00:13:18 +0900 Subject: [PATCH 10/16] add U(1,\mathbb{H}) --- docs/src/examples/rotations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/examples/rotations.md b/docs/src/examples/rotations.md index 13fae890..badb701f 100644 --- a/docs/src/examples/rotations.md +++ b/docs/src/examples/rotations.md @@ -18,7 +18,7 @@ Rotations with quaternions have the following properties: * The negative of a unit quaternion represents the same rotation. * The conjugate of a unit quaternion represents the inverse rotation. * The quaternion has unit length, so conjugate and multiplicative inverse is the same. -* The set of unit quaternion ``\left\{w + ix + jy + kz \in \mathbb{H} \ | \ x, y, z \in \mathbb{R} \right\} \simeq S^3`` is isomorphic to ``SU(2)``. +* The set of unit quaternion ``\left\{w + ix + jy + kz \in \mathbb{H} \ | \ x, y, z \in \mathbb{R} \right\} = U(1,\mathbb{H}) \simeq S^3`` is isomorphic to ``SU(2)``. * These groups are homomorphic to ``SO(3)``. * These groups are double covering of ``SO(3)``. From 7d3793752d40681abe774d1fc9443d35edb821de Mon Sep 17 00:00:00 2001 From: hyrodium Date: Tue, 6 Dec 2022 01:17:08 +0900 Subject: [PATCH 11/16] update the symbols for rotating a vector --- docs/src/examples/rotations.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/src/examples/rotations.md b/docs/src/examples/rotations.md index badb701f..9a4bdacd 100644 --- a/docs/src/examples/rotations.md +++ b/docs/src/examples/rotations.md @@ -47,24 +47,24 @@ q3 = -q2 # additive inverse quaternion represents the same rotation ``` ## Rotate a vector with a quaternion -A vector ``v = (v_x, v_y, v_z)`` can be rotated by a unit quaternion ``q``. -The rotated vector ``v' = (v_x', v_y', v_z')`` can be obtained as +A vector ``u = (u_x, u_y, u_z)`` can be rotated by a unit quaternion ``q``. +The rotated vector ``v = (v_x, v_y, v_z)`` can be obtained as ```math \begin{aligned} -q_v &= iv_x + jv_y + kv_z \\ -q_v' &= q q_v \bar{q} = 0 + iv_x + jv_y + kv_z \\ -v' &= (v_x', v_y', v_z'). +q_u &= iu_x + ju_y + ku_z \\ +q_v &= q q_u \bar{q} = 0 + iu_x + ju_y + ku_z \\ +v &= (v_x, v_y, v_z). \end{aligned} ``` ```@example rotation -function rotate_vector(q::Quaternion, vector) - if length(vector) != 3 +function rotate_vector(q::Quaternion, u) + if length(u) != 3 error("Must be a 3-vector") end - q_v = Quaternion(0, vector[1], vector[2], vector[3]) - q_v′ = q*q_v*conj(q) - return [imag_part(q_v′)...] + q_u = Quaternion(0, u[1], u[2], u[3]) + q_v = q*q_v*conj(q) + return [imag_part(q_v)...] end nothing # hide ``` From 97a8ef81b5aae5571d53dfdcb4436e4c7ac2afd0 Mon Sep 17 00:00:00 2001 From: hyrodium Date: Tue, 6 Dec 2022 01:38:39 +0900 Subject: [PATCH 12/16] update around SO(3) and SU(2) --- docs/src/examples/rotations.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/src/examples/rotations.md b/docs/src/examples/rotations.md index 9a4bdacd..cfa3b91b 100644 --- a/docs/src/examples/rotations.md +++ b/docs/src/examples/rotations.md @@ -18,9 +18,9 @@ Rotations with quaternions have the following properties: * The negative of a unit quaternion represents the same rotation. * The conjugate of a unit quaternion represents the inverse rotation. * The quaternion has unit length, so conjugate and multiplicative inverse is the same. -* The set of unit quaternion ``\left\{w + ix + jy + kz \in \mathbb{H} \ | \ x, y, z \in \mathbb{R} \right\} = U(1,\mathbb{H}) \simeq S^3`` is isomorphic to ``SU(2)``. - * These groups are homomorphic to ``SO(3)``. - * These groups are double covering of ``SO(3)``. +* The set of unit quaternion ``\left\{w + ix + jy + kz \in \mathbb{H} \ | \ x, y, z \in \mathbb{R} \right\} = U(1,\mathbb{H}) \simeq S^3`` forms a group, and the group is homomorphic to the following groups. + * ``SU(2) = \Set{R \in \mathcal{M}(2,\mathbb{C}) | R R^{*} = I}`` is isomorphic to ``U(1,\mathbb{H})``. + * ``SO(3) = \Set{R \in \mathcal{M}(3,\mathbb{R}) | R R^\top = I}`` is homomorphic to ``U(1,\mathbb{H})``, and the mapping ``U(1,\mathbb{H}) \to SO(3)`` is double covering. ## Rotation around a vector A ``\theta`` rotation around a unit vector ``v = (v_x, v_y, v_z)`` can be obtained as From 8b271ca6eceaad1384abb3d7765e71cdabae0dd7 Mon Sep 17 00:00:00 2001 From: hyrodium Date: Tue, 6 Dec 2022 20:14:40 +0900 Subject: [PATCH 13/16] fix typo --- docs/src/examples/rotations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/examples/rotations.md b/docs/src/examples/rotations.md index cfa3b91b..5ba861be 100644 --- a/docs/src/examples/rotations.md +++ b/docs/src/examples/rotations.md @@ -63,7 +63,7 @@ function rotate_vector(q::Quaternion, u) error("Must be a 3-vector") end q_u = Quaternion(0, u[1], u[2], u[3]) - q_v = q*q_v*conj(q) + q_v = q*q_u*conj(q) return [imag_part(q_v)...] end nothing # hide From 524b1781de6afeb5d2e7b06e29a9b659f2c8b6d8 Mon Sep 17 00:00:00 2001 From: hyrodium Date: Tue, 6 Dec 2022 22:52:39 +0900 Subject: [PATCH 14/16] fix LaTeX command --- docs/src/examples/rotations.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/examples/rotations.md b/docs/src/examples/rotations.md index 5ba861be..dec9ce8c 100644 --- a/docs/src/examples/rotations.md +++ b/docs/src/examples/rotations.md @@ -19,8 +19,8 @@ Rotations with quaternions have the following properties: * The conjugate of a unit quaternion represents the inverse rotation. * The quaternion has unit length, so conjugate and multiplicative inverse is the same. * The set of unit quaternion ``\left\{w + ix + jy + kz \in \mathbb{H} \ | \ x, y, z \in \mathbb{R} \right\} = U(1,\mathbb{H}) \simeq S^3`` forms a group, and the group is homomorphic to the following groups. - * ``SU(2) = \Set{R \in \mathcal{M}(2,\mathbb{C}) | R R^{*} = I}`` is isomorphic to ``U(1,\mathbb{H})``. - * ``SO(3) = \Set{R \in \mathcal{M}(3,\mathbb{R}) | R R^\top = I}`` is homomorphic to ``U(1,\mathbb{H})``, and the mapping ``U(1,\mathbb{H}) \to SO(3)`` is double covering. + * ``SU(2) = \{R \in \mathcal{M}(2,\mathbb{C}) \ | \ R R^{*} = I\}`` is isomorphic to ``U(1,\mathbb{H})``. + * ``SO(3) = \{R \in \mathcal{M}(3,\mathbb{R}) \ | \ R R^\top = I\}`` is homomorphic to ``U(1,\mathbb{H})``, and the mapping ``U(1,\mathbb{H}) \to SO(3)`` is double covering. ## Rotation around a vector A ``\theta`` rotation around a unit vector ``v = (v_x, v_y, v_z)`` can be obtained as From 3d7b144a65ea8e609f2dfe48c780a0ea7c159491 Mon Sep 17 00:00:00 2001 From: hyrodium Date: Tue, 6 Dec 2022 22:54:39 +0900 Subject: [PATCH 15/16] fix typo --- docs/src/examples/rotations.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/src/examples/rotations.md b/docs/src/examples/rotations.md index dec9ce8c..806bee5b 100644 --- a/docs/src/examples/rotations.md +++ b/docs/src/examples/rotations.md @@ -52,7 +52,7 @@ The rotated vector ``v = (v_x, v_y, v_z)`` can be obtained as ```math \begin{aligned} q_u &= iu_x + ju_y + ku_z \\ -q_v &= q q_u \bar{q} = 0 + iu_x + ju_y + ku_z \\ +q_v &= q q_u \bar{q} = 0 + iv_x + jv_y + kv_z \\ v &= (v_x, v_y, v_z). \end{aligned} ``` From b7aa35447306070765831529c237036489b0e81e Mon Sep 17 00:00:00 2001 From: hyrodium Date: Tue, 6 Dec 2022 22:58:35 +0900 Subject: [PATCH 16/16] add more argument type specification in rotations.md --- docs/src/examples/rotations.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/src/examples/rotations.md b/docs/src/examples/rotations.md index 806bee5b..3c377c5a 100644 --- a/docs/src/examples/rotations.md +++ b/docs/src/examples/rotations.md @@ -29,7 +29,7 @@ q = \cos(\theta/2) + \sin(\theta/2)(iv_x + jv_y + kv_z). ``` ```@example rotation -function quat_from_axisangle(axis, theta) +function quat_from_axisangle(axis::AbstractVector, theta::Real) if length(axis) != 3 error("Must be a 3-vector") end @@ -58,7 +58,7 @@ v &= (v_x, v_y, v_z). ``` ```@example rotation -function rotate_vector(q::Quaternion, u) +function rotate_vector(q::Quaternion, u::AbstractVector) if length(u) != 3 error("Must be a 3-vector") end