Search code examples
swiftuikitscenekitarkit

How can I convert the code below to let me scan in multiple objects with different .sks files for each?


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

    }
}

Solution

  • 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.xcassetts folder you have the option to set the name (which does actually get set automatically):

    enter image description here

    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.