SceneKit applyTorque

时间:2014-11-15 15:44:40

标签: ios scenekit

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

  

扭矩矢量的每个分量都与绕旋转有关   SCNNode的局部坐标系中的相应轴   包含物理体的对象。例如,施加扭矩   {0.0,0.0,1.0}导致节点围绕其逆时针旋转   z轴。

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

我希望能够应用扭矩,使其相对于对象始终保持不变(例如,导致节点绕节点的presentationNode的z轴逆时针旋转而不是位置节点具有(具有?)场景开始了)

3 个答案:

答案 0 :(得分:2)

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

Physics在表示节点上运行,但在某些情况下 - 就像这样 - 在场景空间中输入。

然而,表示节点和场景之间的唯一区别在于坐标空间,因此您需要做的就是将矢量从表示空间转换为场景空间。 (场景的根节点不应该通过物理,动作或飞行动画进行转换,因此模型场景空间和表现场景空间之间没有实际区别。)为此,请使用其中一种坐标转换方法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轴旋转金字塔,而不是金字塔的y轴 - 穿过金字塔顶点的那个。正如你所指出的那样,在第一次旋转之前,它围绕金字塔的y轴旋转;即场景的y轴(不受物理影响),而不是表示节点(通过物理学旋转)。

要解决此问题,请插入以下行(在以var axis开头的行之后):

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

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


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

答案 1 :(得分:1)

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

试图解决这个难题,我创造了(我相信的)一个非常不合标准的解决方案(更多的概念证明)。它涉及在我想要找到的轴上创建(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中缺乏经验,但即使我可以看到更容易/优化的解决方案确实退出,但我无法找到它:(

答案 2 :(得分:0)

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

GLKMatrix4MultiplyVector3其中

  

输入载体被视为具有0.0的w分量的4分量载体。

GLKMatrix4MultiplyVector3WithTranslation其中

  

输入载体被视为具有1.0分子的w分量的4分量载体。

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

所以,我们可以往返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轴)和棒上的y是俯仰(局部x轴),但是有飞行模式样式“向下拉回/ “,然后您可以将其设置为SCNVector3(input.y, -input.x, 0)