如何使用平移手势识别器旋转SCNSphere

时间:2017-08-24 01:39:02

标签: ios swift scenekit uipangesturerecognizer

我创建了一个SCNSphere所以它现在看起来像一颗行星。这正是我想要的。我的下一个目标是允许用户使用平移手势识别器旋转球体。允许它们围绕X或Y轴旋转它们。我只是想知道如何做到这一点。这是我到目前为止所做的。

origin = sceneView.frame.origin
node.geometry = SCNSphere(radius: 1)
node.geometry?.firstMaterial?.diffuse.contents = UIImage(named: "world.jpg")

let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(CategoryViewController.panGlobe(sender:)))
sceneView.addGestureRecognizer(panGestureRecognizer)

func panGlobe(sender: UIPanGestureRecognizer) {
    // What should i put inside this method to allow them to rotate the sphere/ball
}

2 个答案:

答案 0 :(得分:4)

我们有一个ViewController,它包含一个包含我们球体的节点sphereNode。 要旋转球体,我们可以使用UIPanGestureRecognizer。 由于识别器会报告我们的手指在屏幕上移动的总距离,因此我们会缓存报告给我们的最后一点。

var previousPanPoint: CGPoint?
let pixelToAngleConstant: Float = .pi / 180
func handlePan(_ newPoint: CGPoint) {
    if let previousPoint = previousPanPoint {
        let dx = Float(newPoint.x - previousPoint.x)
        let dy = Float(newPoint.y - previousPoint.y)

        rotateUp(by: dy * pixelToAngleConstant)
        rotateRight(by: dx * pixelToAngleConstant)
    }

    previousPanPoint = newPoint
}

我们计算dxdy,因为我们上次调用识别器后手指在每个方向上移动了多少像素。 使用pixelToAngleConstant,我们将角度(在randians中)转换为像素值以旋转我们的球体。使用更大的常数来加快旋转速度。

手势识别器返回state,我们可以使用它来确定手势是否已开始,已结束或手指是否已移动。 手势开始时,我们将手指位置保存在previousPanPoint中。 当我们的手指移动时,我们调用上面的函数。 当手势结束或取消时,我们会清除previousPanPoint

@objc func handleGesture(_ gestureRecognizer: UIPanGestureRecognizer) {
    switch gestureRecognizer.state {
    case .began:
        previousPanPoint = gestureRecognizer.location(in: view)
    case .changed:
        handlePan(gestureRecognizer.location(in: view))
    default:
        previousPanPoint = nil
    }
}

我们如何旋转球体? 函数rotateUprotateRight只调用我们更通用的函数rotate(by: around:),它不仅接受角度,还接受旋转的轴。 rotateUp绕x轴旋转,rotateRight绕y轴旋转。

func rotateUp(by angle: Float) {
    let axis = SCNVector3(1, 0, 0) // x-axis
    rotate(by: angle, around: axis)
}

func rotateRight(by angle: Float) {
    let axis = SCNVector3(0, 1, 0) // y-axis
    rotate(by: angle, around: axis)
}

rotate(by:around:)在这种情况下相对简单,因为我们假设节点未被翻译/我们想围绕节点局部坐标系的原点旋转。 当我们看一般情况时,一切都有点复杂,但这个答案只是一个很小的起点。

func rotate(by angle: Float, around axis: SCNVector3) {
    let transform = SCNMatrix4MakeRotation(angle, axis.x, axis.y, axis.z)
    sphereNode.transform = SCNMatrix4Mult(sphereNode.transform, transform)
}

我们从angleaxis创建一个旋转矩阵,并将我们球体的旧transform与计算的一个相乘,得到新的transform

这是我创建的小演示:

Demo

这种方法有两个主要缺点。

  1. 它只围绕节点坐标原点旋转,只有在节点位置为SCNVector3Zero时才能正常工作

  2. 当手势停止时,它既不会考虑手势的速度,也不会继续旋转球体。 使用此方法无法轻松实现类似于桌面视图的效果,您可以在其中翻转手指并快速滚动表格视图然后减速。 一种解决方案是使用物理系统。

答案 1 :(得分:2)

以下是我尝试过的,不确定角度是否准确但是......它足以满足我的大部分需求....

@objc func handleGesture(_ gestureRecognizer: UIPanGestureRecognizer) {

    let translation = gestureRecognizer.translation(in: gestureRecognizer.view!)

    let x = Float(translation.x)
    let y = Float(-translation.y)
    let anglePan = (sqrt(pow(x,2)+pow(y,2)))*(Float)(Double.pi)/180.0

    var rotationVector = SCNVector4()
    rotationVector.x = x
    rotationVector.y = y
    rotationVector.z = 0.0
    rotationVector.w = anglePan


    self.earthNode.rotation = rotationVector
}

示例Github-EarthRotate

Rotating Earth with gesture