Drag object on XZ plane - ios

I am working on an augmented reality app and I would like to be able to drag an object in the space. The problem with the solutions I find here in SO, the ones that suggest using projectPoint/unprojectPoint, is that they produce movement along the XY plane.
I was trying to use the fingers movement on the screen as an offset for x and z coordinates of the node. The problem is that there is a lot of stuff to take in consideration (camera's position, node's position, node's rotation, etc..)
Is there a simpler way of doing this?

I have updated #Alok answer as in my case it is drgging in x plane only from above solution. So i have added y coordinates, working for me.
var PCoordx: Float = 0.0
var PCoordy: Float = 0.0
var PCoordz: Float = 0.0
#objc func handleDragGesture(_ sender: UIPanGestureRecognizer) {
switch sender.state {
case .began:
let hitNode = self.sceneView.hitTest(sender.location(in: self.sceneView),
options: nil)
self.PCoordx = (hitNode.first?.worldCoordinates.x)!
self.PCoordy = (hitNode.first?.worldCoordinates.y)!
self.PCoordz = (hitNode.first?.worldCoordinates.z)!
case .changed:
// when you start to pan in screen with your finger
// hittest gives new coordinates of touched location in sceneView
// coord-pcoord gives distance to move or distance paned in sceneview
let hitNode = sceneView.hitTest(sender.location(in: sceneView), options: nil)
if let coordx = hitNode.first?.worldCoordinates.x,
let coordy = hitNode.first?.worldCoordinates.y,
let coordz = hitNode.first?.worldCoordinates.z {
let action = SCNAction.moveBy(x: CGFloat(coordx - PCoordx),
y: CGFloat(coordy - PCoordy),
z: CGFloat(coordz - PCoordz),
duration: 0.0)
self.photoNode.runAction(action)
self.PCoordx = coordx
self.PCoordy = coordy
self.PCoordz = coordz
}
sender.setTranslation(CGPoint.zero, in: self.sceneView)
case .ended:
self.PCoordx = 0.0
self.PCoordy = 0.0
self.PCoordz = 0.0
default:
break
}
}

first you need to create floor or very large plane few meters (i have 10) below origin. This makes sure your hittest always returns value. Then using pan gesture :
//store previous coordinates from hittest to compare with current ones
var PCoordx: Float = 0.0
var PCoordz: Float = 0.0
#objc func move(_ gestureRecognizer: UIPanGestureRecognizer){
if gestureRecognizer.state == .began{
let hitNode = sceneView.hitTest(gestureRecognizer.location(in: sceneView), options: nil)
PCoordx = (hitNode.first?.worldCoordinates.x)!
PCoordz = (hitNode.first?.worldCoordinates.z)!
}
// when you start to pan in screen with your finger
// hittest gives new coordinates of touched location in sceneView
// coord-pcoord gives distance to move or distance paned in sceneview
if gestureRecognizer.state == .changed {
let hitNode = sceneView.hitTest(gestureRecognizer.location(in: sceneView), options: nil)
if let coordx = hitNode.first?.worldCoordinates.x{
if let coordz = hitNode.first?.worldCoordinates.z{
let action = SCNAction.moveBy(x: CGFloat(coordx-PCoordx), y: 0, z: CGFloat(coordz-PCoordz), duration: 0.1)
node.runAction(action)
PCoordx = coordx
PCoordz = coordz
}
}
gestureRecognizer.setTranslation(CGPoint.zero, in: sceneView)
}
if gestureRecognizer.state == .ended{
PCoordx = 0
PCoordz = 0
}
}
In my case there is only one node so i have not checked if required node is taped or not. You can always check for it if you have many nodes.

