This is a more complicated question than it seems at first glance.
SceneKit will store the coordinates for the geometry used to generate a node in the original scale/basis. Those don't change once the object is created. And for primitives like SCNPlane
or SCNBox
, those geometries are shared within a scene. So if you have a bunch of different planes or boxes, all at different positions, rotations, and scaling, you'll get the same vertices out when you query the SCNGeometrySource
. There's a good discussion of that retrieval here: Extracting vertices from scenekit. But that approach won't give you the corners in your local coordinate space.
We have two functions that work on SCNNode
and SCNGeometry
: boundingBox
and boundingSphere
(they're defined in protocol SCNBoundingVolume
). The boundingBox
gives you two points, the minimum and the maximum. From those two opposite corners of a cube, you can figure out the corner vertices of your plane. There's another complication, though. Both of those functions return their answers in local coordinates, the coordinate system used by the node itself. So we're still stuck: every one of my planes or boxes will have the same bounding box.
The answer I came up with is to use SCNNode's convertPosition(_, to: node)
, passing the scene's root node. That finally gives me bounding boxes in the root node's coordinate space.
for node in [custom1, custom2, plainPlane1, plainPlane2] {
print(node.name!)
print(node.boundingBox.max)
print(node.boundingBox.min)
print(node.convertPosition(node.boundingBox.min, to: scene.rootNode))
print(node.convertPosition(node.boundingBox.max, to: scene.rootNode))
}
producing
custom 1
SCNVector3(x: 0.300000011920929, y: 0.100000001490116, z: 0.0)
SCNVector3(x: 0.0, y: -0.100000001490116, z: 0.0)
SCNVector3(x: 0.0, y: -0.100000001490116, z: -1.0)
SCNVector3(x: 0.300000011920929, y: 0.100000001490116, z: -1.0)
custom 2
SCNVector3(x: 0.300000011920929, y: 0.100000001490116, z: 0.0)
SCNVector3(x: 0.0, y: -0.100000001490116, z: 0.0)
SCNVector3(x: 0.200000002980232, y: 0.429289322037836, z: -1.07071067796216)
SCNVector3(x: 0.500000014901161, y: 0.570710677962164, z: -0.929289322037836)
plain plane 1
SCNVector3(x: 0.5, y: 1.0, z: 0.0)
SCNVector3(x: -0.5, y: -1.0, z: 0.0)
SCNVector3(x: -0.5, y: -1.0, z: -2.0)
SCNVector3(x: 0.5, y: 1.0, z: -2.0)
plain plane 2
SCNVector3(x: 0.5, y: 1.0, z: 0.0)
SCNVector3(x: -0.5, y: -1.0, z: 0.0)
SCNVector3(x: -9.18485139438876e-18, y: -0.300000011920929, z: -1.84999999403954)
SCNVector3(x: 9.18485139438876e-18, y: 0.300000011920929, z: -2.15000000596046)
for this scene:

Here's the full macOS playground below:
//: Playground - noun: a place where people can play
import Cocoa
import SceneKit
import PlaygroundSupport
let positions = [SCNVector3Make( 0, -0.1, 0),
SCNVector3Make( 0.3, -0.1, 0),
SCNVector3Make( 0, 0.1, 0),
SCNVector3Make( 0.3, 0.1, 0),
]
let indices :[UInt16] = [
0, 1, 2,
1, 3, 2,
]
let vertexSource = SCNGeometrySource(vertices: positions, count: 4)
let indexData = NSData(bytes: indices, length: MemoryLayout.size * indices.count)
let newElement = SCNGeometryElement(data: NSData(bytes: indices,
length: indices.count * MemoryLayout.size) as Data,
primitiveType: .triangles ,
primitiveCount: 2,
bytesPerIndex: MemoryLayout.size)
let sceneView = SCNView(frame: CGRect(x: 0, y: 0, width: 600, height: 400))
sceneView.allowsCameraControl = true
sceneView.autoenablesDefaultLighting = true
sceneView.backgroundColor = NSColor.darkGray
sceneView.showsStatistics = true
PlaygroundPage.current.liveView = sceneView
let scene = SCNScene()
sceneView.scene = scene
let geometry = SCNGeometry(sources: [vertexSource], elements: [newElement])
geometry.firstMaterial?.diffuse.contents = NSColor.red
geometry.firstMaterial?.isDoubleSided = true
let custom1 = SCNNode(geometry: geometry)
custom1.position = SCNVector3Make(0, 0, -1)
custom1.name = "custom 1"
scene.rootNode.addChildNode(custom1)
let custom2 = SCNNode(geometry: geometry)
custom2.position = SCNVector3Make(0.2, 0.5, -1)
custom2.rotation = SCNVector4Make(1, 0, 0, CGFloat(M_PI_4))
custom2.name = "custom 2"
scene.rootNode.addChildNode(custom2)
let plainPlaneGeometry = SCNPlane(width: 1, height: 2)
plainPlaneGeometry.firstMaterial?.diffuse.contents = NSColor.yellow
plainPlaneGeometry.firstMaterial?.isDoubleSided = true
let plainPlane1 = SCNNode(geometry: plainPlaneGeometry)
plainPlane1.position = SCNVector3Make(0, 0, -2)
plainPlane1.name = "plain plane 1"
scene.rootNode.addChildNode(plainPlane1)
let plainPlane2 = SCNNode(geometry: plainPlaneGeometry)
plainPlane2.position = SCNVector3Make(0, 0, -2)
plainPlane2.rotation = SCNVector4Make(0, 1, 0, CGFloat(M_PI_2))
plainPlane2.scale = SCNVector3Make(0.3, 0.3, 0.3)
plainPlane2.name = "plain plane 2"
scene.rootNode.addChildNode(plainPlane2)
for node in [custom1, custom2, plainPlane1, plainPlane2] {
print(node.name!)
print(node.boundingBox.max)
print(node.boundingBox.min)
// print(node.transform)
print(node.convertPosition(node.boundingBox.min, to: scene.rootNode))
print(node.convertPosition(node.boundingBox.max, to: scene.rootNode))
}
You mentioned the transformation matrix. There's a nice explanation of what it looks like and how it works at Need better and simpler understanding of CATransform3D (with citations to two good Wikipedia articles), and an easy to digest overview at http://sketchytech.blogspot.com/2014/12/explaining-catransform3d-matrix.html.