I’m sure this will change at WWDC, but at the moment there isn’t any support in SceneKit for the new ARMeshAnchor data generated by the 2020 iPad with Lidar.
This is unfortunate as SceneKit’s very handy for quick prototyping but it’s easy enough (even for a SceneKit rookie like myself) to generate some SCNGeometry from the raw data that ARKit provides.
Here’s a drop in file that adds a fromAnchor extension to SCNGeometry
// // SCNGeometry+ARMeshAnchor.swift // // Created by Andrew Grant on 4/17/20. // import SceneKit import ARKit extension SCNGeometry { /** Constructs an SCNGeometry element from an ARMeshAnchor. Note, the underlying vertex data is owned by the ARMeshAnchor so this geometry becomes invalid when the anchor is updated or removed. */ public static func fromAnchor(meshAnchor: ARMeshAnchor) -> SCNGeometry { let vertices = meshAnchor.geometry.vertices let faces = meshAnchor.geometry.faces // use the MTL buffer that ARKit gives us let vertexSource = SCNGeometrySource(buffer: vertices.buffer, vertexFormat: vertices.format, semantic: .vertex, vertexCount: vertices.count, dataOffset: vertices.offset, dataStride: vertices.stride) // but we need to create our own copy of the faces.. let faceData = Data(bytesNoCopy: faces.buffer.contents(), count: faces.buffer.length, deallocator: .none) // create the geometry element let geometryElement = SCNGeometryElement(data: faceData, primitiveType: .triangles, primitiveCount: faces.count, bytesPerIndex: faces.bytesPerIndex) let geometry = SCNGeometry(sources: [vertexSource], elements: [geometryElement]) // assign a material suitable for default visualization let defaultMaterial = SCNMaterial() defaultMaterial.fillMode = .lines defaultMaterial.diffuse.contents = UIColor(displayP3Red:1, green:1, blue:1, alpha:0.7) geometry.materials = [defaultMaterial] return geometry; } }
And here’s an example of how to use it in your ARSessionDelegate handler (with a PlaneAnchor as a comparison)
var knownAnchors = Dictionary<UUID, SCNNode>() func session(_ session: ARSession, didAdd anchors: [ARAnchor]) { for anchor in anchors { var sceneNode : SCNNode? if let meshAnchor = anchor as? ARMeshAnchor { let meshGeo = SCNGeometry.fromAnchor(meshAnchor:meshAnchor) sceneNode = SCNNode(geometry:meshGeo) } else if let planeAnchor = anchor as? ARPlaneAnchor { let planeGeo = ARSCNPlaneGeometry(device: arScnView.device!) planeGeo?.update(from: planeAnchor.geometry) planeGeo?.firstMaterial?.fillMode = .lines sceneNode = SCNNode(geometry: planeGeo) } if let node = sceneNode { node.simdTransform = anchor.transform knownAnchors[anchor.identifier] = node arSceneView.scene.rootNode.addChildNode(node) } } } func session(_ session: ARSession, didUpdate anchors: [ARAnchor]) { for anchor in anchors { // do we know about this anchor? If so update it if let node = knownAnchors[anchor.identifier] { if let planeAnchor = anchor as? ARPlaneAnchor { let planeGeometry = node.geometry as! ARSCNPlaneGeometry planeGeometry.update(from: planeAnchor.geometry) } else if let meshAnchor = anchor as? ARMeshAnchor { // reconstruct it since we don't have an efficient way of updating the underlying data node.geometry = SCNGeometry.fromAnchor(meshAnchor: meshAnchor) } node.simdTransform = anchor.transform } } }
(Note the geometry returned by fromMesh uses a *reference* to the data in ARMeshAnchor so you must implement didUpdate to reconstruct the geometry and didRemove to remove the node so it no longer renders. If this isn’t done expect a crash or validation error of some type).