Skip to content

Commit b04d039

Browse files
authored
rotation_between for general dimensions (#257)
* deprecate rotation_between(::AbstractVector, ::AbstractVector) * move deprecated `rotation_between` to `src/deprecated.jl` * move `rotation_between(::SVector{3}, ::SVector{3})` to `src/rotation_between.jl` * update `src/rotation_between.jl` * add a method for `rotation_between(::SVector{2}, ::SVector{2})` * add a method for `rotation_between(::SVector{N}, ::SVector{N})` * add tests for `rotation_between` * fix `rotation_between` for general dimensions * fix `rotation_between(::SVector{2}, ::SVector{2})` * bump version to v1.5.0
1 parent d56bc26 commit b04d039

File tree

6 files changed

+54
-18
lines changed

6 files changed

+54
-18
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name = "Rotations"
22
uuid = "6038ab10-8711-5258-84ad-4b1120ba62dc"
3-
version = "1.4.0"
3+
version = "1.5.0"
44

55
[deps]
66
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"

src/Rotations.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ include("rotation_generator.jl")
2626
include("logexp.jl")
2727
include("eigen.jl")
2828
include("rand.jl")
29+
include("rotation_between.jl")
2930
include("deprecated.jl")
3031

3132
export

src/deprecated.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
# Deprecate UnitQuaternion => QuatRotation
22
Base.@deprecate_binding UnitQuaternion QuatRotation true
3+
4+
Base.@deprecate rotation_between(from::AbstractVector, to::AbstractVector) rotation_between(SVector{3}(from), SVector{3}(to))

src/rotation_between.jl

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
"""
2+
rotation_between(u, v)
3+
4+
Compute the quaternion that rotates vector `u` so that it aligns with vector
5+
`v`, along the geodesic (shortest path).
6+
"""
7+
function rotation_between end
8+
9+
function rotation_between(u::SVector{2}, v::SVector{2})
10+
c = complex(v[1], v[2]) / complex(u[1], u[2])
11+
theta = Base.angle(c)
12+
return Angle2d(theta)
13+
end
14+
15+
function rotation_between(u::SVector{3}, v::SVector{3})
16+
# Robustified version of implementation from https://www.gamedev.net/topic/429507-finding-the-quaternion-betwee-two-vectors/#entry3856228
17+
normprod = sqrt(dot(u, u) * dot(v, v))
18+
T = typeof(normprod)
19+
normprod < eps(T) && throw(ArgumentError("Input vectors must be nonzero."))
20+
w = normprod + dot(u, v)
21+
v = abs(w) < 100 * eps(T) ? perpendicular_vector(u) : cross(u, v)
22+
@inbounds return QuatRotation(w, v[1], v[2], v[3]) # relies on normalization in constructor
23+
end
24+
25+
function rotation_between(u::SVector{N}, v::SVector{N}) where N
26+
e1 = normalize(u)
27+
e2 = normalize(v - e1 * dot(e1, v))
28+
c = dot(e1, normalize(v))
29+
s = sqrt(1-c^2)
30+
P = [e1 e2 svd([e1 e2]'; full=true).Vt[StaticArrays.SUnitRange(3,N),:]']
31+
Q = one(MMatrix{N,N})
32+
Q[1,1] = c
33+
Q[1,2] = -s
34+
Q[2,1] = s
35+
Q[2,2] = c
36+
R = RotMatrix(P*Q*P')
37+
return R
38+
end

src/unitquaternion.jl

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -311,23 +311,6 @@ end
311311
Base.:\(q1::QuatRotation, q2::QuatRotation) = inv(q1)*q2
312312
Base.:/(q1::QuatRotation, q2::QuatRotation) = q1*inv(q2)
313313

314-
"""
315-
rotation_between(from, to)
316-
317-
Compute the quaternion that rotates vector `from` so that it aligns with vector
318-
`to`, along the geodesic (shortest path).
319-
"""
320-
rotation_between(from::AbstractVector, to::AbstractVector) = rotation_between(SVector{3}(from), SVector{3}(to))
321-
function rotation_between(from::SVector{3}, to::SVector{3})
322-
# Robustified version of implementation from https://www.gamedev.net/topic/429507-finding-the-quaternion-betwee-two-vectors/#entry3856228
323-
normprod = sqrt(dot(from, from) * dot(to, to))
324-
T = typeof(normprod)
325-
normprod < eps(T) && throw(ArgumentError("Input vectors must be nonzero."))
326-
w = normprod + dot(from, to)
327-
v = abs(w) < 100 * eps(T) ? perpendicular_vector(from) : cross(from, to)
328-
@inbounds return QuatRotation(w, v[1], v[2], v[3]) # relies on normalization in constructor
329-
end
330-
331314
"""
332315
slerp(R1::Rotaion{3}, R2::Rotaion{3}, t::Real)
333316

test/rotation_tests.jl

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,18 @@ all_types = (RotMatrix3, RotMatrix{3}, AngleAxis, RotationVec,
395395
end
396396
end
397397
end
398+
399+
@testset "$(N)-dimensional rotation_between" for N in 2:7
400+
for _ in 1:100
401+
u = randn(SVector{N})
402+
v = randn(SVector{N})
403+
R = rotation_between(u,v)
404+
@test isrotation(R)
405+
@test R isa Rotation
406+
@test normalize(v) R * normalize(u)
407+
end
408+
end
409+
398410
#########################################################################
399411
# Check that the eltype is inferred in Rot constructors
400412
@testset "Rot constructor eltype promotion" begin

0 commit comments

Comments
 (0)