Scenekit: вращение и анимация SCNNode

Я пытаюсь отобразить пирамиду, которая указывает на ось z, а затем вращается сама по себе вокруг z. Когда моя камера находится на оси z, я ожидаю увидеть пирамиду сверху. Мне удалось повернуть пирамиду, чтобы увидеть ее таким образом, но когда я добавляю анимацию, она поворачивается на несколько осей.

Вот мой код:

    // The following create the pyramid and place it how I want
    let pyramid = SCNPyramid(width: 1.0, height: 1.0, length: 1.0)
    let pyramidNode = SCNNode(geometry: pyramid)
    pyramidNode.position = SCNVector3(x: 0, y: 0, z: 0)
    pyramidNode.rotation = SCNVector4(x: 1, y: 0, z: 0, w: Float(M_PI / 2))
    scene.rootNode.addChildNode(pyramidNode)

    // But the animation seems to rotate aroun 2 axis and not just z
    var spin = CABasicAnimation(keyPath: "rotation")
    spin.byValue = NSValue(SCNVector4: SCNVector4(x: 0, y: 0, z: 1, w: 2*Float(M_PI)))
    spin.duration = 3
    spin.repeatCount = HUGE
    pyramidNode.addAnimation(spin, forKey: "spin around")

Ответ 1

Попытка как вручную установить, так и оживить одно и то же свойство может вызвать проблемы. Использование анимации byValue делает проблему хуже - она объединяется с текущим преобразованием, поэтому сложнее отслеживать, является ли текущее преобразование byValue анимации.

Вместо этого отделите фиксированную ориентацию пирамиды (ее вершина находится в направлении -z) от анимации (она вращается вокруг оси, на которую она указывает). Есть два хороших способа сделать это:

  1. Сделайте pyramidNode дочерним элементом другого узла, который получает одноразовый поворот (π/2 вокруг оси x) и применяет анимацию вращения непосредственно к pyramidNode. (В этом случае вершина пирамиды будет по-прежнему указывать в направлении +y ее локального пространства, поэтому вам нужно будет вращаться вокруг этой оси вместо оси z.)

  2. Используйте свойство pivot чтобы преобразовать локальное пространство содержимого pyramidNode и анимировать pyramidNode относительно его содержащего пространства.

Вот какой код, чтобы показать второй подход:

let pyramid = SCNPyramid(width: 1.0, height: 1.0, length: 1.0)
let pyramidNode = SCNNode(geometry: pyramid)
pyramidNode.position = SCNVector3(x: 0, y: 0, z: 0)
// Point the pyramid in the -z direction
pyramidNode.pivot = SCNMatrix4MakeRotation(CGFloat(M_PI_2), 1, 0, 0)
scene.rootNode.addChildNode(pyramidNode)

let spin = CABasicAnimation(keyPath: "rotation")
// Use from-to to explicitly make a full rotation around z
spin.fromValue = NSValue(SCNVector4: SCNVector4(x: 0, y: 0, z: 1, w: 0))
spin.toValue = NSValue(SCNVector4: SCNVector4(x: 0, y: 0, z: 1, w: CGFloat(2 * M_PI)))
spin.duration = 3
spin.repeatCount = .infinity
pyramidNode.addAnimation(spin, forKey: "spin around")

Некоторые несвязанные изменения для улучшения качества кода:

  • Используйте CGFloat когда требуется явное преобразование для инициализации компонента SCNVector; используя Float или Double будут разбиты на 32 или 64-битную архитектуру.
  • Используйте .infinity вместо устаревшей математической константы BSD HUGE. Этот тип - относится к типу spin.repeatCount и использует постоянное значение, определенное для всех типов с плавающей точкой.
  • Используйте M_PI_2 для π/2, чтобы быть педантичным относительно точности.
  • Используйте let вместо var для анимации, так как мы никогда не назначаем другое значение для spin.

Подробнее о CGFloat ошибкой CGFloat: в Swift числовые литералы не имеют типа, пока выражение, в котором они нуждаются, нуждается в одном. Вот почему вы можете делать такие вещи, как spin.duration = 3 - хотя duration - это значение с плавающей запятой, Swift позволяет передавать "целочисленный литерал". Но если вы let d = 3; spin.duration = d let d = 3; spin.duration = d вы получите ошибку. Зачем? Поскольку переменные/константы имеют явные типы, а Swift не выполняет неявное преобразование типов. 3 является бесприбыльным, но когда ему присваивается d, введите вывод по умолчанию для выбора Int потому что вы ничего не указали.

Если вы видите ошибки преобразования типов, у вас, вероятно, есть код, который смешивает литералы, константы и/или значения, возвращаемые из функций. Вероятно, вы можете просто убрать ошибки, преобразовывая все в выражение в CGFloat (или независимо от того типа, которым вы передаете это выражение). Конечно, это сделает ваш код нечитаемым и уродливым, поэтому, как только вы его начнете работать, вы можете начать удаление конверсий по одному, пока не найдете тот, который выполняет эту работу.

Ответ 2

SceneKit включает в себя помощники анимации, которые намного проще и короче в использовании, чем CAAnimations. Это ObjC, но он перехватывает точку:

[pyramidNode runAction:
    [SCNAction repeatActionForever:
        [SCNAction rotateByX:0 y:0 z:2*M_PI duration:3]]];

Ответ 3

Я изменил его на Value, и это сработало для меня. Так измените линию...

spin.byValue = NSValue(SCNVector4: SCNVector4(...

Измените его на...

spin.toValue = NSValue(SCNVector4: SCNVector4(x: 0, y:0, z:1, w: 2*float(M_PI))