【问题标题】:SceneKit matrix transformation to match camera angleSceneKit 矩阵变换以匹配相机角度
【发布时间】:2018-07-06 08:37:28
【问题描述】:

我正在构建一个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
}

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

我曾经担任此职位的相关 SO 帖子: How to use iOS (Swift) SceneKit SCNSceneRenderer unprojectPoint properly

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

【问题讨论】:

  • 如果网格是一个平面(否则你可以添加一个平面并使其不可见)你可以简单地在平面节点上做一个hittest来获得平面上的坐标。适用于轻击手势,但也非常准确地适用于平移手势(例如,平移开始时的 hittest 和每次手势状态更改为活动时再次 hittest)。这将排除相机因素以及平面的方向。您知道瓦片的数量及其宽度/高度,因此您只需将坐标除以瓦片的宽度即可得到列,与行类似。
  • 这可行,但是平面上 SCNVector3 的坐标会受到透视失真的影响,并且在错误的位置检测到命中,但是当平面垂直时可以完美运行。
  • 不,无论相机设置如何,飞机上(命中结果的)localCoordinates 将始终保持一致。然后,您可以将结果位置转换为世界空间,以获取要放置在该位置上的节点的位置。
  • 也许我的问题在于翻译位置,我已经用触摸位置漂移的屏幕记录编辑了我的问题。
  • 谢谢,这让事情变得更清楚了 :) 正如 Rickster 在帖子中提到的那样,您提到的这种方法只有在平面垂直于相机时才有效。这就是为什么我建议使用 hittest,因为它使相机和平面方向脱离了等式。我将很快发布带有示例代码的答案。

标签: ios swift scenekit


【解决方案1】:

从 2D 触摸位置到 3D 空间的棘手部分显然是 z 坐标。与其尝试将触摸位置转换为虚构的 3D 空间,不如使用 hittest 将 2D 触摸映射到该 3D 空间中的 2D 平面。特别是当只需要在两个方向上移动时,例如棋盘上的棋子,这种方法非常有效。无论平面的方向和相机设置如何(只要相机没有明显从侧面看平面),这都会将触摸位置映射到触摸手指正下方的 3D 位置并始终如一地跟随。

我通过示例修改了 Xcode 中的游戏模板。 https://github.com/Xartec/PrecisePan/

主要部分有:

  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 进行比较,您可以确定世界空间中的增量/平移并相应地平移其他对象。

【讨论】:

  • 是的,这按预期工作,非常感谢@Xartec
  • 我有类似的问题,我需要平移视角,但是当相机旋转时,我无法平移任何我想要的地方(有时它在 Y 方向上工作一段时间基于旋转的方向)这里我试过stackoverflow.com/questions/66437552/…
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-12-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多