/* * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include "FFilamentAsset.h" #include "FFilamentInstance.h" #include "MorphHelper.h" #include "math.h" #include "upcast.h" #include #include #include #include #include #include #include #include #include #include #include #include using namespace filament; using namespace filament::math; using namespace std; using namespace utils; namespace gltfio { using TimeValues = map; using SourceValues = vector; using BoneVector = vector; struct Sampler { TimeValues times; SourceValues values; enum { LINEAR, STEP, CUBIC } interpolation; }; struct Channel { const Sampler* sourceData; Entity targetEntity; enum { TRANSLATION, ROTATION, SCALE, WEIGHTS } transformType; }; struct Animation { float duration; std::string name; vector samplers; vector channels; }; struct AnimatorImpl { vector animations; BoneVector boneMatrices; FFilamentAsset* asset = nullptr; FFilamentInstance* instance = nullptr; RenderableManager* renderableManager; TransformManager* transformManager; vector weights; MorphHelper* morpher; void addChannels(const NodeMap& nodeMap, const cgltf_animation& srcAnim, Animation& dst); void applyAnimation(const Channel& channel, float t, size_t prevIndex, size_t nextIndex); }; static void createSampler(const cgltf_animation_sampler& src, Sampler& dst) { // Copy the time values into a red-black tree. const cgltf_accessor* timelineAccessor = src.input; const uint8_t* timelineBlob = (const uint8_t*) timelineAccessor->buffer_view->buffer->data; const float* timelineFloats = (const float*) (timelineBlob + timelineAccessor->offset + timelineAccessor->buffer_view->offset); for (size_t i = 0, len = timelineAccessor->count; i < len; ++i) { dst.times[timelineFloats[i]] = i; } // Convert source data to float. const cgltf_accessor* valuesAccessor = src.output; switch (valuesAccessor->type) { case cgltf_type_scalar: dst.values.resize(valuesAccessor->count); cgltf_accessor_unpack_floats(src.output, &dst.values[0], valuesAccessor->count); break; case cgltf_type_vec3: dst.values.resize(valuesAccessor->count * 3); cgltf_accessor_unpack_floats(src.output, &dst.values[0], valuesAccessor->count * 3); break; case cgltf_type_vec4: dst.values.resize(valuesAccessor->count * 4); cgltf_accessor_unpack_floats(src.output, &dst.values[0], valuesAccessor->count * 4); break; default: GLTFIO_WARN("Unknown animation type."); return; } switch (src.interpolation) { case cgltf_interpolation_type_linear: dst.interpolation = Sampler::LINEAR; break; case cgltf_interpolation_type_step: dst.interpolation = Sampler::STEP; break; case cgltf_interpolation_type_cubic_spline: dst.interpolation = Sampler::CUBIC; break; } } static void setTransformType(const cgltf_animation_channel& src, Channel& dst) { switch (src.target_path) { case cgltf_animation_path_type_translation: dst.transformType = Channel::TRANSLATION; break; case cgltf_animation_path_type_rotation: dst.transformType = Channel::ROTATION; break; case cgltf_animation_path_type_scale: dst.transformType = Channel::SCALE; break; case cgltf_animation_path_type_weights: dst.transformType = Channel::WEIGHTS; break; case cgltf_animation_path_type_invalid: GLTFIO_WARN("Unsupported channel path."); break; } } static bool validateAnimation(const cgltf_animation& anim) { for (cgltf_size j = 0; j < anim.channels_count; ++j) { const cgltf_animation_channel& channel = anim.channels[j]; const cgltf_animation_sampler* sampler = channel.sampler; if (!channel.target_node) { continue; } if (!channel.sampler) { return false; } cgltf_size components = 1; if (channel.target_path == cgltf_animation_path_type_weights) { if (!channel.target_node->mesh || !channel.target_node->mesh->primitives_count) { return false; } components = channel.target_node->mesh->primitives[0].targets_count; } cgltf_size values = sampler->interpolation == cgltf_interpolation_type_cubic_spline ? 3 : 1; if (sampler->input->count * components * values != sampler->output->count) { return false; } } return true; } Animator::Animator(FFilamentAsset* asset, FFilamentInstance* instance) { assert(asset->mResourcesLoaded && asset->mSourceAsset); mImpl = new AnimatorImpl(); mImpl->asset = asset; mImpl->instance = instance; mImpl->renderableManager = &asset->mEngine->getRenderableManager(); mImpl->transformManager = &asset->mEngine->getTransformManager(); mImpl->morpher = new MorphHelper(asset, instance); const cgltf_data* srcAsset = asset->mSourceAsset->hierarchy; const cgltf_animation* srcAnims = srcAsset->animations; for (cgltf_size i = 0, len = srcAsset->animations_count; i < len; ++i) { const cgltf_animation& anim = srcAnims[i]; if (!validateAnimation(anim)) { GLTFIO_WARN("Disabling animation due to validation failure."); return; } } // Loop over the glTF animation definitions. mImpl->animations.resize(srcAsset->animations_count); for (cgltf_size i = 0, len = srcAsset->animations_count; i < len; ++i) { const cgltf_animation& srcAnim = srcAnims[i]; Animation& dstAnim = mImpl->animations[i]; dstAnim.duration = 0; if (srcAnim.name) { dstAnim.name = srcAnim.name; } // Import each glTF sampler into a custom data structure. cgltf_animation_sampler* srcSamplers = srcAnim.samplers; dstAnim.samplers.resize(srcAnim.samplers_count); for (cgltf_size j = 0, nsamps = srcAnim.samplers_count; j < nsamps; ++j) { const cgltf_animation_sampler& srcSampler = srcSamplers[j]; Sampler& dstSampler = dstAnim.samplers[j]; createSampler(srcSampler, dstSampler); if (dstSampler.times.size() > 1) { float maxtime = (--dstSampler.times.end())->first; dstAnim.duration = std::max(dstAnim.duration, maxtime); } } // Import each glTF channel into a custom data structure. if (instance) { mImpl->addChannels(instance->nodeMap, srcAnim, dstAnim); } else if (!asset->isInstanced()) { mImpl->addChannels(asset->mNodeMap, srcAnim, dstAnim); } else { for (FFilamentInstance* instance : asset->mInstances) { mImpl->addChannels(instance->nodeMap, srcAnim, dstAnim); } } } } void Animator::addInstance(FFilamentInstance* instance) { const cgltf_data* srcAsset = mImpl->asset->mSourceAsset->hierarchy; const cgltf_animation* srcAnims = srcAsset->animations; for (cgltf_size i = 0, len = srcAsset->animations_count; i < len; ++i) { const cgltf_animation& srcAnim = srcAnims[i]; Animation& dstAnim = mImpl->animations[i]; mImpl->addChannels(instance->nodeMap, srcAnim, dstAnim); } } Animator::~Animator() { delete mImpl->morpher; delete mImpl; } size_t Animator::getAnimationCount() const { return mImpl->animations.size(); } void Animator::applyAnimation(size_t animationIndex, float time) const { const Animation& anim = mImpl->animations[animationIndex]; TransformManager* transformManager = mImpl->transformManager; RenderableManager* renderableManager = mImpl->renderableManager; time = fmod(time, anim.duration); for (const auto& channel : anim.channels) { const Sampler* sampler = channel.sourceData; if (sampler->times.size() < 2) { continue; } const TimeValues& times = sampler->times; // Find the first keyframe after the given time, or the keyframe that matches it exactly. TimeValues::const_iterator iter = times.lower_bound(time); // Compute the interpolant (between 0 and 1) and determine the keyframe pair. float t = 0.0f; size_t nextIndex; size_t prevIndex; if (iter == times.end()) { nextIndex = times.size() - 1; prevIndex = nextIndex; } else if (iter == times.begin()) { nextIndex = 0; prevIndex = 0; } else { TimeValues::const_iterator prev = iter; --prev; nextIndex = iter->second; prevIndex = prev->second; const float nextTime = iter->first; const float prevTime = prev->first; float deltaTime = nextTime - prevTime; assert(deltaTime >= 0); if (deltaTime > 0) { t = (time - prevTime) / deltaTime; } } if (sampler->interpolation == Sampler::STEP) { t = 0.0f; } mImpl->applyAnimation(channel, t, prevIndex, nextIndex); } } void Animator::updateBoneMatrices() { auto renderableManager = mImpl->renderableManager; auto transformManager = mImpl->transformManager; auto update = [=](const SkinVector& skins, BoneVector& boneVector) { for (const auto& skin : skins) { size_t njoints = skin.joints.size(); boneVector.resize(njoints); for (const auto& entity : skin.targets) { auto renderable = renderableManager->getInstance(entity); if (!renderable) { continue; } mat4f inverseGlobalTransform; auto xformable = transformManager->getInstance(entity); if (xformable) { inverseGlobalTransform = inverse(transformManager->getWorldTransform(xformable)); } for (size_t boneIndex = 0; boneIndex < njoints; ++boneIndex) { const auto& joint = skin.joints[boneIndex]; TransformManager::Instance jointInstance = transformManager->getInstance(joint); mat4f globalJointTransform = transformManager->getWorldTransform(jointInstance); boneVector[boneIndex] = inverseGlobalTransform * globalJointTransform * skin.inverseBindMatrices[boneIndex]; } renderableManager->setBones(renderable, boneVector.data(), boneVector.size()); } } }; if (mImpl->instance) { update(mImpl->instance->skins, mImpl->boneMatrices); } else if (!mImpl->asset->isInstanced()) { update(mImpl->asset->mSkins, mImpl->boneMatrices); } else { for (FFilamentInstance* instance : mImpl->asset->mInstances) { update(instance->skins, mImpl->boneMatrices); } } } float Animator::getAnimationDuration(size_t animationIndex) const { return mImpl->animations[animationIndex].duration; } const char* Animator::getAnimationName(size_t animationIndex) const { return mImpl->animations[animationIndex].name.c_str(); } void AnimatorImpl::addChannels(const NodeMap& nodeMap, const cgltf_animation& srcAnim, Animation& dst) { cgltf_animation_channel* srcChannels = srcAnim.channels; cgltf_animation_sampler* srcSamplers = srcAnim.samplers; const Sampler* samplers = dst.samplers.data(); for (cgltf_size j = 0, nchans = srcAnim.channels_count; j < nchans; ++j) { const cgltf_animation_channel& srcChannel = srcChannels[j]; auto iter = nodeMap.find(srcChannel.target_node); if (UTILS_UNLIKELY(iter == nodeMap.end())) { if (GLTFIO_VERBOSE) { slog.w << "No scene root contains node "; if (srcChannel.target_node->name) { slog.w << "'" << srcChannel.target_node->name << "' "; } slog.w << "for animation "; if (srcAnim.name) { slog.w << "'" << srcAnim.name << "' "; } slog.w << "in channel " << j << io::endl; } continue; } Entity targetEntity = iter.value(); Channel dstChannel; dstChannel.sourceData = samplers + (srcChannel.sampler - srcSamplers); dstChannel.targetEntity = targetEntity; setTransformType(srcChannel, dstChannel); dst.channels.push_back(dstChannel); } } void AnimatorImpl::applyAnimation(const Channel& channel, float t, size_t prevIndex, size_t nextIndex) { const Sampler* sampler = channel.sourceData; const TimeValues& times = sampler->times; TransformManager::Instance node = transformManager->getInstance(channel.targetEntity); // Perform the interpolation. This is a simple but inefficient implementation; Filament // stores transforms as mat4's but glTF animation is based on TRS (translation rotation // scale). mat4f xform = transformManager->getTransform(node); float3 scale; quatf rotation; float3 translation; decomposeMatrix(xform, &translation, &rotation, &scale); switch (channel.transformType) { case Channel::SCALE: { const float3* srcVec3 = (const float3*) sampler->values.data(); if (sampler->interpolation == Sampler::CUBIC) { float3 vert0 = srcVec3[prevIndex * 3 + 1]; float3 tang0 = srcVec3[prevIndex * 3 + 2]; float3 tang1 = srcVec3[nextIndex * 3]; float3 vert1 = srcVec3[nextIndex * 3 + 1]; scale = cubicSpline(vert0, tang0, vert1, tang1, t); } else { scale = ((1 - t) * srcVec3[prevIndex]) + (t * srcVec3[nextIndex]); } break; } case Channel::TRANSLATION: { const float3* srcVec3 = (const float3*) sampler->values.data(); if (sampler->interpolation == Sampler::CUBIC) { float3 vert0 = srcVec3[prevIndex * 3 + 1]; float3 tang0 = srcVec3[prevIndex * 3 + 2]; float3 tang1 = srcVec3[nextIndex * 3]; float3 vert1 = srcVec3[nextIndex * 3 + 1]; translation = cubicSpline(vert0, tang0, vert1, tang1, t); } else { translation = ((1 - t) * srcVec3[prevIndex]) + (t * srcVec3[nextIndex]); } break; } case Channel::ROTATION: { const quatf* srcQuat = (const quatf*) sampler->values.data(); if (sampler->interpolation == Sampler::CUBIC) { quatf vert0 = srcQuat[prevIndex * 3 + 1]; quatf tang0 = srcQuat[prevIndex * 3 + 2]; quatf tang1 = srcQuat[nextIndex * 3]; quatf vert1 = srcQuat[nextIndex * 3 + 1]; rotation = normalize(cubicSpline(vert0, tang0, vert1, tang1, t)); } else { rotation = slerp(srcQuat[prevIndex], srcQuat[nextIndex], t); } break; } case Channel::WEIGHTS: { const float* const samplerValues = sampler->values.data(); assert(sampler->values.size() % times.size() == 0); const int valuesPerKeyframe = sampler->values.size() / times.size(); if (sampler->interpolation == Sampler::CUBIC) { assert(valuesPerKeyframe % 3 == 0); const int numMorphTargets = valuesPerKeyframe / 3; const float* const inTangents = samplerValues; const float* const splineVerts = samplerValues + numMorphTargets; const float* const outTangents = samplerValues + numMorphTargets * 2; weights.resize(numMorphTargets); for (int comp = 0; comp < numMorphTargets; ++comp) { float vert0 = splineVerts[comp + prevIndex * valuesPerKeyframe]; float tang0 = outTangents[comp + prevIndex * valuesPerKeyframe]; float tang1 = inTangents[comp + nextIndex * valuesPerKeyframe]; float vert1 = splineVerts[comp + nextIndex * valuesPerKeyframe]; weights[comp] = cubicSpline(vert0, tang0, vert1, tang1, t); } } else { weights.resize(valuesPerKeyframe); for (int comp = 0; comp < valuesPerKeyframe; ++comp) { float previous = samplerValues[comp + prevIndex * valuesPerKeyframe]; float current = samplerValues[comp + nextIndex * valuesPerKeyframe]; weights[comp] = (1 - t) * previous + t * current; } } morpher->applyWeights(channel.targetEntity, weights.data(), weights.size()); return; } } xform = composeMatrix(translation, rotation, scale); transformManager->setTransform(node, xform); } } // namespace gltfio