skip to Main Content

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

2

Answers


  1. Chosen as BEST ANSWER

    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


  2. You need to consider the depth of the 3d model while calculating the distance from the camera to the model or it would be calculated from the model center (or origin point) to the camera.
    This adjustment would take care of everything

    P.S. You don’t need also to be at 90% of the size. you would be perfectly having the model in the camera viewport.

        // get the boundingBox aka maximun size of X Y Z of the model (no need for /0.9)
        let planeX = model.boundingBox.max.x
        let planeY = model.boundingBox.max.y
        
        // you need planZ to get the depth of the 3d model for accurate
        // distance calculation
        let planeZ = model.boundingBox.max.z
        
        let planeLength = max(planeX, planeY)
        
        // to convert to radian you can multiply the angle to (.pi/180)
        let angleInRadian: Float = Float(cameraNode.camera!.fieldOfView * (.pi/180))
    
        
        // this is the distance from the center of the 3d model to the camera
        let distance = planeLength / 2 / tan(angleInRadian/2)
        
        // you have to add half the depth of the 3d model to it
        let actualDistance = distance + (planeZ/2)
        
        cameraNode.position = SCNVector3(x: 0, y: 0, z: actualDistance)
    

    Update:

    The full code would be like that:

    public extension SK3DNode {
        static func forModel(_ model: SCNNode, viewportSize: CGSize) -> SK3DNode {
            let scene = SCNScene()
            scene.background.contents = UIImage.color(size: CGSize(width: 1, height: 1), color: .blue)
            let node3D = SK3DNode()
            node3D.scnScene = scene
            node3D.viewportSize = viewportSize
            
            scene.rootNode.addChildNode(model)
            
            let camera = SCNCamera()
            let cameraNode = SCNNode()
            cameraNode.camera = camera
            node3D.pointOfView = cameraNode
            
            cameraNode.constraints = [SCNLookAtConstraint(target: model)]
            // TODO: adjust camera position so that the viewport fits the model
            
            // get the boundingBox aka maximun size of X Y Z of the model (no need for /0.9)
            let planeX = model.boundingBox.max.x
            let planeY = model.boundingBox.max.y
            
            // you need planZ to get the depth of the 3d model for accurate
            // distance calculation
            let planeZ = model.boundingBox.max.z
            
            let planeLength = max(planeX, planeY)
            
            // to convert to radian you can multiply the angle to (.pi/180)
            let angleInRadian: Float = Float(cameraNode.camera!.fieldOfView * (.pi/180))
            
            // this is the distance from the center of the 3d model to the camera
            let distance = planeLength / 2 / tan(angleInRadian/2)
            
            // you have to add half the depth of the 3d model to it
            let actualDistance = distance + (planeZ/2)
            
            cameraNode.position = SCNVector3(x: 0, y: 0, z: actualDistance)
            
            return node3D
        }
        
    }
    

    And Here is the image from the device.
    enter image description here

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search