How to move a rotated SCNNode in SceneKit, on its "own" axis?
Asked Answered
M

3

8

The image below shows a rotated box that should be moved horizontally on the X and Z axes. Y should stay unaffected to simplify the scenario. The box could also be the SCNNode of the camera, so I guess a projection does not make sense at this point.

So lets say we want to move the box in the direction of the red arrow. How to achieve this using SceneKit?

The red arrow indicates -Z direction of the box. It also shows us it is not parallel to the camera's projection or to the global axes that are shown as dark grey lines of the grid.

My last approach is the product of a translation matrix and a rotation matrix that results in a new transformation matrix. Do I have to add the current transform to the new transform then?

If yes, where is the SceneKit function for the addition of matrices like SCNMatrix4Mult for multiplication or do I have to write it myself using Metal?

If no, what I'm missing out with the matrix calculations?

I don't want to make use of GLKit.

enter image description here

Mightily answered 5/4, 2017 at 17:25 Comment(8)
Can you clarify how you want to move the node "move horizontally on the x and z axes"? 1) you want to move it in a plane parallel to the screen plane? 2) you want to move it in the plane defined by the X and Z axes?Wenda
Adjust x and z, but not y (vertical axis). But that is only to simplify the scenario.Mightily
Ok, so you want to move it in the plane defined by X and Z. Does it have to be parallel to the screen? Or you want for example when swiping left move towards -X, right +X and top towards -Z and down towards +Z? If I managed to explain properly?Wenda
Swiping is not the ideal method to move the box into the direction of the red arrow. It is not left or right or top or bottom. Maybe we should leave out any gestures and keys to simplify the scenario.Mightily
Just imagine the red arrow is the direction vector you want to move the box to. It could be the translation vector. But I don't get it calculated properly.Mightily
Ok, now I thing I understand you want to move the Box (node) along its own X axis. But because a node's position/rotation is according to it's parent's coordinate system and its parent coordinate system is rotated it goes along the parent's X axis. I'll look into it, but one quick fix can be to add an extra (helper) node without geometry as the box's parent node that is not rotated. You can then move the box along the helper X axis which will be parallel to the box's own X axis. And use the helper node to rotate the whole thing.Wenda
Updated the answer belowWenda
Just one note. Very often in a game engine or 3D engine, you do this sort of rig by having a holder node which holds the cube. Then you trivially move it within that parent. (Of course, it depends on just what you are doing.)Doubleripper
W
13

So my understanding is that you want to move the Box Node along its own X axis (not it's parents X axis). And because the Box Node is rotated, its X axis is not aligned with its parent's one, so you have the problem to convert the translation between the two coordinate systems.

The node hierarchy is

parentNode
    |
    |----boxNode // rotated around Y (vertical) axis

Using Transformation Matrices

To move boxNode along its own X axis

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

Please Note: The order matters when multiplying matrices

Another option:

Using SCNNode coordinate conversion functions, looks more straight forward to me

// 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;
Wenda answered 6/4, 2017 at 14:37 Comment(4)
I almost got it, just swapping the multiplication parameters. Also SCNMatrix4Translate isn't helping in this case because it also changes the global position.Mightily
The second options looks understandable as well. I guess it should also work with SCNAction methods like move().Mightily
Second option works great, solved a great problem I had, awesome solution!Stridor
Just be aware that these approaches are incredibly more fragile than ... simply using the 3D engine to do the work! Note that a manual approach like this on the CPU becomes a nightmare if you try to (say) do two animations at once, far less many animations at once.Doubleripper
P
0

Building on @Sulevus's correct answer, here's an extension to SCNNode that simplifies things by using the convertVector rather than the convertPosition transformation, in Swift.

I've done it as a var returning a unit vector, and supplied an SCNVector3 overload of multiply so you can say things like

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)
    }
}
Pneumatology answered 22/8, 2019 at 17:54 Comment(2)
Unfortunately I get the following: Member operator '*' must have at least one argument of type 'SCNNode' also you need to cast rhs to Float and make those overloads of multiply staticValverde
IIRC this is the difference between iOS & Mac OS - SCNVector3 has CGFloats in one platform, Float in the other...Pneumatology
D
-1

The far easier way this is usually done:

The usual, normal, and extremely easy way to do this in any game engine or 3D engine is:

You simply have a wrapper node, which, holds the node in question.

This is indeed the entire point of transforms, they enable you to abstract out a certain motion.

That's the whole point of 3D engines - the GPU just multiplies out all the quaternions on the way down to the object; it's wholly pointless to (A) figure out in your head the math and (B) do it manually (indeed in the CPU).

In Unity it's "game objects", in scene kit it's "nodes" and so on.

In all 3D engines, including scene kit, almost everything has one or more "holders" around it.

To repeat, the reasons for this are (A) it's the entire raison d'etre of a game engine, to achieve performance in multiplying out the quaternions of every vertex and (B) sheer convenience and code solidity.

One of a million examples ...

enter image description here

Of course you can trivially do it in code,

    cameraHolder.addChildNode(camera)

In the OP's example. It looks like you would use cameraHolder only to rotate the camera. And then for the motion the OP is asking about, simply move camera.

It's perfectly normal to have a chain of a number of nodes to get to an object.

This is often used for "effects". Say you have an object, which sometimes has to "vibrate up and down". You can have one node which only does that movement. Note that then, all the animations etc for that movement only have to be on that node. And critically, they can run independently of any other animations or movements. (And indeed you can just use the node elsewhere to jiggle something else.)

Doubleripper answered 19/12, 2022 at 12:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.