SceneKit With Swift For Network Visualization

In my ongoing journey with learning Swift, I decided to experiment with SceneKit last night, which I had not previously used but have wanted to try since it became available for iOS. This was one of those evenings where you just feel like getting your feet wet with something fun that isn’t part of your current projects.

In modern network management applications 3D visualizations are often sought after for managing a large transport network. I wanted to create something that looked like a 3D rendering of an optical network fiber, with inner 3D tubes that represent different optical layers and signals within the fiber. Looking at SceneKit I saw SCNTube which seemed perfect for this.

I’ve put the project in GitHub if you want to jump to running it or if you want a quick start to rendering geometry with SceneKit.

To get started with building the 3D visualization, in Xcode create a new project with iOS>Application>Single View Application. Name it SceneTube.

SceneKit needs to be added to the project, so under the SceneTube target, then Build Phases, click the + under Link Binary With Libraries, type Scene and you should see the SceneKit.framework. Click Add to include it in the project.

We’ll change the UIView in the storyboard to be an instance of SCNView instead of the default UIView. In Main.storyboard, click on the View Controller and then the View. In the Identity Inspector, under Custom Class, change Class to be SCNView.

Let’s create a class to be the SCNScene and import SceneKit. Create a new Swift file and name the class LinkScene.

import SceneKit class LinkScene: SCNScene { }

The scene will be constructed in an init method, but we also need another init since SCNScene conforms to the NSCoder protocol, so you also need the required init method. Add the code below. If you don’t add the second init, xCode will flag that it is needed.

override init() { super.init() } required init(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") }

Rather than go through this in detail, I’ll jump to the definition of how I’ve used SCNTube to represent the nested channels in a fiber. Adding four SCNTube objects and corresponding geometry/nodes does the trick. Here is the resulting init method.

override init() { super.init() let tubeGeometry = SCNTube(innerRadius: 0.9, outerRadius: 1.0, height: 2.5) let tubeNode = SCNNode(geometry: tubeGeometry) tubeNode.position = SCNVector3(x: 0.0, y: 0.0, z: 0.0) tubeNode.name = "LC-Blue" tubeGeometry.firstMaterial?.diffuse.contents = UIColor.blueColor() let tubeGeometry2 = SCNTube(innerRadius: 0.0, outerRadius: 0.9, height: 3.0) let tubeNode2 = SCNNode(geometry: tubeGeometry2) tubeNode2.position = SCNVector3(x: 0.0, y: 0.25, z: 0.0) tubeNode2.name = "LC-Green" tubeGeometry2.firstMaterial?.diffuse.contents = UIColor.greenColor() let tubeGeometry3 = SCNTube(innerRadius: 0.0, outerRadius: 0.4, height: 3.7) let tubeNode3 = SCNNode(geometry: tubeGeometry3) tubeNode3.position = SCNVector3(x: 0.42, y: 0.8, z: 0.0) tubeNode3.name = "LC-Red-1" tubeGeometry3.firstMaterial?.diffuse.contents = UIColor.redColor() let tubeGeometry4 = SCNTube(innerRadius: 0.0, outerRadius: 0.4, height: 3.7) let tubeNode4 = SCNNode(geometry: tubeGeometry4) tubeNode4.position = SCNVector3(x: -0.42, y: 0.8, z: 0.0) tubeNode4.name = "LC-Red-2" tubeGeometry4.firstMaterial?.diffuse.contents = UIColor.redColor() self.rootNode.addChildNode(tubeNode) self.rootNode.addChildNode(tubeNode2) self.rootNode.addChildNode(tubeNode3) self.rootNode.addChildNode(tubeNode4) }

Since this was an exploration, there is nothing fancy about placing the tubes. I’ve defined the positions and sizes so that the desired rendering is achieved as we’ll see when running it. Names are defined for each SCNNode that contains an SCNTube so that we can display them when selected. One thing to note is that the tube is centered in its coordinate system by default, so to position it in the y direction so that the bottoms of the tubes start at the same location, you need to give longer ones a y position that is actually an offset for the y center of the tube, versus where the bottom of the tube will start.

In ViewController we have to add the LinkScene to the SCNView, and configure it for lighting, camera control, and handling of a tap for selection of the tubes representing signals in different optical layers. The following method is defined.

func setupScene() { let scnView = self.view as! SCNView let scene = LinkScene() scnView.scene = scene scnView.backgroundColor = UIColor.blackColor() scnView.autoenablesDefaultLighting = true scnView.allowsCameraControl = true let tapGesture = UITapGestureRecognizer(target: self, action: "sceneTapped:") let gestureRecognizers = NSMutableArray() gestureRecognizers.addObject(tapGesture) if let arr = scnView.gestureRecognizers { gestureRecognizers.addObjectsFromArray(arr) } scnView.gestureRecognizers = gestureRecognizers as [AnyObject] }

We’re defining the LinkScene and adding it to the view, setting the background color, and enabling default lighting and camera control. Enabling camera control is activating the ability to zoom/unzoom, move, and rotate the object. We’ve also added a UITapGestureRecognizer to handle user taps on the nodes, being sure to make sure that we preserve other gesture recognizers that might already exist, which indeed there are because the ability to zoom and pan around is already there with the camera control enabled.

Finally, the sceneTapped method needs to be added to handle taps, and to determine which node was selected, resulting in a console message indicating the selected node.

func sceneTapped(recognizer: UITapGestureRecognizer) { let scnView = self.view as! SCNView let location = recognizer.locationInView(scnView) let hits = scnView.hitTest(location, options: nil) if let tappedNode = hits?.first?.node { println("Node selected: \(tappedNode.name)") } }

If you now build and run, you should see the completed rendering. By rotating, unzooming, and moving the rendering you can view it like my screenshot below.

This is exactly what I was hoping to achieve with SceneKit. The next step for this demo would be to add some math to allow any number of layers and tubes per layer, to support the multilayer aspects of such a visualization.

That was my quick but fun introduction to SceneKit. I was able to see how a small amount of code and about an hour with SceneKit can create the start of what could become a powerful tool in a network management mobile app once more features and math were added.