Search code examples
iosswiftscenekit

How to position camera to center fit a 3d model inside a SK3DNode in SceneKit


I want to be able to center fit a 3D model inside a SK3DNode. I have the following code:

public extension SK3DNode {
  static func forModel(_ model: SCNNode, size: CGSize) -> SK3DNode {
    let scene = SCNScene()
    let node3D = SK3DNode()
    node3D.scnScene = scene
    node3D.viewportSize = size
    
    scene.rootNode.addChildNode(model)

    let camera = SCNCamera()
    let cameraNode = SCNNode()
    cameraNode.camera = camera
    node3D.pointOfView = cameraNode
    
    cameraNode.constraints = [SCNLookAtConstraint(target: model)]
    // TODO: adjust position to center fit the model
    cameraNode.position = SCNVector3(x: 0, y: 0, z: 4)
    
    return node3D
  }
}

I have done the following:

    let planeX = model.boundingSize.x
    let planeY = model.boundingSize.y
    let planeLength = max(planeX, planeY)
    let angleInRadian = NumberUtil.degreeToRadian(degree: camera.fieldOfView)
    let distance = planeLength / 2 / tan(angleInRadian/2)

I got the above calculation based on the picture in this answer: Camera position based on model size?

This seems to be close to what I want, but it doesn't consider the size and aspect ratio of the viewport in the calculation, so I don't think it's correct. Is there a better way to compute this?

EDIT:

Here you can download a sample project: https://drive.google.com/file/d/1W_v0jMR1w9SkP-__SlKcPfiRF9cpTyEb/view?usp=sharing

When you run it, you will see 4 SK3DNodes (see the 4 blue areas in the screenshot below), and they are (from top to bottom):

  • A tall viewport with a wide 3d model inside
  • A tall viewport with a tall 3d model inside
  • A wide viewport with a tall 3d model inside
  • A wide viewport with a wide 3d model inside

I want to control the camera so that all 3D models will appear to "center fit" the viewport.

enter image description here


Solution

  • I came up with the solution: I need to set the projectionDirection based on the model's aspect ratio and the viewport's aspect ratio.

    For example, this code below sets the models (white boxes) to center fit the viewports (blue windows) with a padding of 20%.

        let modelSize = model.boundingSize
        let modelAspectRatio = modelSize.x / modelSize.y
        let viewportAspectRatio = Float(viewportSize.width / viewportSize.height)
        let h: Float
        
        let paddingRatio: Float = 0.8
        let angleInRadian = Float(camera.fieldOfView * .pi / 180)
    
        if modelAspectRatio > viewportAspectRatio {
          // model is wider, use horizontal axis for FOV computation
          camera.projectionDirection = .horizontal
          h = (modelSize.x / paddingRatio) / 2 / tanf(angleInRadian/2)
        } else {
          camera.projectionDirection = .vertical
          h = (modelSize.y / paddingRatio) / 2 / tanf(angleInRadian/2)
        }
        
        cameraNode.position = SCNVector3(x: 0, y: 0, z: h + modelSize.z/2)
    

    And as you can see from the result, all the white boxes are scaled to center fit the blue viewport, with a padding of 20%.

    enter image description here