Monday, August 20, 2012

An Introduction to SceneKit

One of the things that surprises most people when they first start graphics programming with OpenGL on the Mac or OpenGL ES on iOS is that, until very recently, there wasn't any Apple-provided libraries for loading or managing objects or scenes. You had to roll your own code for loading objects, doing skeletal animation, and managing all your objects.

In iOS 5 we got GLKit, which added a lot of higher-level functionality missing from OpenGL ES. It notably does not include object loading or scene graph management, two of the most fundamental needs of 3D programs.

Then, in one of the early Mountain Lion previews, something called SceneKit showed up. I'd been hearing bits and pieces about SceneKit before it was released, but had honestly assumed it was going to show up on iOS. The documentation for SceneKit was (and still is) pretty sparse, but the functionality looks really promising.

Unfortunately, as of right now, SceneKit is a Mac-only technology. I've seen been no evidence yet that SceneKit will be included in iOS 6. It's still possible, but I'm not holding my breath. If not, well… fingers crossed for iOS 7.

Even as a Mac-only technology, SceneKit is a very cool framework. Let's take a look at what it does and how it works.

SceneKit, Conceptually

Before we start using SceneKit, let's talk a little about what SceneKit is and does. Conceptually speaking, SceneKit sits above OpenGL, but below the higher level frameworks like AppKit. SceneKit views are fully layer-aware, so the view can be animated using Core Animation. Not only can the view itself be animated using Core Animation, you can actually use Core Animation to animate the contents of the view.

SceneKit is primarily what's known as a scene graph management library. It manages one or more 3D objects plus lights and camera. These items together, comprise a "scene" that can be manipulated and displayed on screen. Although, some understanding of graphics programming is beneficial when using SceneKit, it has a much smaller learning curve and, by using a paradigm that's familiar to most people (a movie-set like scene with a camera, lights, and objects), it's conceptually much easier.

SceneKit provides some functionality for creating scenes from scratch right in your code, but is primarily designed for working with 3D scene data exported from an external application like Maya, 3DS Max, Modo, LightWave, or Blender. SceneKit uses the Collada file format, which is supported by most (if not all) major 3D programs (though with some caveats I'll discuss later). The Collada file format doesn't support just 3D objects, it can store information about an entire 3D scene including lights and cameras as well as armatures and animation data for both simple object animation and complex skeletal animation.

SceneKit is very well integrated with AppKit. As you'll see a little later, projection (how you determine if a mouse click or tap touches a 3D object in your scene) is actually handled using the standard hit testing mechanism that's already in NSView.

SceneKit is perfectly capable of rendering most scenes for you without any need to interact with the OpenGL API or write shader code. If you need more flexibility, however, SceneKit also offers the ability to let you render the scene, or any individual component of the scene, using a render delegate. This lets you get up and running fast, but also gives you the full flexibility of OpenGL should you need it. Render delegates are beyond the scope of this introductory article, but the very presence of such a mechanism shows how much thought went into the engineering of this framework.

The bottom line is that SceneKit, makes a lot of hard things really easy. But, there are a lot of undocumented nooks and crannies in SceneKit right now. I'll look at a few of those today and hopefully get you to a place where you can start exploring the rest of them.

Setup to Play Along at Home

If you want to use SceneKit, you're going to need to be using Xcode 4.4 or 4.5 and, of course, you need to be on Mountain Lion. If you feel like playing along at home, go ahead and open up Xcode. If you'd rather just download the code, you can get it right here. If you're not going to play along at home, feel free to skip ahead to the section called Setting the Scene Background Color.

In Xcode, create a new OS X Application project in Xcode by pressing ⌘⇧N and selecting the Cocoa Application project template.

Screen Shot 2012 08 17 at 11 55 02 AM

You can call your project whatever you want. I'm calling mine SceneKitFun to keep with the naming convention I started in Beginning iPhone Development. The only setting that matters here is making sure Use Automatic Reference Counting is checked. All the code I post here will be using ARC. I'm using the prefix MCP in my code, which is our internal MartianCraft class prefix. If you use a different prefix (and you should), your filenames will be slightly different than mine.

Screen Shot 2012 08 17 at 11 57 07 AM

After your project has been created, add the SceneKit framework to your project. SceneKit is not included in Xcode projects by default, and we need to use its functionality. You'll also need to add the QuartzCore framework, as SceneKit relies on several Core Animation data structures and macros.

Wiring up the Scene View

Now, we need to create a SceneKit view. SCNView, the class that implements a SceneKit view does not have a delegate, so to add our custom functionality, we're going to subclass SCNView. Alternatively, we could use a stock SCNView and set its properties from another class, such as our app delegate, but I find my code tends to be cleaner with subclassing in cases like this.

Create a new class in your project. You want to use the OS X Cocoa template called Objective-C Class.

Screen Shot 2012 08 17 at 12 26 45 PM

Call the class XXXSceneView (where XXX is the three letter prefix you want to use), and make it a subclass of SCNView.

Screen Shot 2012 08 17 at 1 07 24 PM

Next, click MainMenu.xib to open Xcode's interface builder. From the Object Library in the lower right pane (press ^⌥⌘3 to bring it up if you don't see it), Drag a Scene View object to your application's window, then resize it to take up the full window. Xcode's auto-constraints should do the right thing automatically and make the new view resize with the window, so we shouldn't need to do anything else in terms of layout.

Press ⌥⌘3 to bring up the Identity Inspector and set the class of this new view to XXXSceneView.

Single-click XXXSceneView.h to open the header up in the editor. Right now, our code won't compile because the compiler won't know where to find SCNView. Insert the following #import statement right after the existing import to tell it:

#import <SceneKit/SceneKit.h>


