5
votes

I am using SceneKit to load a Collada (.dae) file.

Whatever I have tried, the animation repeats in a continuous loop whereas I only want it play once and then stop.

Here's the loading code where sceneView is my SCNView:

    //Load the scene and play
    let scene = SCNScene(named: "Scenes.scnassets/test4.dae")
    sceneView.scene = scene
    sceneView.autoenablesDefaultLighting = true
    sceneView.allowsCameraControl = true

The scene loads correctly and the animation plays on loading as required, but in a continuous loop.

I have tried removing all animations as follows:

sceneView.scene?.rootNode.removeAllAnimations()

but this seems to have no effect.

I have also tried retrieving all animations and setting the repeatCount = 1 or even to 0

    let url = NSBundle.mainBundle().URLForResource("Scenes.scnassets/test4", withExtension: "dae")
    let sceneSource = SCNSceneSource(URL:url!, options: nil)


    let animationIndentifiers = sceneSource?.identifiersOfEntriesWithClass(CAAnimation)

    if let ids = animationIndentifiers
    {
        for id in ids {
            let animation: CAAnimation = sceneSource!.entryWithIdentifier(id as! String, withClass: CAAnimation.self)! as! CAAnimation
            animation.repeatCount = 1
        }
    }

I have also tried this another way:

let keys = sceneView.scene!.rootNode.animationKeys() //get all the animation keys

if let theKeys = keys {
    for key in theKeys {
        let animation = sceneView.scene?.rootNode.animationForKey(key as! String)
        animation?.repeatCount = 1
    }
}

but again no luck.

I can find nothing in the Collada file itself that is obviously causing the animation to repeat. However, I notice that the .dae file icon on my Mac disk has a play button and clicking this also plays the animation within the icon on a continuous loop.

Update:

I now notice that in the code above I am setting the constant 'animation' attributes and this is not copied back to the actual scene nodes. Also, the only animation in the .dae file is in a child node. So here's my next attempt:

  //Get the child nodes
  let children = scene?.rootNode.childNodes

  //Initialise a childNode counter
  var childCount = 0

  //Iterate through the child nodes     
  if let theChildren = children {
     for child in theChildren {
        let keys = child.animationKeys()  //get all the animation keys
        //Iterate through the animations
        if let theKeys = keys {
           for key in theKeys {
               let animation = child.animationForKey(key as! String)
               println("Initial Repeat count: \(animation.repeatCount)")
               animation.repeatCount = 1
               //Remove existing animation
               scene?.rootNode.childNodes[childCount].removeAnimationForKey(key as! String)
               //Add amended animation
               scene?.rootNode.childNodes[childCount].addAnimation(animation, forKey: key as! String)
            }
         }
         childCount++
    }
 }

There is actually only one animation attached to one child node. (I have also tried setting this using the the actual animation id string in place of 'key' above.)

The above shows Initial Repeat count: inf and on checking afterwards it is indeed set to 1. However, the animation still runs in an infinite loop :-(

Any help to resolve this would be much appreciated.

Further update

I have now created a new Collada file with simple animation using Maya and for some reason one of the trials attempted above actually works:

    func sceneSetup() {
      let scene = SCNScene(named: "Scenes.scnassets/test10.dae")

      let children = scene?.rootNode.childNodes
      var childCount = 0
      if let theChildren = children {
        for child in theChildren {
          let keys = child.animationKeys()  //get all the animation keys
          if let theKeys = keys {
            for key in theKeys {
              let animation = child.animationForKey(key as! String)
              animation.repeatCount = 1
              scene?.rootNode.childNodes[childCount].removeAnimationForKey(key as! String)
              scene?.rootNode.childNodes[childCount].addAnimation(animation, forKey: key as! String)
            }
          }
          childCount++
        }
    }       
     sceneView.scene = scene
     sceneView.autoenablesDefaultLighting = true            
  }

if anybody can explain that would be great!

Ah, but there's another problem!

Here's the start frame of the animation:

Start frame

and here's the end frame where we want to end up:

End frame

But at the end of the animation the scene jumps back to the start view.

I have 'fixed' this by amending the animation so that frame 1 is copy of the final frame. This works and isn't noticeable but doesn't seem a very elegant solution.

3

3 Answers

3
votes

For the animation jumping back to the first frame, the isAppliedOnCompletion value is what you are looking for.

animation.repeatCount = 1
animation.isAppliedOnCompletion = true

This will make sure that the animation pauses on the final frame.

2
votes

This works for me. It's based off your answer but will go through the node's entire tree and limit all of their animation counts to one.

Personally I found that if I missed any of the descendant nodes when manually traversing and setting the node animation counts to one, I would have a problem. This ensures that the node passed in itself, and all child nodes will only animate once, and then hold the model in place after finishing.

Use like this animateEntireNodeTreeOnce(mostRootNode: nodeYouImportedFromCollada).

func animateEntireNodeTreeOnce(mostRootNode node: SCNNode){
    onlyAnimateThisNodeOnce(node)
    for childNode in node.childNodes {
        animateEntireNodeTreeOnce(mostRootNode: childNode)
    }
}

func onlyAnimateThisNodeOnce(_ node: SCNNode) {
    if node.animationKeys.count > 0 {
        for key in node.animationKeys {
            let animation = node.animation(forKey: key)!
            animation.repeatCount = 1
            animation.isRemovedOnCompletion = false
            node.removeAllAnimations()
            node.addAnimation(animation, forKey: key)
        }
    }
}
0
votes

I know this is an old question, but I came across this same problem and found a solution for the jumping back to the beginning issue. If you set the animation to not be removed on completion, the object should stay at the end location:

if let animation = child.animationForKey(child.animationKeys.first!) {
    animation.repeatCount = 1
    animation.removedOnCompletion = false
...