If I understand you correctly, I do this using a UIPanGestureRecognizer added to the ARSCNView.
In my case I want to check if pan was started on a given virtual object and keep track of which it was because I can have multiple, but if you have just one object you may not need the targetNode variable.
The 700 constant I use to divide I got it by trial and error to make the translation smoother, you may need to change it for your case.
Moving finger up, moves the object further away from camera and moving it down moves it nearer. Horizontal movement of fingers moves object left/right.
#objc func onTranslate(_ sender: UIPanGestureRecognizer) {
let position = sender.location(in: scnView)
let state = sender.state
if (state == .failed || state == .cancelled) {
return
}
if (state == .began) {
// Check pan began on a virtual object
if let objectNode = virtualObject(at: position).node {
targetNode = objectNode
latestTranslatePos = position
}
}
else if let _ = targetNode {
// Translate virtual object
let deltaX = Float(position.x - latestTranslatePos!.x)/700
let deltaY = Float(position.y - latestTranslatePos!.y)/700
targetNode!.localTranslate(by: SCNVector3Make(deltaX, 0.0, deltaY))
latestTranslatePos = position
if (state == .ended) {
targetNode = nil
}
}

Related

Load large 3d Object .scn file in ARSCNView Aspect Fit in to the screen ARKIT Swift iOS

