如何在 SceneKit 中移动旋转的 SCNNode?

Posted

技术标签:

【中文标题】如何在 SceneKit 中移动旋转的 SCNNode?【英文标题】:How to move a rotated SCNNode in SceneKit? 【发布时间】:2017-08-31 11:00:13 【问题描述】:

下图显示了一个应在 X 轴和 Z 轴上水平移动的旋转框。 Y 应保持不受影响以简化场景。该框也可能是相机的 SCNNode,所以我猜此时投影没有意义。

假设我们想要将框沿红色箭头的方向移动。如何使用 SceneKit 实现这一点?

红色箭头表示框的-Z方向。它还向我们展示了它不平行于相机的投影或显示为网格的深灰色线的全局轴。

我的最后一种方法是平移矩阵和旋转矩阵的乘积,从而产生一个新的变换矩阵。那么我是否必须将当前转换添加到新转换中?

如果是,SceneKit 函数在哪里添加矩阵,如 SCNMatrix4Mult 进行乘法运算,还是我必须使用 Metal 自己编写?

如果没有,我在矩阵计算中遗漏了什么?

我不想使用GLKit

【问题讨论】:

你能澄清一下你想如何移动节点“在 x 和 z 轴上水平移动”吗? 1)你想在平行于屏幕平面的平面上移动它吗? 2) 你想在 X 和 Z 轴定义的平面内移动它吗? 调整 x 和 z,但不调整 y(垂直轴)。但这只是为了简化场景。 好的,所以你想在 X 和 Z 定义的平面上移动它。它必须平行于屏幕吗?或者你想例如当向左滑动时向 -X、向右 +X 和顶部向 -Z 和向下向 +Z 滑动?如果我设法解释得当? 滑动不是将框移动到红色箭头方向的理想方法。它不是左或右或顶部或底部。也许我们应该省略任何手势和按键来简化场景。 想象一下红色箭头是您要将框移动到的方向向量。它可能是翻译向量。但我没有正确计算它。 【参考方案1】:

所以我的理解是,您希望框节点沿其自己的 X 轴(而不是其父 X 轴)移动。并且由于 Box Node 是旋转的,它的 X 轴与它的父节点的 X 轴不对齐,所以你在两个坐标系之间进行转换时遇到了问题。

节点层次结构是

parentNode
    |
    |----boxNode // rotated around Y (vertical) axis

使用变换矩阵

沿它自己的 X 轴移动boxNode

// First let's get the current boxNode transformation matrix
SCNMatrix4 boxTransform = boxNode.transform;

// Let's make a new matrix for translation +2 along X axis
SCNMatrix4 xTranslation = SCNMatrix4MakeTranslation(2, 0, 0);

// Combine the two matrices, THE ORDER MATTERS !
// if you swap the parameters you will move it in parent's coord system
SCNMatrix4 newTransform = SCNMatrix4Mult(xTranslation, boxTransform);

// Allply the newly generated transform
boxNode.transform = newTransform;

请注意:矩阵相乘时的顺序很重要

另一种选择:

使用SCNNode坐标转换函数,对我来说看起来更直接

// Get the boxNode current position in parent's coord system
SCNVector3 positionInParent = boxNode.position;

// Convert that coordinate to boxNode's own coord system
SCNVector3 positionInSelf = [boxNode convertPosition:positionInParent fromNode:parentNode];

// Translate along own X axis by +2 points
positionInSelf = SCNVector3Make(positionInSelf.x + 2,
                                positionInSelf.y,
                                positionInSelf.z);

// Convert that back to parent's coord system
positionInParent = [parentNode convertPosition: positionInSelf fromNode:boxNode];

// Apply the new position
boxNode.position = positionInParent;

【讨论】:

我几乎明白了,只是交换了乘法参数。 SCNMatrix4Translate 在这种情况下也无济于事,因为它也会改变全局位置。 第二个选项看起来也可以理解。我想它也应该适用于SCNAction 方法,如move() 第二个选项效果很好,解决了我遇到的一个大问题,很棒的解决方案!【参考方案2】:

在@Sulevus 的正确答案的基础上,这里是SCNNode 的扩展,它通过在Swift 中使用convertVector 而不是convertPosition 转换来简化事情。

我以 var 返回单位向量的形式完成了它,并提供了乘法的 SCNVector3 重载,因此您可以说类似

let action = SCNAction.move(by: 2 * cameraNode.leftUnitVectorInParent, duration: 1)


public extension SCNNode 
    var leftUnitVectorInParent: SCNVector3 
        let vectorInSelf = SCNVector3(x: 1, y: 0, z: 0)
        guard let parent = self.parent else  return vectorInSelf 
        // Convert to parent's coord system
        return parent.convertVector(vectorInSelf, from: self)
    
    var forwardUnitVectorInParent: SCNVector3 
        let vectorInSelf = SCNVector3(x: 0, y: 0, z: 1)
        guard let parent = self.parent else  return vectorInSelf 
        // Convert to parent's coord system
        return parent.convertVector(vectorInSelf, from: self)
    

    func *(lhs: SCNVector3, rhs: CGFloat) -> SCNVector3 
        return SCNVector3(x: lhs.x * rhs, y: lhs.y * rhs, z: lhs.z * rhs)
    
    func *(lhs: CGFloat, rhs: SCNVector3) -> SCNVector3 
        return SCNVector3(x: lhs * rhs.x, y: lhs * rhs.y, z: lhs * rhs.z)
    

【讨论】:

不幸的是,我得到以下信息:Member operator '*' must have at least one argument of type 'SCNNode' 您还需要将 rhs 转换为 Float 并使这些重载的乘法 static IIRC 这是 ios 和 Mac OS 之间的区别 - SCNVector3 在一个平台上具有 CGFloats,在另一个平台上具有 Float...

以上是关于如何在 SceneKit 中移动旋转的 SCNNode?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Xcode 10 在 SceneKit 编辑器中旋转对象

如何在 Scenekit 的 3D 模型上添加文字(评论)?

如何同时使用 SwiftUI 和 SceneKit?

从 Scenekit 中的世界变换中提取旋转

SceneKit 球体法线似乎随着相机移动而改变?

在 SceneKit 中禁用相机平移