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

标签 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

最佳答案

从 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)
    

取消注释 isHidden 行以隐藏辅助平面,它仍然有效。平面显然需要足够大以填满屏幕或至少是允许用户平移的部分。

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

关于ios - SceneKit 矩阵变换以匹配摄像机角度,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48470949/

相关文章:

ios - 卡在 Game Center 沙盒中?

ios - 为什么在尝试向上滚动表格时我的 UITableView 内容为 'jump up'?

ios - 使用 swift 在 UIView draw() 中绘制渐变

swift - 为什么在更改现有值的成员时会运行属性观察器?

ios - xib 内的视频 View 未拉伸(stretch)至 Controller 的宽度

ios - 场景套件 SCNMorpher 不适用于 SCNSphere 等原语

swift - 使用map_Bump初始化SCNNode

ios - 防止 uicollectionview 部分从新行开始

ios - 无法弄清楚如何使用 CLPlacemark 将我的当前位置正确地嵌入到消息正文中

swift - "Cannot assign to the result of this expression"变量 (var) 引起的错误