0

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.

E. Huckabee
  • 1,788
  • 1
  • 13
  • 29
  • See my answer on the following question for an example on both screen space pan and rotate: https://stackoverflow.com/questions/48530159/how-can-i-rotate-an-scnnode-on-the-axis-the-camera-is-looking-down?rq=1 – Xartec Feb 19 '18 at 19:43
  • This doesnt answer my question at all. Its not even related to what I am trying to do. – E. Huckabee Feb 20 '18 at 15:19
  • I am trying to move the camera on the x and z axis it is facing. So basically, some simplistic camera controls set up with a pan gesture. (similar to how the camera functions in any PC tycoon game) I dont even know what to I am trying to calculate to even accomplish this. Even if you answer in Obj-c I can translate it to swift without any problems. Thanks for your help. – E. Huckabee Feb 20 '18 at 15:28
  • Ah, the images and text suggest you want to move a node that is in front of the cam, but you basically want to move the camera itself *in local space*. – Xartec Feb 20 '18 at 17:30
  • Whoops. I never meant it to sound like that. I just wanted to move the camera, attached to a parent node, through space. – E. Huckabee Feb 21 '18 at 05:05

2 Answers2

1

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

}
Xartec
  • 2,369
  • 11
  • 22
1

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)
E. Huckabee
  • 1,788
  • 1
  • 13
  • 29