Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scenekit Pan 2D Translation to Orthographic 3D only horizontal

I am having a little more mathematical problem with 3D programming and I am hoping you can help me!

I am trying to create a 3D game using Scenekit with a isometric angle.

This code creates my orthographic camera:

var cameraNode = SCNNode()
cameraNode.camera = SCNCamera()
cameraNode.name = "Camera"
cameraNode.position = SCNVector3Make(-5.0, -5.0, 10.0)
cameraNode.eulerAngles = SCNVector3Make(PI / 3.0, 0.0, -PI / 4.0)
cameraNode.camera?.usesOrthographicProjection = true
cameraNode.camera?.orthographicScale = 7.0
scene.rootNode.addChildNode(cameraNode)

Now i want to move the camera using a pan gesture, producing a scroll feeling. To make this possible the camera shouldn't move vertically, only horizontally. The touch location on screen and the unprojected position in the 3D world should stay the same while moving.

I thought about calculating the 2D translation into 3D difference and ignoring the vertical component. This code actually works and almost produces the desired result, but the speed is not correct. If I pan, the camera seems to accelerate and not react correctly:

var previousTranslation = CGPointMake(0.0, 0.0)

func pan(gesture: UIPanGestureRecognizer)
{
    let view = self.view as SCNView
    let translation = gesture.translationInView(view)
    let location = gesture.locationInView(view)

    let diffTrans = translation - previousTranslation
    previousTranslation = translation

    let cameraNode = scene.rootNode.childNodeWithName("Camera", recursively: false)

    let worldPointTrans = view.unprojectPoint(SCNVector3Make(-Float(diffTrans.x), -Float(diffTrans.y), 0.0))
    let worldPoint0 = view.unprojectPoint(SCNVector3Make(0.0, 0.0, 0.0))

    var diff = worldPointTrans - worldPoint0
    diff.x = diff.x / Float(cameraNode!.camera!.orthographicScale)
    diff.y = diff.y / Float(cameraNode!.camera!.orthographicScale)
    diff.z = 0
    cameraNode?.position += diff
}

Does anybody know a sophisticated way of calculating a screen translation into a horizontal 3D translation, ignoring the vertical axis?

Thank you in advance :)

EDIT: The pan works for horizontal translation now. But not for vertical, because I set the difference on the z axis to zero.

like image 703
Phil Niedertscheider Avatar asked Dec 07 '14 19:12

Phil Niedertscheider


2 Answers

I found my own solution.!

I am calculating a ray at the start location of the gesture (P1-P2) and a ray at the translated location (Q1-Q2). Now I have two rays and I let both intersect with the XY Plane to receive the points P0 and Q0

The difference of P0 and Q0 is the unprojected translation.

This technique should also work with a non-orthogonal camera, but i didn't test this yet.

It seems to me that it works, but if anybody could mathematically confirm this assumption, I would be glad to read that :)

Here is the code:

 var previousLocation = SCNVector3(x: 0, y: 0, z: 0)

func pan(gesture: UIPanGestureRecognizer)
{
    let view = self.view as SCNView
    let translation = gesture.translationInView(view)

    let location = gesture.locationInView(view)
    let secLocation = location + translation

    let P1 = view.unprojectPoint(SCNVector3(x: Float(location.x), y: Float(location.y), z: 0.0))
    let P2 = view.unprojectPoint(SCNVector3(x: Float(location.x), y: Float(location.y), z: 1.0))

    let Q1 = view.unprojectPoint(SCNVector3(x: Float(secLocation.x), y: Float(secLocation.y), z: 0.0))
    let Q2 = view.unprojectPoint(SCNVector3(x: Float(secLocation.x), y: Float(secLocation.y), z: 1.0))

    let t1 = -P1.z / (P2.z - P1.z)
    let t2 = -Q1.z / (Q2.z - Q1.z)

    let x1 = P1.x + t1 * (P2.x - P1.x)
    let y1 = P1.y + t1 * (P2.y - P1.y)

    let P0 = SCNVector3Make(x1, y1,0)

    let x2 = Q1.x + t1 * (Q2.x - Q1.x)
    let y2 = Q1.y + t1 * (Q2.y - Q1.y)

    let Q0 = SCNVector3Make(x2, y2, 0)

    var diffR = Q0 - P0
    diffR *= -1

    let cameraNode = view.scene!.rootNode.childNodeWithName("Camera", recursively: false)

    switch gesture.state {
    case .Began:
        previousLocation = cameraNode!.position
        break;
    case .Changed:
        cameraNode?.position = previousLocation + diffR
        break;
    default:
        break;
    }
}

Red is screen translation, Blue is world translation

like image 92
Phil Niedertscheider Avatar answered Sep 20 '22 19:09

Phil Niedertscheider


I've calculated the equations for the isometric panning, the code is below.

//camera pan ISOMETRIC logic
func pan(gesture: UIPanGestureRecognizer) {
    let view = self.sceneView as SCNView
    let cameraNode = view.scene!.rootNode.childNode(withName: "Camera", recursively: false)
    let translation = gesture.translation(in: view)

    let constant: Float = 30.0
    var translateX = Float(translation.y)*sin(.pi/4.0)/cos(.pi/3.0)-Float(translation.x)*cos(.pi/4.0)
    var translateY = Float(translation.y)*cos(.pi/4.0)/cos(.pi/3.0)+Float(translation.x)*sin(.pi/4.0)
    translateX = translateX / constant
    translateY = translateY / constant

    switch gesture.state {
    case .began:
        previousLocation = cameraNode!.position
        break;
    case .changed:
        cameraNode?.position = SCNVector3Make((previousLocation.x + translateX), (previousLocation.y + translateY), (previousLocation.z))
        break;
    default:
        break;
    }
}

And to get scaling right, you need to use screenheight as a variable for orthographicScale. The scaling i used here is 30x magnification, note the 30 is also used for the constant in the code above.

    let screenSize: CGRect = UIScreen.main.bounds
    let screenHeight = screenSize.height
    let cameraNode = SCNNode()
    cameraNode.camera = SCNCamera()
    cameraNode.name = "Camera"
    let cameraDist = Float(20.0)
    let cameraPosX = cameraDist*(-1.0)*cos(.pi/4.0)*cos(.pi/6.0)
    let cameraPosY = cameraDist*(-1.0)*sin(.pi/4.0)*cos(.pi/6.0)
    let cameraPosZ = cameraDist*sin(.pi/6)
    cameraNode.position = SCNVector3Make(cameraPosX, cameraPosY, cameraPosZ)
    cameraNode.eulerAngles = SCNVector3Make(.pi / 3.0, 0.0, -.pi / 4.0)
    cameraNode.camera?.usesOrthographicProjection = true
    cameraNode.camera?.orthographicScale = Double(screenHeight)/(2.0*30.0) //30x magnification constant. larger number = larger object
    scene.rootNode.addChildNode(cameraNode)
like image 43
user3204609 Avatar answered Sep 19 '22 19:09

user3204609