Skip to content

Commit 4cf7f2d

Browse files
hyrodiumsethaxen
andauthored
Fix slerp and deprecate linpol (#78)
* fix slerp * fix slerp * deprecate linpol * update README * fix slerp * update type promotion for slerp * add tests for type promotion in slerp * fix type-stablity * add domain error for zero-quaternion * add domain tests for zero-quaternion * replace `@deprecate_binding` with `@deprecate` Co-authored-by: Seth Axen <seth.axen@gmail.com> * replace (qa, qb, _qa, _qb) with (qa0, qb0, qa, qb) * add slerp tests for .norm field, deprecated, normalization * update slerp by adding `@inline` macro, replace sign function and removing unnecessary semi-colon * Fix typo in src/Quaternion.jl (qa0 -> qb0) Co-authored-by: Seth Axen <seth.axen@gmail.com> * bump version to v0.5.6 Co-authored-by: Seth Axen <seth.axen@gmail.com>
1 parent 1f25259 commit 4cf7f2d

File tree

5 files changed

+61
-52
lines changed

5 files changed

+61
-52
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
name = "Quaternions"
22
uuid = "94ee1d12-ae83-5a48-8b1c-48b8ff168ae0"
3-
version = "0.5.5"
3+
version = "0.5.6"
44

55
[deps]
66
DualNumbers = "fa6b7ba4-c1ee-5f82-b5fc-ecf0adba8f74"

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ Implemented functions are:
6060
cospi
6161
sincos
6262
sincospi
63-
linpol (interpolate between 2 normalized quaternions)
63+
slerp
6464
rand
6565
randn
6666

src/Quaternion.jl

Lines changed: 30 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -230,29 +230,6 @@ end
230230

231231
(^)(q::Quaternion, w::Quaternion) = exp(w * log(q))
232232

233-
function linpol(p::Quaternion, q::Quaternion, t::Real)
234-
p = normalize(p)
235-
q = normalize(q)
236-
qm = -q
237-
if abs(p - q) > abs(p - qm)
238-
q = qm
239-
end
240-
c = p.s * q.s + p.v1 * q.v1 + p.v2 * q.v2 + p.v3 * q.v3
241-
if c < 1.0
242-
o = acos(c)
243-
s = sin(o)
244-
sp = sin((1 - t) * o) / s
245-
sq = sin(t * o) / s
246-
else
247-
sp = 1 - t
248-
sq = t
249-
end
250-
Quaternion(sp * p.s + sq * q.s,
251-
sp * p.v1 + sq * q.v1,
252-
sp * p.v2 + sq * q.v2,
253-
sp * p.v3 + sq * q.v3, true)
254-
end
255-
256233
quatrand(rng = Random.GLOBAL_RNG) = quat(randn(rng), randn(rng), randn(rng), randn(rng))
257234
nquatrand(rng = Random.GLOBAL_RNG) = normalize(quatrand(rng))
258235

@@ -340,42 +317,46 @@ function rotationmatrix_normalized(q::Quaternion)
340317
xz - sy yz + sx 1 - (xx + yy)]
341318
end
342319

343-
344-
function slerp(qa::Quaternion{T}, qb::Quaternion{T}, t::T) where {T}
320+
@inline function slerp(qa0::Quaternion{T}, qb0::Quaternion{T}, t::T) where T<:Real
345321
# http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/slerp/
346-
coshalftheta = qa.s * qb.s + qa.v1 * qb.v1 + qa.v2 * qb.v2 + qa.v3 * qb.v3;
322+
iszero(qa0) && throw(DomainError(qa0, "The input quaternion must be non-zero."))
323+
iszero(qb0) && throw(DomainError(qb0, "The input quaternion must be non-zero."))
324+
qa = qa0 / abs(qa0)
325+
qb = qb0 / abs(qb0)
326+
coshalftheta = qa.s * qb.s + qa.v1 * qb.v1 + qa.v2 * qb.v2 + qa.v3 * qb.v3
347327

348328
if coshalftheta < 0
349-
qm = -qb
329+
qb = -qb
350330
coshalftheta = -coshalftheta
351-
else
352-
qm = qb
353-
end
354-
abs(coshalftheta) >= 1.0 && return qa
355-
356-
halftheta = acos(coshalftheta)
357-
sinhalftheta = sqrt(one(T) - coshalftheta * coshalftheta)
358-
359-
if abs(sinhalftheta) < 0.001
360-
return Quaternion(
361-
T(0.5) * (qa.s + qb.s),
362-
T(0.5) * (qa.v1 + qb.v1),
363-
T(0.5) * (qa.v2 + qb.v2),
364-
T(0.5) * (qa.v3 + qb.v3),
365-
)
366331
end
367332

