【问题标题】:Scenekit - convertTransform of a rotated sub-subNode based on world axesScenekit - 基于世界轴的旋转子节点的转换转换
【发布时间】:2018-03-25 05:32:06
【问题描述】:

我有以下场景:

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

我已经设置了平移手势识别器,当您在开放空间中平移时,[主容器] 会在所选方向上旋转 +/- Double.pi/2 (90deg)。当平移从子节点 A、B 之一开始时(我在 touchesBegan 上对此进行了测试),我想沿世界轴的方向旋转子节点(再次以 90 度为增量)。

我正在使用 rootNode 中的 convertTransform() 旋转 [主容器],它工作正常,并且旋转是沿世界轴执行的 - 主容器的位置是 (0,0,0),我相信这会使容易多了。

我包装子节点的原因是它们在包装器内具有局部位置 (0,0,0),这应该有助于围绕它们的原点进行旋转。但是当我在 [Main Container] 上执行旋转时,它们也会旋转,因此它们的局部轴的方向会发生变化,并且围绕与我想要的轴不同的轴进行旋转。

在我对变换矩阵的(非常有限的)理解中,我假设我需要以某种方式链接和乘以父节点的 convertTransform 生成的矩阵,或者以某种方式使用 worldTransform 属性,但是我尝试的任何操作都会导致奇怪的旋转。任何帮助,将不胜感激!

【问题讨论】:

  • 使用包装器节点只会使事情变得不必要地复杂化。还有很多其他方法可以解决这个问题。在我尝试回答之前,我是否正确地说您想要两件事:1. 如果平移未在这些节点上启动,则同时旋转 nodeA 和 nodeB(这意味着您可以旋转相机而不是主容器和所有孩子)。 2. 在世界空间中旋转子节点,如果平移开始于那些节点。
  • 感谢 Xartec.. 澄清一下 - 1. 节点 A 和 B 与主容器一起旋转,因为它们是子容器。在这种情况下,我不想旋转相机。 2.不确定我是否正确解释了这一点,我希望子节点使用它们的本地原点(即立方体的中心)旋转,但旋转它就像它的本地轴与世界对齐一样。就像我将主容器沿 Y 旋转 90 度一样轴,现在 A & B 节点的 Z 轴指向右边。如果我现在想向下旋转 A 节点(向下滑动),旋转应该是沿着它的局部 Z 轴旋转,该 Z 轴现在平行于世界 X。

标签: ios swift rotation transform scenekit


【解决方案1】:

我已经建立了一个基于 SceneKit 模板的小型示例项目,其控件与您描述的类似。它在 Objective C 中,但相关部分几乎相同:

- (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);

        }
    }
}

在handleTap中:

selectedNode = result.node;

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

还有本地变量:

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

MainPlanet 将是您的 mainContainer 并且不必是可见的(在我的示例中是可见的,因为必须点击它才能知道要旋转什么......)。两个卫星是您的节点 A 和 B,主节点的子节点。不需要包装。关键部分显然是注释部分。

通常在本地空间旋转一个子节点(如果父节点在0,0,0)

  1. 首先将其移回节点,方法是将其变换乘以其平移的倒数。
  2. 应用旋转矩阵。
  3. 应用我们在步骤 1 中删除的原始翻译。

正如您所注意到的,这将在其本地枢轴点和其本地轴上旋转子节点。这工作正常,直到您旋转父节点。解决方案是在基于平移手势(步骤 2)旋转子节点之前对子节点应用相同的旋转,然后再次将其移除。

所以要得到你想要的结果:

  1. 首先将其移回节点,方法是仅将其变换乘以其平移的倒数。
  2. 应用父节点的旋转(因为它在 0,0,0 并且我假设没有缩放,我们可以使用变换)。
  3. 根据平移手势应用旋转矩阵。
  4. 移除父节点的旋转
  5. 应用我们在步骤 1 中删除的原始翻译。

我确信还有其他可能的路线,也许可以使用 convert to/from 将旋转矩阵转换为主节点,而不是第 2 步和第 4 步,但这样你就可以清楚地知道发生了什么。

【讨论】:

  • 您先生是我书中的绝对英雄。正是这样的答案使 Stackowerflow 成为最佳去处 - 您的答案不仅为我的问题提供了一个精确的解决方案,而且在我花了几天时间在论文并试图了解转换矩阵的实际工作原理。感激不尽!
  • 很高兴听到该解决方案适合您,并感谢您的友好反馈。
猜你喜欢
  • 2021-10-22
  • 2018-12-17
  • 2017-10-26
  • 2019-10-18
  • 1970-01-01
  • 2018-01-31
  • 2015-07-23
  • 2017-07-26
相关资源
最近更新 更多