0

I'm dealing with children nodes with pivot and position that have been altered. I found a lot of SCNNode transformation topics, but it seems none of them represent my situation.

I have six balls : (can't post more than 2 links, image is at i.stack.imgur.com/v3Lc4.png )

And I select the top four of them, adjust the pivot, adjust the position (to counter the pivot translation effect), and rotate. This is the code I use :

//core code
    let fourBalls = SCNNode()
    for i in 1...4
    {
        let ball = scene.rootNode.childNode(withName: "b" + String(i), recursively: false)!
        ball.removeFromParentNode()
        fourBalls.addChildNode(ball)
    }
    scene.rootNode.addChildNode(fourBalls)

    //adjust the pivot of the fourBalls node
    fourBalls.pivot = SCNMatrix4MakeTranslation(-1.5, 0.5, -5)
    //fix the position
    fourBalls.position = SCNVector3Make(-1.5, 0.5, -5)

    //rotate
    let action = SCNAction.rotateBy(x: 0, y: 0, z: CGFloat(M_PI_2), duration: 2)
    fourBalls.run(action)

It did the job well :

this is the intended result

Now, I need to release back the fourBalls child nodes into the rootNode, I use this code which I put as completion block :

//core problem
//how to release the node with the transform?
            for node in fourBalls.childNodes
            {   node.transform = node.worldTransform
                node.removeFromParentNode()
                self.scene.rootNode.addChildNode(node)
            }

And here comes the problem, I released them wrongly :

pic

So my question is, how to release the children nodes to the rootNode with correct pivot, position, and transform properties?

Here is my full GameViewController.swift for you who want to try :

import SceneKit

class GameViewController: UIViewController {
let scene = SCNScene()
override func viewDidLoad() {
    super.viewDidLoad()

    let ball1 = SCNSphere(radius: 0.4)
    let ball2 = SCNSphere(radius: 0.4)
    let ball3 = SCNSphere(radius: 0.4)
    let ball4 = SCNSphere(radius: 0.4)
    let ball5 = SCNSphere(radius: 0.4)
    let ball6 = SCNSphere(radius: 0.4)
    ball1.firstMaterial?.diffuse.contents = UIColor.purple()
    ball2.firstMaterial?.diffuse.contents = UIColor.white()
    ball3.firstMaterial?.diffuse.contents = UIColor.cyan()
    ball4.firstMaterial?.diffuse.contents = UIColor.green()
    ball5.firstMaterial?.diffuse.contents = UIColor.black()
    ball6.firstMaterial?.diffuse.contents = UIColor.blue()

    let B1 = SCNNode(geometry: ball1)
    B1.position = SCNVector3(x:-2,y:1,z:-5)
    scene.rootNode.addChildNode(B1)
    B1.name = "b1"
    let B2 = SCNNode(geometry: ball2)
    B2.position = SCNVector3(x:-1,y:1,z:-5)
    scene.rootNode.addChildNode(B2)
    B2.name = "b2"
    let B3 = SCNNode(geometry: ball3)
    B3.position = SCNVector3(x:-2,y:0,z:-5)
    scene.rootNode.addChildNode(B3)
    B3.name = "b3"
    let B4 = SCNNode(geometry: ball4)
    B4.position = SCNVector3(x:-1,y:0,z:-5)
    scene.rootNode.addChildNode(B4)
    B4.name = "b4"
    let B5 = SCNNode(geometry: ball5)
    B5.position = SCNVector3(x:-2,y:-1,z:-5)
    scene.rootNode.addChildNode(B5)
    B5.name = "b5"
    let B6 = SCNNode(geometry: ball6)
    B6.position = SCNVector3(x:-1,y:-1,z:-5)
    scene.rootNode.addChildNode(B6)
    B6.name = "b6"

    let cameraNode = SCNNode()
    cameraNode.camera = SCNCamera()
    cameraNode.position = SCNVector3Make(-1.5,0,2)
    scene.rootNode.addChildNode(cameraNode)

    // create and add an ambient light to the scene
    let ambientLightNode = SCNNode()
    ambientLightNode.light = SCNLight()
    ambientLightNode.light!.type = SCNLightTypeAmbient
    ambientLightNode.light!.color = UIColor.yellow()
    scene.rootNode.addChildNode(ambientLightNode)


    let scnView = self.view as! SCNView
    scnView.scene = scene
    scnView.allowsCameraControl = false
    scnView.backgroundColor = UIColor.orange()

    //core code
    let fourBalls = SCNNode()
    for i in 1...4
    {
        let ball = scene.rootNode.childNode(withName: "b" + String(i), recursively: false)!
        ball.removeFromParentNode()
        fourBalls.addChildNode(ball)
    }
    scene.rootNode.addChildNode(fourBalls)

    //adjust the pivot of the fourBalls node
    fourBalls.pivot = SCNMatrix4MakeTranslation(-1.5, 0.5, -5)
    //fix the position
    fourBalls.position = SCNVector3Make(-1.5, 0.5, -5)

    //rotate
    let action = SCNAction.rotateBy(x: 0, y: 0, z: CGFloat(M_PI_2), duration: 2)
    fourBalls.run(action, completionHandler:

        {
            //core problem
            for node in fourBalls.childNodes
            {
                node.transform = node.worldTransform
                node.removeFromParentNode()
                self.scene.rootNode.addChildNode(node)
            }

    })
}

override func shouldAutorotate() -> Bool {
    return true
}

override func prefersStatusBarHidden() -> Bool {
    return true
}

override func supportedInterfaceOrientations() -> UIInterfaceOrientationMask {
    if UIDevice.current().userInterfaceIdiom == .phone {
        return .allButUpsideDown
    } else {
        return .all
    }
}

override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()
    // Release any cached data, images, etc that aren't in use.
}
}
antonio
  • 10,629
  • 13
  • 68
  • 136
  • Where in your top image is x = 0, y = 0 for root? – bpedit Aug 10 '16 at 22:57
  • @bpedit I don't understand what do you ask. I set all the nodes deliberately to avoid x = 0, y = 0. My first image can no longer be displayed, it should be the state before the rotation of fourBalls applied : purple (top left), white (top right), cyan (middle left), green (middle right), the bottom balls are the same. The image where the top left ball's colour is white, is the already rotated state. – Chess Engineer Aug 10 '16 at 23:18
  • From your bottom code, I see your Y = 0 is inline with the center spheres. X = 0 is one ball spacing to the right of any balls. – bpedit Aug 11 '16 at 01:00
  • What pattern do you get if you run everything as above but without the rotation? – bpedit Aug 11 '16 at 03:36
  • @bpedit, the coordinate I use has nothing to do with x = 0, y = 0. Top in here is y = 1, middle is y = 0, bottom is y = -1, while left is x = -2 and right is x = -1. Sure I can change to say y = -6, -7, -8, therefore y = -7 is the middle. The pivot change formula is (min x + max x) / 2 and (min y + max y)/2 where min and max of x and y is from the four balls coordinate. About the pattern without rotation, why don't you try ;) The full code is ready to compile. The goal here is about releasing the children after grouping, not about performing rotation. – Chess Engineer Aug 11 '16 at 09:36
  • addendum : you can change the x and y lining position to whatever, just put the camera on x and y centre of the six balls (using the pivot change formula I gave above, but for six balls). The z position of camera is just distance to the target, that is camera.z - centreSixBalls.z = the z distance. The pivot and position change applied to the four balls after I grouped them, now I want to release them back hence all six balls has rootNode as the parent. The main problem here is, the transform is not correctly handled, even I already used worldTransfrom. – Chess Engineer Aug 11 '16 at 09:55