I am developing ARKit Application using 3d models. So for that I have used 3d models & added gestures for move, rotate & zoom 3d models.
Now I am facing only 1 issue but I am not sure if this issue relates to what. Is there an issue in 3d model or if anything missing in my program.
Issue is the 3d model I am using shows very big & goes out of the screen. I am trying to scale it down size but its very big.
Here is my code :
#IBOutlet var mySceneView: ARSCNView!
var selectedNode = SCNNode()
var prevLoc = CGPoint()
var touchCount : Int = 0
override func viewDidLoad() {
super.viewDidLoad()
self.lblTitle.text = self.sceneTitle
let mySCN = SCNScene.init(named: "art.scnassets/\(self.sceneImagename).scn")!
self.mySceneView.scene = mySCN
let cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.position = SCNVector3Make(0, 0, 0)
self.mySceneView.scene.rootNode.addChildNode(cameraNode)
self.mySceneView.allowsCameraControl = true
self.mySceneView.autoenablesDefaultLighting = true
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(detailPage.doHandleTap(_:)))
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(detailPage.doHandlePan(_:)))
let gesturesArray = NSMutableArray()
gesturesArray.add(tapGesture)
gesturesArray.add(panGesture)
gesturesArray.addObjects(from: self.mySceneView.gestureRecognizers!)
self.mySceneView.gestureRecognizers = (gesturesArray as! [UIGestureRecognizer])
}
//MARK:- Handle Gesture
#objc func doHandlePan(_ sender: UIPanGestureRecognizer) {
var delta = sender.translation(in: self.view)
let loc = sender.location(in: self.view)
if sender.state == .began {
self.prevLoc = loc
self.touchCount = sender.numberOfTouches
} else if sender.state == .changed {
delta = CGPoint(x: loc.x - prevLoc.x, y: loc.y - prevLoc.y)
prevLoc = loc
if self.touchCount != sender.numberOfTouches {
return
}
var rotMat = SCNMatrix4()
if touchCount == 2 {
rotMat = SCNMatrix4MakeTranslation(Float(delta.x * 0.025), Float(delta.y * -0.025), 0)
} else {
let rotMatX = SCNMatrix4Rotate(SCNMatrix4Identity, Float((1.0/100) * delta.y), 1, 0, 0)
let rotMatY = SCNMatrix4Rotate(SCNMatrix4Identity, Float((1.0/100) * delta.x), 0, 1, 0)
rotMat = SCNMatrix4Mult(rotMatX, rotMatY)
}
let transMat = SCNMatrix4MakeTranslation(selectedNode.position.x, selectedNode.position.y, selectedNode.position.z)
selectedNode.transform = SCNMatrix4Mult(selectedNode.transform, SCNMatrix4Invert(transMat))
let parentNodeTransMat = SCNMatrix4MakeTranslation((selectedNode.parent?.worldPosition.x)!, (selectedNode.parent?.worldPosition.y)!, (selectedNode.parent?.worldPosition.z)!)
let parentNodeMatWOTrans = SCNMatrix4Mult(selectedNode.parent!.worldTransform, SCNMatrix4Invert(parentNodeTransMat))
selectedNode.transform = SCNMatrix4Mult(selectedNode.transform, parentNodeMatWOTrans)
let camorbitNodeTransMat = SCNMatrix4MakeTranslation((self.mySceneView.pointOfView?.worldPosition.x)!, (self.mySceneView.pointOfView?.worldPosition.y)!, (self.mySceneView.pointOfView?.worldPosition.z)!)
let camorbitNodeMatWOTrans = SCNMatrix4Mult(self.mySceneView.pointOfView!.worldTransform, SCNMatrix4Invert(camorbitNodeTransMat))
selectedNode.transform = SCNMatrix4Mult(selectedNode.transform, SCNMatrix4Invert(camorbitNodeMatWOTrans))
selectedNode.transform = SCNMatrix4Mult(selectedNode.transform, rotMat)
selectedNode.transform = SCNMatrix4Mult(selectedNode.transform, camorbitNodeMatWOTrans)
selectedNode.transform = SCNMatrix4Mult(selectedNode.transform, SCNMatrix4Invert(parentNodeMatWOTrans))
selectedNode.transform = SCNMatrix4Mult(selectedNode.transform, transMat)
}
}
#objc func doHandleTap(_ sender: UITapGestureRecognizer) {
let p = sender.location(in: self.mySceneView)
var hitResults = self.mySceneView.hitTest(p, options: nil)
if (p.x > self.mySceneView.frame.size.width-100 || p.y < 100) {
self.mySceneView.allowsCameraControl = !self.mySceneView.allowsCameraControl
}
if hitResults.count > 0 {
let result = hitResults[0]
let material = result.node.geometry?.firstMaterial
selectedNode = result.node
SCNTransaction.begin()
SCNTransaction.animationDuration = 0.3
SCNTransaction.completionBlock = {
SCNTransaction.begin()
SCNTransaction.animationDuration = 0.3
SCNTransaction.commit()
}
material?.emission.contents = UIColor.white
SCNTransaction.commit()
}
}
My Question is :
Can we set any size of 3d object model Aspect fit in screen size in the centre of the screen ? Please suggest if there is some way for it.
Any guidence or suggestions will be highly appreciated.
What you need to is to use getBoundingSphereCenter to get the bounding sphere size, then can project that to the screen. Or alternatively get the ratio of that radius over the distance between scenekit camera and the object position. This way you will know how big the object will look on the screen. To the scale down, you simple set the scale property of your object.
For the second part, you can use projectPoint.
The way I handled this is making sure the 3D model always has a fixed size.
For example, if the 3D model is a small cup or a large house, I insure it always has a width of 25 cm on the scene's coordinate space (while maintaining the ratios between x y z).
You can calculate the width of the bounding box of the node like this:
let mySCN = SCNScene(named: "art.scnassets/\(self.sceneImagename).scn")!
let minX = mySCN.rootNode.boundingBox.min.x
let maxX = mySCN.rootNode.boundingBox.max.x
// change 0.25 to whatever you need
// this value is in meters
let scaleValue = 0.25 / abs(minX - maxX)
// scale all axes of the node using `scaleValue`
// this maintains ratios and does not stretch the model
mySCN.rootNode.scale = SCNVector3(scaleValue, scaleValue, scaleValue)
self.mySceneView.scene = mySCN
You can also calculate the scale value based on height or depth by using the y or z value of the bounding box.

Interactive UIView flip transition