Now you can compile your app. It won't do much, but it will compile and run and show you an all-white window.

Setting the Scene Background Color

Let's make sure everything is wired up correctly. We can do that by implementing awakeFromNib in our scene view class and setting the scene's backgroundColor property. Setting this is exactly the same as clearing the framebuffer to a specific color in OpenGL. It will change the color of the view "behind" the objects in the scene. Essentially, this will the set the color everywhere in the view where there's no 3D object visible. Since we haven't given the view a scene to display, this will change the entire view to that color for now. This will tell us if we have everything set up correctly. If we set the background color and the window is still white, we know something's gone awry.

In XXXSceneView.m, add the following method after the @implementation line to set the view's color. I'm using a nice neutral gray, but you can use whatever color gives you a warm fuzzy.

-(void)awakeFromNib
{
self.backgroundColor = [NSColor grayColor];
}





If you run the app now and everything is wired up correctly, the app window should now be gray (or some other color) rather than white. If it is, we're ready to start building.

Building a Simple Scene

As I stated earlier, SceneKit is primarily designed for loading 3D scene data from other programs. It does have the ability to construct a scene programmatically, though. Let's construct a simple scene in code, which will introduce you to the basic tools and terminology of SceneKit.

The most basic useful SceneKit scene consists of a camera, a light, and an object. Without those basic components, there's not really much to see. You need an object to look at, a camera to look with, and a light so the object can be seen.

We'll start out by creating an empty SCNScene object. Once we have that object, in order to add items to a scene, we simply create instances of something called a "node". A node, represented by the class SCNNode, is designed to hold the different types of objects that can go into a scene. Nodes are hierarchical, so every node can contain child nodes and can be added as a child of any other node.

Every scene has a root node, imaginatively called rootNode, which is where you add the top level items of your scene. By creating nodes, assigning a 3D object, light, or camera to that node, and then adding the node as a child of rootNode or one of rootNode's children, we add objects to our scene. That's pretty straightforward, right?

For our simple example, we just need to add the objects to the scene once. Once they're added, they'll stay in the scene until removed. That means we can go ahead and add the code to create the scene right to the awakeFromNib method, which will build our scene as soon as our nib is loaded.

Our first step is to create an empty scene and assign it to our view's scene property.

// Create an empty scene
SCNScene *scene = [SCNScene scene];
self.scene = scene;


Next, let's add a camera. We have to create the camera and set some properties on it, create a node to hold the camera, assign the camera to that node, and then add the node to the scene. That looks something like this:

// Create a camera
SCNCamera *camera = [SCNCamera camera];
camera.xFov = 45; // Degrees, not radians
camera.yFov = 45;
SCNNode *cameraNode = [SCNNode node];
cameraNode.camera = camera;
cameraNode.position = SCNVector3Make(0, 0, 30);
[scene.rootNode addChildNode:cameraNode];


I've given the camera a field of view of 45° in both directions. For a wide-angle effect, you could increase the field of view by providing a larger number. For a telephoto effect, you could decrease the values. Notice that the FOV values need to be specified in degrees rather than radians unlike most other things in SceneKit or GLKit. I've also assigned the camera node a position 30 units back from the origin, so when later I add an object at the origin, the object will be in front of the camera and, therefore, visible.

If you look at SCNCamera, you'll notice that its properties fairly closely mimic those that we'd use to set up OpenGL viewport. That's not a coincidence. A "camera" in SceneKit isn't all that much more than a GL viewport along with a position and transformation in 3D space. If the camera is at position {0,0,30}, for example, then every node in the scene will be shifted -30 along the Z axis before it is rendered to account for the camera's position. The camera can also be rotated, meaning we can move the camera around the virtual world very much like you could move a physical camera around the real world. Because of this, the scene will always be rendered from the perspective of the camera.

Once we have a camera, we need something for the camera to look at. Let's use one of the built-in primitives that SceneKit provides. SceneKit has built-in classes that represent a cylinder, cube, sphere, cone, capsule, plane, tube, torus, pyramid, and text. Each of these primitives is a subclass of the class SCNGeometry, which represents any kind of renderable object. Each of these items have different properties needed to instantiate them, but they all work fundamentally the same way. You create them, set any necessary properties, assign them to a node, and then add the node to the scene. Here's the basic process using a torus:

// Create a torus
SCNTorus *torus = [SCNTorus torusWithRingRadius:8 pipeRadius:3];
SCNNode *torusNode = [SCNNode nodeWithGeometry:torus];
CATransform3D rot = CATransform3DMakeRotation(MCP_DEGREES_TO_RADIANS(45), 1, 0, 0);
torusNode.transform = rot;
[scene.rootNode addChildNode:torusNode];


The only new thing here is that we've rotated the torus forty-five degrees along the X axis. We've done this by creating a CATransform3D and assigning it to the node's transform property. A CATransformCD is a 4x4 transformation matrix similar to GLKMatrix4. It can hold information about where an object is located in 3D space as well as how it's rotated, scaled, or otherwise transformed.

Now we have an object to look at, but we still need a light in order for it to draw correctly. If you run the program now, the torus will be drawn, but it will be drawn in a completely flat white, which is probably not the behavior you want.

As you might remember from my OpenGL ES From the Ground Up blog post on light, the most basic components of light in 3D graphics are the diffuse, ambient, and specular components. Here's the description of the various components of light from that post:

The following illustration shows what effect each component has on the resulting image.

component.jpg


Source image from Wikimedia Commons, modified as allowed by license.


The specular component defines direct light that is very direct and focused and which tends to reflect back to the viewer such that it forms a "hot spot" or shine on the object. The size of the spot can vary based on a number of factors, but if you see an area of more pronounced light such as the two white dots on the yellow sphere above, that's generally going to be coming from the specular component of one or more lights.