368-
ratio_a = sin((one(T) - t) * halftheta) / sinhalftheta
369-
ratio_b = sin(t * halftheta) / sinhalftheta
333+
if coshalftheta < 1
334+
halftheta = acos(coshalftheta)
335+
sinhalftheta = sqrt(1 - coshalftheta^2)
336+
337+
ratio_a = sin((1 - t) * halftheta) / sinhalftheta
338+
ratio_b = sin(t * halftheta) / sinhalftheta
339+
else
340+
ratio_a = float(1 - t)
341+
ratio_b = float(t)
342+
end
370343

371-
Quaternion(
372-
qa.s * ratio_a + qm.s * ratio_b,
373-
qa.v1 * ratio_a + qm.v1 * ratio_b,
374-
qa.v2 * ratio_a + qm.v2 * ratio_b,
375-
qa.v3 * ratio_a + qm.v3 * ratio_b,
344+
return Quaternion(
345+
qa.s * ratio_a + qb.s * ratio_b,
346+
qa.v1 * ratio_a + qb.v1 * ratio_b,
347+
qa.v2 * ratio_a + qb.v2 * ratio_b,
348+
qa.v3 * ratio_a + qb.v3 * ratio_b,
349+
true
376350
)
377351
end
378352

353+
function slerp(qa::Quaternion{Ta}, qb::Quaternion{Tb}, t::T) where {Ta, Tb, T}
354+
S = promote_type(Ta,Tb,T)
355+
return slerp(Quaternion{S}(qa),Quaternion{S}(qb),S(t))
356+
end
357+
358+
Base.@deprecate linpol(p::Quaternion, q::Quaternion, t::Real) slerp(p, q, t)
359+
379360
function sylvester(a::Quaternion{T}, b::Quaternion{T}, c::Quaternion{T}) where {T<:Real}
380361
isreal(a) && return sylvester(real(a), b, c)
381362
isreal(b) && return sylvester(a, real(b), c)

src/Quaternions.jl

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ module Quaternions
3535
export angleaxis
3636
export angle
3737
export axis
38-
export linpol
3938
export normalize
4039
export normalizea
4140
export dconj

test/Quaternion.jl

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -599,6 +599,9 @@ Base.:(/)(a::MyReal, b::Real) = a.val / b
599599
@test slerp(a, b, 0.0) a
600600
@test slerp(a, b, 1.0) b
601601
@test slerp(a, b, 0.5) qrotation([0, 0, 1], deg2rad(90))
602+
@test slerp(a, b, 0.0).norm
603+
@test slerp(a, b, 1.0).norm
604+
@test slerp(a, b, 0.5).norm
602605
for _ in 1:100
603606
q1 = quat(1, 0, 0, 0.0)
604607
# there are numerical stability issues with slerp atm
@@ -630,6 +633,32 @@ Base.:(/)(a::MyReal, b::Real) = a.val / b
630633
@test q linpol(q1, q2, t) linpol(q q1, q q2, t)
631634
end
632635
end
636+
637+
@testset "type promotion" begin
638+
@test slerp(quat(1),quat(1),1) isa Quaternion{Float64}
639+
@test slerp(quat(1),quat(1),big(1)) isa Quaternion{BigFloat}
640+
@test slerp(quat(1),quat(1),Float32(1)) isa Quaternion{Float32}
641+
@test slerp(quat(1),quat(Float32(1)),Float32(1)) isa Quaternion{Float32}
642+
@test slerp(quat(Float64(1)),quat(Float32(1)),Float32(1)) isa Quaternion{Float64}
643+
end
644+
645+
@testset "DomainError" begin
646+
@test_throws DomainError slerp(quat(1),quat(0),1)
647+
@test_throws DomainError slerp(quat(0),quat(1),0)
648+
end
649+
650+
@testset "Deprecated warning" begin
651+
@test_deprecated linpol(quat(1),quat(1),0)
652+
end
653+
654+
@testset "Normalizing input quaternions" begin
655+
for _ in 1:100
656+
q1 = randn(QuaternionF64)
657+
q2 = randn(QuaternionF64)
658+
t = rand()
659+
@test slerp(sign(q1),sign(q2),t) slerp(q1,q2,t)
660+
end
661+
end
633662
end
634663
end
635664

0 commit comments

Comments
 (0)