简体   繁体   中英

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)

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