The diffuse component defines flat, fairly even directional light that shines on the side of objects that faces the light source.

Ambient light is light with no apparent source. It is light that has bounced off of so many things that its original source can't be identified. The ambient component defines light that shines evenly off of all sides of all objects in the scene.


SceneKit's lighting options are considerably more advanced than what we were discussing in that blog post, but those descriptions are still fairly accurate. In SceneKit, specular highlights comes not from settings on the light, but rather from materials, which we'll look at later, but we can get the diffuse and ambient components just by adding some lights to our scene.

In SceneKit, lights can be one of several types. One type of light provides ambient light to the entire scene. Several other types can be used to provide different forms of direct light. These direct lights are used to calculate the diffuse and specular components of an object.

First, let's add a single ambient light to the scene by adding a light node set to be of the ambient type. Generally speaking, you should never have more than one ambient light node in a scene, though for more dramatic lighting effects, you might choose not to have an ambient light in your scene. The location and transformation of an ambient light doesn't matter because ambient light is applied evenly to all objects. Here's how we add an ambient light node:

// Create ambient light
SCNLight *ambientLight = [SCNLight light];
SCNNode *ambientLightNode = [SCNNode node];
ambientLight.type = SCNLightTypeAmbient;
ambientLight.color = [NSColor colorWithDeviceWhite:0.1 alpha:1.0];
ambientLightNode.light = ambientLight;
[scene.rootNode addChildNode:ambientLightNode];


Next, we'll add a direct light node like so:

// Create a diffuse light
SCNLight *diffuseLight = [SCNLight light];
SCNNode *diffuseLightNode = [SCNNode node];
diffuseLight.type = SCNLightTypeOmni;
diffuseLightNode.light = diffuseLight;
diffuseLightNode.position = SCNVector3Make(-30, 30, 50);
[scene.rootNode addChildNode:diffuseLightNode];


For our direct light, I've specified a type of SCNLightTypeOmni. This creates a light at a specific point in 3D space that shines out in all directions equally. Basically, it's like a bare light bulb floating somewhere in the scene. There are a couple other types we could have selected, including SCNLightTypeDirectional, which mimics a distant directional light source like the sun, and SCNLightTypeSpot which creates a light that shines in a specific direction from a specific point in space. Unlike ambient light, you often will want to add multiple direct lights to your scene to create different effects, though be aware that every light you add to the scene adds processing overhead.

At this point, we have a scene. It's nothing fancy, but it should look okay. Go ahead and run the app, and you should get something like this:

Screen Shot 2012 08 17 at 3 21 41 PM

That's pretty good for about thirty lines of code, right? But there's so much more to SceneKit. Let's keep going.

Animating with Core Animation

A little later on, we'll look at using animations imported from a 3D program, but one thing that's really cool about SceneKit is that it's fully integrated with Core Animation. I'm not just talking about being able to animate the SceneKit view itself, but being able to actually animate the contents of the view. So, for example, we can rotate our torus just by creating a CAAnimation. Add the following code to your awakeFromNib method if you want to animate your torus.

CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"transform"];
animation.values = [NSArray arrayWithObjects:
[NSValue valueWithCATransform3D:CATransform3DRotate(torusNode.transform, 0 * M_PI / 2, 1.f, 0.5f, 0.f)],
[NSValue valueWithCATransform3D:CATransform3DRotate(torusNode.transform, 1 * M_PI / 2, 1.f, 0.5f, 0.f)],
[NSValue valueWithCATransform3D:CATransform3DRotate(torusNode.transform, 2 * M_PI / 2, 1.f, 0.5f, 0.f)],
[NSValue valueWithCATransform3D:CATransform3DRotate(torusNode.transform, 3 * M_PI / 2, 1.f, 0.5f, 0.f)],
[NSValue valueWithCATransform3D:CATransform3DRotate(torusNode.transform, 4 * M_PI / 2, 1.f, 0.5f, 0.f)],
nil
]
;
animation.duration = 3.f;
animation.repeatCount = HUGE_VALF;



[torusNode addAnimation:animation forKey:@"transform"];


Yep, that's it. If you've ever worked with Core Animation, everything there should look pretty familiar. The only difference from what you'd be used to with Core Animation is that we're adding the animation to a node instead of to a layer.

Of course, you still have the option of setting up a CVDisplayLink or NSTimer and doing your animations manually if you're a glutton for punishment and want to risk not getting the full benefit of hardware acceleration. SceneKit doesn't take anything away, it just gives you easier ways to do stuff.

Scene Graph and Parenting

Earlier, I mentioned that SceneKit nodes can contain other nodes. This is actually a really powerful mechanism because children inherit there parent's position and transformations. If you move a node, all of its child nodes will move along with it. If the child was 5 units away before the move, it will be 5 units away after the move. If you rotate an object, all of its children will rotate also. But they won't rotate around their own centers, they'll rotate around their parents center.

It probably makes sense to talk a little about "spaces" in graphics programming. Objects can be transformed in different "spaces". The two most common spaces are "local space" and "world space".

Normally, when you rotate an object, that object rotates around its own origin. We refer to this as an "object space" (or sometimes "local space") rotation. When we talk about "world space", we're talking about the actual position of the object in our 3D world. We moved our camera 30 units, so in world space, the camera is now located at {0, 0, 30}. In object space, however, the camera is still located at {0,0,0}.

Spaces matter when transforming an object. Transformations need to be applied to the right node to get the result you want. If you apply a rotation in object space, the object rotates around its own origin. If you apply a rotation in world space (which you could do by transforming the root node, for example), the object would orbit around the world's origin. It's also possible to apply a rotation to one object in another object's space, and that's what happens with scene inheritance.

