I have a simple plane node that tracks a face.
func renderer(_ renderer: SCNSceneRenderer, nodeFor anchor: ARAnchor) -> SCNNode? {
guard anchor is ARFaceAnchor else { return nil }
let plane = SCNPlane(width: 0.1, height: 0.2)
let planeNode = SCNNode(geometry: plane)
planeNode.geometry?.firstMaterial?.fillMode = .lines
planeNode.geometry?.firstMaterial?.diffuse.contents = UIColor.blue
planeNode.name = "Plane Node"
return planeNode
}
I want to be able to track the coordinates of all four corners of the plane. I'm looking to get the 2D coordinates that are projected on the screen.
func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
guard let faceAnchor = anchor as? ARFaceAnchor else {
return
}
node.enumerateChildNodes { childNode, _ in
guard childNode.name == "Plane Node" else { return }
let worldPosition = childNode.worldPosition
let screenPosition = renderer.projectPoint(worldPosition)
print(CGPoint(x: Int(screenPosition.x), y: Int(screenPosition.y)))
}
}
Above tracks the center position of the plane, but how do I track the four corner coordinates?
I tried using the width and the height of the plane using the following to calculate the distance from the center coordinate, but I'm unable to get the proper width and the height that I can work with screen position I've obtained for the center coordinate.
extension SCNNode {
var width: Float {
return (boundingBox.max.x - boundingBox.min.x) * scale.x
}
var height: Float {
return (boundingBox.max.y - boundingBox.min.y) * scale.y
}
}
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)
}
}
}
}
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])
}