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

How to move a rotated SCNNode in SceneKit?

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

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

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

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

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

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

我不想使用 GLKit

所以我的理解是,您想沿其自己的 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;

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

我把它作为 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)
    }
}