如何检测ARKit中哪个SCNNode被触摸了

5

我在SCNode的命中检测方面遇到了一些问题。我需要检测场景中哪个对象被触摸了,我已经实现了这段代码,但当我触摸该对象时似乎会崩溃,但是当我触摸场景中其他部分时它却工作正常。

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个回答

6
这里存在多个问题,现有的答案仅解决了其中一些。
1. 从您发布的代码中无法确定当此方法运行时bottleNode是否可以为nil。如果在可选项(即bottle?.contains中的“?”)中调用方法,其值为nil,则会默默失败-导致整个表达式结果包装在一个值为nil的Optional中-但是您在整个表达式周围加上括号和强制解包,因此空解包将崩溃。 2. contains(_ :)不是SCNNode上的方法。不清楚您的bottleNode可能是何种类型,以便您甚至可以编写此方法调用而不会获得编译器错误...但是如果bottleNode实际上是SCNNode,并且您已执行了某些类型擦除/Any转换的操作以允许调用编译器,则由于不存在的方法,该调用将在运行时失败。
如果您使用bottleNode.contains行的目的是确定命中测试结果是bottleNode本身还是其子节点,我建议定义并使用类似以下的扩展方法:
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")
}

如果您的目标是确定 result.node 是否在或与 bottleNode 的边界框重叠(不考虑节点层次结构),那么回答这个问题会更加复杂。一个简单的 boundingSphere 中的 position 检查很容易,或者如果您正在寻找包含/重叠,可以使用 octree

1
虽然问题中没有提到,但如果你真的想假设在特定节点执行hittest之前它可能为nil,那么在执行hittest之前实际上进行该检查是有意义的。 - Xartec
P2. 除了无效的contains方法之外,没有理由认为bottleNode有childnodes,但如果是这种情况,使用自定义扩展遍历结果节点的父节点是否有任何附加值,而不是仅检查bottlenode childnodes是否包含结果节点?(即,在bottle节点上枚举层次结构将避免检查结果节点的所有父节点)。我相信在复制和粘贴代码后它能够工作,但这是由于扩展的self==node部分,因此我不认为这应该是被接受的答案。 - Xartec
@Xartec 很好的观点。由于不知道涉及的节点层次结构,我选择了一种在一般情况下算法效率高的解决方案。向上遍历父层次结构受节点树的高度限制,但搜索目标节点的子树受该子树完整广度的限制。对于任何一个任意大的树中的点,最坏情况下子树大小可能比返回到根的高度要大得多。但是,值得考虑您的应用程序中预期拥有的节点层次结构-如果它相当平坦,则针对测试节点的简单指针相等性测试可能足够。 - rickster

4

可能是因为 bottleNode 为空。哪一行导致了崩溃?在比较之前检查 bottleNode 是否存在:

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

您是否已经创建了一个bottleNode?如果bottleNode为nil,则表达式bottleNode?.contains(result.node)也将为nil,因此当您强制解包它时,它会引发错误。

如果您知道bottleNode始终被定义,那么它不应该是可选的。如果您不知道它是否存在,则在使用之前需要检查其是否存在。这应该可以满足您的要求:

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

请注意,如果bottleNode不存在,这个测试将会默默地失败。

Contains不是SCNNode的一个方法。 - Xartec

1

您目前正在测试您的SCNNode是否“包含”hit test结果中的节点,而实际上您应该简单地比较它们,即if (bottlenode == result.node) ...


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接