First steps in SceneKit

Animating a rigged model in SceneKit

So you’ve exported your rigged model from Blender as an Xcode-friendly .dae as described in the the previous post. In this post we’re going to look at bringing the .dae into a Swift SceneKit project in Xcode, and programmatically triggering the animations.

Cleaning up the animations

Before you drag and drop the .dae into Xcode though, we need make some adjustments to the animation hierarchy in the file. If you were to bring the .dae as it stands into Xcode, you’d see scores of animations, one for each bone in the armature. This is because Blender exports a flat animation structure. It’ll be much easier to work with the animation if the animations for each bone are all organized within a single animation for the action. Luckily, user FlippinFun on this StackOverflow thread has created a ConvertToXcodeCollada service script that does exactly this. Install the script, right click on the .dae in Finder, and from the service menu, run the script. Now you’re ready to bring the .dae into Xcode.

Which way up?

While Blender, presumably because of its CAD lineage, is Z-Up, SceneKit, as well as the low level frameworks beneath it OpenGL(ES) and Metal, is Y-Up, negative-Z-Forwards. And while every single other exporter that Blender offers has an option to format the export as Y-Up, mystifyingly, the .dae exporter doesn’t. You could try rotating the geometry 90 degrees around its X-axis before you export, but I suspect that this could create problems, as, unlike a relatively simple format like .obj, .dae files also have an up-vector variable that would presumably also need to be set (I haven’t experimented with this).

Luckily Xcode SceneKit asset folders have an Always use Y-Up axis option, which will automatically convert the .dae you place there into Y-Up orientation. So make sure that your Blender-exported .dae go into the .scnassets folder. There is one caveat though. If you are composing a SceneKit scene in the Xcode Scene Editor, then any .dae you drag into that scene will display with the wrong (Z-Up) orientation in the Scene Editor. If you run your project however, the models display with the correct, Y-Up orientation. This is unfortunate, as it limits the usefulness of the Xcode Scene Editor as a tool to arrange groups of Blender-exported .dae files (eg for designing a level of a game).1

A simple Collada rig class

This is a first attempt at a simple Collada rig class that stores and triggers all of the animations. This is derived partly from Apple examples such as the Fox demo, and a number of other sources, but particularly this Stack Overflow thread. The class assumes that the animation contained in the master .dae file (the one with the geometry) is the “rest” animation. The “walk” animation starts by pausing the “rest” animation and ends with “rest” resuming.

    import SceneKit

    class ColladaRig {
        let node: SCNNode
        var animations = [String: CAAnimation]()
        
        init(modelNamed: String, daeNamed: String){
            
            let sceneSource = ColladaRig.getSceneSource(daeNamed)
            node = sceneSource.entryWithIdentifier(modelNamed, withClass: SCNNode.self)!
            
            //Find and add the armature
            let armature = sceneSource.entryWithIdentifier("Armature", withClass: SCNNode.self)!
            //In some Blender output DAE, animation is child of armature, in others it has no child. Not sure what causes this. Hence:
            armature.removeAllAnimations()
            node.addChildNode(armature)
            
            //store and trigger the "rest" animation
            loadAnimation("rest", daeNamed: daeNamed)
            playAnimation("rest")
            
            //position node on ground
            var min = SCNVector3(0,0,0)
            var max = SCNVector3(0,0,0)
            node.getBoundingBoxMin(&min, max: &max)
            node.position = SCNVector3(0, -min.y, 0)
        }
        
        static func getSceneSource(daeNamed: String) -> SCNSceneSource {
            let collada = NSBundle.mainBundle().URLForResource("art.scnassets/\(daeNamed)", withExtension: "dae")!
            return SCNSceneSource(URL: collada, options: nil)!
        }
        
        func loadAnimation(withKey: String, daeNamed: String, fade: CGFloat = 0.3){
            let sceneSource = ColladaRig.getSceneSource(daeNamed)
            let animation = sceneSource.entryWithIdentifier("\(daeNamed)-1", withClass: CAAnimation.self)!
            
            // animation.speed = 1
            animation.fadeInDuration = fade
            animation.fadeOutDuration = fade
            // animation.beginTime = CFTimeInterval( fade!)
            animations[withKey] = animation
        }
        
        func playAnimation(named: String){ //also works for armature
            if let animation = animations[named] {
                node.addAnimation(animation, forKey: named)
            }
        }
        
        func walk() {
            node.pauseAnimationForKey("rest")
            //  node.removeAnimationForKey("rest", fadeOutDuration: 0.3)
            playAnimation("walk")
            let run = SCNAction.repeatActionForever( SCNAction.moveByX(0, y: 0, z: 12, duration: 1))
            run.timingMode = .EaseInEaseOut //ease the action in to try to match the fade-in and fade-out of the animation
            node.runAction(run, forKey: "walk")
        }
        
        func stopWalking() {
            node.resumeAnimationForKey("rest")
            //   node.addAnimation(animations["rest"]!, forKey: "rest")
            node.removeAnimationForKey("walk", fadeOutDuration: 0.3)
            node.removeActionForKey("walk")
        }
    }

Full repo is here.

Next steps

This is all a work-in-progress, comments, questions, pull-requests etc are welcome.

There are a number of areas still to explore, and several directions this project could be taken in:

  • Attaching objects to a specific bone in the armature, for instance to allow a model to carry an object, or wear an accessory. The SceneKit SCNSkinner reference suggests applying the character’s skeleton to the object being carried, but in my experiments the object being carried ends up not being oriented correctly. Possible it’s possible just to make the object being carried/ worn a child node of the bone?
  • Combining animations for different areas of the armature (eg lower-body upper-body), so that a character could, for instance, run while waving their arms, run while carrying something etc.
  • Manipulating the armature/ skinner in an arbitrary way, for example getting a character to point a limb in an arbitrary direction (and combine this with animation cycles),
  • How to handle meshes with multiple materials. Often Blender models use multiple materials that are really just different diffuse colours, as a quick way of colouring different vertex groups within the mesh without having to unwrap the mesh and apply a texture to it or do vertex painting. It seems really inefficient to represent these as multiple SceneKit materials however, as they are just different colours, and each SCNMaterial requires a separate draw call. What is the best solution here? To bake a diffuse texture image in Blender? Or is there some way to represent multiple materials via vertex-painting in SceneKit, using a color attribute for each vertex?
  1. Converting the .dae to .scn in Xcode doesn’t apply the Y-Up transform either. Presumably the transform to Y-Up is applied at compile time. I need to experiment to see whether saving the .scn during run-time results in a file that has the Y-Up transformation applied.

Built with Jekyll      © Salt Pig Media