I have the front and the back of a card. I animate the transition between the two like this:
private func flipToBack() {
UIView.transition(from: frontContainer, to: backContainer, duration: 0.5, options: [.transitionFlipFromRight, .showHideTransitionViews], completion: nil)
}
private func flipToFront() {
UIView.transition(from: backContainer, to: frontContainer, duration: 0.5, options: [.transitionFlipFromLeft, .showHideTransitionViews], completion: nil)
}
This works perfectly. However, I want to make this animation interactive, so that if the user pans horizontally across the card, the flip animation will advance proportionally. Usually, I would do this kind of interactive animation with a UIViewPropertyAnimator, but I do not know what property I would animate in the animator without building up the flip animation from scratch.
Is it possible to use UIViewPropertyAnimator, or is there some other alternative to make the flip interactive?
I ended up writing it myself. The code is pretty long, so here's a link to the full program on GitHub. Here are the key parts:
Everything is encapsulated in an InteractiveFlipAnimator object that takes a front view (v1) and a back view (v2). Each view also gets a black cover that functions as a shadow to add that darkening effect when the view turns in perspective.
Here is the panning function:
/// Add a `UIPanGestureRecognizer` to the main view that contains the card and pass it onto this function.
#objc func pan(_ gesture: UIPanGestureRecognizer) {
guard let view = gesture.view else { return }
if isAnimating { return }
let translation = gesture.translation(in: view)
let x = translation.x
let angle = startAngle + CGFloat.pi * x / view.frame.width
// If the angle is outside [-pi, 0], then do not rotate the view and count it as touchesEnded. This works because the full width is the screen width.
if angle < -CGFloat.pi || angle > 0 {
if gesture.state != .began && gesture.state != .changed {
finishedPanning(angle: angle, velocity: gesture.velocity(in: view))
}
return
}
var transform = CATransform3DIdentity
// Perspective transform
transform.m34 = 1 / -500
// y rotation transform
transform = CATransform3DRotate(transform, angle, 0, 1, 0)
self.v1.layer.transform = transform
self.v2.layer.transform = transform
// Set the shadow
if startAngle == 0 {
self.v1Cover.alpha = 1 - abs(x / view.frame.width)
self.v2Cover.alpha = abs(x / view.frame.width)
} else {
self.v1Cover.alpha = abs(x / view.frame.width)
self.v2Cover.alpha = 1 - abs(x / view.frame.width)
}
// Set which view is on top. This flip happens when it looks like the two views make a vertical line.
if abs(angle) < CGFloat.pi / 2 {
// Flipping point
v1.layer.zPosition = 0
v2.layer.zPosition = 1
} else {
v1.layer.zPosition = 1
v2.layer.zPosition = 0
}
// Save state
if gesture.state != .began && gesture.state != .changed {
finishedPanning(angle: angle, velocity: gesture.velocity(in: view))
}
}
The code to finish panning is very similar, but it is also much longer. To see it all come together, visit the GitHub link above.

SceneKit: Using pan gesture to move a node that's not at origin

I have a SCNNode that I've set at a position of SCNVector3(0,0,-1). The following code moves the node forward or backward along the Z-axis, however, the initial pan gesture moves the node to (0,0,0) and then moves the node in line with the pan gesture.
let currentPositionDepth = CGPoint()
#objc func handlePan(sender: UIPanGestureRecognizer) {
let translation = sender.translation(in: sceneView)
var newPos = CGPoint(x: CGFloat(translation.x), y: CGFloat(translation.y))
newPos.x += currentPositionDepth.x
newPos.y += currentPositionDepth.y
node.position.x = Float(newPos.x)
node.position.z = Float(newPos.y)
if(sender.state == .ended) { currentPositionDepth = newPos }
}
I'd like the node to move from it's set position of (0,0,-1). I've tried setting currentPositionDepth.y to -1, however it does not achieve the desired effect. How can I achieve this?
Try something like this:
var previousLoc = CGPoint.init(x: 0, y: 0)
#objc func panAirShip(sender: UIPanGestureRecognizer){
var delta = sender.translation(in: self.view)
let loc = sender.location(in: self.view)
if sender.state == .changed {
delta = CGPoint.init(x: 2 * (loc.x - previousLoc.x), y: 2 * (loc.y - previousLoc.y))
airshipNode.position = SCNVector3.init(airshipNode.position.x + Float(delta.x * 0.02), airshipNode.position.y + Float(-delta.y * (0.02)), 0)
previousLoc = loc
}
previousLoc = loc
}
I have multiplied the 0.02 factor to make the translation smoother and in turn easier for the end user. You may change that factor to anything else you like.

