ARKit CocoaPod — Portals

I believe the biggest missing piece from the ARKit developer ecosystem right now is reusable code. There are so many amazing pods in use for iOS development in general, but not many specifically for using with ARKit yet.

Here’s the CocoaPod I’ll be explaining in this post, please feel free to report issues, fork, and contribute!

SceneKit-PortalMask

When I started my career in Augmented Reality I was mostly working with marker based AR, using something similar to Vuforia. There was one trend that a lot of clients wanted, and I can see this being a trend going forward in ARKit. The idea is to mask everything outside of the tracking image itself, but allow you to look in through a hole where the image is.

Here’s two simple examples that can be achieved with this Pod, one with just a few lines of code:

Here’s a code sample to create something similar to the example on the left.

The Portal is just fed the physicalSize and it creates the mask around the object.

func renderer(_ renderer: SCNSceneRenderer, didAdd node: SCNNode, for anchor: ARAnchor) {
if let imageAnchor = anchor as? ARImageAnchor {
let nodeRotated = SCNNode()
// nodes on ARImageAnchors need to be rotated -90 degrees
nodeRotated.eulerAngles.x = -.pi / 2
let imageSize = imageAnchor.referenceImage.physicalSize
// the next 2 lines add the portal
let portal = PortalMask(frameSize: imageSize)
nodeRotated.addChildNode(portal)
// the next 5 lines add a cube inside the image area
let width = imageAnchor.referenceImage.physicalSize.width
let boxNode = SCNNode(geometry: SCNBox(width: width, height: width, length: width, chamferRadius: 0))
// push the box behind the marker
boxNode.position.z = -boxNode.width
nodeRotated.addChildNode(boxNode)
node.addChild(nodeRotated)
}
}

This can also be created without a tracking image and placed on any surface, anchor point, or otherwise location in the scene graph.

If you have a tracking image that is instead spherical there are two different ways you can do that. One is using PortalMask.tube(radius:) or PortalMask(radius:). The first of which creates a circular inside hole with a circular mask around it, the latter creates effectively a cuboid with a circular hole into it. The latter would be preferred for most use-cases as it uses much less vertices. There are other parameters on the function documented in-line here.

Here’s a simple example adding a circular portal in mid air with a few objects behind it creating a scene.

let holeNode = PortalMask(radius: 0.5)
holeNode.position = SCNVector3(0, 0, -2)
// Just for a nicer effect, I've added a border around the portal
let holeEdge = SCNNode(geometry:
SCNTube(innerRadius: 0.5, outerRadius: 0.55, height: 0.05)
)
holeEdge.eulerAngles.x = .pi / 2
// Add the nodes that make up your scene inside the portal
let insideChild = BeachScene()
self.sceneView.scene.rootNode.addChildNode(holeNode)
holeNode.addChildNode(holeEdge)
holeNode.addChildNode(insideChild)

The beach scene in this case is just 2 planes, a sphere and a light with one parent node inside the portal area.

And finally, I think the most useful initializer for the PortalMask class lets you create a portal using just a collection of CGPoints. This allows you to create portals of any shape and size, the best example I could think of creates a portal using points collected from surface detection, as in the video below:

I think in this video I set the location to the centre of the ARPlaneAnchor, don’t do that!

The function to initialize this portal is init(path: [CGPoint]), and then updating the geometry as I’ll demonstrate below.

In the code below, planeAnchor is of type ARPlaneAnchor and node is the respective root node for that anchor, taken from renderer(_:didAdd:for:)

let points = planeAnchor.geometry.boundaryVertices
.map { (point) -> CGPoint in
return
CGPoint(x: CGFloat(point.x), y: CGFloat(point.z))
}
let portalNode = PortalMask(path: points)
// The portal will be by default horizontal, to make it vertical
portalNode.eulerAngles.x = -.pi / 2
// add things inside the portal
portalNode.addChildNode(BeachScene())
// add the portal to the anchor node
node.addChildNode(portalNode)

The portal is then added to the wall that’s being scanned, we just need to update that using renderer(_:didUpdate:for:) as follows:

let updatedPoints = planeAnchor.geometry.boundaryVertices.map {
(point) -> CGPoint in
return
CGPoint(x: CGFloat(point.x), y: CGFloat(point.z))
}
portalNode.updateGeometry(with: updatedPoints)

Before doing this you just need to find the node of class PortalMask that is in the ARAnchor node.

You can do this by simply node.childNodes.first as? PortalMask, or looping through the children if there’s more than one, or assigning the anchor’s root node a name and looking it up in a hash map.

That covers most of what I’ve done so far with the repository, I’m hoping that other people will find it useful and I’ll continue to contribute to it when there’s any relevant updates to ARKit or any other ideas I’ve had. Please check out my GitHub for this and some other libraries I’m working on.

--

--

--

Excited about all things Augmented Reality! Developer Evangelist at Agora.io.

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

Multi-Tenant Architecture in React JS

TryHackMe Web Enumeration Write-up

Ember JS-Easiest way to create a web application

Is jQuery still relevant?

JavaScript library distribution

How to connect Meteor with Slack?

Type-Safe Next.js API Routes with Nextkit

Solving Node DNS issues and other things

Build React Native Reanimated 2 Switch from scratch

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Max Cobb

Max Cobb

Excited about all things Augmented Reality! Developer Evangelist at Agora.io.

More from Medium

Universal Links and SwiftUI Video Calls

Thirty Days of Metal — Day 19: Directional Shadows

Classifying Sounds with the SoundAnalysis framework and custom CoreML models in Swift

SwiftUI. How To pass Binding variable to PreviewProvider