CMMotionData转换为SceneKit SCNNode方向

4
尝试使用CoreMotion正确旋转SceneKit相机。我建立的场景非常简单...我只是创建了一堆盒子,分布在一个区域内,相机只朝向Z轴。
不幸的是,从设备运动返回的数据似乎与设备的物理位置和方向无关。它似乎只是随机漫游。
此SO帖子中建议的那样,我直接将姿态四元数传递给相机节点的方向属性。
我是否误解了CoreMotion在这里给我的数据?姿态不应反映设备的物理方向吗?还是它是增量移动,我应该建立在先前的方向上?
2个回答

7
这段代码可能会对你有所帮助:
    var motionManager = CMMotionManager()
    motionManager?.deviceMotionUpdateInterval = 1.0 / 60.0
    motionManager?.startDeviceMotionUpdatesToQueue(
        NSOperationQueue.mainQueue(),
        withHandler: { (motion: CMDeviceMotion!, error: NSError!) -> Void in

            let currentAttitude = motion.attitude
            var roll = Float(currentAttitude.roll) + (0.5*Float(M_PI))
            var yaw = Float(currentAttitude.yaw)
            var pitch = Float(currentAttitude.pitch)

            self.cameraNode.eulerAngles = SCNVector3(
                x: -roll,
                y: yaw,
                z: -pitch)
    })

这个设置是针对设备横向向右的。通过改变 + 和 - 可以尝试不同的方向。
导入 CoreMotion。

纵向方向如何工作?我已经尝试过一段时间了,只能在横向方向上工作。 - stanley

4
如果有人遇到此问题,以下是更完整的答案,以便您理解否定和pi/2偏移的必要性。首先需要了解您的参考系。球面坐标系将点定义为与z轴和x轴成角度的向量。对于地球,让我们将z轴定义为从地心到北极的线,将x轴定义为从中心通过本初子午线(大西洋中部非洲)穿过赤道的线。

对于(纬度、经度、高度),我们可以定义绕z轴和y轴的翻滚偏航角(以弧度为单位):

let roll = lon * Float.pi / 180
let yaw = (90 - lat) * Float.pi / 180

我正在将rollpitchyawzxy配对,分别按照eulerAngles的定义。
额外的90度是为了考虑到北极在90度纬度而不是0度。
为了将我的SCNCamera放置在地球仪上,我使用了两个SCNNodes:一个“arm”节点和相机节点:
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-轴参考。基于磁力计的轴使用指向重力的z轴和指向北极的x轴。因此,零俯仰、横滚和偏航的手机屏幕将面向重力,后置摄像头指向地面,纵向模式的右侧面向北方。请注意,这种方向是相对于重力而不是手机的纵向/横向模式(也是相对于重力)。因此,纵向/横向模式无关紧要。
如果您想象一下,当手机的相机在本初子午线附近的北极时,您会注意到CMMotion参考与虚拟相机(SCNCamera)处于不同的方向。两个相机都面向地面,但它们各自的y轴(和x轴)相差180度。为了使它们对齐,我们需要围绕各自的z轴旋转一个相机,即将roll加 / 减180度...或者,由于它们是用弧度表示的,因此可以将其取反以达到相同的效果。

另外,据我所知,CMAttitude没有明确说明其roll值表示从手机屏幕出来的z轴旋转,而且经过实验,似乎attitude.roll和attitude.yaw具有与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位置的场景。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接