1 Answers1

0

SCNActions update the presentation tree directly, not the model tree, this is probably best explained in the 2014 WWDC video (skip to 16:38). What this means is that throughout the animation the current transform for each of the animating nodes is only available from the presentationNode. Your code works if we get the transform from here instead.

Apologies for the Swift 2 backport...

//rotate
let action = SCNAction.rotateByX(0, y: 0, z: CGFloat(M_PI_2), duration: 2)
fourBalls.runAction(action, completionHandler: {
    //core problem
    for node in fourBalls.childNodes
    {
        node.transform = node.presentationNode.worldTransform
        node.removeFromParentNode()
        self.scene.rootNode.addChildNode(node)
    }
})

TBH I was expecting node.worldTransform == node.presentationNode.worldTransform when it got to the completion handler.

lock
  • 2,861
  • 1
  • 13
  • 20
  • Ah, it's my stupidity. Thanks for figuring it out ! I tried to do various position conversion, and also tried to reset the pivot back to normal, but nothing works. For Swift 3, the code is node.transform = node.presentation.worldTransform, and I wonder did you make a typo, == instead of = ? Because I don't see what's the node.worldTransform == node.presentation.worldTransform check means. – Chess Engineer Aug 11 '16 at 11:48
  • Ha, what I was trying to say with that last remark is that I expected the node and presentationNode to have the same transform at the end of the animation. I guess SCNActions don't change the 'actual' position. – lock Aug 11 '16 at 11:54
  • Well, actually I tried using both SCNAction and SCNTransaction, and it is node.worldTransform != node.presentation.worldTransform from both case. So yeah, both don't change the 'actual' position. – Chess Engineer Aug 11 '16 at 15:21