SceneKit: move camera towards direction its facing with pan gesture

I have set up some custom camera controls in my SceneKit game. I am having a problem with my pan gesture auto-adapting based on the cameras y euler angle. The pan gesture I have works by panning the camera on the x and z axis (by using the gestures translation) The problem is, despite the cameras rotation, the camera will continue to pan on the x and z axis. I want it so that the camera pans on the axis its facing.
here are my gestures I am using to pan/rotate:
panning:
var previousTranslation = SCNVector3(x: 0.0,y: 15,z: 0.0)
var lastWidthRatio:Float = 0
var angle:Float = 0
#objc func pan(gesture: UIPanGestureRecognizer) {
gesture.minimumNumberOfTouches = 1
gesture.maximumNumberOfTouches = 1
if gesture.numberOfTouches == 1 {
let view = self.view as! SCNView
let node = view.scene!.rootNode.childNode(withName: "Node", recursively: false)
let secondNode = view.scene!.rootNode.childNode(withName: "CameraHandler", recursively: false)
let translation = gesture.translation(in: view)
let constant: Float = 30.0
angle = secondNode!.eulerAngles.y
//these were the previous values I was using to handle panning, they worked but provided really jittery movement. You can change the direction they rotate by multiplying the sine/cosine .pi values by any integer.
//var translateX = Float(translation.y) * sin(.pi) / cos(.pi) - Float(translation.x) * cos(.pi)
//var translateY = Float(translation.y) * cos(.pi) / cos(.pi) + Float(translation.x) * sin(.pi)
//these ones work a lot smoother
var translateX = Float(translation.x) * Float(Double.pi / 180)
var translateY = Float(translation.y) * Float(Double.pi / 180)
translateX = translateX * constant
translateY = translateY * constant
switch gesture.state {
case .began:
previousTranslation = node!.position
break;
case .changed:
node!.position = SCNVector3Make((previousTranslation.x + translateX), previousTranslation.y, (previousTranslation.z + translateY))
break
default: break
}
}
}
rotation:
#objc func rotate(gesture: UIPanGestureRecognizer) {
gesture.minimumNumberOfTouches = 2
gesture.maximumNumberOfTouches = 2
if gesture.numberOfTouches == 2 {
let view = self.view as! SCNView
let node = view.scene!.rootNode.childNode(withName: "CameraHandler", recursively: false)
let translate = gesture.translation(in: view)
var widthRatio:Float = 0
widthRatio = Float(translate.x / 10) * Float(Double.pi / 180)
switch gesture.state {
case .began:
lastWidthRatio = node!.eulerAngles.y
break
case .changed:
node!.eulerAngles.y = lastWidthRatio + widthRatio
print(node!.eulerAngles.y)
break
default: break
}
}
}
the CameraHandler Node is the parent node of the Camera Node. It all works, it just doesnt work like I want it to. Hopefully this is clear enough for you guys to understand.
In Objective C. The key part is the last three lines and specifically the order of multiplication of the matrices in the last line (causing the movement to happen in local space). If the transmat and cammat are switched it would behave again like you have now (moving in world space). The refactor part is just something that works for my specific situation where both perspective and orthographic camera is possible.
-(void)panCamera :(CGPoint)location {
CGFloat dx = _prevlocation.x - location.x;
CGFloat dy = location.y - _prevlocation.y;
_prevlocation = location;
//refactor dx and dy based on camera distance or orthoscale
if (cameraNode.camera.usesOrthographicProjection) {
dx = dx / 416 * cameraNode.camera.orthographicScale;
dy = dy / 416 * cameraNode.camera.orthographicScale;
} else {
dx = dx / 720 * cameraNode.position.z;
dy = dy / 720 * cameraNode.position.z;
}
SCNMatrix4 cammat = self.cameraNode.transform;
SCNMatrix4 transmat = SCNMatrix4MakeTranslation(dx, 0, dy);
self.cameraNode.transform = SCNMatrix4Mult(transmat, cammat);
}
I figured it out, based off of what Xartec answered. I translated it into swift and retro-fitted it to work with what I needed. I'm not truly happy with it because the movement is not smooth. I will work on smoothing it out later today.
pan gesture: This gesture pans the cameras parent node around the scene in the direction that the camera is rotated. It works exactly like I wanted.
#objc func pan(gesture: UIPanGestureRecognizer) {
gesture.minimumNumberOfTouches = 1
gesture.maximumNumberOfTouches = 1
if gesture.numberOfTouches == 1 {
let view = self.view as! SCNView
let node = view.scene!.rootNode.childNode(withName: "CameraHandler", recursively: false)
let translation = gesture.translation(in: view)
var dx = previousTranslation.x - translation.x
var dy = previousTranslation.y - translation.y
dx = dx / 100
dy = dy / 100
print(dx,dy)
let cammat = node!.transform
let transmat = SCNMatrix4MakeTranslation(Float(dx), 0, Float(dy))
switch gesture.state {
case .began:
previousTranslation = translation
break;
case .changed:
node!.transform = SCNMatrix4Mult(transmat, cammat)
break
default: break
}
}
}
rotation gesture: This gesture rotates the camera with two fingers.
#objc func rotate(gesture: UIPanGestureRecognizer) {
gesture.minimumNumberOfTouches = 2
gesture.maximumNumberOfTouches = 2
if gesture.numberOfTouches == 2 {
let view = self.view as! SCNView
let node = view.scene!.rootNode.childNode(withName: "CameraHandler", recursively: false)
let translate = gesture.translation(in: view)
var widthRatio:Float = 0
widthRatio = Float(translate.x / 10) * Float(Double.pi / 180)
switch gesture.state {
case .began:
lastWidthRatio = node!.eulerAngles.y
break
case .changed:
node!.eulerAngles.y = lastWidthRatio + widthRatio
break
default: break
}
}
}
To get the same functionality that I have, you need to attach the cameraNode to a parent node. Like so:
//create and add a camera to the scene
let cameraNode = SCNNode()
//cameraHandler is declared outside viewDidLoad.
let cameraHandler = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.name = "Camera"
cameraNode.position = SCNVector3(x: 0.0,y: 10.0,z: 20.0)
//This euler angle only rotates the camera downward a little bit. It is not neccessary
cameraNode.eulerAngles = SCNVector3(x: -0.6, y: 0, z: 0)
cameraHandler.addChildNode(cameraNode)
cameraHandler.name = "CameraHandler"
scene.rootNode.addChildNode(cameraHandler)

