SceneKit applyTorque

I am trying to apply Torque to a node in my scene. The documentation states:

Each component of the torque vector refers to a rotation around the corresponding axis in the local coordinate system SCNNode the object containing the physical body. For example, applying torque {0.0, 0.0, 1.0} causes the node to rotate counterclockwise around its z-axis.

However, in my tests, it seems that the physical animation does not affect the actual position of the object. Therefore the axis remains stationary (although the actual node is clearly moved). This causes the torque to always be applied from the same direction (wherever the z-axes were when the scene was started).

I would like to be able to apply torque so that it is always constant with respect to the object (for example, to make the node rotate counterclockwise around the z-axis of the nodeNode view, not the position of the node (has?) When the scene was started)

+3


source to share


3 answers


SceneKit uses two versions of each node: the node model defines static behavior, and the node view is what is actually associated with dynamic behavior and is used on the screen. This separation mirrors what Core Animation uses and includes features like implicit animation (where you can do things like set node.position

and update it, without other parts of your code that ask for node.position

intermediate values ​​during animation) ...

Physics works with a node representation, but in some cases - like this one - accepts input in scene space.

However, the only difference between a presentation node and a scene is in terms of coordinate spaces, so all you have to do is convert the vector from presentation space to scene space. (The scene root node should not be transformed by physics, actions, or animation under light, so there is no practical difference between model-scene-space and presentation-scene space.) To do this, use one of the coordinates the transform methods SceneKit provides, for example convertPosition:fromNode:

.

There is a quick play area that illustrates your dilemma:

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

      

The second rotation effectively rotates the pyramid about the y-axis of the screen, not the y-axis of the pyramid - the one that goes through the top of the pyramid. As you can see, it rotates around what was the y-axis of the pyramid before the first rotation; those. the y-axis of the scene (which is independent of physics), not the representation of the node (which is rotated by physics).



To fix this, insert the following line (after the one that starts with var axis

):

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

      

The call convertPosition:fromNode:

says, "Give me a vector in scene coordinate space that is equivalent to this in presentation space node." When you apply torque around the converted axis, it is effectively converted back to the representation space of the node for physics simulation, so you can see it rotate around the desired axis.


Update: if some coordinate spaces are wrong, the end result is about the same.

+1


source


Unfortunately the solution provided by rickster doesn't work for me :(

While trying to solve this puzzle, I created (what I believe) a very custom solution (more proof of concept). It involves creating (null) objects on the axis I'm trying to find, then I use their position to find the axis-aligned vector.

Since I have a rather complex scene, I load it from the COLLADA file. Inside this file I have modeled a simple coordinate tripod: three orthogonal cylinders with cones on top (makes it easier to visualize what is happening).



Then I constrain this tripod object to the object to which I am trying to apply torque. This way I have objects that allow me to fetch two points on the axes of the PresentationNode of the object to which I am trying to apply torque. Then I can use these two points to define a vector for applying the torque.

// 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 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];

      

As you can probably see, I'm quite inexperienced in SceneKit, but I even see that a much simpler / optimized solution comes out, but I can't find it :(

+1


source


I recently had the same issue how to convert torque from object local space to world space required by the method applyTorque

. The problem with using node methods convertPosition:toNode

and fromNode

is that they also apply the translation of the node to the torque, so this will only work when the node is 0,0,0. What these methods do is treat SCNVector3 as vec4 with a w-component 1.0. In other words, we want to apply rotation, in other words, we want the w-component of vec4 to be 0. Unlike SceneKit, GLKit gives us 2 options for how we want our vec3s to be multiplied:

GLKMatrix4MultiplyVector3 where

The input vector is treated as a 4-component vector with a w-component of 0.0.

and GLKMatrix4MultiplyVector3WithTranslation where

The input vector is treated as a 4-component vector with a w-component of 1.0.

What we want here is the first one, just a rotation, not a translation.

So we can travel around the world to GLKit. To convert, for example, the local x-axis (1,0,0), such as the rotation of the step, to the global axis needed for the application torque would look like this:

    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)

      

However, a more swiftian approach would be to add an operator *

for mat4 * vec3 that treats vec3 as vec4 with a 0.0 w component. Like this:

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

      

While this operator makes an assumption about how we want our vec3s to be multiplied, my reasoning here is that since the methods convertPosition

already treat w as 1, it would be overkill to have an operator *

that did this as well.

You can also add the mat4 * SCNVector4 operator, which would let the user explain if they want w or 0.

So, instead of looping from SceneKit to GLKit, we can simply write:

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

      

You can use this method to apply rotation along multiple axes with one call applyTorque

. So tell me if you have an input input where you want the x on the stick to be yaw (local yUp axis) and y on the stick is step (local x axis) but with the flight type-sim "down to retreat / up ", then you can set it toSCNVector3(input.y, -input.x, 0)

0


source







All Articles