When an object inherits a transformation from a parent or other ancestor in the scene graph, that transformation happens in the ancestor's object space, not the in the child's. If that's confusing, a small code example may make it clear.

Let's add a child to the torus and see how the torus' rotation affects the child. That should help you understand how node inheritance affects object transformations. Let's create a text object. I'm choosing text because traditionally, programmatically creating 3D text is kind of hard, and SceneKit makes it extremely easy.

Before we add any code, we need to make one change to the existing code. We need to move the camera back a little further so we'll be able to see the child object as it rotates with the parent. Change this line of code:

cameraNode.position = SCNVector3Make(0, 0, 30);


to this:

cameraNode.position = SCNVector3Make(0, 0, 50);


Now, add the following code to create a text object as a child of the torus rather than a child of the root node:

// Give the Torus a child
SCNText *text = [SCNText textWithString:@"Whee!" extrusionDepth:4.f];
SCNNode *textNode = [SCNNode nodeWithGeometry:text];
textNode.position = SCNVector3Make(-1, 5, 0);
textNode.transform = CATransform3DScale(textNode.transform, .1f, .1f, .1f);
[torusNode addChildNode:textNode];


Run this now and you'll see that the text object rotates, but it orbits the parent rather than rotating around its own center. That's because the rotation is happening in its parent's space.

Screen Shot 2012 08 18 at 9 33 24 AM

Just because the node inherits transformations from the parent doesn't prevent it from being transformed on its own. We could, for example, also rotate the text in its own local space, like so:

// Animate the text
CAKeyframeAnimation *textAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform"];
textAnimation.values = [NSArray arrayWithObjects:
[NSValue valueWithCATransform3D:CATransform3DRotate(textNode.transform, 0 * M_PI / 2, 0.f, 0.5f, 1.f)],
[NSValue valueWithCATransform3D:CATransform3DRotate(textNode.transform, 1 * M_PI / 2, 0.f, 0.5f, 1.f)],
[NSValue valueWithCATransform3D:CATransform3DRotate(textNode.transform, 2 * M_PI / 2, 0.f, 0.5f, 1.f)],
[NSValue valueWithCATransform3D:CATransform3DRotate(textNode.transform, 3 * M_PI / 2, 0.f, 0.5f, 1.f)],
[NSValue valueWithCATransform3D:CATransform3DRotate(textNode.transform, 4 * M_PI / 2, 0.f, 0.5f, 1.f)],
nil
]
;
textAnimation.duration = 3.f;
textAnimation.repeatCount = HUGE_VALF;



[textNode addAnimation:textAnimation forKey:@"transform"];


With that code added, the text will orbit around the parent and also rotate around its own origin.

Parenting is an extremely important and and powerful concept. It allows you to combine multiple objects that behave as components of a whole. Each object is capable of moving independent of its parent, but also inherits the movements of the parents. Without this structure, complex skeletal animation would be all but impossible. Think about this: when you move your upper arm, your lower arm, hand, and fingers all go along for the ride. In 3D terms, your lower arm and fingers inherit the transformations of your upper arm in your upper arm's object space. But, you can still bend your lower arm and fingers independently, however. In 3D terms, the lower arm and fingers would be transforming in their own object space in addition to inheriting the parent's transformation.

Materials

If you've ever worked with a 3D program, you're probably aware that most of them have extensive options for defining how an object looks. They allow creating "materials" using images, colors, and procedural textures to define not just the diffuse qualities of the object's surface, but also to define the specular, transparency, reflective, and emissive components (emissive components are used to simulate materials that emit light, like LED displays). These programs also allow you to affect the opacity of an object and to use something called a normal map to give the material greater detail than allowed by the number of vertices in the underlying object.

SceneKit has a lot of this same material functionality built right into it. It doesn't have the ability to define procedural textures because those tend to be computationally expensive and SceneKit is primarily designed as a realtime display technology. SceneKit does have the ability to use either images or flat colors to define several different properties of a material.

To create a material, you create an instance of SCNMaterial and set some properties on it. Let's start with a simple example. We'll create a shiny plastic material for our torus. Add the following code to awakeFromNib to do that:

// Give the Torus a shiny blue material
SCNMaterial *material = [SCNMaterial material];
material.diffuse.contents = [NSColor blueColor];
material.specular.contents = [NSColor whiteColor];
material.shininess = 1.0;
torus.materials = @[material];


If you run the app now, you'll get a blue torus with a white hotspot rather than the flat gray torus we had before. Since we defined a blue diffuse and a white specular, gave the material some shininess, and have a direct light in our scene, we get this blue plastic look:

Screen Shot 2012 08 17 at 3 51 19 PM

Instead of assigning colors to the diffuse and specular contents, we could also assign images. SceneKit will automatically map the image to the surface based on the UV values of the object's geometry. UVs, which define how an image maps onto a 3D object, are created automatically for SceneKit primitives and will be imported for objects created in an external 3D program.

Let's use an image for our diffuse value instead of a flat color. We'll leave the specular component defines as a simple color (white). I created this simple image in Photoshop using the Add Noise filter. You can download it or you can use any other image you want for the next code example.

Noise

Once you've added the image to your project, replace the existing blue material code with the following version:

// Give the Torus an image-based diffuse
SCNMaterial *material = [SCNMaterial material];
NSImage *diffuseImage = [NSImage imageNamed:@"noise"];
material.diffuse.contents = diffuseImage;
material.specular.contents = [NSColor whiteColor];
material.shininess = 1.0;
torus.materials = @[material];


