From a8bb38f11045662fc5ecd1c0780da20bc48070c9 Mon Sep 17 00:00:00 2001 From: Par Winzell Date: Mon, 30 Oct 2017 20:48:46 -0700 Subject: [PATCH] Progressive morphing + tiny critical bugfix --- src/Fbx2Raw.cpp | 85 +++++++++++++++++++++++++++++--------- src/glTF/PrimitiveData.cpp | 8 ++-- 2 files changed, 71 insertions(+), 22 deletions(-) diff --git a/src/Fbx2Raw.cpp b/src/Fbx2Raw.cpp index d1bfa65..b07bfce 100644 --- a/src/Fbx2Raw.cpp +++ b/src/Fbx2Raw.cpp @@ -505,7 +505,7 @@ public: channels(extractChannels(scene, mesh)) { } - size_t GetShapeCount() const { return channels.size(); } + size_t GetChannelCount() const { return channels.size(); } FbxDouble GetDefaultDeform(size_t channelIx) const { return channels.at(channelIx).defaultDeform; } @@ -711,7 +711,7 @@ static void ReadMesh(RawModel &raw, FbxScene *pScene, FbxNode *pNode, const std: } rawSurface.blendChannels.clear(); - for (size_t shapeIx = 0; shapeIx < blendShapes.GetShapeCount(); shapeIx ++) { + for (size_t shapeIx = 0; shapeIx < blendShapes.GetChannelCount(); shapeIx ++) { for (size_t targetIx = 0; targetIx < blendShapes.GetTargetShapeCount(shapeIx); targetIx ++) { const auto &shape = blendShapes.GetTargetShape(shapeIx, targetIx); float defaultDeform = static_cast(blendShapes.GetDefaultDeform(shapeIx)); @@ -823,7 +823,7 @@ static void ReadMesh(RawModel &raw, FbxScene *pScene, FbxNode *pNode, const std: rawSurface.bounds.AddPoint(vertex.position); - for (size_t shapeIx = 0; shapeIx < blendShapes.GetShapeCount(); shapeIx ++) { + for (size_t shapeIx = 0; shapeIx < blendShapes.GetChannelCount(); shapeIx ++) { for (size_t targetIx = 0; targetIx < blendShapes.GetTargetShapeCount(shapeIx); targetIx ++) { const auto &shape = blendShapes.GetTargetShape(shapeIx, targetIx); @@ -1044,6 +1044,8 @@ static void ReadNodeHierarchy( static void ReadAnimations(RawModel &raw, FbxScene *pScene) { FbxTime::EMode eMode = FbxTime::eFrames24; + const double epsilon = 1e-5f; + const int animationCount = pScene->GetSrcObjectCount(); for (size_t animIx = 0; animIx < animationCount; animIx++) { FbxAnimStack *pAnimStack = pScene->GetSrcObject(animIx); @@ -1088,15 +1090,6 @@ static void ReadAnimations(RawModel &raw, FbxScene *pScene) RawChannel channel; channel.nodeIndex = raw.GetNodeByName(pNode->GetName()); - std::vector shapeAnimCurves; - FbxNodeAttribute *nodeAttr = pNode->GetNodeAttribute(); - if (nodeAttr != nullptr && nodeAttr->GetAttributeType() == FbxNodeAttribute::EType::eMesh) { - // it's inelegant to recreate this same access class multiple times, but it's also dirt cheap... - FbxBlendShapesAccess blendShapes(pScene, dynamic_cast(nodeAttr)); - for (size_t channelIx = 0; channelIx < blendShapes.GetShapeCount(); channelIx ++) { - shapeAnimCurves.push_back(blendShapes.GetAnimation(channelIx, animIx)); - } - } for (FbxLongLong frameIndex = firstFrameIndex; frameIndex <= lastFrameIndex; frameIndex++) { FbxTime pTime; pTime.SetFrame(frameIndex, eMode); @@ -1106,7 +1099,6 @@ static void ReadAnimations(RawModel &raw, FbxScene *pScene) const FbxQuaternion localRotation = localTransform.GetQ(); const FbxVector4 localScale = computeLocalScale(pNode, pTime); - const double epsilon = 1e-5f; hasTranslation |= ( fabs(localTranslation[0] - baseTranslation[0]) > epsilon || fabs(localTranslation[1] - baseTranslation[1]) > epsilon || @@ -1124,14 +1116,69 @@ static void ReadAnimations(RawModel &raw, FbxScene *pScene) channel.translations.push_back(toVec3f(localTranslation)); channel.rotations.push_back(toQuatf(localRotation)); channel.scales.push_back(toVec3f(localScale)); + } - for (auto curve : shapeAnimCurves) { - float weight = 0f; - if (curve) { - weight = curve->Evaluate(pTime) * 0.01f; // [0, 100] in FBX, [0, 1] in glTF - hasMorphs |= (fabs(weight) > epsilon); + std::vector shapeAnimCurves; + FbxNodeAttribute *nodeAttr = pNode->GetNodeAttribute(); + if (nodeAttr != nullptr && nodeAttr->GetAttributeType() == FbxNodeAttribute::EType::eMesh) { + // it's inelegant to recreate this same access class multiple times, but it's also dirt cheap... + FbxBlendShapesAccess blendShapes(pScene, dynamic_cast(nodeAttr)); + + for (FbxLongLong frameIndex = firstFrameIndex; frameIndex <= lastFrameIndex; frameIndex++) { + FbxTime pTime; + pTime.SetFrame(frameIndex, eMode); + + for (size_t channelIx = 0; channelIx < blendShapes.GetChannelCount(); channelIx++) { + auto curve = blendShapes.GetAnimation(channelIx, animIx); + float influence = curve->Evaluate(pTime); // 0-100 + + int targetCount = static_cast(blendShapes.GetTargetShapeCount(channelIx)); + + // the target shape 'fullWeight' values are a strictly ascending list of floats (between + // 0 and 100), forming a sequence of intervals -- this convenience function figures out if + // 'p' lays between some certain target fullWeights, and if so where (from 0 to 1). + auto findInInterval = [&](double p, int n) { + if (n >= targetCount) { + // p is certainly completely left of this interval + return NAN; + } + double leftWeight = (n >= 0) ? blendShapes.GetTargetShape(channelIx, n).fullWeight : 0; + if (p < leftWeight) { + return NAN; + } + double rightWeight = (n < targetCount-1) ? blendShapes.GetTargetShape(channelIx, n+1).fullWeight : 100; + if (p > rightWeight) { + return NAN; + } + // at this point leftWeight <= p <= rightWeight, return [0, 1] + return static_cast((p - leftWeight) / (rightWeight - leftWeight)); + }; + + for (int targetIx = 0; targetIx < targetCount; targetIx++) { + if (curve) { + float result = findInInterval(influence, targetIx-1); + if (!isnan(result)) { + // we're transitioning into targetIx + channel.weights.push_back(result); + hasMorphs = true; + continue; + } + if (targetIx > 0) { + result = findInInterval(influence, targetIx); + if (!isnan(result)) { + // we're transitioning AWAY from targetIx + channel.weights.push_back(1.0f - result); + hasMorphs = true; + continue; + } + } + } + + // this is here because we have to fill in a weight for every channelIx/targetIx permutation, + // regardless of whether or not they participate in this animation. + channel.weights.push_back(0.0f); + } } - channel.weights.push_back(weight); } } diff --git a/src/glTF/PrimitiveData.cpp b/src/glTF/PrimitiveData.cpp index 9423117..ab39eab 100644 --- a/src/glTF/PrimitiveData.cpp +++ b/src/glTF/PrimitiveData.cpp @@ -62,9 +62,11 @@ void to_json(json &j, const PrimitiveData &d) { int pIx, nIx, tIx; for (auto accessor : d.targetAccessors) { std::tie(pIx, nIx, tIx) = accessor; - if (pIx >= 0) { targets.push_back({{ "POSITION", pIx }}); } - if (nIx >= 0) { targets.push_back({{ "NORMAL", nIx }}); } - if (tIx >= 0) { targets.push_back({{ "TANGENT", tIx }}); } + json target {}; + if (pIx >= 0) { target["POSITION"] = pIx; } + if (nIx >= 0) { target["NORMAL"] = nIx; } + if (tIx >= 0) { target["TANGENT"] = tIx; } + targets.push_back(target); } j["targets"] = targets; }