swiftaugmented-realityscenekitarkitusdz

Node name was overwritten by USDZ model


I'm currently developing an iOS app that uses ARKit and SceneKit for the augmented reality. I'm having a problem while loading an .usdz model into the scene. I can load it correctly into the scene, but, when I try to get the node name (in which I loaded the .usdz model) after tapping on it, it returns the .usdz name and not the name I gave to it.

The code I use to load the .usdz model is:

let mdlAsset = MDLAsset(url: urlPath)
mdlAsset.loadTextures()
let asset = mdlAsset.object(at: 0) // extract first object
var assetNode = SCNNode(mdlObject: asset)
assetNode = SCNNode(mdlObject: asset)
assetNode.name = "Node-2"
sceneView.scene.rootNode.addChildNode(assetNode)

To capture the tap on the node, the code is:

@objc func handleTap(recognizer: UITapGestureRecognizer){
       
    let location = recognizer.location(in: sceneView)
    let results = sceneView.hitTest(location, options: nil)

    guard recognizer.state == .ended else { return }

    if results.count > 0 {
        let result = results[0] as SCNHitTestResult
        let node = result.node  
        print(node.name)    
    }     
}

As I mentioned before, when I tap on the object it prints Optional("Sphere_0") value that can be found in the top right corner, in the model details page. The correct value that I expected was "Node-2".


Solution

  • The name of your USDZ model isn't overridden. It's the peculiarities of SceneKit hit-testing and scene hierarchy. When you perform a hit-test search, SceneKit looks for SCNGeometry objects (not a main node) along the ray you specify. So, all you need to do, once the hit-test is completed, is to find the corresponding parent nodes.

    Try this code:

    import SceneKit.ModelIO
    
    class GameViewController: UIViewController {
        
        var sceneView: SCNView!
            
        override func viewDidLoad() {
            super.viewDidLoad()
            
            sceneView = (self.view as! SCNView)
            let scene = SCNScene(named: "art.scnassets/ship.scn")!
            sceneView.scene = SCNScene()
            sceneView.backgroundColor = .black
            
            let recog = UITapGestureRecognizer(target: self, action: #selector(tap))
            sceneView.addGestureRecognizer(recog)
            
            // ASSET
            let mdlAsset = MDLAsset(scnScene: scene)
            let asset = mdlAsset.object(at: 0)
            let node = SCNNode(mdlObject: asset.children[0])
            node.name = "Main-Node-Name"                       // former "ship"
            node.childNodes[0].name = "SubNode-Name"           // former "shipMesh"
            node.childNodes[0].childNodes[0].name = "Geo-Name" // former "Scrap_MeshShape"
            sceneView.scene?.rootNode.addChildNode(node)
        }
    }
    

    And your hit-testing method:

    extension GameViewController {
        
        @objc func tap(recognizer: UITapGestureRecognizer) {
               
            let location = recognizer.location(in: sceneView)
            let results = sceneView.hitTest(location)
    
            guard recognizer.state == .ended else { return }
    
            if results.count > 0 {
                let result = results[0] as SCNHitTestResult
                let node = result.node
                
                print(node.name!)                            // Geo-Name
                print(node.parent!.name!)                    // SubNode-Name
                print(node.parent!.parent!.name!)            // Main-Node-Name
            }
        }
    }