I am creating an augmented reality application that detects 3D objects in space and has a label pop up above them. The current code lets me detect multiple object, but only lets one type of label (.sks file) pop up. I would like to be able to detect multiple objects each with a different label popping up above them.
import UIKit
import SceneKit
import ARKit
class ViewController: UIViewController, ARSCNViewDelegate {
@IBOutlet var sceneView: ARSCNView!
override func viewDidLoad() {
super.viewDidLoad()
// Set the view's delegate
sceneView.delegate = self
// Show statistics such as fps and timing information
sceneView.showsStatistics = true
// Create a new scene
let scene = SCNScene()
// Set the scene to the view
sceneView.scene = scene
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
// Create a session configuration
let configuration = ARWorldTrackingConfiguration()
// Object Detection
configuration.detectionObjects = ARReferenceObject.referenceObjects(inGroupNamed: "FlowerObjects", bundle: Bundle.main)!
// Run the view's session
sceneView.session.run(configuration)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
// Pause the view's session
//sceneView.session.pause()
}
// MARK: - ARSCNViewDelegate
func renderer(_ renderer: SCNSceneRenderer, nodeFor anchor: ARAnchor) -> SCNNode? {
let node = SCNNode()
if let objectAnchor = anchor as? ARObjectAnchor {
let plane = SCNPlane(width: CGFloat(objectAnchor.referenceObject.extent.x * 1.0), height: CGFloat(objectAnchor.referenceObject.extent.y * 0.7))
plane.cornerRadius = plane.width / 8
let spriteKitScene = SKScene(fileNamed: "ProductInfo")
plane.firstMaterial?.diffuse.contents = spriteKitScene
plane.firstMaterial?.isDoubleSided = true
plane.firstMaterial?.diffuse.contentsTransform = SCNMatrix4Translate(SCNMatrix4MakeScale(1, -1, 1), 0, 1, 0)
let planeNode = SCNNode(geometry: plane)
planeNode.position = SCNVector3Make(objectAnchor.referenceObject.center.x, objectAnchor.referenceObject.center.y + 0.5, objectAnchor.referenceObject.center.z) //y was 0.25
node.addChildNode(planeNode)
}
return node
}
func session(_ session: ARSession, didFailWithError error: Error) {
// Present an error message to the user
}
func sessionWasInterrupted(_ session: ARSession) {
// Inform the user that the session has been interrupted, for example, by presenting an overlay
}
func sessionInterruptionEnded(_ session: ARSession) {
// Reset tracking and/or remove existing anchors if consistent tracking is required
}
}
An ARReferenceObject
has a name
variable of type String
which is simply:
A descriptive name for the reference object.
When your add an ARReferenceObject
to your Assets.xcassett
s folder you have the option to set the name (which does actually get set automatically):
As such you can use this property name to handle what to show depending on the ARReferenceObject
detected.
Personally, I would use the following delegate callback to add content although this is up to you:
func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor)
As such a working example might look something like the following:
//-------------------------
//MARK: - ARSCNViewDelegate
//-------------------------
extension ViewController: ARSCNViewDelegate{
func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
/*
Check To See Whether AN ARObject Anhcor Has Been Detected
Get The The Associated ARReferenceObject
Get The Name Of The ARReferenceObject
*/
guard let objectAnchor = anchor as? ARObjectAnchor else { return }
let detectedObject = objectAnchor.referenceObject
guard let detectedObjectName = detectedObject.name else { return }
//Get The Extent & Center Of The ARReferenceObject
let detectedObjectExtent = detectedObject.extent
let detectedObjecCenter = detectedObject.center
//Log The Data
print("""
An ARReferenceObject Named \(detectedObjectName) Has Been Detected
The Extent Of The Object Is \(detectedObjectExtent)
The Center Of The Object Is \(detectedObjecCenter)
""")
//Create A Different Scene For Each Detected Object
node.addChildNode(createSKSceneForReferenceObject(detectedObject: detectedObject))
}
/// Creates A Unique SKScene Based On A Detected ARReferenceObject
///
/// - Parameter detectedObject: ARReferenceObject
/// - Returns: SCNNode
func createSKSceneForReferenceObject(detectedObject: ARReferenceObject) -> SCNNode{
let plane = SCNPlane(width: CGFloat(detectedObject.extent.x * 1.0),
height: CGFloat(detectedObject.extent.y * 0.7))
plane.cornerRadius = plane.width / 8
guard let validName = detectedObject.name else { return SCNNode() }
let spriteKitScene = SKScene(fileNamed: validName)
plane.firstMaterial?.diffuse.contents = spriteKitScene
plane.firstMaterial?.isDoubleSided = true
plane.firstMaterial?.diffuse.contentsTransform = SCNMatrix4Translate(SCNMatrix4MakeScale(1, -1, 1), 0, 1, 0)
let planeNode = SCNNode(geometry: plane)
planeNode.position = SCNVector3Make(detectedObject.center.x, detectedObject.center.y + 0.5, detectedObject.center.z)
return planeNode
}
}
All the code is fully commented so it should make sense, and you will notice that I created a reusable function to generate different SKScenes
, which of course could be modified to add different content e.g. SCNScene's, SCNNodes etc.
I am using the name of the ARReferenceObject to load a scene of the same name, but you could use an if/else or switch statement depending on what you need.
Hope it helps... Hope it points you in the right direction.