我正在构建一个 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
最佳答案
从 2D 触摸位置到 3D 空间的棘手部分显然是 z 坐标。与其尝试将触摸位置转换为虚构的 3D 空间,不如使用 hittest 将 2D 触摸映射到该 3D 空间中的 2D 平面。 .特别是当只需要在两个方向上移动时,例如棋盘上的棋子,这种方法非常有效。无论平面的方向和相机设置如何(只要相机不明显从侧面看平面),这都会将触摸位置映射到触摸手指正下方的 3D 位置并始终如一地跟随。
我通过示例修改了 Xcode 中的游戏模板。 https://github.com/Xartec/PrecisePan/
主要部分是:
平移手势代码:
// 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; } } }
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/