Lerp SCNVector4

Я должен кватернионов

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)

и если мы создадим два объекта, ориентация будет очень похожа

но если мы попытаемся выполнить Lerp с первого на второе, то позиция изменится довольно странно (и, глядя на значения, это ожидается, но не правильно)

Lerp Прогресс демо

Я гуглил и нашел много функций для выполнения lerp, например, простой:

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)

    }
}

Но как избежать такого странного переворота?

PS: я пробовал разные функции от GLKit, но результат тот же

Как и предполагалось, попытался перевернуть знак, но проблема в том, что я получаю точечный продукт больше 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)
    }

}

4 ответа

Решение

Указанные вами кват-значения кажутся неправильными, если вы спросите меня. Значения 'w', равные 2 или 4, не суммируют с нормализованным кватом, поэтому я не удивляюсь, что их отклонение дает вам нечетные значения. При использовании кватов для поворотов они должны быть длиной в единицу (а эти два квата не являются длиной в единицу).

Что касается lerping, вы в основном хотите использовать normalized-lerp (nlerp) или spherical-lerp (slerp). NLerp приводит к небольшому ускорению / замедлению при повороте от одного квада к другому. Slerp дает вам постоянную угловую скорость (хотя он использует синус, поэтому он медленнее вычисляется).

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));
}

/Редактировать

Вы уверены, что они являются квотами? Эти значения очень похожи на значения угла оси, если вы спросите меня. Попробуйте запустить эти значения через эту функцию преобразования и посмотрите, поможет ли это:

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);
}

Я получаю эти два итоговых квата из ваших исходных значений:

(-0,479296037597, -0,520682836178, 0,496325592199, 0,50281768624)

(0,47649598094, 0,523503659143, -0,499014409188, -0,499880083257)

  1. Сделайте знак перевернуть, если кватернион точка продукт отрицательный.
  2. Нормализуем полученный кватернион.

Последние SDK имеют <simd/quaternion.h> который выставляет simd_quatf тип для представления кватерниона. Также выставлены различные утилиты для работы с кватернионами и среди них есть simd_slerp что делает "правильную вещь" для интерполяции кватернионов.

В iOS 11 и macOS 10.13 SceneKit предоставляет новые API для прямой работы с SIMD-типами. Например, в дополнение к SCNNode.orientation теперь у вас также есть доступ к SCNNode.simdOrientation,

редактировать

Большинство SIMD API являются встроенными и поэтому могут использоваться в версиях ОС ранее, чем версия SDK. Если вы действительно хотите придерживаться GLKit, то их версия сферической интерполяции GLKQuaternionSlerp,

В первую очередь хочу поблагодарить robthebloke за алгоритмическое решение. То, что я публикую, - это просто перевод того же алгоритма на Swift. Надеюсь, это поможет кому-то, кто пытается сгладить CMQuaternion, исходящий от 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))
    }
}
Другие вопросы по тегам