5
votes

I'm having some troubles with SCNode hit detection. I need to detect which object was touched in the scene having a SCNNode, I have implemented this piece of code but it seems crashing when I'm touching the object but working good when I'm touching the rest of the sceneView.

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        let touch = touches.first as! UITouch
        if(touch.view == self.sceneView){
            print("touch working")
            let viewTouchLocation:CGPoint = touch.location(in: sceneView)
            guard let result = sceneView.hitTest(viewTouchLocation, options: nil).first else {
                return
            }
            if (bottleNode?.contains(result.node))! { //bottleNode is declared as  SCNNode? and it's crashing here
                print("match")
            }

        }
    }
4

4 Answers

6
votes

There are multiple problems here, and the existing answers are addressing only some of them.

  1. It's unclear from the code you've posted whether bottleNode can be nil when this method runs. Calling a method through the optional (the ? in bottle?.contains) when its value is nil would fail silently — causing the entire expression result to wrap in an Optional whose value is nil — but you've got parens and a force unwrap around the whole expression, so the nil-unwrap would crash.

  2. contains(_:) is not a method on SCNNode. It's unclear what type your bottleNode could be that you could even write this method call without getting compiler errors... but if bottleNode actually is an SCNNode and you've done some type-erasing/Any-casting goop to allow the call to compile, the call would fail at runtime due to the non-existent method.

If your goal with the bottleNode.contains line is to determine whether the hit test result is either bottleNode itself or a child node thereof, I'd recommend defining and using an extension method like this:

extension SCNNode {
    func hasAncestor(_ node: SCNNode) -> Bool {
        if self === node {
            return true // this is the node you're looking for
        }
        if self.parent == nil {
            return false // target node can't be a parent/ancestor if we have no parent
        } 
        if self.parent === node {
            return true // target node is this node's direct parent
        }
        // otherwise recurse to check parent's parent and so on
        return self.parent.hasAncestor(node)
    }
}

// in your touchesBegan method...
if let bottleNode = bottleNode, result.node.hasAncestor(bottleNode) { 
    print("match")
}

If instead your goal is to determine whether result.node lies within or overlaps the bounding box of bottleNode (regardless of node hierarchy), answering that question is a little more complicated. A simple position within boundingSphere check is pretty easy, or if you're looking for containment/overlap, an octree might help.

4
votes

It could be that, bottleNode is nil. What line is causing the crash? Check if bottleNode exists before the comparison:

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        let touch = touches.first as! UITouch
        if(touch.view == self.sceneView){
            print("touch working")
            let viewTouchLocation:CGPoint = touch.location(in: sceneView)
            guard let result = sceneView.hitTest(viewTouchLocation, options: nil).first else {
                return
            }
            if let bottleNode = bottleNode, bottleNode == result.node { //bottleNode is declared as  SCNNode? and it's crashing here
                print("match")
            }

        }
    }
1
votes

You are currently testing if your SCNNode “contains” the node from the hittest result while you should instead simply compare them, i.e. if (bottlenode == result.node)...

1
votes

Have you actually created a bottleNode? If bottleNode is nil, the expression bottleNode?.contains(result.node) will also be nil, so when you force unwrap it, it will throw an error.

If you know that bottleNode will always be defined, it shouldn't be an optional. If you don't know that, you need to check that it exists before using it. This should do what you want:

if let bottleNode = bottleNode, bottleNode.contains(result.node) {

Be advised that if bottleNode doesn't exist, this test will fail silently.