Invisible tracking spheres
Try the following solution. Despite the fact that it's a macOS project, you can implement this idea in your ARKit project. Also, this technique is very easy to understand.

import SceneKit
class ViewController: NSViewController {
var sceneView: SCNView? = nil
let planeNode = SCNNode()
var vertices: [SCNVector3] = []
override func viewDidLoad() {
super.viewDidLoad()
sceneView = self.view as? SCNView
sceneView?.scene = SCNScene()
sceneView?.delegate = self
sceneView?.backgroundColor = .black
sceneView?.autoenablesDefaultLighting = true
// Plane
let plane = SCNPlane(width: 0.5, height: 0.5)
planeNode.name = "planar"
planeNode.geometry = plane
planeNode.geometry?.firstMaterial?.isDoubleSided = true
sceneView?.scene?.rootNode.addChildNode(planeNode)
planeNode.runAction(SCNAction.move(to: SCNVector3(0.2, 0.2,-0.2),
duration: 2))
self.trackingSpheres()
}
}
Delegate's method (in your case it's renderer(_:didUpdate:for:)
instance method).
extension ViewController: SCNSceneRendererDelegate {
func renderer(_ renderer: SCNSceneRenderer,
updateAtTime time: TimeInterval) {
if let spheres = sceneView?.scene?.rootNode.childNodes[0].childNodes {
for (_, sphere) in spheres.enumerated() {
let truncateX = String(format: "%.2f", sphere.worldPosition.x)
let truncateY = String(format: "%.2f", sphere.worldPosition.y)
let truncateZ = String(format: "%.2f", sphere.worldPosition.z)
print("\(sphere.name!):", truncateX, truncateY, truncateZ)
}
}
}
}
A method creating four invisible tiny tracking spheres.
extension ViewController {
fileprivate func trackingSpheres() {
// retrieving a plane node from scene
if let node = sceneView?.scene?.rootNode.childNode(
withName: "planar",
recursively: true) {
let left = node.boundingBox.min.x
let right = node.boundingBox.max.x
let lower = node.boundingBox.min.y
let upper = node.boundingBox.max.y
let south = node.boundingBox.min.z
// Counter clock-wise
let ll = SCNVector3(x: left, y: lower, z: south)
let lr = SCNVector3(x: right, y: lower, z: south)
let ur = SCNVector3(x: right, y: upper, z: south)
let ul = SCNVector3(x: left, y: upper, z: south)
self.vertices += [ll, lr, ur, ul]
for i in 1...4 {
let sphereNode = SCNNode(geometry: SCNSphere(radius: 0.01))
sphereNode.position = SCNVector3(vertices[i-1].x,
vertices[i-1].y,
vertices[i-1].z)
sphereNode.name = "sphere\(i)"
sphereNode.opacity = 0.0 // 100% transparent
sphereNode.geometry?.firstMaterial?.diffuse.contents =
NSColor.red
node.addChildNode(sphereNode)
}
}
}
}

Model's decomposition using vertex semantics
This technique is much tougher than the first one, it's something like reverse engineering of this.
extension SCNGeometry {
func getVerticesPositions() -> [SCNVector3] {
let sources = self.sources(for: .vertex)
guard let source = sources.first else { return [] }
let stride = source.dataStride / source.bytesPerComponent
let offset = source.dataOffset / source.bytesPerComponent
let vectorCount = source.vectorCount
return source.data.withUnsafeBytes {
(pointer: UnsafePointer<Float>) -> [SCNVector3] in
var vertices: [SCNVector3] = []
for index in 0 ... (vectorCount - 1) {
let bytes = index * stride + offset
let x = pointer[bytes + 0]
let y = pointer[bytes + 1]
let z = pointer[bytes + 2]
vertices.append(SCNVector3(x, y, z))
}
return vertices
}
}
}
Let's see how we can use it.
var vertices: [SCNVector3] = []
fileprivate func trackingSpheres() {
if let node = sceneView?.scene?.rootNode.childNode(withName: "planar",
recursively: true) {
vertices = (node.geometry?.getVerticesPositions())!
}
print("vertex1 :", vertices[0])
print("vertex2 :", vertices[1])
print("vertex3 :", vertices[2])
print("vertex4 :", vertices[3])
}