SceneKit cube rotation with multiple UIPanGestureRecognizers

Thanks for taking a look at this. I'm sure it is something very basic. I am a beginner.
I am trying to rotate a cube in in a SceneKit view with pan gestures. I have so far been successful in load this sample app onto my iPad and panning my finger on the y axis to rotate the cube on its x axis, or panning along the screens x-axis to rotate the cube along its y-axis.
Currently, I've noticed that whichever gesture recognizer is added last to the sceneView is the one that works. My question is how can I have the cube respond to either a x pan gesture then a y pan gesture or vice versa.
Here is the code I have written so far:
import UIKit
import SceneKit
class ViewController: UIViewController {
//geometry
var geometryNode: SCNNode = SCNNode()
//gestures
var currentYAngle: Float = 0.0
var currentXAngle: Float = 0.0
override func viewDidLoad() {
super.viewDidLoad()
sceneSetup()
}
func sceneSetup () {
//setup scene
let scene = SCNScene()
let sceneView = SCNView(frame: self.view.frame)
self.view.addSubview(sceneView)
//add camera
let camera = SCNCamera()
let cameraNode = SCNNode()
cameraNode.camera = camera
cameraNode.position = SCNVector3(x: 0.0, y: 0.0, z: 5.0)
//add light
let light = SCNLight()
light.type = SCNLightTypeOmni
let lightNode = SCNNode()
lightNode.light = light
lightNode.position = SCNVector3(x: 1.5, y: 1.5, z: 1.5)
//add cube
let cubeGeometry = SCNBox(width: 1.0, height: 1.0, length: 1.0, chamferRadius: 0.0)
let boxNode = SCNNode(geometry: cubeGeometry)
scene.rootNode.addChildNode(lightNode)
scene.rootNode.addChildNode(cameraNode)
scene.rootNode.addChildNode(boxNode)
geometryNode = boxNode
//add recognizers
let panXRecognizer = UIPanGestureRecognizer(target: self, action: "rotateXGesture:")
let panYRecognizer = UIPanGestureRecognizer(target: self, action: "rotateYGesture:")
sceneView.addGestureRecognizer(panXRecognizer)
sceneView.addGestureRecognizer(panYRecognizer)
sceneView.scene = scene
}
func rotateXGesture (sender: UIPanGestureRecognizer) {
let translation = sender.translationInView(sender.view)
var newXAngle = (Float)(translation.y)*(Float)(M_PI)/180.0
newXAngle += currentXAngle
geometryNode.transform = SCNMatrix4MakeRotation(newXAngle, 1, 0, 0)
if(sender.state == UIGestureRecognizerState.Ended) {
currentXAngle = newXAngle
}
}
func rotateYGesture (sender: UIPanGestureRecognizer) {
let translation = sender.translationInView(sender.view)
var newYAngle = (Float)(translation.x)*(Float)(M_PI)/180.0
newYAngle += currentYAngle
geometryNode.transform = SCNMatrix4MakeRotation(newYAngle, 0, 1, 0)
if(sender.state == UIGestureRecognizerState.Ended) {
currentYAngle = newYAngle
}
}
}
Combine your current two gestures into one. Here's the relevant portion of the code I'm using:
func panGesture(sender: UIPanGestureRecognizer) {
let translation = sender.translationInView(sender.view!)
var newAngleX = (Float)(translation.y)*(Float)(M_PI)/180.0
newAngleX += currentAngleX
var newAngleY = (Float)(translation.x)*(Float)(M_PI)/180.0
newAngleY += currentAngleY
baseNode.eulerAngles.x = newAngleX
baseNode.eulerAngles.y = newAngleY
if(sender.state == UIGestureRecognizerState.Ended) {
currentAngleX = newAngleX
currentAngleY = newAngleY
}
}
Here's a gesture for zooming as well:
func pinchGesture(sender: UIPinchGestureRecognizer) {
let zoom = sender.scale
var z = cameraNode.position.z * Float(1.0 / zoom)
z = fmaxf(zoomLimits.min, z)
z = fminf(zoomLimits.max, z)
cameraNode.position.z = z
}
Edit: I found a better way to rotate the model. In the panGesture code at the top, the x-axis pivots as you rotate about the y. This means if you rotate 180 about the y, rotation about the x is opposite your finger motion. The method also restricts motion to two degrees of freedom. The method linked to below, even though it doesn't directly affect the z-axis, somehow seems to allow three degrees of freedom. It also makes all vertical swipes rotate about the x in the logical direction.
How to rotate object in a scene with pan gesture - SceneKit
The way you set up your two gesture recognizers is identical, they will both fire for the same events (which is why the last one to be added predominates). There is no control within pan to specifically limit it to vertical or horizontal pans. Instead, consider analyzing the direction of the pan and then decide whether to rotate your gesture one way or the other, based upon which is greater.

Resources