ARKit 无法在垂直平面上正确旋转 SCNNode

ARKit cannot rotate a SCNNode correctly on a vertical plane

我想旋转一个SCNNode,它是一个绘画图像

我可以在地板上旋转它,但我不能在墙上正确旋转它。

(我正在使用 UIRotationGestureRecognizer)

...
  func addPainting(_ hitResult: ARHitTestResult, _ grid: Grid) {

    ...

    // Add the painting
    let newPaintingNode = SCNNode(geometry: planeGeometry)
    newPaintingNode.transform = SCNMatrix4(hitResult.anchor!.transform)
    newPaintingNode.eulerAngles = SCNVector3(newPaintingNode.eulerAngles.x + (-Float.pi / 2), newPaintingNode.eulerAngles.y, newPaintingNode.eulerAngles.z)
    newPaintingNode.position = SCNVector3(hitResult.worldTransform.columns.3.x, hitResult.worldTransform.columns.3.y, hitResult.worldTransform.columns.3.z)
    self.paintingNode = newPaintingNode

    self.currentAngleY = newPaintingNode.eulerAngles.y
    self.currentAngleZ = newPaintingNode.eulerAngles.z

    augmentedRealityView.scene.rootNode.addChildNode(self.paintingNode!)
    grid.removeFromParentNode()
  }
...

  @objc func rotateNode(_ gesture: UIRotationGestureRecognizer){

    if let currentNode = self.paintingNode {
      //1. Get The Current Rotation From The Gesture
      let rotation = Float(gesture.rotation)

      if self.paintingAlignment == "horizontal" {

        log.verbose("rotate horizontal!")

        //2. If The Gesture State Has Changed Set The Nodes EulerAngles.y
        if gesture.state == .changed {
          currentNode.eulerAngles.y = currentAngleY + rotation
        }

        //3. If The Gesture Has Ended Store The Last Angle Of The Cube
        if(gesture.state == .ended) {
          currentAngleY = currentNode.eulerAngles.y
        }

      } else if self.paintingAlignment == "vertical" {

        log.verbose("rotate vertical!")

        //2. If The Gesture State Has Changed Set The Nodes EulerAngles.z
        if gesture.state == .changed {
          currentNode.eulerAngles.z = currentAngleZ + rotation
        }

        //3. If The Gesture Has Ended Store The Last Angle Of The Cube
        if(gesture.state == .ended) {
          currentAngleZ = currentNode.eulerAngles.z
        }
      }
    }
  }

有谁知道如何在墙上正确旋转它?谢谢!

您正在代码中使用 eulerAngles 实例 属性:

var eulerAngles: SCNVector3 { get set }

根据 Apple 文档:

SceneKit applies eulerAngles rotations relative to the node’s pivot property in the reverse order of the components: first roll (Z), then yaw (Y), then pitch (X).

...但是三分量旋转会导致 Gimbal Lock.

Gimbal lock is the loss of one degree of freedom in a three-dimensional, three-gimbal mechanism that occurs when the axes of two of the three gimbals are driven into a parallel configuration, "locking" the system into rotation in a degenerate two-dimensional space.

所以你需要使用四分量旋转属性:

var rotation: SCNVector4 { get set }

The four-component rotation vector specifies the direction of the rotation axis in the first three components (XYZ) and the angle of rotation, expressed in radians, in the fourth (W).

currentNode.rotation = SCNVector4(x: 1, 
                                  y: 0, 
                                  z: 0,
                                  w: -Float.pi / 2)

如果您想了解更多关于 SCNVector4 结构和四分量旋转(及其以弧度表示的 W 分量)的信息,请查看 and THIS POST

P.S.

RealityKit 框架中,您需要使用 SIMD4<Float> 通用结构或 simd_float4 类型别名而不是 SCNVector4 结构。

var rotation: simd_float4 { get set }

我尝试了 SCNNode eulerAnglesSCNNode rotation 但我没有运气...

我想那是因为我在添加绘画时调用了 SCNNode.transform()。稍后我修改SCNNode.eulerAngles并调用SCNNode.rotation()时,它们可能会影响第一个transform

我终于使用 SCNMatrix4Rotate 而不是 eulerAnglesrotation 让它工作了。

  func addPainting(_ hitResult: ARHitTestResult, _ grid: Grid) {
    ...
    let newPaintingNode = SCNNode(geometry: planeGeometry)
    newPaintingNode.transform = SCNMatrix4(hitResult.anchor!.transform)
    newPaintingNode.transform = SCNMatrix4Rotate(newPaintingNode.transform, -Float.pi / 2.0, 1.0, 0.0, 0.0)
    newPaintingNode.position = SCNVector3(hitResult.worldTransform.columns.3.x, hitResult.worldTransform.columns.3.y, hitResult.worldTransform.columns.3.z)
    self.paintingNode = newPaintingNode
    ...
  }
...
  @objc func rotateNode(_ gesture: UIRotationGestureRecognizer){

    if let _ = self.paintingNode {

      // Get The Current Rotation From The Gesture
      let rotation = Float(gesture.rotation)

      if gesture.state == .began {
        self.rotationZ = rotation
      }

      if gesture.state == .changed {
        let diff = rotation - self.rotationZ
        self.paintingNode?.transform = SCNMatrix4Rotate(self.paintingNode!.transform, diff, 0.0, 0.0, 1.0)
        self.rotationZ = rotation
      }

    }

  }

上面的代码在垂直和水平平面上都有效。