The new version will look like this (assuming you've used the same image):

Screen Shot 2012 08 17 at 4 04 33 PM

All of the material properties that are defined as an SCNMaterialProperty can be set to use either an image or a color. There are also a number of properties on the SCNMaterialProperty that can be set to modify how the material is drawn. These will look familiar to anyone who has worked with OpenGL textures before.

For example, we could tell SceneKit to use trilinear filter for scaling the image. This will improve the appearance of the texture when SceneKit has to increased or decrease the texture's size:

material.diffuse.minificationFilter = SCNLinearFiltering;
material.diffuse.magnificationFilter = SCNLinearFiltering;
material.diffuse.mipFilter = SCNLinearFiltering;


You won't notice much difference in our current app if you add this code, but it's worth changing to a trilinear filter if you're using an image-based material. There is a tiny bit of additional overhead associated with doing this, but it will make images look much better when they are scaled up or down when mapped to an object. Note, you have to specify properties for each material independently.

Let's play around with a little more cool stuff in the materials settings.

Normal Maps

Normal mapping is a really cool way to make models look better without increasing their vertex count, which is a really good thing for real-time graphics where performance considerations really matter. Normal maps are typically generated out of a 3D program using a higher resolution version of the model you're displaying. Basically, a normal map uses the pixels of an image to store normal data from the higher resolution object. Normals, as you may recall, are used for doing lighting calculations. Normal maps allow a low resolution model to use the normal data from a higher resolution image in their lighting calculations, so they render as if they were much more complex models than they actually are. Because modern graphics hardware has efficient, accelerated methods for dealing with bitmap image data, this normal information can be fed to the shaders with a fairly nominal amount of overhead compared with increasing the vertex count.

That may sound like gibberish, but it'll make more sense in a moment. I've created another image. This is a normal map based on that noise image we used earlier for the diffuse texture. If you want to create your own normal maps, be aware that there are multiple types of normal maps. Make sure you're creating a tangent space normal map for SceneKit (if it's purple, it's probably correct). This is the default in most 3D packages. You can download my normal map here:

Noise normal

This particular normal map defines the normals of a rough, craggy surface. Creating this surface in geometry would require an extraordinarily high vertex count. We use this image exactly the same way we used the diffuse image earlier, except we assign it to the normal property rather than the diffuse:

NSImage *normalImage = [NSImage imageNamed:@"noise_normal"];
material.normal.contents = normalImage;


That will give us a torus that no longer has a nice smooth surface, but instead looks like it's very rough. (I've removed the diffuse texture to make the effect easier to see in the following screenshot):

Screen Shot 2012 08 18 at 9 40 11 AM

But, wait! There's more.

Transparency

We can also use an image to control which parts of an object are fully transparent, which are partially transparent, and which are opaque. Here's an image I created that has black and white stripes. The black strips have an alpha value of 1.0 (fully opaque), the gray stripes have an alpha value of 0.5 (partially transparent), and the white stripes have an alpha of 0.0 (fully transparent). Note, SceneKit will use the alpha component of each pixel and not the saturation (how dark the pixel is), I just made the transparent ones white and the opaque ones black so it would be easier to see what was going on.

Stripes

If we assign this image to the material's transparent property, this image will then control which parts of the image are opaque or transparent. That code would look like this:

NSImage *opacityImage = [NSImage imageNamed:@"stripes"];
material.transparent.contents = opacityImage;


and the result of that code would be this:

Screen Shot 2012 08 18 at 9 59 19 AM Similarly, you can also use an image to make parts of a model look like they're emitting light. The light emitted by this property will not affect other objects in the scene, but it can be used to create interesting science-fictioney looking illuminated displays. The process is exactly the same as what we did above.

NSImage *emissionImage = [NSImage imageNamed:@"h_stripes"];
material.emission.contents = emissionImage;


The white stripes you see in the following image are areas that are emitting light based on a similar image to the one we used for controlling transparency:

Screen Shot 2012 08 19 at 11 54 48 AM

Environment Maps and Reflection

Creating shiny metallic objects can also be done with SceneKit, and it's relatively trivial. The trick to creating reflective objects is having something for the object to reflect. In the real world, shiny metal objects will have the sky and clouds and all the various things around them in the world to reflect back to the viewer's eye. In our virtual world, we usually don't have all that stuff defined, and even if we did, it would be too computationally costly to do physically accurate reflection calculations in real time using modern computers.

Instead, we can create something called a "cube map" (or "environment map") which is simply a collection of six images: front, back, left, right, top bottom. These six images define a cube surrounding the viewer. That cube won't (in this case) actually be shown to the user, but it will be use for calculating the reflections off a reflective surface. Calculating reflections from a cube map is much faster than calculating real reflections can be done in real time. If you select a cube map that is appropriate to your scene, the results can be fairly convincing.

You can find tutorials for creating cube maps using photographs or by generating them from a 3D program and there are many license-free cube maps available if you search the internet. For simplicity, I'm using the environment map taken from Apple's SceneKit Material Editor sample code in this example.

To create a cube map with SceneKit, we simply build an NSArray containing the six images in the following order: right, left, top, bottom, back front. Once you have the cube map array, simply assign it to the contents of the material's reflective property, and you're done. Here's how to create a shiny metallic material this way:

// Give the Torus a metallic surface
SCNMaterial *material = [SCNMaterial material];


material.diffuse.contents = [NSColor blackColor];


NSArray *mapArray = @[
[NSImage imageNamed:@"right.jpg"],
[NSImage imageNamed:@"left.jpg"],
[NSImage imageNamed:@"top.jpg"],
[NSImage imageNamed:@"bottom.jpg"],
[NSImage imageNamed:@"back.jpg"],
[NSImage imageNamed:@"front.jpg"]
]
;
material.reflective.contents = mapArray;
material.specular.contents = [NSColor whiteColor];
material.shininess = 100.0;
torus.materials = @[material];


