Giovanni Piliego
Giovanni Piliego

Reputation: 11

iOS: How to make a node rotate around a specific axis in SceneKit

I'm new to Swift programming and SceneKit framework. I'm try to build a Rubik's twist app and what I've got so far is the chain of pieces positioned at my camera's center. Rubik's twist chain

This is how I achieved it:

 var snake = [SCNNode]()
    var bounds_z = Float()
    var bounds_x = Float()
    var j = 0
    for i in 0...11{
        let piece_scene_1 = SCNScene(named: "piece.scnassets/piece.scn")!
        let piece_scene_2 = SCNScene(named: "piece.scnassets/piece.scn")!
        piece_scene_1.rootNode.childNodes[0].scale = SCNVector3(0.9, 0.9, 0.9)
        piece_scene_2.rootNode.childNodes[0].scale = SCNVector3(0.9, 0.9, 0.9)
        bounds_z = Float(2*(piece_scene_1.rootNode.childNodes[0].geometry?.boundingBox.max.z ?? 0.0))
        bounds_x = Float(2*(piece_scene_1.rootNode.childNodes[0].geometry?.boundingBox.min.x ?? 0.0))
        snake.append(piece_scene_1.rootNode.childNodes[0])
        snake.append(piece_scene_2.rootNode.childNodes[0])
        snake[j].name = "piece_\(j)"
        snake[j+1].name = "piece_\(j+1)"
        snake[j+1].rotation = SCNVector4(0, 1, 0, Float.pi)
        snake[j].position = SCNVector3(x: Float(i)*bounds_x,y: Float(0),z: Float(i)*bounds_z)
        snake[j+1].position = SCNVector3(x: Float(i)*bounds_x,y: Float(0),z: Float(i+1)*bounds_z)
        scene.rootNode.addChildNode(snake[j])
        scene.rootNode.addChildNode(snake[j+1])
        j+=2
    }
    cameraNode.look(at: snake[(snake.count/2)-1].position)

My problem is now to make a set of pieces rotate when tapped. My idea was to make a container node, add all nodes before the one tapped and then rotate the container around the normal axis of the tapped piece face (adjacent to the container). This is my try:

@objc func handleTap(_ gestureRecognize: UIGestureRecognizer){

            let sceneView = self.view as! SCNView
            let p = gestureRecognize.location(in: sceneView)
            let hitResults = sceneView.hitTest(p, options: [:])
            if hitResults.count > 0 {
                // retrieved the first clicked object
                let result: SCNHitTestResult = hitResults[0]
                let container = SCNNode()
                let id_name = result.node.name
                let id_array = id_name?.components(separatedBy: CharacterSet.decimalDigits.inverted)
                  // add nodes to container
                    for item in id_array! {
                         if let id = Int(item){
                            for i in 0...id-1{
                                container.addChildNode(scene.rootNode.childNode(withName: "piece_\(i)", recursively: true)!)
                                }
                           // add container to scene
                            scene.rootNode.addChildNode(container)
                            // get container orientation
                            var GLKQuat = GLKQuaternionMake(container.orientation.x, container.orientation.y, container.orientation.z, container.orientation.w)
                            // get future orientation
                            let multiplier = GLKQuaternionMakeWithAngleAndAxis(Float.pi/2, 0, 0, 1)                        
                            // assign new orientation to container
                            GLKQuat = GLKQuaternionMultiply(GLKQuat, multiplier)
                            container.orientation = SCNQuaternion(GLKQuat.x, GLKQuat.y, GLKQuat.z, GLKQuat.w)           
                            // maintain childs position after rotation
                            for childnode in container.childNodes{
                                let child_transform = childnode.parent!.convertTransform(childnode.transform, to: scene.rootNode)
                                childnode.removeFromParentNode()
                                childnode.transform = child_transform
                                scene.rootNode.addChildNode(childnode)}
                           }
                         }
                       }
                     }

Problem is that since container is a scene.rootNode.child, will rotate around its z axis like this: Rubiks twist chain after rotation

Upvotes: 0

Views: 898

Answers (2)

Giovanni Piliego
Giovanni Piliego

Reputation: 11

After days of struggling I finally found a solution:

 let container = SCNNode()
 for i in 0...id-1{
 container.addChildNode(scene.rootNode.childNode(withName: "piece_\(i)", recursively: true)!)
 }
 container.name = "body_to_rotate"
 // add the container to scene root node
 scene.rootNode.addChildNode(container)
 // transform from container to tapped node 
 let container_transform = container.parent!.convertTransform(container.transform, to: result.node)
 container.transform = container_transform
 // add container as child node to tapped node
 result.node.addChildNode(container)
 // make the rotation happen
 let old_rotation = result.node.orientation
 var quat_rot = GLKQuaternionMake(old_rotation.x, old_rotation.y, old_rotation.z, old_rotation.w)
 let multiplier = GLKQuaternionMakeWithAngleAndAxis(Float.pi/2, 0, 0, 1)
                                    quat_rot = GLKQuaternionMultiply(quat_rot, multiplier)
 result.node.orientation = SCNQuaternion(quat_rot.x, quat_rot.y, quat_rot.z, quat_rot.w)
 for childnode in container.childNodes{
     let child_transform = childnode.parent!.convertTransform(childnode.transform, to: scene.rootNode)
     childnode.removeFromParentNode()
     childnode.transform = child_transform
     scene.rootNode.addChildNode(childnode)
     }
 // delete container
 container.removeFromParentNode()

Upvotes: 1

Stefan
Stefan

Reputation: 5451

Rotation around z axis is caused by this line of code

let multiplier = GLKQuaternionMakeWithAngleAndAxis(Float.pi/2, 0, 0, 1) 

If you want to rotate around another axis you have to specify the x, y and z component: from the Apple documentation

Upvotes: 0

Related Questions