如何将 SCNNode 旋转多个角度?

Posted

技术标签:

【中文标题】如何将 SCNNode 旋转多个角度?【英文标题】:How to rotate a SCNNode by multiple angles? 【发布时间】:2020-04-06 21:07:05 【问题描述】:

我在SCNView 中有一个圆柱体作为SCNCylinder。我想将圆柱体旋转多个给定的角度。我正在使用 SwiftUI 提供所需的输入(旋转角度)。假设我给它一个输入 90° 。目前它可以轻松旋转 90°,但如果我给它第二个输入 180°,它会回到原来的位置,然后旋转 180°。我希望它从旋转 90° 后的位置旋转 180°。

这是我的代码:

struct ContentView: View 
 @State var rotationAngle: Float = 0
 var body: some View 

    VStack

        Text("180°").onTapGesture 
            self.rotationAngle = 180.0
        

        Divider()

        Text("90°").onTapGesture 
            self.rotationAngle = 90.0
        

        SceneKitView(angle: $rotationAngle)
            .position(x: 225.0, y: 175)
            .frame(width: 300, height: 300, alignment: .center)

    
      


struct SceneKitView: UIViewRepresentable 
  @Binding var angle: Float


func degreesToRadians(_ degrees: Float) -> CGFloat 
    return CGFloat(degrees * .pi / 180)


func makeUIView(context: UIViewRepresentableContext<SceneKitView>) -> SCNView 

    let sceneView = SCNView()
    sceneView.scene = SCNScene()
    sceneView.allowsCameraControl = true
    sceneView.autoenablesDefaultLighting = true
    sceneView.backgroundColor = UIColor.white
    sceneView.frame = CGRect(x: 0, y: 10, width: 0, height: 1)

    return sceneView


func updateUIView(_ sceneView: SCNView, context: UIViewRepresentableContext<SceneKitView>) 

        sceneView.scene?.rootNode.enumerateChildNodes  (node, stop) in
            node.removeFromParentNode() 


        let cylinder = SCNCylinder(radius: 0.02, height: 2.0)
        let cylindernode = SCNNode(geometry: cylinder)
        cylindernode.position = SCNVector3(x: 0, y: 0, z: 0)
        cylinder.firstMaterial?.diffuse.contents = UIColor.green


        cylindernode.pivot = SCNMatrix4MakeTranslation(0, -1, 0)




        let rotation = SCNAction.rotate(by: self.degreesToRadians(self.angle),
                                        around: SCNVector3(1, 0, 0), duration: 5)



         cylindernode.runAction(rotation)


        sceneView.scene?.rootNode.addChildNode(cylindernode)

         

无论我给它多少角度,我都希望它能够正确旋转。

【问题讨论】:

【参考方案1】:

明确地说,您希望每次按下按钮时圆柱体都从当前位置继续旋转让圆柱体转到固定的 90 或 180 位置。

因为您当前的代码似乎已设置为执行第二件事(rotate:by 除外)。旋转角度保持为固定状态。确实,您应该向节点发送旋转命令,而不会将其旋转的量保留在状态变量中。

但是,使用您现在拥有的代码:

updateUIView 中坚持只更新你的节点。现在,每次rotationAngle 更改时,您都在拆除整个节点层次结构并将其重新创建到其原始位置。这可能就是为什么事情正在重绘。 而是在 makeUIViewSceneKitView 的初始化程序中进行设置,在 updateUIView 中只应用新的旋转。

第二个问题是,当你通过按下按钮设置rotationAngle 说 180 时,如果再次按下相同的按钮,值再次设置为 180。就 SwiftUI 而言,该值没有改变,无需致电updateUIView。有几种方法可以让它更新,但一种快速简单的方法可能是先旋转 0,然后再旋转你想要的角度。 但是你真的不应该在状态变量中保持这个角度。最好在SceneKitView 中创建一个rotateCylinder(angle: Angle) 函数并从按钮调用它。

