SceneKit in Mountain Lion

Jonathan Blocksom's Headshot
Jonathan Blocksom

Special guest post by Jonathan Blocksom, director of the Big Nerd Ranch DC office, and Advanced iOS course co-creator.

OK, you've updated to Mountain Lion and the latest Xcode, now what? Time to play with SceneKit, the new 3D graphics API!

SceneKit is a high level 3D graphics on Mac OS X 10.8; it's been favorably reviewed and some folks are frankly a bit nuts about it. SceneKit creates a scene graph, a more abstract way of working with 3D shapes and rendering than OpenGL — sort of like dealing with web pages instead of strings. It's very well integrated with other Mac APIs and has some pretty neat features.

Like many new APIs, it's woefully documented; fortunately it doesn't seem to diverge much from 20 years or so of scenegraph development. Here at BNR we've been able to get up to speed on it pretty quickly, and now it's your turn.

Let's make a little app with a few 3D shapes that jump when you click on them.

Start by creating a new Cocoa project in Xcode, I called mine HelloShapes.

  • Add the SceneKit and QuartzCore frameworks.

  • Add a new Objective-C class that subclasses NSViewController and call it **HelloShapeViewController**.

  • Open the xib file and add an NSViewController object, changing the class to HelloShapeViewController.

  • Select the view of the main window and change its class to **SCNView**.

  • Connect the SCNView to the view outlet of the HelloShapeViewController object.

Now add a bit of code to make sure things are working. Go into HelloShapeViewController.m and add the import for SceneKit:

#import <SceneKit/SceneKit.h>

Now add an awakeFromNib method in the implementation:

- (void)awakeFromNib
{
    SCNView *sceneView = (SCNView *)self.view;
    sceneView.backgroundColor = [NSColor blueColor];
}

Build and run and you should get a pleasant blue background, just waiting for a 3D scene!

How much bluer could it get?  None more blue.

One of the selling points of SceneKit is that designers can build fancy 3D artwork with their tools of choice like Maya or 3D Studio Max. They export their files as Digital Asset Entities (.dae) files, also known as COLLADA. Then programmers embed the files in their apps and have their programs hook in to the parts of them. If you wanted to, at this point you could add a 3D scene and show it in the view, just using Interface Builder.

But our goal is to understand what's inside of SceneKit, so instead of dropping in 3D data files we're going to build our own from scratch. It's the way we roll around here.

First let's add a cube.

    // Create the scene and get the root
    sceneView.scene = [SCNScene scene];
    SCNNode *root = sceneView.scene.rootNode;

    // Create the cube geometry and node
    SCNBox *cubeGeom = [SCNBox boxWithWidth:1.0
                                     height:1.0
                                     length:1.0
                              chamferRadius:0.0];
    SCNNode *cubeNode = [SCNNode nodeWithGeometry:cubeGeom];
    [root addChildNode:cubeNode];

Can you see the cube? It's not much to look at yet.

First Cube!

Let's add a few more shapes and see what happens.

    // Add cylinder
    SCNCylinder *cylGeom = [SCNCylinder cylinderWithRadius:0.5 height:1.0];
    SCNNode *cylNode = [SCNNode nodeWithGeometry:cylGeom];
    cylNode.position = SCNVector3Make(1.5, 0.0, 0.0);
    [root addChildNode:cylNode];

    // Add sphere
    SCNSphere *sphereGeom = [SCNSphere sphereWithRadius:0.5];
    SCNNode *sphereNode = [SCNNode nodeWithGeometry:sphereGeom];
    sphereNode.position = SCNVector3Make(3.0, 0.0, 0.0);
    [root addChildNode:sphereNode];

Three Shapes

SceneKit is helpfully positioning the camera to include all three shapes in the shot so we can see their geometry a little better. Let's see if we can help it with some lighting:

    SCNLight *light = [SCNLight light];
    light.type = SCNLightTypeDirectional;
    root.light = light;

