How to split out multiple animations from Collada file in SceneKit
Asked Answered
D

3

6

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 am trying to figure out how I can split these out and reference each individual animation by reference name. There are no intelligible reference names in the dae file - the animations are all set as one single animation.

I am able to parse out the animations into a CAAnimation object, and verify that I have successfully done so with the following code:

SCNScene *scene = [SCNScene sceneNamed:@"art.scnassets/man.dae"];
SCNNode *man = [scene.rootNode childNodeWithName:@"Bip01" recursively:YES];
CAAnimation *animation = [man animationForKey:@"test_Collada_DAE-1"];
[man removeAllAnimations];
[man addAnimation:animation forKey:@"animation"];

Is there any way to set a start and end frame or time to my CAAnimation object? What is the best way to parse out the various animations? I'm hoping that I do not have to manually split the dae file into many and load each one individually.

Diecious answered 13/5, 2015 at 14:33 Comment(4)
Out of curiosity, what happens if you output -animationKeys:?Venturous
That's actually how I came to find out that the animation was called "test_Collada_DAE-1" -- that's the only thing it outputs.Diecious
I see... Do you know where the animations are supposed to start/stop? Keyframes or percents?Venturous
Yes, I do. I know keyframes, and I could probably calculate percentages if I need to, but I can't figure out in Core Animation how to specify a keyframe range to animate -- it always just animates the entire sequence, including all animations.Diecious
N
3

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.

Nuncle answered 14/5, 2015 at 8:23 Comment(0)
T
2

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];
}
Tonsillotomy answered 9/2, 2016 at 20:22 Comment(0)
F
2

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
}
Fining answered 11/8, 2017 at 14:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.