Search code examples
swift3dcamerascenekitviewport

Viewport? or Camera? of Field of View? changes on rotating iOS device


I have a 3d world using a SceneKit view in my app.

I need to support all iOS devices and orientations.

I do not understand 3d maths, matrices nor really understand cameras and stuff, yet I've built a 3d world, and I have a camera that works, and orbits with a Pan Gesture an object at the centre of the world.

My problem is that when I rotate the device from portrait to landscape the SceneKit view automatically changes it's perspective (of what I'm not sure - the camera? or the world itself?) and the object that looked like it was close, now goes way back into the distance.

What I want to happen is that regardless of rotation the 'viewport'? or camera? or world perspective? stays the same and the distance the camera is from the object doesn't "look" different i.e. the object visually stays at the same place.

I have not done any matrix manipulations or set up anything complicated.

I understand that the units of SceneKit are metres, so my object is in the correct size, and the camera is the right number of metres from it... it looks 'correct'

What I don't understand is why it all changes in landscape.

Yes, the SceneKit view is using AutoLayout constraints to hug the edges of the screen.

Here is my camera setup:

private func setupCamera() -> SCNNode {
    let camera = SCNCamera()
    camera.usesOrthographicProjection = false
    camera.orthographicScale          = 9
    camera.zNear                      = 0.01
    camera.zFar                       = 9500
    camera.focalLength                = 50
    camera.focusDistance              = 33
    camera.fieldOfView                = 100
    let cameraNode                    = SCNNode()
    cameraNode.position               = SCNVector3(x:0, y:0, z: 500.0)
    cameraNode.camera                 = camera
    self.cameraNode                   = cameraNode
    let cameraOrbit                   = SCNNode()

    cameraOrbit.addChildNode(self.cameraNode!)

    return cameraOrbit
}

The only way I've found to 'fix' this is to make the SceneKit view always be a square and sized so that it's always the largest of the devices width or height and keep it's centre in the screen's centre.

But this approach isn't working with other parts of the app, so instead I know I need to do this 'properly'.

So would someone please provide the code or tell me what I need to understand so that in the function that gets called when the device rotates i.e. override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) then I can correct this automatic change and keep the view's 'perspective' the same.

Thank you.


Solution

  • After 4 days of playing with constraints, manually moving the frame and all sorts of stuff, I have fixed it.

    I constrained the scenekitview to the superview with 0 constants, so it's always the size of the screen.

    Then ALL I had to do to solve this is ONE LINE... sigh.

    I just had to halve the 'sensorHeight' and then it worked perfectly.

    if UIDevice.current.orientation == .landscapeLeft  || 
       UIDevice.current.orientation == .landscapeRight
    {
        self.cameraNode?.camera?.sensorHeight  = 12
    }
    else
    {
        self.cameraNode?.camera?.sensorHeight  = 24
    }
    

    EDIT:

    Because of the constraints to the screen size, the default Field of View meant that you have a fish-eye effect.

    Dropping the FoV to almost half cured this and now objects rotate without distortions:

        camera.fieldOfView = 50