How to Rotate a Scnsphere Using a Pan Gesture Recognizer

How to rotate a SCNSphere/Node through a pan gesture

I figured it out, It was working the whole time, its just that the lighting made appear like nothing moved. Instead, I needed to rotate the light.

SceneKit - Adding drift to a sphere rotated via a pan gesture

The way I've handled things like this before is to split the gesture handler into sections for the gesture state beginning, changing and ending. If the rotation is working for you all you need to do is add code to the bottom of your if (sender.state == .Ended) statement.

After lastHeightRatio = heightRatio you can find the velocity of the pan in the view using sender.velocityInView(sender.view!) and then find the horizontal component as before, then add an SCNAction to rotate the node with an EaseOut timing mode.

You'll likely need a multiplier to translate the velocity in the view (measured in points) to the angle you wish to rotate through (measured in radians). A velocity of 10 points, which is relatively small, would result in a very fast rotation.

Also you'll need to remove all actions from the node when the gesture begins again if you want a touch to stop the residual rotation.

Some sampel code would be:

if (sender.state == .Ended) {
lastWidthRatio = widthRatio
lastHeightRatio = heightRatio

// Find velocity
let velocity = sender.velocityInView(sender.view!)

// Create Action for whichever axis is the correct one to rotate about.
// The 0.00001 is just a factor to provide the rotation speed to pan
// speed ratio you'd like. Play with this number and the duration to get the result you want
let rotate = SCNAction.rotateByX(velocity.x * 0.00001, y: 0, z: 0, duration: 2.0)

// Run the action on the camera orbit node (if I understand your setup correctly)

This should be enough to get you started.

How to use a pan gesture to rotate a camera in SceneKit using quaternions

I was able to get this working using quaternions. The full code is here: ThreeSixtyPlayer. A sample is here:

    let orientation = cameraNode.orientation

// Use the pan translation along the x axis to adjust the camera's rotation about the y axis (side to side navigation).
let yScalar = Float(translationDelta.x / translationBounds.size.width)
let yRadians = yScalar * maxRotation

// Use the pan translation along the y axis to adjust the camera's rotation about the x axis (up and down navigation).
let xScalar = Float(translationDelta.y / translationBounds.size.height)
let xRadians = xScalar * maxRotation

// Represent the orientation as a GLKQuaternion
var glQuaternion = GLKQuaternionMake(orientation.x, orientation.y, orientation.z, orientation.w)

// Perform up and down rotations around *CAMERA* X axis (note the order of multiplication)
let xMultiplier = GLKQuaternionMakeWithAngleAndAxis(xRadians, 1, 0, 0)
glQuaternion = GLKQuaternionMultiply(glQuaternion, xMultiplier)

// Perform side to side rotations around *WORLD* Y axis (note the order of multiplication, different from above)
let yMultiplier = GLKQuaternionMakeWithAngleAndAxis(yRadians, 0, 1, 0)
glQuaternion = GLKQuaternionMultiply(yMultiplier, glQuaternion)

cameraNode.orientation = SCNQuaternion(x: glQuaternion.x, y: glQuaternion.y, z: glQuaternion.z, w: glQuaternion.w)

swift SceneKit decline node move

You can put similar code into your UIViewController:

// Gesture Recognizers
// MARK: Gesture Recognizers
@objc func handleTap(recognizer: UITapGestureRecognizer)
if(data.isNavigationOff == true) { return } // No panel select if Add, Update, EndWave, or EndGame
if(gameMenuTableView.isHidden == false) { return } // No panel if game menu is showing

let location: CGPoint = recognizer.location(in: gameScene)

if(data.isAirStrikeModeOn == true)
let projectedPoint = gameScene.projectPoint(SCNVector3(0, 0, 0))
let scenePoint = gameScene.unprojectPoint(SCNVector3(location.x, location.y, CGFloat(projectedPoint.z)))
gameControl.airStrike(position: scenePoint)
let hitResults = gameScene.hitTest(location, options: hitTestOptions)
for vHit in hitResults
if( == "Panel")
// May have selected an invalid panel or auto upgrade was on
if(gameControl.selectPanel(vPanel:!) == false) { return }
@objc func handlePan(recognizer: UIPanGestureRecognizer)
if(data.gameState != .run || data.isGamePaused == true) { return }

currentLocation = recognizer.location(in: gameScene)

switch recognizer.state
case UIGestureRecognizer.State.began:
beginLocation = recognizer.location(in: gameScene)
case UIGestureRecognizer.State.changed:
if(currentLocation.x > beginLocation.x * 1.1)
beginLocation.x = currentLocation.x
if(currentLocation.x < beginLocation.x * 0.9)
beginLocation.x = currentLocation.x
case UIGestureRecognizer.State.ended:

Getting location of tap on SCNSphere - Swift (SceneKit) / iOS

In the hitResult, you can get result.textureCoordinates which tells you the point in your map textures. From this point, you are supposed to know the location of your map as the map should have coordinations which was mapped to textures.

 @objc func handleTap(_ gestureRecognize: UIGestureRecognizer) {
// retrieve the SCNView
let sceneView = self.view as! SCNView

// check what nodes are tapped
let p = gestureRecognize.location(in: scnView)
let hitResults = sceneView.hitTest(p, options: [:])
// check that we clicked on at least one object
if hitResults.count > 0 {
// retrieved the first clicked object
let result: SCNHitTestResult = hitResults[0]

print(result.textureCoordinates(withMappingChannel 0)) // This line is added here.
print("x: \(p.x) y: \(p.y)") // <--- THIS IS WHERE I PRINT THE COORDINATES


Related Topics

Leave a reply