首页 文章

SceneKit - 旋转和动画 SCNNode

提问于
浏览
15

我正在尝试显示一个指向 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")

3 回答

  • 15

    尝试手动设置和动画相同的属性可能会导致问题。使用byValue动画会使问题变得更糟 - 与当前变换连接,因此更难跟踪当前变换是否是动画期望开始的变换。

    相反,将金字塔的固定方向(其顶点在-z 方向上)与动画分开(它围绕它指向的轴旋转)。有两种好方法可以做到这一点:

    • 使pyramidNode成为另一个获得 one-time 旋转(π/2 在 x-axis 附近)的节点的子节点,并将旋转动画直接应用于pyramidNode。 (在这种情况下,金字塔的顶点仍将指向其局部空间的 y 方向,因此您需要围绕该轴旋转而不是 z-axis.)

    • 使用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")
    

    一些不相关的更改,以提高代码质量:

    • 当需要显式转换来初始化SCNVector组件时,请使用CGFloat;使用FloatDouble特别会在 32 位或 64 位架构上中断。

    • 使用.infinity而不是传统的 BSD 数学常量HUGE。无论spin.repeatCount的类型是什么,这都是 type-infers,并使用为所有 floating-point 类型定义的常量值。

    • 使用M_PI_2表示π/2 对于精度是迂腐的。

    • 使用let而不是var作为动画,因为我们从不为spin分配不同的值。


    关于CGFloat错误业务的更多信息:在 Swift 中,数字文字在它们所需的表达式需要之前没有类型。这就是为什么你可以做spin.duration = 3这样的事情 - 即使duration是一个 floating-point 值,Swift 也允许你传递一个“整数文字”。但如果你做let d = 3; spin.duration = d就会出错。为什么?因为 variables/constants 有显式类型,而 Swift 不进行隐式类型转换。 3是无类型的,但是当它被分配给d时,类型推断默认选择Int,因为您没有指定其他内容。

    如果你看到类型转换错误,你可能有代码混合从函数返回的文字,常量,and/or 值。您可以通过将表达式中的所有内容转换为CGFloat(或者您将该表达式传递给的任何类型)来使错误消失。当然,这会使你的代码变得难以理解和丑陋,所以一旦你开始工作,你可能会开始逐个删除转换,直到找到完成工作的转换为止。

  • 14

    SceneKit 包含动画助手,它比 CAAnimations 更简单,更短。这是 ObjC,但是可以解决这个问题:

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

    我将 byValue 改为 toValue,这对我有用。所以换线......

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

    把它改成......

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

相关问题