另外,为什么不使用新的Angle 类型呢?这是您的代码,其中包含一些更改。我认为这是你想要做的,但我可能是错的。

import SwiftUI
import UIKit
import SceneKit

struct ContentView: View 

  @State var rotationAngle: Angle = .zero

  var body: some View 

    VStack

      HStack 

        Spacer()
        Text("180°").onTapGesture 
          self.rotationAngle = .zero
          self.rotationAngle = .degrees(180)
        
        Spacer()

        Divider()

        Spacer()
        Text("90°").onTapGesture 
          self.rotationAngle = .zero
          self.rotationAngle = .degrees(90)
        
        Spacer()

        .frame(height: 100).padding()

      SceneKitView(radius: 0.2, height: 2, angle: $rotationAngle)

    
  


struct SceneKitView: UIViewRepresentable 

  @Binding var angle: Angle

  let node: SCNNode

  init(radius: CGFloat, height: CGFloat, angle: Binding<Angle>) 

    let cylinder = SCNCylinder(radius: radius, height: height)
    cylinder.firstMaterial?.diffuse.contents = UIColor.green
    self.node = SCNNode(geometry: cylinder)

    self._angle = angle
  

  func makeUIView(context: UIViewRepresentableContext<SceneKitView>) -> SCNView 

    let sceneView = SCNView()
    sceneView.scene = SCNScene()
    sceneView.autoenablesDefaultLighting = true
    sceneView.scene?.rootNode.addChildNode(node)

    return sceneView
  

  func updateUIView(_ sceneView: SCNView, context: UIViewRepresentableContext<SceneKitView>) 
    print("Updating view \(angle.degrees)")

    // To continue rotating
    let rotation = SCNAction.rotate(by: CGFloat(angle.radians), around: SCNVector3(1, 0, 0), duration: 3)

    // To go to a fixed angle state
    //let rotation = SCNAction.rotate(toAxisAngle: SCNVector4(1, 0, 0, angle.radians), duration: 3)

    node.runAction(rotation)

  


【讨论】:

非常感谢您的回复。您的解决方案确实有效。我只是想问你这个问题的一个稍微不同的变体。我想围绕两个不同的轴旋转每个角度(180° 和 90°),比如围绕 SCNVector(1,0,0)旋转 180°,围绕(0,0,1)旋转 90°。我真的很难找到解决方法。有没有办法一样? 如果您只想在角度为 180 时将枢轴旋转为 (1, 0, 0),您可以在 updateUIView 中使用 if 语句并检查是 180 还是 90 或其他值。现在,如果您想要另一个是枢轴变量,它并不像看起来那么简单。您可以为其创建另一个状态变量(这次它应该是一个状态)。但是,视图将在枢轴更改时更新。我不认为这是你想要的。您必须使整个视图符合Equatable 才能仅在角度发生变化而不是枢轴时更新。 感谢您的回复。您的解决方案有效。但是当我使用self.rotationAngle = .zero 时,整个事情就崩溃了。不知道为什么会这样。它运行,但当我删除它时它不会更新视图。 代码和你推荐给我的一样。如果您在 Playground 中运行它,我会收到错误消息“运行此 Playground 时遇到问题。请检查您的代码是否有错误。” 如果您在 Playground 中尝试运行某些东西时遇到这样的错误,请在项目中尝试一下,看看会发生什么。

以上是关于如何将 SCNNode 旋转多个角度?的主要内容,如果未能解决你的问题,请参考以下文章

如何围绕多个轴旋转 SCNNode?

如何在 SceneKit 中移动旋转的 SCNNode?

如何在“func renderer(SCNSceneRenderer, nodeFor: ARAnchor) -> SCNNode?”中旋转 SCNNode?

SceneKit - 旋转和动画SCNNode

未经许可将 SCNNode 向北旋转以进行位置跟踪

如何旋转具有固定锚点的 SCNNode?