swiftscenekitscnnode

Get radius of a SCNSphere that is used to create a SCNNode


How do i return the radius of a sphere geometry(SCNSphere) used to set up a SCNNode. I want to use the radius in a method where i move some child nodes in relation to a parent node. The code below fails since radius appears to be unknown to the resulting node, should i not pass the node to the method?

Also my array index fails saying that Int is not a Range.

I am trying to build something from this

import UIKit
import SceneKit

class PrimitivesScene: SCNScene {

    override init() {
        super.init()
        self.addSpheres();
    }

    func addSpheres() {
        let sphereGeometry = SCNSphere(radius: 1.0)
        sphereGeometry.firstMaterial?.diffuse.contents = UIColor.redColor()
        let sphereNode = SCNNode(geometry: sphereGeometry)
        self.rootNode.addChildNode(sphereNode)

        let secondSphereGeometry = SCNSphere(radius: 0.5)
        secondSphereGeometry.firstMaterial?.diffuse.contents = UIColor.greenColor()
        let secondSphereNode = SCNNode(geometry: secondSphereGeometry)
        secondSphereNode.position = SCNVector3(x: 0, y: 1.25, z: 0.0)

        self.rootNode.addChildNode(secondSphereNode)
        self.attachChildrenWithAngle(sphereNode, children:[secondSphereNode, sphereNode], angle:20)
    }

    func attachChildrenWithAngle(parent: SCNNode, children:[SCNNode], angle:Int) {
        let parentRadius = parent.geometry.radius //This fails cause geometry does not know radius.

        for var index = 0; index < 3; ++index{
            children[index].position=SCNVector3(x:Float(index),y:parentRadius+children[index].radius/2, z:0);// fails saying int is not convertible to range.
        }


    }

    required init(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

}

Solution

  • The issue with radius is that parent.geometry returns a SCNGeometry and not an SCNSphere. If you need to get the radius, you'll need to cast parent.geometry to SCNSphere first. To be safe, it's probably best to use some optional binding and chaining to do that:

    if let parentRadius = (parent.geometry as? SCNSphere)?.radius {
        // use parentRadius here
    }
    

    You'll also need to do that when accessing the radius on your children nodes. If you put all that together and clean things up a little, you get something like this:

    func attachChildrenWithAngle(parent: SCNNode, children:[SCNNode], angle:Int) {
        if let parentRadius = (parent.geometry as? SCNSphere)?.radius {
            for var index = 0; index < 3; ++index{
                let child = children[index]
                if let childRadius = (child.geometry as? SCNSphere)?.radius {
                    let radius = parentRadius + childRadius / 2.0
                    child.position = SCNVector3(x:CGFloat(index), y:radius, z:0.0);
                }
            }
        }
    }
    

    Note though that you're calling attachChildrenWithAngle with an array of 2 children:

    self.attachChildrenWithAngle(sphereNode, children:[secondSphereNode, sphereNode], angle:20)
    

    If you do that, you're going to get a runtime crash in that for loop when accessing the 3rd element. You'll either need to pass an array with 3 children every time you call that function, or change the logic in that for loop.