Search code examples
animationscenekitios11arkitxcode9-beta

How to “crop” SCNAnimationPlayer to a specific start and end time, iOS 11


I have a .dae model with a long animation. The animation includes segments of walking, running, hitting, death, etc. I know the frame numbers of the start and end of each segment. I also know the frame per second rate. So getting the time of the start and end of each segment is pretty easy.

I can get the full animation as a SCNAnimationPlayer object. What I’ve been experimenting with is making a copy of the full animation and then setting the timeOffset and duration of the animation.

let walkPlayer = fullPlayer.copy() as! SCNAnimationPlayer
walkPlayer.stop()
walkPlayer.animation.timeOffset = walk.offset
walkPlayer.animation.duration = walk.duration

I then add the walkPlayer back to the Bip01 node (where I got the full animation from).

I can play the walk easily enough by calling animationPlayer(forKey:"walk")?.play()

I can change the duration and other aspects of the animation easily enough. But the animation always starts at frame 0. Whatever value I put into .timeOffset, it just gets ignored.

How can I play from a start frame to an end frame of an SCNAnimation found in SCNAnimationPlayer?


Solution

  • The key bit was to find

    CAAnimation(scnAnimation: animation)
    

    and

    SCNAnimation(caAnimation: animation)
    

    Once I found these then I could use CAAnimationGroup to “crop” the full animation.

    Here’s my Troll.swift that I was working on. There is, of course, much to do but now I can at least make the poor beast walk and die.

    class Troll: SCNNode {
        var body:SCNNode!
    
        static func timeRange(forStartingAtFrame start:Int, endingAtFrame end:Int, fps:Double = 30) -> (offset:TimeInterval, duration:TimeInterval) {
            let startTime   = self.time(atFrame: start, fps: fps) //TimeInterval(start) / fps
            let endTime     = self.time(atFrame: end, fps: fps) //TimeInterval(end) / fps
            return (offset:startTime, duration:endTime - startTime)
        }
    
        static func time(atFrame frame:Int, fps:Double = 30) -> TimeInterval {
            return TimeInterval(frame) / fps
        }
    
        static func animation(from full:CAAnimation, startingAtFrame start:Int, endingAtFrame end:Int, fps:Double = 30) -> CAAnimation {
            let range = self.timeRange(forStartingAtFrame: start, endingAtFrame: end, fps: fps)
            let animation = CAAnimationGroup()
            let sub = full.copy() as! CAAnimation
            sub.timeOffset = range.offset
            animation.animations = [sub]
            animation.duration = range.duration
            return animation
        }
    
    
        func load() {
    
            guard let trollScene = SCNScene(named: "Models.scnassets/troll/troll.dae") else {
                fatalError("Can't load the scene")
            }
    
            guard let troll_body = trollScene.rootNode.childNode(withName: "troll", recursively: true) else {
                fatalError( "found no troll")
            }
    
            guard let troll_weapon = trollScene.rootNode.childNode(withName: "troll_weapon", recursively: true) else {
                fatalError( "found no troll_weapon")
            }
    
            guard let troll_bracelet = trollScene.rootNode.childNode(withName: "troll_bracelet", recursively: true) else {
                fatalError( "found no troll_bracelet")
            }
    
            guard let bips = trollScene.rootNode.childNode(withName: "Bip01", recursively: true) else {
                fatalError( "found no Bip01")
            }
    
            guard let fullKey = bips.animationKeys.first else {
                fatalError( "Bip01 got no animation")
            }
    
            guard let fullPlayer = bips.animationPlayer(forKey: fullKey) else {
                fatalError( "Bip01 got no player for \(fullKey)")
            }
            let fullAnimation = CAAnimation(scnAnimation: fullPlayer.animation)
    
            self.addChildNode(troll_body)
            self.addChildNode(troll_weapon)
            self.addChildNode(troll_bracelet)
            self.addChildNode(bips)
    
            self.body = bips
            self.body.removeAllAnimations()
    
            let walkAnimation = Troll.animation(from: fullAnimation, startingAtFrame: 10, endingAtFrame: 60)
            walkAnimation.repeatCount = .greatestFiniteMagnitude
            walkAnimation.fadeInDuration = 0.3
            walkAnimation.fadeOutDuration = 0.3
            let walkPlayer = SCNAnimationPlayer(animation: SCNAnimation(caAnimation: walkAnimation))
            self.body.addAnimationPlayer(walkPlayer, forKey: "walk")
    
            let deathAnimation = Troll.animation(from: fullAnimation, startingAtFrame: 1810, endingAtFrame: 1850)
            deathAnimation.isRemovedOnCompletion = false
            deathAnimation.fadeInDuration = 0.3
            deathAnimation.fadeOutDuration = 0.3
            let deathPlayer = SCNAnimationPlayer(animation: SCNAnimation(caAnimation: deathAnimation))
            self.body.addAnimationPlayer(deathPlayer, forKey: "death")
    
            self.scale     = SCNVector3(0.1,0.1,0.1)
    
        }
    
        func walk() {
            print( "+++ walk +++" )
            self.body.animationPlayer(forKey: "walk")?.play()
        }
    
        func death() {
            print( "+++ death +++" )
            self.body.animationPlayer(forKey: "walk")?.stop(withBlendOutDuration: 0.3)
            self.body.animationPlayer(forKey: "death")?.play()
        }
    }