简体   繁体   中英

How to rotate a SCNSphere using a pan gesture recognizer

I created an SCNSphere so now it looks like a planet kind of. This is exactly what I want. My next goal is to allow users to rotate the sphere using a pan gesture recognizer. They are allowed to rotate it around the X or Y axis. I was just wondering how I can do that. This is what I have so far.

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
}

We have a ViewController that contains a node sphereNode that contains our sphere. To rotate the sphere we could use a UIPanGestureRecognizer . Since the recognizer reports the total distance our finger has traveled on the screen we cache the last point that was reported to us.

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
}

We calculate dx and dy with how much pixel our finger has traveled in each direction since we last called the recognizer. With the pixelToAngleConstant we convert our pixel value in an angle (in randians) to rotate our sphere. Use a bigger constant for a faster rotation.

The gesture recognizer returns a state that we can use to determine if the gesture has started, ended, or the finger has been moved. When the gesture starts we save the fingers location in previousPanPoint . When our finger moves we call the function above. When the gesture is ended or canceled we clear our 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
    }
}

How do we rotate our sphere? The functions rotateUp and rotateRight just call our more general function, rotate(by: around:) which accepts not only the angle but also the axis to rotate around. rotateUp rotates around the x-axis, rotateRight around the y-axis.

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)
}

The rotate(by:around:) is in this case relative simple because we assume that the node is not translated/ we want to rotate around the origin of the nodes local coordinate system. Everything is a little more complicated when we look at a general case but this answer is only a small starting point.

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

We create a rotation matrix from the angle and the axis and multiply the old transform of our sphere with the calculated one to get the new transform .

This is the little demo I created:

演示


This approach has two major downsides.

  1. It only rotates around the nodes coordinate origin and only works properly if the node's position is SCNVector3Zero

  2. It does takes neither the speed of the gesture into account nor does the sphere continue to rotate when the gesture stops. An effect similar to a table view where you can flip your finger and the table view scrolls fast and then slows down can't be easily achieved with this approach. One solution would be to use the physics system for that.

Below is what I tried, not sure whether it is accurate with respect to angles but...it sufficed most of my needs....

@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
}

Sample Github-EarthRotate

用手势旋转地球

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM