0
votes

I have a following scene:

         [Root Node]
              |
      [Main Container]
        |           |          
[Node A Wrapper]   [Node B Wrapper] 
   |                   |
[Node A]            [Node B]

I've set up pan gesture recognizers in a way that when u pan in open space, the [Main Container] rotates in the selected direction by +/- Double.pi/2 (90deg). When the pan starts on one of the subnodes A, B (i'm hittesting for this on touchesBegan), i want to rotate the subnode along the direction of world axis (again 90deg increments).

I'm rotating the [Main Container] using convertTransform() from rootNode, which works fine, and the rotations are performed along the world axes - the position of main container is (0,0,0) which i believe makes it lot easier.

The reason why i wrapped the subnodes is so they have local positions (0,0,0) inside the wrapper, which should help with the rotation around their origin. But as they are rotated also when i perform rotate on [Main Container] , the direction of their local axes is changed and the rotation is performed around different axis than what i want.

In my (very limited) understanding of transformation matrices, i assume i need to somehow chain and multiply the matrices produced by convertTransform of the parent nodes, or to use the worldTransform property somehow, but anything i tried results in weird rotations. Any help would be appreciated!

1
Using the wrapper nodes only complicates matters unnecessarily. There are many other ways you could approach this. Before I take an attempt at answering, Am I correct saying you want two things: 1. Rotate nodeA and nodeB at the same time if the pan does not start on those nodes (which would mean you could rotate the camera instead of a main container and all childs) . And 2. Rotate the child nodes, in world space, if the pan starts on those.Xartec
Thanks Xartec.. To clarify - 1. Nodes A and B rotate with main container as they are it's children. I don't want to rotate camera in this case. 2. Not sure if i explained this correctly, i want the child nodes to rotate using their local origin (i.e. center of the cube) but rotate it like it's local axes are aligned with world.. Like when i rotate Main Container 90deg along Y axis, now the A & B node's Z axis point right. If i want to now rotate the A node down (swipe down), the rotation should be to rotate along it's local Z axis which is now parallel to worlds X.mike_t

1 Answers

1
votes

I've set up a small sample project based on the SceneKit template, with controls similar as what you described. It's in Objective C but the relevant parts are pretty much the same:

- (void) handlePan:(UIPanGestureRecognizer*)gestureRecognize {

   CGPoint delta = [gestureRecognize translationInView:(SCNView *)self.view];
   if (gestureRecognize.state == UIGestureRecognizerStateChanged) {
       panHorizontal = NO;
       if (fabs(delta.x) > fabs(delta.y)) {
           panHorizontal = YES;
       }

   } else if (gestureRecognize.state == UIGestureRecognizerStateEnded) {

       SCNMatrix4 rotMat;
       int direction = 0;
       if (panHorizontal) {
           if (delta.x <0) {
               direction = -1;
           } else if (delta.x >1) {
               direction = 1;
           }
           rotMat= SCNMatrix4Rotate(SCNMatrix4Identity, M_PI_2, 0, direction, 0);
       } else {
           if (delta.y <0) {
               direction = -1;
           } else if (delta.y >1) {
               direction = 1;
           }
           rotMat= SCNMatrix4Rotate(SCNMatrix4Identity, M_PI_2, direction, 0, 0);
       }


        if (selectedNode == mainPlanet) {
            selectedNode.transform = SCNMatrix4Mult(selectedNode.transform, rotMat);

        } else { //_selectedNode is a child node of mainPlanet, i.e. moons.
            //get the translation matrix of the child node
            SCNMatrix4 transMat = SCNMatrix4MakeTranslation(selectedNode.position.x, selectedNode.position.y, selectedNode.position.z);

            //move the child node the origin of its parent (but keep its local rotation)
            selectedNode.transform = SCNMatrix4Mult(selectedNode.transform, SCNMatrix4Invert(transMat));

            //apply the "rotation" of the mainPlanet extra (we can use the transform because mainPlanet is at world origin)
            selectedNode.transform = SCNMatrix4Mult( selectedNode.transform, mainPlanet.transform);

            //perform the rotation based on the pan gesture
            selectedNode.transform = SCNMatrix4Mult(selectedNode.transform, rotMat);

            //remove the extra "rotation" of the mainPlanet (we can use the transform because mainPlanet is at world origin)
            selectedNode.transform = SCNMatrix4Mult(selectedNode.transform,SCNMatrix4Invert(mainPlanet.transform));

            //add back the translation mat
            selectedNode.transform = SCNMatrix4Mult(selectedNode.transform,transMat);

        }
    }
}

In handleTap:

selectedNode = result.node;

In viewDidLoad:

 mainPlanet = [scene.rootNode childNodeWithName:@"MainPlanet" recursively:YES];
 orangeMoon = [scene.rootNode childNodeWithName:@"orangeMoon" recursively:YES];
 yellowMoon = [scene.rootNode childNodeWithName:@"yellowMoon" recursively:YES];

UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)];
[gestureRecognizers addObject:panGesture];

And local vars:

SCNNode *mainPlanet;
SCNNode *orangeMoon;
SCNNode *yellowMoon;
SCNNode *selectedNode;
BOOL panHorizontal;

Layout of the 3 nodes, the cubes were merely to test

MainPlanet would be your mainContainer and doesn't have to be visible (it does in my example because it has to be tapped to know what to rotate...). The two moons are your node A and B, child nodes of the main node. No wrappers necessary. The key part is obviously the commented portion.

Normally to rotate a child node in local space (IF the parent node is at 0,0,0)

  1. First move it back to the node by multiplying its transform with the inverse of its translation only.
  2. Apply the rotation matrix.
  3. Apply the original translation we removed in step 1.

As you noticed that will rotate the child node on its local pivot point and over its local axis. This works fine until you rotate the parent node. The solution is to apply that same rotation to the child node before rotating it based on the pan gesture (step 2), and then after that remove it again.

So to get the results you desire:

  1. First move it back to the node by multiplying its transform with the inverse of its translation only.
  2. Apply the rotation of the parent node (since it's at 0,0,0 and I assume not scaled, we can use the transform).
  3. Apply the rotation matrix based on the pan gesture.
  4. Remove the rotation of the parent node
  5. Apply the original translation we removed in step 1.

I’m sure there are other possible routes and perhaps instead of step 2 and 4 the rotation matrix could be converted to the main node using convert to/from but this way you can clearly tell what’s going on.