ARKit Pods — SCNPath


A lot of ARKit developers are using SceneKit for their go-to framework, it’s a great choice I believe, mostly due to its access to all the native features iOS developers are used to for all other applications. With SceneKit you have access to quite a few default geometry types, SCNBox, SCNSphere, SCNPyramid, and even SCNShape. These are great building blocks, but when you want to build things that are a little different then you have to go a fair bit deeper to build the geometry yourself. This is something I think all ARKit devs should at least be aware of, which is why I have demonstrated examples in a few of my previous Medium posts, starting with my first one:

Most iOS developers do not necessarily have much of a background in 3D geometry or graphics, and I don’t think they should need to have that to build awesome things!


So my latest open source framework is used to create paths that are essentially triangle strips that can wrap around a collection of points (array of SCNVector3) as in the GIF below.

SCNGeomtry.path(path: positionArray)

SCNPathNode(path: positionArray)

To create a path that starts at the world origin (or 1m below), goes forward and then right, all you need to do is create the class like this:

let pathNode = SCNPathNode(path: [
SCNVector3(0, -1, 0),
SCNVector3(0, -1, -1),
SCNVector3(1, -1, -1)
])

The path is by default 0.5m wide, and it has other properties that can be altered, all information for that can be found in the GitHub repository:


Ok, so let’s make the app I showcased in my tweet here:

I am going to make assumptions about readers’ abilities in iOS, but if there’s anything I say that makes no sense please message me and I’ll potentially update this post to make it clearer or point you in the right direction for reading materials.

First off, for this project we’ll need 2 pods, one is SCNPath, and the other is FocusNode. As I stress on the README for the FocusNode repo, it’s almost entirely code I took from Apple, I wanted it to be easily added to my projects (and in turn others’ too) because it’s a really nice looking UI.

Your podfile should look something like this:

project 'PathVisualiser.xcodeproj'
platform :ios, '12.0'
target 'PathVisualiser' do
use_frameworks!
  # Pods for PathVisualiser
pod 'FocusNode'
pod 'SCNPath'
end

After installing those pods open up your ARKit project workspace, remove the spaceship if you created the project that way and then we can start by adding the FocusNode! Please follow this :

and we should have an app that looks just like this:

Next we want to add a tap gesture. I chose to just use the tap gesture to get the current location of the FocusSquare if and only if the square is not initializing (attached to the screen), but if you wanted you could perform a hitTest where your finger hits the screen instead. These functions are either in ViewController or in an extension of it.

var hitPoints = [SCNVector3]()
func setupGestures() {
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
tapGesture.delegate = self
self
.view.addGestureRecognizer(tapGesture)
}
@IBAction func handleTap(_ gestureRecognizer: UITapGestureRecognizer) {
guard gestureRecognizer.state == .ended else {
return
}
if
self.focusSquare.state != .initializing {
print(self.focusSquare.position)
self.hitPoints.append(self.focusSquare.position)
}
}

Make sure setupGestures is called, either from your viewDidLoad or somewhere similar so that whenever you tap you should see a SCNVector3 printed and added to an array.

Next what’s needed is to actually create a SCNPathNode object and add these points to it whenever updated. To create our object, I’m going to add this to our ViewController:

let pathNode = SCNPathNode(path: [])

Add any materials directly to the self.pathNode, so that whenever the geometry is updated it will pass along the materials array too. For now the geometry only uses the first material in the array. If you try this example without a texture, it will default to a material with a blue diffuse — I set it up that way, I might change it to just the default white if people hate it though. But in the example here I’m going to apply one of two textures to the path randomly; one of which I have also set up to use the textureRepeats property, which is explained in the GitHub README.md.

And finally for this part all we need to do is update the pathNode.path every time a position is added to our hitPoints array. I’ve found the best way of doing things like this is adding a didSet to the variable, in this case to the hitPoints variable as such:

var hitPoints = [SCNVector3]() {
didSet {
self.pathNode.path = self.hitPoints
}
}

Now all we need to add a bit of occlusion, but don’t worry, this is almost as easy as using the SCNPathNode class.

We need to first render one ARSCNPlaneGeometry on-top of each vertical detected surface, and then have that write only the alpha channel to the depth buffer.

func renderer(
_ renderer: SCNSceneRenderer,
didAdd node: SCNNode, for anchor: ARAnchor
) {
if let planeAnchor = anchor as? ARPlaneAnchor,
planeAnchor.alignment == .vertical,
let geom = ARSCNPlaneGeometry(device: MTLCreateSystemDefaultDevice()!)
{
geom.update(from: planeAnchor.geometry)
geom.firstMaterial?.colorBufferWriteMask = .alpha
node.geometry = geom
}
}
func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
if let planeAnchor = anchor as? ARPlaneAnchor,
planeAnchor.alignment == .vertical,
let geom = node.geometry as? ARSCNPlaneGeometry
{
geom.update(from: planeAnchor.geometry)
}
}

I’ve created a Gist on GitHub for occlusions in ARKit too.

That’s all it takes for occlusion, If you want to see the walls being created, simply comment out the line where the firstMaterial.colorBufferWriteMask is being altered. Here’s examples of how our app should now be looking:

That’s just about it!

I hope you’ve enjoyed this tutorial of SCNPath, and will use it to make something awesome! There’s a few things I want to change with this Pod in the next few weeks, and will update this document if there’s any significant changes to its use.

Download this full example inside the same repository as the Pod:


If you have any suggestions for this Pod or ideas of other projects you’d like to see please either suggest updates or let me know what you think is missing in the world of ARKit or even SceneKit. My aim is to cut out the last remaining bits of complex mathematics from ARKit development so that all developers have every tool they need to make amazing things!

Find me on Twitter or LinkedIn and check out my GitHub for other public projects I’m working on or have been in the past.