我已经建立了一个基于 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)旋转子节点之前对子节点应用相同的旋转,然后再次将其移除。
所以要得到你想要的结果:
- 首先将其移回节点,方法是仅将其变换乘以其平移的倒数。
- 应用父节点的旋转(因为它在 0,0,0 并且我假设没有缩放,我们可以使用变换)。
- 根据平移手势应用旋转矩阵。
- 移除父节点的旋转
- 应用我们在步骤 1 中删除的原始翻译。
我确信还有其他可能的路线,也许可以使用 convert to/from 将旋转矩阵转换为主节点,而不是第 2 步和第 4 步,但这样你就可以清楚地知道发生了什么。