diff --git a/.gitmodules b/.gitmodules index e9b9d1cfd..244341e12 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ [submodule "dependencies/OpenCOLLADA/OpenCOLLADA"] path = dependencies/OpenCOLLADA/OpenCOLLADA - url = https://github.com/KhronosGroup/OpenCOLLADA.git + url = https://github.com/COLLADA2GLTF/OpenCOLLADA.git [submodule "dependencies/rapidjson"] path = GLTF/dependencies/rapidjson url = https://github.com/miloyip/rapidjson.git diff --git a/CHANGES.md b/CHANGES.md index 9fcc3fd13..9eeef50c6 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,6 +5,7 @@ Change Log ##### Additions :tada: * Added support for morph targets [#166](https://github.com/KhronosGroup/COLLADA2GLTF/issues/166) * Support converting models with transparency [#168](https://github.com/KhronosGroup/COLLADA2GLTF/issues/168) +* Added support for exporting COLLADA animation clips as groups [#227](https://github.com/KhronosGroup/COLLADA2GLTF/pull/227) ### v2.1.4 - 2018-08-29 diff --git a/GLTF/include/GLTFAsset.h b/GLTF/include/GLTFAsset.h index a68588668..742a44e8e 100644 --- a/GLTF/include/GLTFAsset.h +++ b/GLTF/include/GLTFAsset.h @@ -49,6 +49,7 @@ namespace GLTF { std::vector getAllImages(); std::vector getAllPrimitiveAccessors(GLTF::Primitive* primitive) const; void mergeAnimations(); + void mergeAnimations(std::vector> groups); void removeUnusedSemantics(); void removeUnusedNodes(GLTF::Options* options); GLTF::Buffer* packAccessors(); diff --git a/GLTF/src/GLTFAsset.cpp b/GLTF/src/GLTFAsset.cpp index ba154fb72..3f1e4f47f 100755 --- a/GLTF/src/GLTFAsset.cpp +++ b/GLTF/src/GLTFAsset.cpp @@ -378,20 +378,57 @@ std::vector GLTF::Asset::getAllCompressedBufferView() { } void GLTF::Asset::mergeAnimations() { + return this->mergeAnimations(std::vector>()); +} + +void GLTF::Asset::mergeAnimations(std::vector> groups) { if (animations.size() == 0) { return; } - GLTF::Animation* mergedAnimation = new GLTF::Animation(); + std::vector mergedAnimations; - // Merge all animations. In the future, animations should be grouped - // according to COLLADA nodes. - for (GLTF::Animation* animation : animations) { - for (GLTF::Animation::Channel* channel : animation->channels) { - mergedAnimation->channels.push_back(channel); + // Keep track of which animations we've written + std::set animationIndexes; + for (size_t i = 0; i < animations.size(); i++) { + animationIndexes.insert(i); + } + + // Merge animations by the specified groups + for (std::vector group : groups) { + if (group.size() == 0) { continue; } + + GLTF::Animation* mergedAnimation = new GLTF::Animation(); + for (size_t index : group) { + GLTF::Animation* animation = animations[index]; + if (mergedAnimation->name == "") { + mergedAnimation->name = animation->name; + } + animationIndexes.erase(index); + for (GLTF::Animation::Channel* channel : animation->channels) { + mergedAnimation->channels.push_back(channel); + } + mergedAnimations.push_back(mergedAnimation); + } + } + + // Merge any leftovers + if (animationIndexes.size() > 0) { + GLTF::Animation* mergedAnimation = new GLTF::Animation(); + for (size_t index : animationIndexes) { + GLTF::Animation* animation = animations[index]; + if (mergedAnimation->name == "") { + mergedAnimation->name = animation->name; + } + for (GLTF::Animation::Channel* channel : animation->channels) { + mergedAnimation->channels.push_back(channel); + } + mergedAnimations.push_back(mergedAnimation); } } animations.clear(); - animations.push_back(mergedAnimation); + for (GLTF::Animation* animation : mergedAnimations) { + animations.push_back(animation); + } } void GLTF::Asset::removeUncompressedBufferViews() { diff --git a/dependencies/OpenCOLLADA/OpenCOLLADA b/dependencies/OpenCOLLADA/OpenCOLLADA index 619d94213..a23719d1e 160000 --- a/dependencies/OpenCOLLADA/OpenCOLLADA +++ b/dependencies/OpenCOLLADA/OpenCOLLADA @@ -1 +1 @@ -Subproject commit 619d9421365637c5e22b1192aedda9aa71c81f12 +Subproject commit a23719d1ee6a08b9c848b255284e40add0ee00f7 diff --git a/include/COLLADA2GLTFWriter.h b/include/COLLADA2GLTFWriter.h index f9748313d..fe5f8e800 100644 --- a/include/COLLADA2GLTFWriter.h +++ b/include/COLLADA2GLTFWriter.h @@ -14,6 +14,7 @@ namespace COLLADA2GLTF { class Writer : public COLLADAFW::IWriter { private: + COLLADASaxFWL::Loader* _loader; GLTF::Asset* _asset; COLLADA2GLTF::Options* _options; COLLADA2GLTF::ExtrasHandler* _extrasHandler; @@ -24,6 +25,7 @@ namespace COLLADA2GLTF { std::map _cameraInstances; std::map _meshInstances; std::map _nodeInstances; + std::map _animationInstances; std::map> _nodeInstanceTargets; std::map>> _meshMaterialPrimitiveMapping; std::map _lightInstances; @@ -41,6 +43,7 @@ namespace COLLADA2GLTF { std::map, std::vector>> _animationData; std::map> _effectTextureMapping; std::map _meshMorphTargets; + std::map> _animationClips; bool writeNodeToGroup(std::vector* group, const COLLADAFW::Node* node); bool writeNodesToGroup(std::vector* group, const COLLADAFW::NodePointerArray& nodes); @@ -48,11 +51,17 @@ namespace COLLADA2GLTF { GLTF::Texture* fromColladaTexture(const COLLADAFW::EffectCommon* effectCommon, COLLADAFW::Texture texture); public: - Writer(GLTF::Asset* asset, COLLADA2GLTF::Options* options, COLLADA2GLTF::ExtrasHandler* handler); + Writer(COLLADASaxFWL::Loader* loader, GLTF::Asset* asset, COLLADA2GLTF::Options* options, COLLADA2GLTF::ExtrasHandler* handler); - /** Deletes the entire scene. - @param errorMessage A message containing informations about the error that occurred. - */ + /** + * Get animation groupings (from animation clips) by index. + */ + std::vector> getAnimationGroups(); + + /** + * Deletes the entire scene. + * @param errorMessage A message containing informations about the error that occurred. + */ void cancel(const std::string& errorMessage); /** Prepare to receive data.*/ @@ -107,6 +116,10 @@ namespace COLLADA2GLTF { @return True on succeeded, false otherwise.*/ virtual bool writeAnimation(const COLLADAFW::Animation* animation); + /** Writes the animation clip. + @return True on succeeded, false otherwise.*/ + virtual bool writeAnimationClip(const COLLADAFW::AnimationClip* animationClip); + /** Writes the animation. @return True on succeeded, false otherwise.*/ virtual bool writeAnimationList(const COLLADAFW::AnimationList* animationList); diff --git a/src/COLLADA2GLTFWriter.cpp b/src/COLLADA2GLTFWriter.cpp index 011ac5838..d52c52b43 100755 --- a/src/COLLADA2GLTFWriter.cpp +++ b/src/COLLADA2GLTFWriter.cpp @@ -4,7 +4,28 @@ const double PI = 3.14159; -COLLADA2GLTF::Writer::Writer(GLTF::Asset* asset, COLLADA2GLTF::Options* options, COLLADA2GLTF::ExtrasHandler* extrasHandler) : _asset(asset), _options(options), _extrasHandler(extrasHandler) {} +COLLADA2GLTF::Writer::Writer(COLLADASaxFWL::Loader* loader, GLTF::Asset* asset, COLLADA2GLTF::Options* options, COLLADA2GLTF::ExtrasHandler* extrasHandler) : _loader(loader), _asset(asset), _options(options), _extrasHandler(extrasHandler) {} + +std::vector> COLLADA2GLTF::Writer::getAnimationGroups() { + std::map animationIndexes; + for (size_t i = 0; i < _asset->animations.size(); i++) { + animationIndexes[_asset->animations[i]] = i; + } + + std::vector> groups; + for (const auto& clip : _animationClips) { + std::vector group; + for (COLLADAFW::UniqueId id : clip.second) { + GLTF::Animation* animation = _animationInstances[id]; + if (animation) { + animation->name = clip.first; + group.push_back(animationIndexes[animation]); + } + } + groups.push_back(group); + } + return groups; +} void COLLADA2GLTF::Writer::cancel(const std::string& errorMessage) { @@ -1311,6 +1332,46 @@ void interpolateTranslation(float* base, std::vector input, std::vector and store its animation group + * associations. + * + * Currently, animations that target the same node cannot be split apart by + * animation clip. This is because COLLADA animations often target for example, + * X, Y, and Z translations as seperate animations and we flatten them together + * because glTF doesn't have the ability to target these channels seperately + * and it would take 3x the space to store them. + * + * This is also done because in practice, animations targetting nodes are + * usually intended to be run together, and if two animations target the + * same node, it is unclear what order their transforms should be applied in + * if there are overlapping keyframes in the animations. To avoid this + * potentially undefined case, node affinity trumps animation clip groups. + * + * However, you could imagine the case (game assets, etc.) where a skinned + * figure has multiple different animations representing different actions + * to be taken. The current animation approach does not handle this, and I + * think it would have to be done as a COLLADA2GLTF::Options because separating + * these animations is non-trivial. They may not even be grouped by clip, + * so that probably isn't a reliable way to do separation. + * + * @return True on succeeded, false otherwise. + */ +bool COLLADA2GLTF::Writer::writeAnimationClip(const COLLADAFW::AnimationClip* animationClip) { + std::vector animationIds; + const COLLADAFW::UniqueIdArray& instanceAnimationUniqueIds = animationClip->getInstanceAnimationUniqueIds(); + for (size_t i = 0; i < instanceAnimationUniqueIds.getCount(); i++) { + const COLLADAFW::UniqueId animationId = instanceAnimationUniqueIds[i]; + animationIds.push_back(animationId); + } + std::string name = animationClip->getName(); + if (name == "") { + name = "animation_clip_" + std::to_string(_animationClips.size()); + } + _animationClips[name] = animationIds; + return true; +} + /** * Reads a and writes a object. * @@ -1634,6 +1695,11 @@ bool COLLADA2GLTF::Writer::writeAnimationList(const COLLADAFW::AnimationList* an channel->sampler = sampler; animation->channels.push_back(channel); } + // Map unique ids to the written animation instances + for (size_t i = 0; i < bindings.getCount(); i++) { + const COLLADAFW::AnimationList::AnimationBinding& binding = bindings[i]; + _animationInstances[binding.animation] = animation; + } _asset->animations.push_back(animation); return true; } diff --git a/src/main.cpp b/src/main.cpp index 02f2666f4..5f3d28c44 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -182,7 +182,7 @@ int main(int argc, const char **argv) { COLLADASaxFWL::Loader* loader = new COLLADASaxFWL::Loader(); COLLADA2GLTF::ExtrasHandler* extrasHandler = new COLLADA2GLTF::ExtrasHandler(loader); - COLLADA2GLTF::Writer* writer = new COLLADA2GLTF::Writer(asset, options, extrasHandler); + COLLADA2GLTF::Writer* writer = new COLLADA2GLTF::Writer(loader, asset, options, extrasHandler); loader->registerExtraDataCallbackHandler((COLLADASaxFWL::IExtraDataCallbackHandler*)extrasHandler); COLLADAFW::Root root(loader, writer); if (!root.loadDocument(options->inputPath)) { @@ -190,7 +190,7 @@ int main(int argc, const char **argv) { return -1; } - asset->mergeAnimations(); + asset->mergeAnimations(writer->getAnimationGroups()); asset->removeUnusedNodes(options); asset->removeUnusedSemantics(); diff --git a/test/src/COLLADA2GLTFWriterTest.cpp b/test/src/COLLADA2GLTFWriterTest.cpp index a12bc235a..2779a5845 100644 --- a/test/src/COLLADA2GLTFWriterTest.cpp +++ b/test/src/COLLADA2GLTFWriterTest.cpp @@ -9,7 +9,7 @@ COLLADA2GLTFWriterTest::COLLADA2GLTFWriterTest() { options = new COLLADA2GLTF::Options(); COLLADASaxFWL::Loader* loader = new COLLADASaxFWL::Loader(); extrasHandler = new COLLADA2GLTF::ExtrasHandler(loader); - writer = new COLLADA2GLTF::Writer(asset, options, extrasHandler); + writer = new COLLADA2GLTF::Writer(loader, asset, options, extrasHandler); } COLLADA2GLTFWriterTest::~COLLADA2GLTFWriterTest() {