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

1.5k views Asked by At

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")
    }

}
2

There are 2 answers

1
Mike S On BEST ANSWER

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.

0
mirrmurr On

At this point I'm only working with spheres, hence the radius is quite easy to manipulate. But still, I think you should try to look at the geometry of the SCNNode. I use this function besides the code snippet you posted (the two works together just fine for me).

func updateRadiusOfNode(_ node: SCNNode, to radius: CGFloat) {
    if let sphere = (node.geometry as? SCNSphere) {
        if (sphere.radius != radius) {
            sphere.radius = radius
        }
    }
}

Also the scaling factor (at the radius) is also important. I use a baseRadius then i'm multiplying that with

maxDistanceObserved / 2

Always updating the max distance if a hitTest has futher results.