Placing SCNScene on the other SCNScene

76 Views Asked by At

I am trying to built an app where you can place dices at the table, that spawns automatically if the app detects horizontal plane. I will explain the problem after the code:

import UIKit
import SceneKit
import ARKit

class ViewController: UIViewController, ARSCNViewDelegate {

    @IBOutlet var sceneView: ARSCNView!
    
    var isTableAdded = false
    
    var diceArray = [SCNNode]()
        
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Set the view's delegate
        sceneView.delegate = self
        
        self.sceneView.debugOptions = [ARSCNDebugOptions.showFeaturePoints]
        
        sceneView.autoenablesDefaultLighting = true
        
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        
        // Create a session configuration
        let configuration = ARWorldTrackingConfiguration()
        
        configuration.planeDetection = .horizontal
        
        // Run the view's session
        sceneView.session.run(configuration)
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        
        // Pause the view's session
        sceneView.session.pause()
    }
     
// MARK: - ADDS DICE
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        
        print("Tapped")

        if let touch = touches.first {
            let touchLocation = touch.location(in: sceneView)
            
            let results = sceneView.hitTest(touchLocation, types: .existingPlaneUsingExtent)
            
            if let hitResults = results.first {
                
                // Create a new scene
                let diceScene = SCNScene(named: "art.scnassets/diceCollada.scn")!
                
                if let diceNode = diceScene.rootNode.childNode(withName: "Dice", recursively: true) {
                    
                    diceNode.position = SCNVector3(
                        x: hitResults.worldTransform.columns.3.x,
                        y: hitResults.worldTransform.columns.3.y,
                        z: hitResults.worldTransform.columns.3.z)
                    
                    diceArray.append(diceNode)
                    
                    sceneView.scene.rootNode.addChildNode(diceNode)
                    
                }
            }
        }
    } 

// MARK: - RENDERS TABLE AT THE HORIZONTAL PLANE

    func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
        print("plane detected")

        if isTableAdded == false {
            let planeAnchor = anchor
            // let planeNode = SCNNode()
            let x = planeAnchor.transform.columns.3.x
            let y = planeAnchor.transform.columns.3.y
            let z = planeAnchor.transform.columns.3.z
            //planeNode!.position = SCNVector3(x: x, y: y, z: z)
            
            let idleScene = SCNScene(named: "art.scnassets/Table.scn")!
            let node = SCNNode()
            for child in idleScene.rootNode.childNodes{
                node.addChildNode(child)
            }
            node.position = SCNVector3(x,y,z)
            node.scale = SCNVector3(0.001,0.001,0.001)
            
            sceneView.scene.rootNode.addChildNode(node)
            
            isTableAdded = true
        }
        print("Model placed")
    }
    
    func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
        guard let planeAnchor = anchor as? ARPlaneAnchor else { return }
        
        print("plane updated")
    }
}

The problem is here. (Video at Imgur). As you can see table is placed, but I don't quite understand how to place SCNScene directly at the other one. The only thing I have achieved is creating a model at the tap and it's placed only at the horizontal plane.

How can I spawn model on the other model?

1

There are 1 best solutions below

1
On

You could probably use this tiny little extension to achive what you want. It basically is a helper function that allows you to grab all the geometry objects from any scene-file (.scn) and you can place them wherever you want. I use this function to compose/build one single scene (the default) from out of plenty of other scene-files, which hold individual geometries. It will return a SCNNode instead of a new SCNScene. Let me know, if you need more information, or if I missunderstand you.

extension SCNNode {
    convenience init(named name: String) {
        self.init()
        guard let scene = SCNScene(named: name) else {return}
        for childNode in scene.rootNode.childNodes {addChildNode(childNode)}
    }
}

(place this code-snippet somewhere outside your ViewController)

Then you can use it like this:

let objectNode = SCNNode(named:"art.scnassets/some_directory/some_scene.scn").flattenedClone()

The flattenedClone is useful if you want to merge all geometries within a signle SCNNode to one object. If you don't use flattenedClone, you will find your geometries as individual childNodes of the objectNode.

Keep in mind to add a static physicsBody to the Table (and probably the floor) and dynamic physicsBodies to your dices, if you want to keep them on the top of the table and prevent them to fall trough the ground (floor).