iosswiftscenekit

SCNNode boundingBox not updated after scaled or rotated in SceneKit


I have this code:

    let box = SCNBox(width: 1, height: 2, length: 4, chamferRadius: 0.2)
    box.firstMaterial?.diffuse.contents = UIColor.yellow
    box.firstMaterial?.emission.contents = UIColor.green
    let boxNode = SCNNode(geometry: box)
    print(boxNode.boundingBox)
    boxNode.eulerAngles = SCNVector3(0.785, 0.5, 0.5)
    print(boxNode.boundingBox)
    boxNode.scale = SCNVector3(1.3, 2.3, 3.3)
    print(boxNode.boundingBox)
    scene.rootNode.addChildNode(boxNode)

I got this:

(min: __C.SCNVector3(x: -0.5, y: -1.0, z: -2.0), max: __C.SCNVector3(x: 0.5, y: 1.0, z: 2.0))
(min: __C.SCNVector3(x: -0.5, y: -1.0, z: -2.0), max: __C.SCNVector3(x: 0.5, y: 1.0, z: 2.0))
(min: __C.SCNVector3(x: -0.5, y: -1.0, z: -2.0), max: __C.SCNVector3(x: 0.5, y: 1.0, z: 2.0))

It turn out that the bounding box is not updated, even after applying rotation or scaling. This is different from SpriteKit behavior. I am wondering how can I get the updated bounding box?


Solution

  • This occurs because the bounding-box values returned by boxNode.boundingBox represent the local bounding box of the SCNNode (as you initially created the geometry), which doesn’t automatically account for transformations such as rotation, scaling, or translation applied to the node.

    To get the actual bounding-box dimensions in world space, you need to apply transformations to get the bounding-box in world coordinates, which is what you expect, if I understood your question correctly:

    Add this new function:

    func getWorldSpaceBoundingBox(of node: SCNNode) -> (min: SCNVector3, max: SCNVector3) {
        let (min, max) = node.boundingBox
        let corners = [
            SCNVector3(min.x, min.y, min.z),
            SCNVector3(max.x, min.y, min.z),
            SCNVector3(min.x, max.y, min.z),
            SCNVector3(max.x, max.y, min.z),
            SCNVector3(min.x, min.y, max.z),
            SCNVector3(max.x, min.y, max.z),
            SCNVector3(min.x, max.y, max.z),
            SCNVector3(max.x, max.y, max.z)
        ]
    
        // Transform each corner to world space
        let transformedCorners = corners.map { node.convertPosition($0, to: nil) }
        
        // Initialize min and max points
        var worldMin = transformedCorners[0]
        var worldMax = transformedCorners[0]
        
        // Find min and max points among transformed corners
        for corner in transformedCorners {
            worldMin = SCNVector3(
                x: Swift.min(worldMin.x, corner.x),
                y: Swift.min(worldMin.y, corner.y),
                z: Swift.min(worldMin.z, corner.z)
            )
            worldMax = SCNVector3(
                x: Swift.max(worldMax.x, corner.x),
                y: Swift.max(worldMax.y, corner.y),
                z: Swift.max(worldMax.z, corner.z)
            )
        }
        
        return (min: worldMin, max: worldMax)
    }
    

    You can then use your example box-setup like so:

    func testBox() {
        let box = SCNBox(width: 1.0, height: 2.0, length: 4.0, chamferRadius: 0.2)
        box.firstMaterial?.diffuse.contents = UIColor.yellow
        box.firstMaterial?.emission.contents = UIColor.green
        let boxNode = SCNNode(geometry: box)
        sceneView.scene?.rootNode.addChildNode(boxNode)
        
        print("Original bounding box (local):", boxNode.boundingBox)
        
        // Apply transformations
        boxNode.eulerAngles = SCNVector3(0.785, 0.5, 0.5)
        boxNode.scale = SCNVector3(1.3, 2.3, 3.3)
        
        // Get transformed bounding box in world space
        let worldBoundingBox = getWorldSpaceBoundingBox(of: boxNode)
        print("Transformed bounding box (world):", worldBoundingBox)
    }
    

    This are the print-outs from the debugger:

    Original bounding box (local): (min: __C.SCNVector3(x: -0.5, y: -1.0, z: -2.0), max: __C.SCNVector3(x: 0.5, y: 1.0, z: 2.0))

    Transformed bounding box (world): (min: __C.SCNVector3(x: -4.7974877, y: -5.0958166, z: -5.835535), max: __C.SCNVector3(x: 4.7974877, y: 5.0958166, z: 5.835535))