Search code examples
swiftarkitscnnodearscnview

Getting the world x, y, and z of placed SCNNode - Swift


My objective is to generate a SCNNode in the middle of another SCNNode that contains a SCNPlane.

The SCNPlane is generated in a renderer function like this:

func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
    guard let planeAnchor = anchor as? ARPlaneAnchor else { return }

    let width = CGFloat(planeAnchor.extent.x)
    let height = CGFloat(planeAnchor.extent.z)
    let plane = SCNPlane(width: width, height: height)

    plane.materials.first?.diffuse.contents = UIColor.orange

    let planeNode = SCNNode(geometry: plane)

    let x = CGFloat(planeAnchor.center.x)
    let y = CGFloat(planeAnchor.center.y)
    let z = CGFloat(planeAnchor.center.z)
    planeNode.position = SCNVector3(x,y,z)
    planeNode.eulerAngles.x = -.pi / 2

    node.addChildNode(planeNode)
    node.name = "plane"

    loadSphere(x:x, y:y, z:z)

Im trying to put the sphere right in the middle of a plane as such:

func loadSphere (x:CGFloat, y:CGFloat, z:CGFloat) {
    let idleScene = SCNScene(named: "art.scnassets/bounceFixed.dae")!

    // This node will be parent of all the animation models
    let node = SCNNode()

    for child in idleScene.rootNode.childNodes {
        node.addChildNode(child)
    }

    // Set up some properties
    node.name = "Sphere"

    node.position = SCNVector3(x, y, z)
    node.scale = SCNVector3(0.001, 0.001, 0.001)
    ...

But the issue is that whenever I run this, x, y, z, all end up being zero. I've tried things like sceneView.scene.rootNode.childNodes[3].worldPosition or node.worldPosition but that always ends up generating the sphere right in the view of the camera and not in the middle of the plane.

I've attached a picture here to show you what I mean by the "middle".

enter image description here

Please let me know how I can remedy this.

EDIT: My goal with this post is to try to get the x, y, z (center) of the plane node without appending the sphere as a child.

Best.


Solution

  • Without actually seeing your model, or exactly how you are adding your Sphere, I would hazard a guess, that either you aren't adding your model to the correct SCNNode, or that the pivot of your model is not centered.

    If you want to place something centrally on your SCNNode with a plane geometry you can set it's position at SCNVector3Zero or ommit it altogether e.g.

    /// Loads An SCNSphere On A Generated SCNNode
    ///
    /// - Parameter plane: SCNNode
    func loadSphereOnPlane(_ plane: SCNNode) {
    
        let sphereNode = SCNNode()
        let sphereNodeGeometry = SCNSphere(radius: 0.01)
        sphereNodeGeometry.firstMaterial?.diffuse.contents = UIColor.cyan
        sphereNode.geometry = sphereNodeGeometry
        plane.addChildNode(sphereNode)
    
    }
    

    Whereby you would call this at the end of the rendererDidAddNode delegate callback e.g:

    loadSphereOnPlane(planeNode)
    

    If the pivot of your model is not centered you can use something like this to alter it:

    /// Changes The Pivot To The Center Of The Model
    ///
    /// - Parameter model: SCNNode
    func createCentralPivotFor(_ model: SCNNode){
    
        let (min, max) = model.boundingBox
    
        let dx = min.x + 0.5 * (max.x - min.x)
        let dy = min.y + 0.5 * (max.y - min.y)
        let dz = min.z + 0.5 * (max.z - min.z)
        model.pivot = SCNMatrix4MakeTranslation(dx, dy, dz)
    
    }
    

    An example usage might thus be like so:

    /// Loads Our Little Scatterbug
    func loadScatterBugOnPlane(_ node: SCNNode) {
    
        let modelPath = "ARModels.scnassets/Scatterbug.scn"
    
        //1. Get The Reference To Our SCNScene & Get The Model Root Node
        guard let model = SCNScene(named: modelPath),
            let pokemonModel = model.rootNode.childNode(withName: "RootNode", recursively: false) else { return }
    
        let scatterBug = pokemonModel
    
        //2. Scale The Scatterbug
        scatterBug.scale = SCNVector3(0.002, 0.002, 0.002)
    
        //3. Set The Pivot
        createCentralPivotFor(scatterBug)
    
        //4. Add It To The Plane
        node.addChildNode(scatterBug)
    
    }
    

    Which you could also call at the end of the rendererDidAddNode delegate callback e.g:

    Both methods result in something like this:

    enter image description here

    Update:

    Since you have now changed your question, you can try something like this:

    /// Loads An SCNSphere At The Position Of An ARAnchor
    ///
    /// - Parameter plane: SCNNode
    func loadSphereAtAnchorPosition(_ anchor: ARPlaneAnchor) {
    
        let sphereNode = SCNNode()
        let sphereNodeGeometry = SCNSphere(radius: 0.01)
        sphereNodeGeometry.firstMaterial?.diffuse.contents = UIColor.cyan
        sphereNode.geometry = sphereNodeGeometry
    
        sphereNode.position = SCNVector3(anchor.transform.columns.3.x,
                                         anchor.transform.columns.3.y,
                                         anchor.transform.columns.3.z)
    
        augmentedRealityView?.scene.rootNode.addChildNode(sphereNode)
    
    }
    

    Hope it helps...