0

I have slightly modified Apple's example about recognising images in ARKit 1.5 so that every time an image is recognised I show some text geometry in front of it.

Unfortunately I noticed that when pressing the reset button in the StatusView View, my text geometry doesn't get removed but only repositioned somewhere around the new center of the scene.

Instead if I press the back button (because I embedded the main view controller in a navigation controller in order to start the AR session by means of a button and not automatically as in the example), and start the session again, then everything disappears correctly.

Analysing the code I can affirm that both the reset button and going back and forth from the ViewController trigger the same function as follows

func resetTracking() {

    guard let referenceImages = loadImageReferences() else {
        fatalError("Missing expected asset catalog resources.")
    }

    let configuration = ARWorldTrackingConfiguration()
    configuration.detectionImages = referenceImages
    configuration.planeDetection = [.horizontal, .vertical]
    session.run(configuration, options: [.resetTracking, .removeExistingAnchors])

    statusViewController.scheduleMessage("Look around to detect images", inSeconds: 7.5, messageType: .contentPlacement)
}

As I mentioned, this function is called either in the ViewDidLoad method of the ViewController

override func viewDidLoad() {
    super.viewDidLoad()

    sceneView.delegate = self
    sceneView.session.delegate = self

    // debug scene to see feature points and world's origin ARSCNDebugOptions.showFeaturePoints,
    self.sceneView.debugOptions = [ARSCNDebugOptions.showWorldOrigin]

    // Hook up status view controller callback(s).
    statusViewController.restartExperienceHandler = { [unowned self] in
        self.restartExperience()
    }   
}

func restartExperience() {
    guard isRestartAvailable else { return }
    isRestartAvailable = false

    statusViewController.cancelAllScheduledMessages()

    resetTracking()

    // Disable restart for a while in order to give the session time to restart.
    DispatchQueue.main.asyncAfter(deadline: .now() + 5.0) {
        self.isRestartAvailable = true
    }
}

and also upon pressing the reset button in StatusViewController.

@IBAction private func restartExperience(_ sender: UIButton) {
    restartExperienceHandler()
}

var restartExperienceHandler: () -> Void = {}

for further reference, I create may text as follows

func createBillboardText(text: String, position: SCNNode, width: CGFloat, height: CGFloat){

    let newText = splitString(every: 10, string: text)
    let textGeometry = SCNText(string: newText, extrusionDepth: 1.0)

    textGeometry.firstMaterial!.diffuse.contents = UIColor.red
    let textNode = SCNNode(geometry: textGeometry)

    // Update object's pivot to its center
    // https://stackoverflow.com/questions/44828764/arkit-placing-an-scntext-at-a-particular-point-in-front-of-the-camera
    let (min, max) = textGeometry.boundingBox
    let dx = min.x + 0.5 * (max.x - min.x)
    let dy = min.y + 0.5 * (max.y - min.y)
    let dz = min.z + 0.5 * (max.z - min.z)
    textNode.pivot = SCNMatrix4MakeTranslation(dx, dy, dz)
    textNode.scale = SCNVector3(0.005, 0.005, 0.005)

    let plane = SCNPlane(width: width, height: height)
    let blueMaterial = SCNMaterial()
    blueMaterial.diffuse.contents = UIColor.blue
    blueMaterial.transparency = 0                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   
    plane.firstMaterial = blueMaterial
    let parentNode = SCNNode(geometry: plane) // this node will hold our text node

    let yFreeConstraint = SCNBillboardConstraint()
    yFreeConstraint.freeAxes = [.Y] // optionally
    parentNode.constraints = [yFreeConstraint] // apply the constraint to the parent node

    parentNode.addChildNode(textNode)

    position.addChildNode(parentNode)

    sceneView.autoenablesDefaultLighting = true

}

called in the func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) method as self.createBillboardText(text: message, position: node, width: referenceImage.physicalSize.width, height: referenceImage.physicalSize.height )

I also found this thread but I have no idea how to call that function in the scene.

MaX
  • 489
  • 7
  • 18

2 Answers2

2

I believe the way that you want to handle the removal of your SCNNodes is to call the following function in your resetFunction:

self.augmentedRealityView.scene.rootNode.enumerateChildNodes { (existingNode, _) in
    existingNode.removeFromParentNode()
}

Whereby augmentedRealityView refers to an ARSCNView.

You could also store specific nodes in an array of [SCNNode] and then loop through them to remove them e.g:

for addedNode in nodesAdded{

addedNode.removeFromParentNode()

}

Hope it helps...

BlackMirrorz
  • 7,217
  • 2
  • 20
  • 31
  • thx! I marked your answer because it helped me understand that I had to call that on the ARSCNView . The thread I linked in my Question had that same code but I didn't understand where to place it. The tip about storing specific nodes and removing them even answered to my next problem as well :) – MaX Apr 25 '18 at 07:14
2

If you want to remove all nodes use this:

self.sceneView.scene.rootNode.enumerateChildNodes { (node, stop) in
    node.removeFromParentNode()
}

Now if you want to remove a specific node use this:

self.sceneView.scene.rootNode.enumerateChildNodes { (node, stop) in
        if node.name == "textNode" {
            node.removeFromParentNode()
        }
}

These also works on SCNNode and not only ARSCNView which means you can loop through childNodes to a SCNNode and not only from the scenes rootNode

This is the code i use in a project. Here i first remove all childNodes to a node and then remove the SCNNode itself. Then i proceed with pausing the session, which will end it. I run the new session and finally reset the worldOrigin to the current camera position. As shapes can get the wrong angle if the startup angle of the app is off.

sceneView.scene.rootNode.enumerateHierarchy { (node, stop) in
       node.childNodes.forEach({
          $0.removeFromParentNode()
       })
       node.removeFromParentNode()
}
sceneView.session.pause()
sceneView.session.run(configuration, options: .resetTracking)
sceneView.session.setWorldOrigin(relativeTransform: matrix)
Vollan
  • 1,887
  • 11
  • 26
  • 1
    thank you for your help! Unfortunately the other colleague answered faster with the same content. But I upvoted nonetheless because the tip about removing a node by name is certainly useful for what I planned to do next! – MaX Apr 25 '18 at 07:15
  • 1
    Ok, Thank you and good luck with your project. If you want help with anything else, write here, i have gotten pretty good knowledge about ARKit :). – Vollan Apr 25 '18 at 07:19