场景工具包 - 映射物理360度旋转

18

我在将设备动作(传感器融合)映射到SceneKit节点旋转时遇到了困难。问题的前提如下:

我有一个球体,并且相机被放置在球体内部,使得球体的几何中心和相机节点的中心重合。我想要实现的是当我围绕一个点物理旋转时,运动需要准确地映射到相机上。

实现细节如下:

我有一个节点,其几何形状为球体,并且是根节点的子节点。我正在使用传感器融合获取姿态四元数,然后将它们转换为欧拉角。其代码如下:

- (SCNVector3)eulerAnglesFromCMQuaternion:(CMQuaternion) {
    GLKQuaternion gq1 =  GLKQuaternionMakeWithAngleAndAxis(GLKMathDegreesToRadians(90), 1, 0, 0);
    GLKQuaternion gq2 =  GLKQuaternionMake(q.x, q.y, q.z, q.w);
    GLKQuaternion qp  =  GLKQuaternionMultiply(gq1, gq2);
    CMQuaternion rq =   {.x = qp.x, .y = qp.y, .z = qp.z, .w = qp.w};
    CGFloat roll = atan2(-rq.x*rq.z - rq.w*rq.y, .5 - rq.y*rq.y - rq.z*rq.z);
    CGFloat pitch = asin(-2*(rq.y*rq.z + rq.w*rq.x));
    CGFloat yaw = atan2(rq.x*rq.y - rq.w*rq.z, .5 - rq.x*rq.x - rq.z*rq.z);
    return SCNVector3Make(roll, pitch, yaw);
}

转换方程来自《3D Math Primer for Graphics and Game Development》。我用这本书作为参考,但没有读过它。但绝对在我的阅读列表上。

现在要在SceneKit中模拟物理旋转,我正在旋转包含SCNCamera的节点。这是一个子节点,位于rootNode下面。使用.rotation属性进行操作。需要注意的一点是:

现在我的设备动作更新线程看起来像这样

[self.motionManager startDeviceMotionUpdatesUsingReferenceFrame:CMAttitudeReferenceFrameXArbitraryCorrectedZVertical toQueue:mq withHandler:^(CMDeviceMotion *motion, NSError *error) {
    CMAttitude *currentAttitude = motion.attitude;
    [SCNTransaction begin];
    [SCNTransaction setDisableActions:YES];
    SCNVector3 v = [self eulerAnglesFromCMQuaternion:currentAttitude.quaternion];
    self.cameraNode.eulerAngles = SCNVector3Make( v.y, -v.x, 0); //SCNVector3Make(roll, yaw, pitch)
    [SCNTransaction commit];
}];

需要注意的是,设备欧拉角与SceneKit节点的欧拉角是不同的。下面的链接解释了它们之间的区别。

SceneKit Euler Angles

CMAttitude Euler Angles

在我的理解中,它们之间的映射关系如下图所示。

[Image]

现在我面临的问题是,在我固定点旋转一圈之前,相机的 360 度旋转已经完成了。我的情况类似于以下图片。

[Image2]

我怀疑四元数 -> 欧拉角转换导致了错误,而四元数数学知识目前对我来说还过于高深难懂。

我希望我已经提供了所有信息,如果需要更多信息,我很乐意添加。


1
这个答案解决了所有设备方向的问题:https://dev59.com/WVwZ5IYBdhLWcg3wINPQ - Alfie Hanssen
2个回答

11
感谢您描述您的问题和解决方案的开端!我非常确定这不能用欧拉角来完成,因为存在gimbal lock(失去了一个自由度)。
解决方案是获取态度并使用四元数设置相机的方向:
[self.motionManager startDeviceMotionUpdatesUsingReferenceFrame:CMAttitudeReferenceFrameXArbitraryCorrectedZVertical toQueue:myQueue withHandler:^(CMDeviceMotion *motion, NSError *error) {
    CMAttitude *currentAttitude = motion.attitude;
    [SCNTransaction begin];
    [SCNTransaction setDisableActions:YES];

    SCNQuaternion quaternion = [self orientationFromCMQuaternion:currentAttitude.quaternion];
    _cameraNode.orientation = quaternion;

    [SCNTransaction commit];
}];
- (SCNQuaternion)orientationFromCMQuaternion:(CMQuaternion)q
{
    GLKQuaternion gq1 =  GLKQuaternionMakeWithAngleAndAxis(GLKMathDegreesToRadians(-90), 1, 0, 0); // add a rotation of the pitch 90 degrees
    GLKQuaternion gq2 =  GLKQuaternionMake(q.x, q.y, q.z, q.w); // the current orientation
    GLKQuaternion qp  =  GLKQuaternionMultiply(gq1, gq2); // get the "new" orientation
    CMQuaternion rq =   {.x = qp.x, .y = qp.y, .z = qp.z, .w = qp.w}; 

    return SCNVector4Make(rq.x, rq.y, rq.z, rq.w);
}

1
很高兴看到如何调整代码以适应横向方向。 - walker
1
这个答案解决了所有设备方向的问题:https://dev59.com/WVwZ5IYBdhLWcg3wINPQ - Alfie Hanssen

3
如果CoreMotion和SceneKit没有使用相同的欧拉角约定,则可以使用中间节点来简化转换。例如,通过拥有以下层次结构:
```HTML

如果CoreMotion和SceneKit没有使用相同的欧拉角约定,则可以使用中间节点来简化转换。例如,通过拥有以下层次结构:

```
YawNode
  |
  |___PitchNode
      |
      |___RollNode
          |
          |___CameraNode

YawNode、PitchNode和RollNode只会绕单一轴旋转。通过调整层次结构顺序,您可以再现CoreMotion的约定。
话虽如此,我不确定将四元数转换为欧拉角是否是一个好主意。我怀疑在角度为0 / PI / 2PI等时,您将无法获得连续/平滑的过渡......我建议尝试使用四元数->矩阵4并更新节点的“transform”。

如果我将四元数转换为旋转矩阵(书上说它将是一个3x3的矩阵),如何将其变成4x4的矩阵(填充?)。或者如何进行四元数->矩阵4的转换? - egghese
将CM四元数作为四元数传递到SceneKit中怎么样?您可以使用SCNNode.orientation属性来实现。 (如果坐标空间不匹配,请使用包含节点的节点或pivot来转换包含节点的空间。) - rickster
@rickster:我试过了,但是开始和结束之间仍然存在差异。我检查了传感器的值,看起来存在大约20度的漂移。传感器没问题,因为其他应用程序完全正常。可能出了什么问题? - egghese
此外,令人惊讶的是,这种差异只会在我向右移动/旋转时发生。而当我向左移动时,旋转是完美的。 - egghese
@Toyos 如果可以添加代码片段就太好了,特别是因为这是一个有效的解决方案。否则很难将图表翻译成代码。 - Alfie Hanssen

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