对于任何偶然发现此问题的人,这里有一个更完整的答案,以便您了解否定和 pi/2 转换的必要性。您首先需要知道您的参考框架。球坐标系将点定义为与 z 轴和 x 轴成一定角度的矢量。对于地球,让我们将 z 轴定义为从地球中心到北极的线,将 x 轴定义为从中心穿过本初子午线(大西洋中部非洲)的赤道的线。
对于(lat, lon, alt),然后我们可以围绕z 轴和y 轴以弧度定义roll 和yaw:
let roll = lon * Float.pi / 180
let yaw = (90 - lat) * Float.pi / 180
我将 roll、pitch 和 yaw 分别与 z、x 和 y 配对,如为 eulerAngles 定义的那样。
额外的 90 度说明北极位于纬度 90 度而不是零。
为了将我的 SCNCamera 放在地球上,我使用了两个 SCNNode:一个“手臂”节点和一个相机节点:
let scnCamera = SCNNode()
scnCamera.camera = SCNCamera()
scnCamera.position = SCNVector3(x: 0.0, y: 0.0, z: alt + EARTH_RADIUS)
let scnCameraArm = SCNNode()
scnCameraArm?.position = SCNVector3(x: 0, y: 0, z: 0)
scnCameraArm?.addChildNode(scnCamera)
手臂位于地球中心,相机位于alt + EARTH_RADIUS 之外,即相机现在位于北极。要在每次位置更新时移动相机,我们现在只需使用新的滚动和偏航值旋转手臂节点:
scnCameraArm.eulerAngles.z = roll
scnCameraArm.eulerAngles.y = yaw
在不改变相机方向的情况下,它的虚拟镜头始终面向地面,并且它的虚拟“向上”方向指向西。
要更改虚拟相机的方向,CMMotion 回调会返回一个 CMAttitude,其中包含相对于您选择的不同 z 轴和 x 轴 reference 的滚动、俯仰和偏航值。基于磁力计的磁力计使用远离重力的 z 轴和指向北极的 x 轴。因此,具有零俯仰、滚动和偏航的手机将使其屏幕背对重力,后置摄像头指向地面,纵向模式右侧朝北。请注意,这个方向是相对于重力的,而不是手机的纵向/横向模式(这也是相对于重力)。所以纵向/横向无关紧要。
如果您想象手机摄像头在本初子午线北极附近的这个方向,您会注意到 CMMotion 参考与虚拟摄像头 (SCNCamera) 的方向不同。两个摄像头都面向地面,但它们各自的 y 轴(和 x)相距 180 度。要将它们对齐,我们需要围绕其各自的 z 轴旋转一个,即在滚动中添加/减去 180 度......或者,由于它们以弧度表示,因此将它们取反以获得相同的效果。
此外,据我所知,CMAttitude 并未明确记录其 roll 值表示围绕 z 轴从手机屏幕出来的旋转,并且从实验来看,似乎 attitude.roll 和attitude.yaw 的定义与 eulerAngles 中的定义相反,但也许这是使用 eulerAngles (?) 在虚拟空间中应用旋转变换的顺序的产物。无论如何,回调:
motionManager?.startDeviceMotionUpdates(using: .xTrueNorthZVertical, to: OperationQueue.main, withHandler: { (motion: CMDeviceMotion?, err: Error?) in
guard let m = motion else { return }
scnCamera.eulerAngles.z = Float(m.attitude.yaw - Double.pi)
scnCamera.eulerAngles.x = Float(m.attitude.pitch)
scnCamera.eulerAngles.y = Float(m.attitude.roll)
})
您还可以从虚拟相机的不同参考框架开始,例如z 轴指向赤道本初子午线,x 轴指向北极(即 CMMotion 参考),但您仍需要在某处反转经度。
通过此设置,您可以非常轻松地构建严重依赖 GPS 位置的场景。