Run this, and your torus will be metallic looking. The torus is "reflecting" the contents of those six images back to our eye to create the illusion of a metallic surface.

Screen Shot 2012 08 18 at 10 19 04 AM

You can combine the different material properties to create all sorts of new appearances. If we combine the reflective material with the normal map from earlier, for example, we'd get something like this:

Screen Shot 2012 08 18 at 10 22 09 AM

Hit Testing

One of the common problems in 3D graphics is determining if a mouse click or tap is on a specific 3D object. To do this, you have to project an imaginary line (a vector) into the 3D world using the camera's projection matrix (the projection matrix is used to calculate how the world is displayed in two dimensions) and then do collision detection with the various objects in the world. Doing that with good performance is not always trivial. Fortunately, SceneKit does all the heavy lifting for us and it's integrated with NSView's existing hit testing mechanism.

Let's figure out if a click is on the torus. Let's make it even more fun. If there's a click on the torus, let's animate the torus' material so it glows red for a moment. Yes, Virginia, you can use Core Animation to animate material properties too.

Add the following method to XXXSceneView:

- (void)mouseDown:(NSEvent *)event
{
NSPoint mouseLocation = [self convertPoint:[event locationInWindow] fromView:nil];
NSArray *hits = [self hitTest:mouseLocation options:nil];

if ([hits count] > 0)
{
SCNHitTestResult *hit = hits[0];
SCNMaterial *material = [hit.node.geometry.materials objectAtIndex:0];

CABasicAnimation *highlightAnimation = [CABasicAnimation animationWithKeyPath:@"contents"];
highlightAnimation.toValue = [NSColor redColor];
highlightAnimation.fromValue = [NSColor blackColor];
highlightAnimation.repeatCount = 1;
highlightAnimation.autoreverses = YES;
highlightAnimation.duration = 0.35;
highlightAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];

[material.emission addAnimation:highlightAnimation forKey:@"highlight"];
}


[super mouseDown:event];
}


All we have to do is get and convert to local the mouse down coordinates. Then, we pass that to hitTest:options:, which will return an array of SCNHitTestResult objects ordered based on the their proximity to the active camera. So, the first object in the array, if there are any objects in the array, will be the nearest object underneath the the cursor. If the array is empty, nothing was clicked.

In our case, there is only one object, but the technique would be the same with a more complex scene. We just grab the node from the SCNHitTestResult and have our way with it. In this case, I've animated its material to glow briefly red when it's clicked.

Loading and Using Scenes from Files

That should give you a pretty good idea of the basics of using SceneKit to build and manipulate scenes programmatically. Where SceneKit really shines, however, is when you use it to load more complex scenes created in an external 3D program.

Unfortunately, SceneKit only supports one file format - Collada - and there are some limitations in its support of that file format. It appears, for example, to only support a single animation (or "take") per file, and it doesn't support the most recent version of Collada, 1.5. Collada has the ability to support multiple animations in a file, but I haven't been able to get SceneKit to recognize more than one. Apple's own SceneKit Animations sample code uses separate Collada files for each individual animation, so my suspicion is that this is a limitation of SceneKit, though it's not document and there could be something else going on.

SceneKit also seems to be a little picky about the Collada files it will load. Loading scenes with no animations worked fine for me using scenes exported from several different applications, but once I started adding animations, SceneKit got very picky and refused to load files not exported from Maya. From looking at the metadata in the example Collada files included in the SceneKit source code, it appears Apple used Maya to create their own example files. Not only that, used a custom exporter, not the built-in AutoDesk Collada exporter. It does not appear that Apple has made this exporter public, but files with a single animation exported from Maya with the default exporter appear to work okay.

