SceneKit矩阵变换以匹配摄像机角度

时间:2018-01-26 23:21:19

标签: ios swift scenekit

我正在构建UIPanGestureRecognizer,因此我可以在3D空间中移动节点。

目前,我有一些有效的方法,但只有当相机与飞机完全垂直时,我的UIPanGestureRecognizer看起来像这样:

@objc func handlePan(_ sender:UIPanGestureRecognizer) {
  let projectedOrigin = self.sceneView!.projectPoint(SCNVector3Zero)

  let viewCenter = CGPoint(
    x: self.view!.bounds.midX,
    y: self.view!.bounds.midY
  )

  let touchlocation = sender.translation(in: self.view!)

  let moveLoc = CGPoint(
    x: CGFloat(touchlocation.x + viewCenter.x),
    y: CGFloat(touchlocation.y + viewCenter.y)
  )

  let touchVector = SCNVector3(x: Float(moveLoc.x), y: Float(moveLoc.y), z: Float(projectedOrigin.z))
  let worldPoint = self.sceneView!.unprojectPoint(touchVector)
  let loc = SCNVector3( x: worldPoint.x, y: 0, z: worldPoint.z )

  worldHandle?.position = loc
}

旋转相机时会出现问题,坐标受透视变化的影响。在这里你可以看到漂移的触摸位置:

enter image description here

相关的SO帖子我曾经到过这个位置: How to use iOS (Swift) SceneKit SCNSceneRenderer unprojectPoint properly

它引用了这些精彩的幻灯片:http://www.terathon.com/gdc07_lengyel.pdf

1 个答案:

答案 0 :(得分:2)

从2D触摸位置到3D空间的棘手部分显然是z坐标。不是尝试将触摸位置转换为假想的3D空间,而是使用hittest将2D触摸映射到该3D空间中的2D平面。特别是当仅在两个方向上需要移动时,例如像棋盘上的棋子一样,这种方法非常有效。无论平面的方向和相机设置如何(只要相机没有明显地从侧面看平面),这将触摸位置直接映射到触摸手指下方的3D位置,并按照一致。

我用Xcode修改了游戏模板。 https://github.com/Xartec/PrecisePan/

enter image description here

主要部分是:

  1. 平移手势代码:

    // retrieve the SCNView
        let scnView = self.view as! SCNView
        // check what nodes are tapped
        let p = gestureRecognize.location(in: scnView)
        let hitResults = scnView.hitTest(p, options: [SCNHitTestOption.searchMode: 1, SCNHitTestOption.ignoreHiddenNodes: false])
    
        if hitResults.count > 0 {
            // check if the XZPlane is in the hitresults
            for result in hitResults {
                if result.node.name == "XZPlane" {
                    //NSLog("Local Coordinates on XZPlane %f, %f, %f", result.localCoordinates.x, result.localCoordinates.y, result.localCoordinates.z)
    
                    //NSLog("World Coordinates on XZPlane %f, %f, %f", result.worldCoordinates.x, result.worldCoordinates.y, result.worldCoordinates.z)
                    ship.position = result.worldCoordinates
                    ship.position.y += 1.5
                    return;
                }
            }
        }
    
  2. 在viewDidload:

    中添加XZ平面节点
    let XZPlaneGeo = SCNPlane(width: 100, height: 100)
    let XZPlaneNode = SCNNode(geometry: XZPlaneGeo)
    XZPlaneNode.geometry?.firstMaterial?.diffuse.contents = UIImage(named: "grid")
    XZPlaneNode.name = "XZPlane"
    XZPlaneNode.rotation = SCNVector4(-1, 0, 0, Float.pi / 2)
    //XZPlaneNode.isHidden = true
    scene.rootNode.addChildNode(XZPlaneNode)
    
  3. 取消注释isHidden行以隐藏辅助平面,它仍然可以工作。飞机显然需要足够大以填满屏幕或至少允许用户平移的部分。

    通过设置全局变量来保存平移的startWorldPosition(处于状态.began)并将其与状态.change中的命中worldPosition进行比较,您可以确定世界空间中的增量/平移并相应地转换其他对象。 / p>