How to store additional information in SCNNode?

1.8k views Asked by At

I am writing an iOS application using ARKit and SceneKit on iOS 11(beta).

This application adds many items onto an augmented reality scene. In order to identify each of these item, I subclassed SCNNode as MySCNNode and put an id field in it:

MySCNNode: SCNNode {
    var id: Int
    ...some initializer to assign value to the id
}

I also implemented a touchesBegan delegate method. When user touches a item, I want to retrieve its corresponding id. So I tried cast that item's corresponding SCNNode back to MySCNNode:

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    super.touchesBegan(touches, with: event)

    // hitTest to retrieve item's id
    if let touchLocation = touches.first?.location(in: sceneLocationView) {
        DDLogDebug("received touch location")
        if let hit = sceneLocationView.hitTest(touchLocation, options: nil).first {
            DDLogDebug("received a hit")
            if let scnNode = hit.node as? SCNNode {
                DDLogDebug("received a scnNode)")
                    if let mySCNNode = scnNode as? MySCNNode {
                        DDLogDebug("received a MySCNNode") // ====> Never reaches here
                    }

            }


            return
        }


    }


}

However, the step to cast from SCNNode to MySCNNode never happens

Did I wrote some place wrong when trying to cast the SCNNode back to MySCNNode in the above hit test? Is there any alternative way to store additional information in a SCNNode and later retrieve it ?

Thank you

2

There are 2 answers

0
John Scalo On

I think you're on the right track but you don't need so many casts and it's easier if you "stack" the if lets. Try this:

if let touchLocation = touches.first?.location(in: sceneLocationView),
    let hit = sceneLocationView.hitTest(touchLocation, options: nil).first,
    let myNode = hit.node as? MySCNNode {
    DDLogDebug("received a MySCNNode")
}
0
Vasilii Muravev On

First of all, you don't need id. Instead you could use existing name attribute. But if you want to check type of node, you should also check it's parents, because of hitTest result node starts from the child. You could add SCNNode extension:

extension SCNNode {
    /// Returns parent node of specified type.
    ///
    /// - Returns: An optional `SCNNode` of specified `T` class type.
    func parentOfType<T: SCNNode>() -> T? {
        var node: SCNNode! = self
        repeat {
            if let node = node as? T { return node }
            node = node?.parent
        } while node != nil
        return nil
    }
}

And then use:

if let myNode = node.parentOfType() as? MySCNNode {
    // do something
}