diff --git a/doomsday/sdk/libgui/include/de/graphics/modeldrawable.h b/doomsday/sdk/libgui/include/de/graphics/modeldrawable.h index 1816d3438e..7bbf9c2b50 100644 --- a/doomsday/sdk/libgui/include/de/graphics/modeldrawable.h +++ b/doomsday/sdk/libgui/include/de/graphics/modeldrawable.h @@ -164,6 +164,18 @@ class LIBGUI_PUBLIC ModelDrawable : public AssetGroup /// Referenced node or animation was not found in the model. @ingroup errors DENG2_ERROR(InvalidError); + enum Flag + { + /** + * Node transformations always done, even when there are no + * animation sequences. + */ + AlwaysTransformNodes = 0x1, + + DefaultFlags = 0 + }; + Q_DECLARE_FLAGS(Flags, Flag) + public: Animator(Constructor sequenceConstructor = OngoingSequence::make); Animator(ModelDrawable const &model, @@ -173,6 +185,10 @@ class LIBGUI_PUBLIC ModelDrawable : public AssetGroup void setModel(ModelDrawable const &model); + void setFlags(Flags const &flags, FlagOp op = SetFlags); + + Flags flags() const; + /** * Returns the model with which this animation is being used. */ @@ -278,6 +294,9 @@ class LIBGUI_PUBLIC ModelDrawable : public AssetGroup /** * Rendering pass. When no rendering passes are specified, all the meshes * of the model are rendered in one pass with regular alpha blending. + * + * @todo Use GLState instead of than having individual GL parameters? + * The state must be set up to not touch viewport, clipping, etc., though. */ struct LIBGUI_PUBLIC Pass { @@ -559,8 +578,9 @@ class LIBGUI_PUBLIC ModelDrawable : public AssetGroup DENG2_PRIVATE(d) }; -Q_DECLARE_OPERATORS_FOR_FLAGS(ModelDrawable::Appearance::Flags) +Q_DECLARE_OPERATORS_FOR_FLAGS(ModelDrawable::Animator::Flags) Q_DECLARE_OPERATORS_FOR_FLAGS(ModelDrawable::Animator::OngoingSequence::Flags) +Q_DECLARE_OPERATORS_FOR_FLAGS(ModelDrawable::Appearance::Flags) } // namespace de diff --git a/doomsday/sdk/libgui/src/graphics/modeldrawable.cpp b/doomsday/sdk/libgui/src/graphics/modeldrawable.cpp index 2c5c5d942e..e11a254fe3 100644 --- a/doomsday/sdk/libgui/src/graphics/modeldrawable.cpp +++ b/doomsday/sdk/libgui/src/graphics/modeldrawable.cpp @@ -737,6 +737,7 @@ DENG2_PIMPL(ModelDrawable) void buildNodeLookup(aiNode const &node) { String const name = node.mName.C_Str(); + qDebug() << "Node:" << name; if(!name.isEmpty()) { nodeNameToPtr.insert(name, &node); @@ -1053,19 +1054,18 @@ DENG2_PIMPL(ModelDrawable) struct AccumData { Animator const &animator; - ddouble time; - aiAnimation const *anim; + ddouble time = 0.0; + aiAnimation const *anim = nullptr; QVector finalTransforms; AccumData(Animator const &animator, int boneCount) : animator(animator) - , time(0) - , anim(0) , finalTransforms(boneCount) {} aiNodeAnim const *findNodeAnim(aiNode const &node) const { + if(!anim) return nullptr; for(duint i = 0; i < anim->mNumChannels; ++i) { aiNodeAnim const *na = anim->mChannels[i]; @@ -1074,18 +1074,19 @@ DENG2_PIMPL(ModelDrawable) return na; } } - return 0; + return nullptr; } }; void accumulateAnimationTransforms(Animator const &animator, ddouble time, - aiAnimation const &animSeq, + aiAnimation const *animSeq, aiNode const &rootNode) const { AccumData data(animator, boneCount()); - data.anim = &animSeq; - data.time = std::fmod(secondsToTicks(time, animSeq), animSeq.mDuration); // wrap animation + data.anim = animSeq; + // Wrap animation time. + data.time = animSeq? std::fmod(secondsToTicks(time, *animSeq), animSeq->mDuration) : time; accumulateTransforms(rootNode, data); @@ -1101,6 +1102,10 @@ DENG2_PIMPL(ModelDrawable) { Matrix4f nodeTransform = convertMatrix(node.mTransformation); + // Additional rotation? + Vector4f const axisAngle = data.animator.extraRotationForNode(node.mName.C_Str()); + + // Transform according to the animation sequence. if(aiNodeAnim const *anim = data.findNodeAnim(node)) { // Interpolate for this point in time. @@ -1108,15 +1113,23 @@ DENG2_PIMPL(ModelDrawable) Matrix4f const scaling = Matrix4f::scale(interpolateScaling(data.time, *anim)); Matrix4f rotation = convertMatrix(aiMatrix4x4(interpolateRotation(data.time, *anim).GetMatrix())); - // Check for an additional rotation. - Vector4f const axisAngle = data.animator.extraRotationForNode(node.mName.C_Str()); if(!fequal(axisAngle.w, 0)) { + // Include the custom extra rotation. rotation = Matrix4f::rotate(axisAngle.w, axisAngle) * rotation; } nodeTransform = translation * rotation * scaling; } + else + { + // Model does not specify animation information for this node. + // Only apply the possible additional rotation. + if(!fequal(axisAngle.w, 0)) + { + nodeTransform = Matrix4f::rotate(axisAngle.w, axisAngle) * nodeTransform; + } + } Matrix4f globalTransform = parentTransform * nodeTransform; @@ -1198,7 +1211,19 @@ DENG2_PIMPL(ModelDrawable) void updateMatricesFromAnimation(Animator const *animator) const { - if(!scene->HasAnimations() || !animator) return; + // Cannot do anything without an Animator. + if(!animator) return; + + if(!scene->HasAnimations() || !animator->count()) + { + // If requested, run through the bone transformations even when + // no animations are active. + if(animator->flags().testFlag(Animator::AlwaysTransformNodes)) + { + accumulateAnimationTransforms(*animator, 0, nullptr, *scene->mRootNode); + return; + } + } // Apply all current animations. for(int i = 0; i < animator->count(); ++i) @@ -1211,7 +1236,7 @@ DENG2_PIMPL(ModelDrawable) accumulateAnimationTransforms(*animator, animator->currentTime(i), - *scene->mAnimations[animSeq.animId], + scene->mAnimations[animSeq.animId], *nodeNameToPtr[animSeq.node]); } } @@ -1669,6 +1694,7 @@ DENG2_PIMPL_NOREF(ModelDrawable::Animator) Constructor constructor; ModelDrawable const *model = nullptr; QList anims; + Flags flags = DefaultFlags; Instance(Constructor ctr, ModelDrawable const *mdl = 0) : constructor(ctr) @@ -1768,6 +1794,16 @@ void ModelDrawable::Animator::setModel(ModelDrawable const &model) d->setModel(&model); } +void ModelDrawable::Animator::setFlags(Flags const &flags, FlagOp op) +{ + applyFlagOperation(d->flags, flags, op); +} + +ModelDrawable::Animator::Flags ModelDrawable::Animator::flags() const +{ + return d->flags; +} + ModelDrawable const &ModelDrawable::Animator::model() const { DENG2_ASSERT(d->model != 0);