I put together a quick sample file with two animations in Blender. After several attempts at converting using several different techniques (including AutoDesk's free FBX Converter), I finally gave up and settled on a single animation file converted using the 30-day free trial of Maya. It's a rather roundabout way of getting data, so I hope Apple adds support for other file formats. FBX would be great. Despite it being a proprietary format, it seems to have jumped ahead of Collada in terms of update and features.

Anyway, if you want to use my sample Collada file, you can find it here. You can also grab Apple's sample files from their sample code projects. If you're interested in creating your own files, at the moment, I think using Maya is your best bet (Note - I've been getting reports that Cinema4D export with animations works also). If I have time, I'm going to compare the output of Maya and Blender and see if I can't create a custom Collada exporter for Blender that works with SceneKit, but I make no promises that I'll be able to get to that any time soon.

Adding a Collada file to your Xcode project is identical to adding any other resource. Just drag it into your project. If you single-click on the Collada file after it's been added to your project, you'll see that Xcode now has a nifty new editor for Collada files. It's a little crashy as of yet, but it's still very neat. Here's my simple Collada file opened up in Xcode's editor:

Screen Shot 2012 08 18 at 3 36 28 PM

Creating a SceneKit scene from a .dae file couldn't be much easier. This is all you have to do:

NSURL *url = [[NSBundle mainBundle] URLForResource:@"Bendy" withExtension:@"dae"];



NSError * __autoreleasing error;
SCNScene *scene = [SCNScene sceneWithURL:url options:nil error:&error];
if (scene)
self.scene = scene;
else
NSLog(@"Yikes! You're going to do real error handling, right?");


SceneKit will parse the file for you and create a full scene with nodes for all the objects contained in the file - lights, objects, cameras, etc. It will also play any animations that the file contains if you want it to, if you set the view to play:

self.playing = YES;


SceneKit also has built-in mouse handling if you want to let the user rotate the object. If you want to try that, it's just one additional line of code:

self.allowsCameraControl = YES;


You can go ahead an manipulate any of the nodes in a loaded scene just as you did with scenes you built programmatically. Want to animate a node? Do it exactly like we did with the torus - build a CAAnimation and assign it to the node. Finding nodes is usually best done by filtering on the node's name. You can find the name of various nodes in the Collada editor in Xcode. If we want to retrieve the Armature node in my sample file, for example, all we have to do is make a note of the name of the node, and retrieve the node like this:

SCNNode *armatureNode = [self.scene.rootNode childNodeWithName:@"Armature" recursively:YES];


SceneKit even creates CAAnimation objects for the animation contained in the Collada file. Be aware, however, that these animations are often complex and may be made up of several nested CAAnimationGroup objects. To get the animations for a node, just do this:

CAAnimation *armatureAnimation = [armatureNode animationForKey:@"Armature-anim"];


Notice that we used the animation name ("Armature-anim") from the editor screenshot to retrieve the animation. That CAAnimation it returns be used just like ones we create ourselves.

Adding the Items from Collada Files to an Existing Scenes

SceneKit also allows you to add individual parts of a Collada file to an existing scene using the SCNSceneSource class. For example, you can pull an animation out of another file and add it to a node in your current scene (at least, assuming the animation you're pulling uses the same armature as the node you're adding it to). Doing that would look like this:

SCNNode *armatureNode = [self.scene.rootNode childNodeWithName:@"Armature" recursively:YES];    
NSURL *sceneURL = [[NSBundle mainBundle] URLForResource:@"OtherFile" withExtension:@"dae"];
SCNSceneSource *sceneSource = [SCNSceneSource sceneSourceWithURL:sceneURL options:nil];
CAAnimation *animationObject = [sceneSource entryWithIdentifier:@"idle-animation" withClass:[CAAnimation class]];
[armatureNode addAnimation:animationObject forKey:@"idle-animation"];


This same technique can be used to combine any elements from any number of different files. So you could, for example, create your initial scene from a file that contains the camera, lights, and static elements, then add other objects from various other files. You could add a player from one file, enemies from another file, and moving obstacles from yet a third file, for example.

The Bottom Line

SceneKit is a really great addition to Cocoa. It's an extremely well engineered framework that's easy to use. About the only criticism I have is that the choice of Collada combined with an importer that has some limitations and isn't very fault tolerant makes creating content for SceneKit a bit of a headache. That's a minor complaint for a brand new framework, though, and I'm really hoping for an iOS version of SceneKit before too long.

Further Reading

Apple has posted three really nice pieces of SceneKit sample code. If you're interested in using SceneKit, I would highly suggest downloading these three, running them, and then dissecting the code.

Thursday, August 16, 2012

More on Wang Ting

I received a fairly prompt response from AppStoreNotices@apple.com with regards to the Tile Cutter infringement I blogged about earlier today. They've sent an e-mail to Wang Ting, the "developer" fond of taking apps off of GitHub and selling them as his own creations. Apple has asked him to voluntarily take down TileCutter or provide evidence that he has the right to publish the app.

That's something, I guess. 

As of right now, Apple won't take any action on any of the other infringing apps. This is true even though the developer's own page gives clear evidence of a pattern and practice of blatant copyright infringement. Apple is aware of it, but will not take any action unless they receive individual takedown requests from each of the individual rights holders. All 154 of them. 

Why? Because they don't have to, legally speaking. They are protected by the DMCA for any infringing material until the moment they receive a takedown notice from a rights holder. So, they're playing it safe. Revoking developer privileges doesn't even seem to be on the table.

It's frustrating that Apple has no problem playing gatekeeper and keeping apps out of the store for the most trivial and arbitrary of reasons, but won't play gatekeeper when presented with clear evidence of repeated copyright infringement. A serial infringer who is stealing the creative output of dozens of developers, designers, and publishers gets a pass because that's the legal path of least resistance for Apple. 

What's really annoying is that if I'm interested in seeing the right thing happen here, I have to track down the parties infringed upon for by hundred and fifty-four different apps. I would like to. Honestly, I would. But as a practical matter, I don't have the time. I can't really afford the time I've lost to this issue today already.

The simple truth is, I have no leverage to make Apple do the right thing here except with regards to my one little app (which is still for sale as I write this). At the moment, it seems pretty unlikely that Apple will do the right thing and revoke the account of a serial infringer. I can't see how this does anything but encourage the Wang Ting's of the world. It's a clear message that as long as you steal from lots of different people or keep a low profile, Apple won't stop you unless they absolutely, positively have to.

The benefit of the App Store over the Android Marketplace, we've been repeatedly told, is that Apple acts as gatekeeper. They curate and inspect the apps to insure a better experience, higher quality apps, and to protect users and developers. Right? 

Too bad protecting developers' livelihoods is less deserving of gatekeeper protection than defending the App Store from an occasional nipple.

So, if you are the right's owner of an app, publication, or other content on this page (or any other page, for that matter), you should send a DMCA takedown notice to AppStoreNotices@apple.com right this very moment. Don't wait. Do it now. Apple, at least, will do what they are legally obligated to do. If you're not the right's holder, but know how to reach any of the right's holders of content on that page, please make them aware of the situation so they can send a takedown notice.

Maybe if Apple gets enough DMCA takedown notices for the same developer in a short enough period of time, they'll consider doing the right thing. I'm not holding my breath, mind you, but I feel like I have to try and make the right thing happen if I can. Finding a way to get word all the rights holders seem the best and only way to do that right now.

Irksome, but Nothing New

I woke up this morning to a friendly tweet from someone who noticed that another "developer" had taken one of my Github Projects and had put the app on the App Store for money.


Now, I license my source code very liberally.  Generally, using my stuff - even recompiling and releasing it unchanged - is allowed. In this case, the source code headers require nothing except that code redistribution include the copyright notice and license. In other words, you can use the code from Tile Cutter without any obligation to me at all. However, the project license, which covers everything else besides the code, specifically requires copyright notice and attribution. That means the assets like images, icons, and nib files cannot be used without at very least attributing me and including my copyright notice. Not only is there no attribution in this app, the developer has taken steps to remove the license and copyright notices to make it look like it was written by him or her. Hell, they even used my GitHub description, word for word, except for the part about the licensing terms.

If the app were free, I'd probably just shake my head and move on. If someone wants to pretend they wrote some code I wrote, it doesn't really hurt me all that much. I'm a little irked by the fact that they're charging money for something that I intended to be free and can't fathom how they can view this as a legitimate thing to do, but even with them charging for it, it doesn't really hurt me. It bothers me a little that they are enriching themselves on something I intended to be free, but I can get over that.

But, it turns out, it's not just my stuff this developer has done this with. This same developer also took EmberWind, Stone Mill, and Stash (renamed Personal Finance) from GitHub and did the exact same thing. Seeing this makes me far more angry, because these works represent a fair amount more effort than this weekend project of mine. In all these cases, the developer has taken steps to make it appear that they are the original author of the apps by removing links or attributions to the original authors. Even if not a direct violation of the licenses (I'm pretty sure it is in these cases), it's clearly unethical. The theft of these other apps makes me far more mad than the misappropriation of mine.

But wait, there's even more!

Someone sent me this link to the developer's web site. Apparently, Wang Ting is a very prolific developer whose list of apps include several major American Magazines and dozens of other Mac and iPhone apps. Several people have identified the sources of several of the app icons on that page (hint: at least some of them are not original to this developer), and I have a sneaking suspicion that Cosmopolitan, Marie-Claire, T3, and Seventeen haven't licensed their work to some random individual developer in China. I could be wrong, but I'd be willing to put money on it if you're in a betting mood.

I've notified Apple, and will be attempting to notify others whose work was stolen. Hopefully, Apple will take down the offending apps and consider revoking Wang Ting's developer privileges.

I know Apple can't and shouldn't be expected to catch all copyright infringements and license violations in submitted apps, and when you open source something, there's always a risk that people will use it and not respect the original license. But I have to wonder why the more blatant cases don't send up a big red flag.  If I were a reviewer and saw a major US magazine's content included in an app by a random developer, I'd be a little hesitant to approve the app without some proof that they had the right to publish it.

Tuesday, June 5, 2012

Pris

My MartianCraft business partner, Rob, today released a new, very slick camera app called Pris. It's available on the App Store here, and you can read Rob's blog post introducing Pris' features and his motivation for writing it here.

I don't normally do product announcements here, even for friends, but given the ordeal Rob went through with his last app, Briefs, which sat in review for over a year and was never approved for App Store sale, I wanted to get the word out about his new app, which (thankfully) made it through review with only one hiccup.

There are a lot of camera apps, but Pris has been under development for a very long time and is pretty awesome. It gives you, for example, live feedback, including a realtime histogram, and precise control over focus and exposure. 

Pris is on sale for a limited time at $1.99.

Tuesday, May 29, 2012

WWDC 2012 Pilgrimage to Apple HQ


Just a reminder: Once again this year, there will be a bus pilgrimage to Cupertino on Sunday, June 10, which is the day before WWDC starts. While the morning bus has sold out, there is still some room on the afternoon buses. If you have any thoughts of visiting the Apple campus, this is the best way to go if you don't have a car.  Hell, it's probably the best way to go even if you do have a car. Bay Area traffic is no fun, and we use big comfortable touring buses.

The Company Store will be open and you don't need a WWDC ticket to shop, so this is a great chance to stock up on Apple swag, including a whole lot of stuff that they won't be selling up at Moscone during the week. It's also a chance to get your picture taken in front of the Apple HQ sign. You'll get to hang out with several other Mac and iOS developer geeks and meet some people before the official WWDC craziness gets started. 

The last several years have been a blast. I think this is, what? The third annual WWDC Pilgrimage? Or is it the fourth? Either way, it's been great fun every year. If you're interested in going, the signup and information page is here

If you think you want to go, don't wait. This event has sold out every year so far and there's no reason to think this year will be different.

Thursday, May 24, 2012

2012 WWDC T-Shirts

Well, pre-ordering of the WWDC MartianCraft T-Shirts for 2012 is now closed. We're going to leave the order form up until WWDC ends and do one final print run at that point. After that, the 2012 shirt is going out of production. We'll be back next year (or maybe even sooner) with a new design, but this particular one will be retired for good.

As I mentioned before, we will be bringing some shirts with us just like in past years, but we're not promising them to people an advance. There's too many people who'd like them.  It's too hard to keep track of who was promised what size and we end up carrying dozens of shirts all over the place looking for people. When we have shirts available for givesies, either Rob or I will tweet where we are or how you can get one.

Tuesday, May 22, 2012

Appsterdam WWDC HQ

I normally leave reporting of WWDC events to other blogs, but I thought this was interesting enough that it warranted its own post, especially given the number of people who didn't get tickets this year but still plan to be in San Francisco showcializing.

The folks from Appsterdam have arranged free space, electricity, internet, and coffee at the StockMob HQ at 541 8th street all week long. Any developers in town from June 11th to 15th are welcome to work during the days for free.

You can find out more details at the Appsterdam WWDC HQ page.