SceneKit:命中测试无法使用广告牌四边形

时间:2016-10-08 09:38:13

标签: ios swift scenekit

我使用SceneKit创建了广告牌四边形。 cameraNode与UIDeviceMotion同步,广告牌节点按我的预期出现。 问题是,我想在点击它时调用这些节点。 为此,我将UITapGestureRecognizer与hitTest一起使用。 这是我的一些代码。

// ==== in viewDidLoad

// initialize tap gesture
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(onNodeTapped))
    sceneView.addGestureRecognizer(tapGesture)

// initialize scenekit.scene
let scene = SCNScene()
scene.rootNode.addChildNode(cameraNode)
scene.rootNode.addChildNode(worldNode)
sceneView.scene = scene
sceneView.autoenablesDefaultLighting = true
sceneView.pointOfView = cameraNode

这是点击处理程序

func onNodeTapped(_ gestureRecognize: UIGestureRecognizer) {
    let location = gestureRecognize.location(in: sceneView)   // <---- updated
    let hitResults = sceneView.hitTest(location, options: nil)
    for result in hitResults {
        // FOR_TEST: hit test visualization
        if let material = result.node.geometry?.materials.first {
            SCNTransaction.begin()
            SCNTransaction.animationDuration = 0.5
            SCNTransaction.completionBlock = {
                SCNTransaction.begin()
                SCNTransaction.animationDuration = 0.5
                material.emission.contents = UIColor.black
                SCNTransaction.commit()
            }
            material.emission.contents = UIColor.red
            SCNTransaction.commit()
        }

        // target tap event handling
        if let target = (result.node as? TargetNode)?.target {
            if onTargetTapped(target) {
                return
            }
        }
    }
}

此代码非常简单。我的意思是可视化部分只响应20次中的1次,并且onTargetTapped仅被调用100次中的1次... 令人厌恶的事情是定位很好,这意味着这不是坐标问题。

我找到了与SCNHitTestOption.categoryBitMask相关的内容,但它根本没有帮助。

此外,当我打开此Scenview时,控制台上会显示此错误消息。 &#34; [SceneKit]错误:_C3DUnProjectPoints&#34;中的错误 也许这条消息与hitTest故障有关?

更新

此代码用于构建广告牌SCNGeometry和SCNNode

override func initializeGeometry() -> SCNGeometry {
    let geometry = SCNPlane(width: width, height: height)
    let material = geometry.materials.first
    material?.diffuse.contents = initializeTexture()
    material?.writesToDepthBuffer = false
    material?.readsFromDepthBuffer = false
    return geometry
}

// ====构建节点

node = SCNNode()
node.geometry = initializeGeometry()
node.categoryBitMask = MyConstraints.targetNodeHitTestCategoryBitMask
node.constraints = [SCNBillboardConstraint()]

2 个答案:

答案 0 :(得分:0)

您正在检索annotationsDisplayView中的点按坐标,但随后将这些坐标传递给sceneView以进行点击测试查找。除非这些观点恰好相互叠加,否则你会发现不匹配。

我想你想要

    let location = gestureRecognize.location(in: sceneView)

代替。

我很好奇命中测试是否会使用广告牌的旋转版本。我确认它确实如此。这是一个工作(Mac)样本。

SCNView子类中:

override func mouseDown(with theEvent: NSEvent) {
    /* Called when a mouse click occurs */

    // check what nodes are clicked
    let p = self.convert(theEvent.locationInWindow, from: nil)
    let hitResults = self.hitTest(p, options: [:])
    // check that we clicked on at least one object
    if hitResults.count > 0 {
        Swift.print("hit me")
    }
    for result in hitResults {
        Swift.print(result.node.name)

        // get its material
        let material = result.node.geometry!.firstMaterial!

        // highlight it
        SCNTransaction.begin()
        SCNTransaction.animationDuration = 0.5

        // on completion - unhighlight
        SCNTransaction.completionBlock = {
            SCNTransaction.begin()
            SCNTransaction.animationDuration = 0.5

            material.emission.contents = NSColor.black

            SCNTransaction.commit()
        }

        material.emission.contents = NSColor.yellow

        SCNTransaction.commit()
    }

    super.mouseDown(with: theEvent)
}

在视图控制器中:

override func awakeFromNib(){
    super.awakeFromNib()

    // create a new scene
    let scene = SCNScene()

    let side = CGFloat(2.0)
    let quad1 = SCNNode.init(geometry: SCNPlane(width: side, height: side))
    quad1.name = "green"
    quad1.geometry?.firstMaterial?.diffuse.contents = NSColor.green

    let quad2 = SCNNode.init(geometry: SCNPlane(width: side, height: side))
    quad2.name = "red"
    quad2.geometry?.firstMaterial?.diffuse.contents = NSColor.red

    let quad3 = SCNNode.init(geometry: SCNPlane(width: side, height: side))
    quad3.name = "blue"
    quad3.geometry?.firstMaterial?.diffuse.contents = NSColor.blue

    scene.rootNode.addChildNode(quad1)
    scene.rootNode.addChildNode(quad2)
    scene.rootNode.addChildNode(quad3)

    let rotation = CGFloat(M_PI_2) * 0.9
    quad1.position.y = side/2
    quad1.eulerAngles.y = rotation

    quad2.position.x = side/2
    quad2.eulerAngles.x = rotation

    // comment out these constraints to verify that they matter
    quad1.constraints = [SCNBillboardConstraint()]
    quad2.constraints = [SCNBillboardConstraint()]

    // create and add a camera to the scene
    let cameraNode = SCNNode()
    cameraNode.camera = SCNCamera()
    scene.rootNode.addChildNode(cameraNode)

    // place the camera
    cameraNode.position = SCNVector3(x: 0, y: 0, z: 15)

    // create and add a light to the scene
    let lightNode = SCNNode()
    lightNode.light = SCNLight()
    lightNode.light!.type = .omni
    lightNode.position = SCNVector3(x: 0, y: 10, z: 10)
    scene.rootNode.addChildNode(lightNode)

    // create and add an ambient light to the scene
    let ambientLightNode = SCNNode()
    ambientLightNode.light = SCNLight()
    ambientLightNode.light!.type = .ambient
    ambientLightNode.light!.color = NSColor.darkGray
    scene.rootNode.addChildNode(ambientLightNode)

    // set the scene to the view
    self.gameView!.scene = scene

    // allows the user to manipulate the camera
    self.gameView!.allowsCameraControl = true

    // show statistics such as fps and timing information
    self.gameView!.showsStatistics = true

    // configure the view
    self.gameView!.backgroundColor = NSColor.black
}

答案 1 :(得分:0)

无论您的节点是否受广告牌约束,命中测试都应该有效。它适合我。

override func viewDidLoad() {
  self.sceneView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(tap(_:))))
  ...
}

@objc func tap(_ recognizer: UITapGestureRecognizer) {
    let loc = recognizer.location(in: self.sceneView)
    if let hit = self.sceneView.hitTest(loc) {
      ...
    }
}