Как разделить несколько анимаций из файла Collada в SceneKit

Я загружаю сторонний файл .dae Collada в качестве сцены в проект SceneKit.

В файле .dae много разных анимаций, установленных в разное время / кадры. Я пытаюсь понять, как я могу разделить их и ссылаться на каждую отдельную анимацию по имени ссылки. В файле dae нет понятных справочных имен - все анимации настроены как одна единственная анимация.

Я могу преобразовать анимацию в объект CAAnimation и проверить, успешно ли я сделал это, с помощью следующего кода:

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"];

Есть ли способ установить начальный и конечный кадр или время для моего объекта CAAnimation? Как лучше всего разбирать различные анимации? Я надеюсь, что мне не придется вручную разбивать файл dae на множество и загружать каждый отдельно.


person itnAAnti    schedule 13.05.2015    source источник
comment
Из любопытства, что произойдет, если вы выведете - animationKeys:?   -  person Moustach    schedule 13.05.2015
comment
Именно так я узнал, что анимация называется test_Collada_DAE-1 - это единственное, что она выводит.   -  person itnAAnti    schedule 13.05.2015
comment
Понятно ... Вы знаете, где должна начинаться / останавливаться анимация? Ключевые кадры или проц?   -  person Moustach    schedule 14.05.2015
comment
Да. Я знаю ключевые кадры и, вероятно, мог бы вычислить проценты, если бы мне нужно было, но я не могу понять в Core Animation, как указать диапазон ключевых кадров для анимации - он всегда просто анимирует всю последовательность, включая все анимации.   -  person itnAAnti    schedule 14.05.2015
comment
@itnAAnti: Эй, я столкнулся с похожей проблемой. Вы смогли найти решение?   -  person The X-Coder    schedule 06.07.2019
comment
@ TheX-Coder К сожалению, нет, я так и не нашел хорошего решения. Однако я не возвращался к этой проблеме в течение нескольких лет, поэтому могут быть решения, которые не были доступны, когда я работал над этим (например, с тех пор SceneKit имеет некоторые обновления; я не знаю их достаточно хорошо, чтобы быть полезным, хотя .) Я также никогда не пробовал альтернативные решения, указанные ниже, которые были добавлены после того, как я ушел. Возможно, стоит попробовать два других неприемлемых ответа ниже.   -  person itnAAnti    schedule 30.07.2019


Ответы (3)


3D-инструменты часто экспортируют несколько анимаций как одну анимацию с вспомогательными анимациями. В этом случае SceneKit загрузит эти анимации как CAAnimationGroup с подчиненными анимациями. Таким образом, один из вариантов - «проанализировать» суб-анимации группы анимаций и получить те, которые вам нужны. Другой вариант - получить (под) анимацию по имени с помощью SCNSceneSource (но это будет работать только в том случае, если ваш 3D-инструмент экспортировал имена при экспорте DAE).

Если вам нужно «обрезать» анимацию (т.е. извлечь анимацию, которая начинается в t0 с длительностью D из более длинной анимации), у CoreAnimation есть API для этого:

  • создать группу анимации для «обрезки» с длительностью D.

  • добавьте анимацию, которую вы хотите обрезать, в качестве суб-анимации и установите для нее timeOffset равным t0.

person Toyos    schedule 14.05.2015
comment
Спасибо! Я не знал всего протокола CAMediaTiming. Свойство duration отлично работает для обрезки хвоста файла, где бы я ни указывал, но мне не удалось получить свойства beginTime или timeOffset, чтобы обрезать голову, как мне хотелось бы. Если я правильно понимаю ваш комментарий, на самом деле это работает не так, и мне понадобится файл DAE, который мне нужен только для первой анимации в последовательности, если я хочу попытаться обрезать анимацию. Это правильно? Я все еще изучаю другие ваши предложения. - person itnAAnti; 15.05.2015
comment
Я нашел эту статью ronnqvi.st/controlling-animation-timing, в которой есть хорошее пошаговое руководство по протокол CAMediaTiming и, в частности, как работают timeOffset и beginTime. Также нельзя обрезать начало файла. Я возвращаюсь к использованию Blender для разделения моего файла .dae на отдельные файлы для каждой анимации. :( - person itnAAnti; 16.05.2015

Как Тойос упомянул в своем ответе, перечислите CAAnimationGroup с помощью SCNSceneSource и получите объекты CAAnimation следующим образом:

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];
}
person JaredH    schedule 09.02.2016

Вот код, который преобразует номера кадров во времена, а затем воспроизводит только ту часть анимации, используя CAAnimationGroup, как описано в @Toyos. В этом примере кода воспроизводится анимация ожидания, повторяя кадры с 10 по 160 из 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
}
person Mike Vosseller    schedule 11.08.2017