1

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.stack.imgur.com/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)
    }
    
}
Kromster
  • 7,181
  • 7
  • 63
  • 111

4 Answers4

2

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)

robthebloke
  • 9,331
  • 9
  • 12
  • >When using quats for rotations, they should be unit length what do you think, should normalize fix this? I am getting such "weird" values from colada files and don't have ability to change it – Андрей Первушин Sep 13 '17 at 07:41
  • Tried to respond, but ended up adding to my original answer. Those values look like axis angle values to me. Looks like you need to convert them to quats first? – robthebloke Sep 14 '17 at 04:21
  • this values I am getting directly from rotation property of the SCNNode and actual orientation of the objects is almost the same – Андрей Первушин Sep 14 '17 at 14:37
  • Yes, the two axis angle rotations, once converted to quaternions, are more or less the negate of each other. The problem here is that the data you are getting is not in quaternion form - it's an axis angle representation. You'll want to preproccess that data so it is in quaternion form (because quats are somewhat nicer to work with, compared to axis-angles). The nlerp code I presented above will work perfectly on those two quat values I listed at the end of my edited post. – robthebloke Sep 15 '17 at 02:33
  • Ok got you, you are right the issue is that SCNVector4 is not quaternion. I should use orientation property of the SCNNode so simple... – Андрей Первушин Nov 21 '17 at 13:23
0

The latest SDKs have <simd/quaternion.h> which exposes the simd_quatf type to represent a quaternion. Also exposed are different utils to deal with quaternions and among them is simd_slerp which does "the right thing" for quaternion interpolation.

In iOS 11 and macOS 10.13 SceneKit exposes new APIs to deal with SIMD types directly. For instance in addition to SCNNode.orientation you now also have access to SCNNode.simdOrientation.

Edit

Most SIMD APIs are inlined and can thus be used on OS versions earlier than the SDK version. If you really wan to stick to GLKit their version of the spherical interpolation is GLKQuaternionSlerp.

mnuages
  • 13,049
  • 2
  • 23
  • 40
  • Updated my comment is case you really want to stick to GLKit. But you shouldn't have yo as SIMD utils are inlined and it's fairly easily to bridge `simd_quatf ` to `SCNQuaternion`. – mnuages Sep 12 '17 at 16:11
0
  1. Do sign flip, if quaternions dot product is negative.
  2. Normalize resulting quaternion.
minorlogic
  • 1,872
  • 1
  • 17
  • 24
  • Will try it soon. Or maybe you have complete function? – Андрей Первушин Sep 12 '17 at 08:48
  • No that is not working after normalizing the node get broken, please check it yourself before posting, see my listing 3 – Андрей Первушин Sep 12 '17 at 14:10
  • 1. GLKQuaternionInvert probably invert quaternion but NOT change the sign. Manualy change the sign of all quaternion components. 2. You make GLKQuaternionSlerp before normalization but i suggested LERP instead. 3. GLKQuaternionSlerp can do what you need. It is some kind of interpolation between quaternions. Try to use it instead of your handmade LERP. ? – minorlogic Sep 12 '17 at 16:27
0

First of all I want to thank robthebloke for the algorithmic solution. This I am posting is just the translation in Swift of the same algorithm. Hopes this helps someone that is trying to smooth the CMQuaternion coming from CMMotionManager.

extension CMQuaternion {
    static func dot(_ a: CMQuaternion, _ b: CMQuaternion) -> Double {
      return a.x*b.x + a.y*b.y + a.z*b.z + a.w*b.w
    }

    static func negate(_ a: CMQuaternion) -> CMQuaternion {
        return CMQuaternion(x:-a.x, y:-a.y, z:-a.z, w:-a.w)
    }

    static func normalise(_ a: CMQuaternion) -> CMQuaternion {
        let l : Double = 1.0 / sqrt(dot(a, a))
        return CMQuaternion(x: l*a.x, y: l*a.y, z: l*a.z, w: l*a.w)
    }

    static func lerp(_ a: CMQuaternion, _ b: CMQuaternion, _ t: Double) -> CMQuaternion {
      // negate second quat if dot product is negative
      //const float l2 = dot(a, b);
        let l2 = dot(a, b)
        var b = b
        if l2 < 0.0 {
            b = negate(b)
        }
        // c = a + t(b - a)  -->   c = a - t(a - b)
        // the latter is slightly better on x64
        let x = a.x - t*(a.x - b.x)
        let y = a.y - t*(a.y - b.y)
        let z = a.z - t*(a.z - b.z)
        let w = a.w - t*(a.w - b.w)
        return CMQuaternion(x: x, y: y, z: z, w: w)
    }

    // this is the method you want
    static func nlerp(_ a: CMQuaternion, _ b: CMQuaternion, _ t: Double) -> CMQuaternion {
      return normalise(lerp(a, b, t))
    }
}
kaharoth
  • 160
  • 1
  • 9