如何在SceneKit中移动一个已旋转的SCNNode,使其绕“自己”的轴旋转?

8
下面的图片显示了一个旋转的盒子,应该在X和Z轴上水平移动。为简化情况,Y应该保持不变。该盒子也可以是相机的SCNNode,因此我想在这一点上投影没有意义。
假设我们想要沿着红色箭头的方向移动盒子。如何使用SceneKit实现这一点?
红色箭头表示盒子的-Z方向。它还向我们展示它不与相机的投影或全局轴平行,这些轴显示为网格的深灰色线条。
我的最后一种方法是将翻译矩阵和旋转矩阵相乘得到一个新的转换矩阵。我必须将当前的变换添加到新的变换中吗?
如果是,则SceneKit函数用于添加矩阵的位置在哪里,例如SCNMatrix4Mult用于乘法,还是我必须使用Metal自己编写?
如果不是,那么我在矩阵计算中缺少什么?
我不想使用GLKit。

enter image description here


你能澄清一下你想如何移动节点“在x和z轴上水平移动”吗?1)你想将其移动到与屏幕平面平行的平面中吗?2)你想将其移动到由X和Z轴定义的平面中吗? - Sulevus
调整 x 和 z,但不要动 y(垂直轴)。但这只是为了简化情况。 - Marc
好的,所以你想在由X和Z定义的平面上移动它。它必须与屏幕平行吗?或者例如,当向左滑动时,您希望向-X移动,向右滑动时向+X移动,向上移动时向-Z移动,向下移动时向+Z移动?如果我解释清楚了的话? - Sulevus
好的,现在我明白你想沿着 Box (node) 自身的 X 轴移动它。但是因为一个节点的位置/旋转是根据其父坐标系的,而且其父坐标系是旋转的,所以它沿着父坐标系的 X 轴移动。我会研究一下,但一个快速解决方法是添加一个额外的(助手)节点,不带几何体作为盒子的父节点,它没有旋转。然后您可以沿着助手 X 轴移动盒子,该轴将与盒子自己的 X 轴平行。并使用助手节点旋转整个物体。 - Sulevus
只有一个注意事项。在游戏引擎或3D引擎中,通常通过使用一个包含方块的容器节点来完成这种绑定。然后你可以轻松地在父节点内移动它。(当然,这取决于你要做什么。) - Fattie
显示剩余3条评论
3个回答

14
所以我的理解是,您想沿着Box节点的自身X轴移动它(而不是其父级X轴)。由于Box节点被旋转,其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在这种情况下也没有帮助,因为它还会改变全局位置。 - Marc
1
第二个选项看起来也很容易理解。我猜它应该也适用于SCNAction的方法,比如move() - Marc
第二个选项非常好,解决了我遇到的一个大问题,太棒了! - Nermin Sehic
请注意,这些方法比仅使用3D引擎来完成工作要脆弱得多!请注意,如果您尝试同时执行两个动画,甚至更多的动画,那么在CPU上进行此类手动方法将成为一场噩梦。 - Fattie

0

通常情况下更简单的方法:

在任何游戏引擎或3D引擎中,通常、正常且极其简单的方法是:

你只需要一个包装器节点,它包含了所需节点。

这确实是变换的全部意义所在,它们使您能够抽象出某种运动。

这就是3D引擎的全部意义 - GPU只需在对象下降时乘以所有四元数;在脑海中计算数学并手动执行(确实在CPU中)完全没有意义。

在Unity中是“游戏对象”,在Scene Kit中是“节点”等等。

在包括Scene Kit在内的所有3D引擎中,几乎所有东西都有一个或多个“持有者”。

重申一遍,这样做的原因是(A)这是游戏引擎的全部存在理由,以实现在乘以每个顶点的四元数时的性能和(B)纯粹的便利和代码稳定性。

这是一百万个例子之一...

enter image description here

当然,你可以轻松地在代码中实现它,

    cameraHolder.addChildNode(camera)

在 OP 的例子中,看起来你只需要使用 cameraHolder旋转相机。然后对于 OP 所询问的运动,只需移动camera
通常有一系列节点才能到达一个对象是完全正常的。
这经常用于“效果”。比如说你有一个物体,有时必须“上下震动”。你可以有一个节点,只做那个动作。请注意,所有与该动作相关的动画等都必须在该节点上进行。而且关键是,它们可以独立于任何其他动画或运动运行。(实际上,你可以将该节点用于其他地方来摆弄其他东西。)

0

在 @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)
    }
}

不幸的是,我得到了以下错误:成员运算符“*”必须至少有一个类型为“SCNNode”的参数,您还需要将rhs强制转换为Float并使这些乘法重载为static - krjw
据我所知,这就是iOS和Mac OS之间的区别 - 在一个平台上SCNVector3具有CGFloats类型,而在另一个平台上则为Float类型。 - Grimxn

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