A bit stark but we can work with it. We're doing pretty well -- in about 25 lines of very simple code we've got a neat 3D scene:

Thre Shapes with Lighting

Now creating some shapes and lights is not that big a deal, scenegraph APIs have been able to do that for a long time. But SceneKit integrates with Core Animation, and that is a pretty big deal. Let's make these shapes appear from out of nowhere:

    CABasicAnimation *startAnim = [CABasicAnimation animationWithKeyPath:@"opacity"];
    startAnim.duration = 1.0;
    startAnim.fromValue = @0.0;
    startAnim.toValue = @1.0;
    [root addAnimation:startAnim forKey:@"fadeIn"];

Try this and you'll see the shapes fade in. And doesn't it feel good to use those Objective-C Literals to keep things nice and tidy?

Next up let's put some textures on the shapes to make them a bit more interesting:

Three Shapes with Lighting and Texturing

Time to take a step back for a bit. This code creates an object graph that looks a bit like this:

The scenegraph is a tree, and the root node is where all the other nodes descend from. Individual nodes are positioned relative to their parents, so you can build up hierarchical models and move the parts around without their parents, or you can move the parents and the other parts will come along. Think of a robot arm, move the base and the rest will move with it, or you could just move the elbow or wrist.

Nodes have geometry properties which define the shapes they represent. The geometry objects are shared; if you have a video game with a bunch of complicated spaceships you don't need to define separate 3D data for each one. This helps OpenGL cache the data and store the scene in less memory.

The geometry objects also have materials, and that's where we've set the texture. Materials have properties whose contents can be an NSColor, an NSImage, or a CALayer. Yes, a CALayer — that opens up all sorts of doors!

Let's finish up this demo by making the shapes jump when the user clicks on one. If you've ever tried to pick a 3D object in OpenGL from a mouse click you'll know how difficult this can be; SceneKit makes it easy. Set the controller as the next responder and add the following mouse handler:

    self.view.nextResponder = self;
}

- (void)mouseDown:(NSEvent *)theEvent
{
    NSPoint winp = [theEvent locationInWindow];
    NSPoint p = [self.view convertPoint:winp fromView:nil];

    CGPoint p2 = CGPointMake(p.x, p.y);
    NSArray *pts = [(SCNView *)self.view hitTest:p2 options:@{}];
    for (SCNHitTestResult *result in pts) {
        SCNNode *n = result.node;

        CAKeyframeAnimation *jumpAnim = [CAKeyframeAnimation animationWithKeyPath:@"position"];
        float x = n.position.x;
        jumpAnim.duration = 0.75;
        jumpAnim.values = @[
            [NSValue valueWithSCNVector3:SCNVector3Make(x, 0.0, 0.0)],
            [NSValue valueWithSCNVector3:SCNVector3Make(x, 1.5, 0.0)],
            [NSValue valueWithSCNVector3:SCNVector3Make(x, 0.0, 0.0)]
        ];
        jumpAnim.timingFunction =
            [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
        [n addAnimation:jumpAnim forKey:@"jump"];
    }
}

Run it and click on a shape!

This only scratches the surface of what SceneKit can do. Animated materials, textures from views and video, 3D text, and custom shaders are all parts of it. Here's some things you might try, just fork HelloShapes on Github and go nuts:

  • Set the SCNView allowsCameraControl property to true and zoom around with your mouse (note this swallows the events for the view controller's mouseDown handler)

  • Add some 3D text with the SCNText class

  • Put in an SCNFloor and see them reflect off of it

  • Group the shapes together and animate them all at once

  • Render a web page offscreen and put it on a shape

Have fun, and stay tuned for more about SceneKit and the other new SDKs in Mountain Lion!

(Got a hankering to learn more about OpenGL or Advanced iOS programming directly from the master himself? Join Jonathan in one of the courses he's teaching in September and October.)

Recent Comments

comments powered by Disqus