I have two quaternions:
SCNVector4(x: -0.554488897, y: -0.602368534, z: 0.57419008, w: 2.0878818)
SCNVector4(x: 0.55016619, y: 0.604441643, z: -0.576166153, w: 4.18851328)
and if we create two objects the orientation will be quite similar
but if we try to Lerp from first to second then the position changing quite weird (and looking on the values it's expected but not correct)
[Lerp progress demo][1]
I've googled and found many functions to do lerp e.g. simple one:
extension SCNVector4 {
func lerp(to: SCNVector4, v: Float) -> SCNVector4 {
let aX = x + (to.x - x) * v
let aY = y + (to.y - y) * v
let aZ = z + (to.z - z) * v
let aW = w + (to.w - w) * v
return SCNVector4Make(aX, aY, aZ, aW)
}
}
But how to avoid such weird flipping?
PS: I've tried different functions from GLKit but the result is the same [1]: https://i.sstatic.net/8jEvm.png
- As suggested tried to flip sign but the issue is that I get dot product greater then 0
extension SCNVector4 {
func glk() -> GLKQuaternion {
return GLKQuaternion(q: (x, y, z, w))
}
func lerp(to: SCNVector4, v: Float) -> SCNVector4 {
let a = GLKQuaternionNormalize(glk())
let b = GLKQuaternionNormalize(to.glk())
let dot =
a.x * b.x +
a.y * b.y +
a.z * b.z +
a.w * b.w
var target = b
if dot < 0 {
target = GLKQuaternionInvert(b)
}
let norm = GLKQuaternionNormalize(GLKQuaternionSlerp(a, target, v))
return norm.scn()
}
}
extension GLKQuaternion {
func scn() -> SCNVector4 {
return SCNVector4Make(x, y, z, w)
}
}
The quat values you listed seem wrong if you ask me. 'w' values of 2 or 4 don't add up to a normalised quat, so I'm not surprised lerping them give you odd values. When using quats for rotations, they should be unit length (and those two quats are not unit length).
As for lerping, you basically want to be using a normalised-lerp (nlerp), or spherical-lerp (slerp). NLerp leads to a slight acceleration / deceleration as you rotate from one quat to the other. Slerp gives you a constant angular speed (although it uses sine, so it is slower to compute).
float dot(quat a, quat b)
{
return a.x*b.x + a.y*b.y + a.z*b.z + a.w*b.w;
}
quat negate(quat a)
{
return quat(-a.x, -a.y, -a.z, -a.w);
}
quat normalise(quat a)
{
float l = 1.0f / std::sqrt(dot(a, a));
return quat(l*a.x, l*a.y, l*a.z, l*a.w);
}
quat lerp(quat a, quat b, float t)
{
// negate second quat if dot product is negative
const float l2 = dot(a, b);
if(l2 < 0.0f)
{
b = negate(b);
}
quat c;
// c = a + t(b - a) --> c = a - t(a - b)
// the latter is slightly better on x64
c.x = a.x - t*(a.x - b.x);
c.y = a.y - t*(a.y - b.y);
c.z = a.z - t*(a.z - b.z);
c.w = a.w - t*(a.w - b.w);
return c;
}
// this is the method you want
quat nlerp(quat a, quat b, float t)
{
return normalise(lerp(a, b, t));
}
/Edit
Are you sure they are quat values? Those values look a lot like axis angle values if you ask me. Try running those values through this conversion func and see if that helps:
quat fromAxisAngle(quat q)
{
float ha = q.w * 0.5f;
float sha = std::sin(ha);
float cha = std::cos(ha);
return quat(sha * q.x, sha * q.y, sha * q.z, cha);
}
I get these two resulting quats from your original values:
(-0.479296037597, -0.520682836178, 0.496325592199, 0.50281768624)
(0.47649598094, 0.523503659143, -0.499014409188, -0.499880083257)