ysong4
ysong4

Reputation: 191

ARKit cannot rotate a SCNNode correctly on a vertical plane

I want to rotate a SCNNode, which is a painting image.

I can rotate it on the floor, but I cannot rotate it correctly on wall.

(I am using 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
        }
      }
    }
  }

enter image description here

Does anyone know how can I rotate it correctly on wall? Thank you!

Upvotes: 2

Views: 1305

Answers (2)

Andy Jazz
Andy Jazz

Reputation: 58063

You're using eulerAngles instance property in your code:

var eulerAngles: SCNVector3 { get set }

According to Apple documentation:

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).

...but three-component rotation can lead to 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.

So you need to use a four-component rotation property:

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)

If you want to know more about SCNVector4 structure and four-component rotation (and its W component expressed in radians) look at THIS POST and THIS POST.

P.S.

In RealityKit framework instead of SCNVector4 structure you need to use SIMD4<Float> generic structure or simd_float4 type alias.

var rotation: simd_float4 { get set }

Upvotes: 2

ysong4
ysong4

Reputation: 191

I tried SCNNode eulerAngles and SCNNode rotation and I have no luck...

I guess that is because I invoked SCNNode.transform() when I add the painting. When later I modify SCNNode.eulerAngles and invoke SCNNode.rotation(), they may have influence on the first transform.

I finally get it working using SCNMatrix4Rotate, not using eulerAngles and rotation.

  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
      }

    }

  }

And the above code works on both vertical and horizontal plane.

Upvotes: 0

Related Questions