I am loading a third-party .dae Collada file as a scene into a SceneKit project.
The .dae file has many different animations in it, set at different times/frames. I
As Toyos mentioned in his answer, enumerate the CAAnimationGroup
using SCNSceneSource
and retrieve the CAAnimation
objects as follows:
NSURL *daeURL = [[NSBundle mainBundle] URLForResource:@"exportedFilename" withExtension:@"dae"];
SCNSceneSource *sceneSource = [SCNSceneSource sceneSourceWithURL:daeURL options:nil];
NSMutableArray *myAnimations = [@[] mutableCopy];
for (NSString *singleAnimationName in [sceneSource identifiersOfEntriesWithClass:[CAAnimation class]]) {
CAAnimation *thisAnimation = [sceneSource entryWithIdentifier:singleAnimationName withClass:[CAAnimation class]];
[myAnimations addObject:thisAnimation];
}
3d tools often export multiple animations as a single animation with sub animations. In that case SceneKit will load these animations as a CAAnimationGroup with sub animations. So one option is to "parse" the animation group's sub animations and retrieve the ones you want. Another option is to retrieve (sub)animations by name using SCNSceneSource (but this will work only if your 3d tool exported names when it exported your DAE).
If you need to "crop" animation (i.e extract an animation that starts at t0 with duration D from a longer animation), CoreAnimation has a APIs for that:
create an animation Group to "crop" with duration D.
add the animation you want to crop as a sub-animation and set it's timeOffset to t0.
Here is code that converts frame numbers to times and then plays only that portion of the animation by using a CAAnimationGroup
as @Toyos described. This sample code plays an "idle" animation by repeating frames 10 through 160 of fullAnimation
:
func playIdleAnimation() {
let animation = subAnimation(of:fullAnimation, startFrame: 10, endFrame: 160)
animation.repeatCount = .greatestFiniteMagnitude
addAnimation(animation, forKey: "animation")
}
func subAnimation(of fullAnimation:CAAnimation, startFrame:Int, endFrame:Int) -> CAAnimation {
let (startTime, duration) = timeRange(startFrame:startFrame, endFrame:endFrame)
let animation = subAnimation(of: fullAnimation, offset: startTime, duration: duration)
return animation
}
func subAnimation(of fullAnimation:CAAnimation, offset timeOffset:CFTimeInterval, duration:CFTimeInterval) -> CAAnimation {
fullAnimation.timeOffset = timeOffset
let container = CAAnimationGroup()
container.animations = [fullAnimation]
container.duration = duration
return container
}
func timeRange(startFrame:Int, endFrame:Int) -> (startTime:CFTimeInterval, duration:CFTimeInterval) {
let startTime = timeOf(frame:startFrame)
let endTime = timeOf(frame:endFrame)
let duration = endTime - startTime
return (startTime, duration)
}
func timeOf(frame:Int) -> CFTimeInterval {
return CFTimeInterval(frame) / framesPerSecond()
}
func framesPerSecond() -> CFTimeInterval {
// number of frames per second the model was designed with
return 30.0
}