首页 文章

SceneKit applyTorque

提问于
浏览
3

我正在尝试将 toTorque 应用于场景中的节点。文件说明:

扭矩矢量的每个分量涉及围绕包含物理主体的 SCNNode 对象的局部坐标系中的相应轴的旋转。例如,应用{0.0,0.0,1.0}的扭矩会导致节点绕其 z-axis 逆时针旋转。

但是在我的测试中,似乎物理动画不会影响对象的实际位置。因此,轴保持静止(即使实际节点明显移动)。这导致始终从相同方向(在启动场景时 z 轴的任何位置)施加扭矩。

我希望能够应用扭矩,使其相对于对象始终保持不变(e.g. 导致节点在节点的 presentationNode 的 z-axis 周围逆时针旋转而不是位置节点 had(has?)当场景启动时)

3 回答

  • 2

    SceneKit 使用每个节点的两个版本:模型节点定义静态行为,表示节点实际上涉及动态行为并在屏幕上使用。这个分区镜像了 Core Animation 中使用的分区,并启用了隐式动画等功能(你可以在其中执行诸如 set node.position之类的操作并让它为新值设置动画,而不需要代码的其他部分查询node.position必须处理中间值动画)。

    物理在表示节点上运行,但在场景空间中的某些 cases--like 这个 one--takes 输入。

    但是,表示节点和场景之间的唯一区别在于坐标空间,因此您需要做的就是将矢量从表示空间转换为场景空间。 (场景的根节点不应该通过物理,动作或飞行动画进行转换,因此 model-scene 空间和 presentation-scene space.)之间没有实际区别。为此,请使用 SceneKit 提供的坐标转换方法之一,例如convertPosition:fromNode:

    这是一个 Swift 游乐场,展示了你的困境:

    import Cocoa
    import SceneKit
    import XCPlayground
    
    // Set up a scene for our tests
    let scene = SCNScene()
    let view = SCNView(frame: NSRect(x: 0, y: 0, width: 500, height: 500))
    view.autoenablesDefaultLighting = true
    view.scene = scene
    let cameraNode = SCNNode()
    cameraNode.camera = SCNCamera()
    cameraNode.position = SCNVector3(x: 0, y: 0, z: 5)
    scene.rootNode.addChildNode(cameraNode)
    XCPShowView("view", view)
    
    // Make a pyramid to test on
    let node = SCNNode(geometry: SCNPyramid(width: 1, height: 1, length: 1))
    scene.rootNode.addChildNode(node)
    node.physicsBody = SCNPhysicsBody.dynamicBody()
    scene.physicsWorld.gravity = SCNVector3Zero // Don't fall off screen
    
    // Rotate around the axis that looks into the screen
    node.physicsBody?.applyTorque(SCNVector4(x: 0, y: 0, z: 1, w: 0.1), impulse: true)
    
    // Wait a bit, then try to rotate around the y-axis
    node.runAction(SCNAction.waitForDuration(10), completionHandler: {
        var axis = SCNVector3(x: 0, y: 1, z: 0)
        node.physicsBody?.applyTorque(SCNVector4(x: axis.x, y: axis.y, z: axis.z, w: 1), impulse: true)
    })
    

    第二次旋转有效地旋转金字塔围绕屏幕 y-axis,而不是金字塔的 y-axis - 穿过金字塔顶点的金字塔。正如你所指出的那样,它在第一次轮换之前绕着金字塔的 y-axis 旋转; i.e。场景的 y-axis(不受物理影响),而不是表示节点(通过物理旋转)。

    要修复它,请插入以下行(在以var axis开头的行之后):

    axis = scene.rootNode.convertPosition(axis, fromNode: node.presentationNode())
    

    convertPosition:fromNode:的调用说“给我一个场景坐标空间中的矢量,它等于 presentation-node 空间中的这个”。当您在转换轴周围应用扭矩时,它会有效地转换回演示节点的空间以模拟物理,因此您可以看到它围绕您想要的轴旋转。


    更新:有一些坐标空格错误,但最终结果几乎相同。

  • 1

    不幸的是,rickster 提供的解决方案对我不起作用:(

    试图解决这个难题,我创造了(我相信的)一个非常 sub-standard 解决方案(更多的概念证明)。它涉及在我想要找到的轴上创建(null)对象,然后我使用它们的位置来找到与轴对齐的向量。

    因为我有一个相当复杂的场景,我从 COLLADA 文件加载它。在该文件中,我已经建模了一个简单的坐标三脚架:三个正交的圆柱体,顶部有锥体(可以更容易地看到正在发生的事情)。

    然后我将这个三脚架物体约束到我试图施加扭矩的物体上。这样我就有了一些对象,它允许我在我试图施加扭矩的对象的 presentationNode 的轴上检索两个点。然后我可以使用这两个点来确定应用扭矩的矢量。

    // calculate orientation vector in the most unimaginative way possible
    
    // retrieve axis tripod objects. We will be using these as guide objects.
    // The tripod is constructed as a cylinder called "Xaxis" with a cone at the top.
    // All loaded from an external COLLADA file.
    
    SCNNode *XaxisRoot = [scene.rootNode childNodeWithName:@"XAxis" recursively:YES];
    SCNNode *XaxisTip = [XaxisRoot childNodeWithName:@"Cone" recursively:NO];
    
    // To devise the vector we will need two points. One is the root of our tripod,
    // the other is at the tip. First, we get their positions. As they are constrained
    // to the _rotatingNode, presentationNode.position is always the same .position
    // because presentationNode returns position in relation to the parent node.
    
    SCNVector3 XaxisRootPos = XaxisRoot.position;
    SCNVector3 XaxisTipPos = XaxisTip.position;
    
    // We then convert these two points into _rotatingNode coordinate space. This is
    // the coordinate space applyTorque seems to be using.
    
    XaxisRootPos = [_rotatingNode convertPosition:XaxisRootPos fromNode:_rotatingNode.presentationNode];
    XaxisTipPos = [_rotatingNode convertPosition:XaxisTipPos fromNode:_rotatingNode.presentationNode];
    
    // Now, we have two *points* in _rotatingNode coordinate space. One is at the center
    // of our _rotatingNode, the other is somewhere along it's Xaxis. Subtracting them
    // will give us the *vector* aligned to the x axis of our _rotatingNode
    
    GLKVector3 rawXRotationAxes = GLKVector3Subtract(SCNVector3ToGLKVector3(XaxisRootPos), SCNVector3ToGLKVector3(XaxisTipPos));
    
    // we now normalise this vector
    GLKVector3 normalisedXRotationAxes = GLKVector3Normalize(rawXRotationAxes);
    
    //finally we are able to apply toque reliably
    [_rotatingNode.physicsBody applyTorque:SCNVector4Make(normalisedXRotationAxis.x,normalisedXRotationAxis.y,normalisedXRotationAxis.z, 500) impulse:YES];
    

    正如你可能看到的,我在 SceneKit 中相当缺乏经验,但即使我可以看到很多 easier/optimised 解决方案确实退出,但我无法找到它:(

  • 0

    我最近遇到了同样的问题,如何将扭矩从物体的局部空间转换为applyTorque方法所需的世界空间。使用节点的convertPosition:toNodefromNode方法的问题在于它们也将节点的转换应用于转矩,因此这仅在节点为 0,0,0 时才有效。这些方法的作用是将 SCNVector3 视为具有 1.0 的 w 分量的 vec4。我们只想应用旋转,换句话说,我们希望 vec4 的 w 分量为 0.与 SceneKit 不同,GLKit 为我们希望 vec3s 乘以的方式提供了 2 个选项:

    GLKMatrix4MultiplyVector3在哪里

    输入向量被视为具有 w-component 0.0 的 4-component 向量。

    GLKMatrix4MultiplyVector3WithTranslation在哪里

    输入向量被视为具有 w-component 1.0 的 4-component 向量。

    我们想要的是前者,只是轮换,而不是翻译。

    所以,我们可以往返 GLKit。例如,将局部 x 轴(1,0,0)(例如俯仰旋转)转换为施加扭矩所需的全局轴,将如下所示:

    let local = GLKMatrix4MultiplyVector3(SCNMatrix4ToGLKMatrix4(node.presentationNode.worldTransform), GLKVector3(v: (1,0,0)))
        node.physicsBody?.applyTorque(SCNVector4(local.x, local.y, local.z, 10), impulse: false)
    

    然而,更多的 Swiftian 方法是为 mat4 * vec3 添加一个*运算符,它将 vec3 视为具有 0.0 w 组件的 vec4。像这样:

    func * (left: SCNMatrix4, right: SCNVector3) -> SCNVector3 { //multiply mat4 by vec3 as if w is 0.0
        return SCNVector3(
            left.m11 * right.x + left.m21 * right.y + left.m31 * right.z,
            left.m12 * right.x + left.m22 * right.y + left.m32 * right.z,
            left.m13 * right.x + left.m23 * right.y + left.m33 * right.z
        )
    }
    

    虽然这个算子假设我们希望我们的 vec3s 如何成倍增加,但我的理由是,由于convertPosition方法已经将 w 视为 1,因此使用*运算符也是多余的。

    您还可以添加一个 mat4 * SCNVector4 运算符,让用户明确选择他们是否希望 w 为 0 或 1。

    因此,我们可以写下:而不是从 SceneKit 往返到 GLKit。

    let local = node.presentationNode.worldTransform * SCNVector3(1,0,0)
        node.physicsBody?.applyTorque(SCNVector4(local.x, local.y, local.z, 10), impulse: false)
    

    您可以使用此方法通过一次applyTorque调用在多个轴上应用旋转。所以说如果你有棒输入你希望棒上的 x 是偏航(局部 yUp-axis)和棒上的 y 是俯仰(局部 x-axis),但是 flight-sim 样式“向下拉回/向上”,那么你可以设置为SCNVector3(input.y, -input.x, 0)

相关问题