From 55ffb6d7d2ec6201ad78ab1df58f37f63bfc10a7 Mon Sep 17 00:00:00 2001 From: Max Yari Date: Thu, 4 Apr 2024 01:30:27 +0100 Subject: [PATCH 01/34] Animation blending implementation for NIF and osgAnimation through YAML files Signed-off-by: Sam Hellawell --- apps/openmw/mwrender/animation.cpp | 190 ++++++++++- apps/openmw/mwrender/animation.hpp | 29 +- apps/openmw/mwrender/animblendcontroller.cpp | 318 ++++++++++++++++++ apps/openmw/mwrender/animblendcontroller.hpp | 109 ++++++ components/CMakeLists.txt | 4 +- components/nif/niftypes.hpp | 11 + components/nifosg/controller.cpp | 39 ++- components/nifosg/controller.hpp | 2 + components/nifosg/matrixtransform.cpp | 12 +- components/nifosg/matrixtransform.hpp | 3 +- components/resource/animblendrulesmanager.cpp | 94 ++++++ components/resource/animblendrulesmanager.hpp | 36 ++ components/resource/resourcesystem.cpp | 8 + components/resource/resourcesystem.hpp | 3 + components/sceneutil/animblendrules.cpp | 177 ++++++++++ components/sceneutil/animblendrules.hpp | 49 +++ components/sceneutil/keyframe.hpp | 12 + components/settings/categories/game.hpp | 1 + files/data/CMakeLists.txt | 3 + files/data/animations/animation-config.yaml | 70 ++++ files/settings-default.cfg | 4 + 21 files changed, 1143 insertions(+), 31 deletions(-) create mode 100644 apps/openmw/mwrender/animblendcontroller.cpp create mode 100644 apps/openmw/mwrender/animblendcontroller.hpp create mode 100644 components/resource/animblendrulesmanager.cpp create mode 100644 components/resource/animblendrulesmanager.hpp create mode 100644 components/sceneutil/animblendrules.cpp create mode 100644 components/sceneutil/animblendrules.hpp create mode 100644 files/data/animations/animation-config.yaml diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 38aa8b9f07..9afc5baafa 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -13,8 +13,11 @@ #include #include +#include + #include +#include #include #include @@ -56,6 +59,7 @@ #include "../mwmechanics/weapontype.hpp" #include "actorutil.hpp" +#include "animblendcontroller.cpp" #include "rotatecontroller.hpp" #include "util.hpp" #include "vismask.hpp" @@ -400,6 +404,61 @@ namespace namespace MWRender { + void assignBoneBlendCallbackRecursive( + BoneAnimBlendController* controller, ActiveControllersVector& activeControllers, osg::Node* parent, bool isRoot) + { + // Attempt to cast node to an osgAnimation::Bone + osgAnimation::Bone* bone = dynamic_cast(parent); + if (!isRoot && bone) + { + // Wrapping in a custom callback object allows for nested callback chaining, otherwise it has link to self + // issues we need to share the base BoneAnimBlendController as that contains blending information and is + // guaranteed to update before + osg::ref_ptr cb = new BoneAnimBlendControllerWrapper(controller, bone); + + // Ensure there is no other AnimBlendController - this can happen if using + // multiple animations with different roots, such as NPC animation + osg::Callback* updateCb = bone->getUpdateCallback(); + while (updateCb) + { + if (updateCb->className() == std::string(controller->className())) + { + osg::ref_ptr nextCb = updateCb->getNestedCallback(); + bone->removeUpdateCallback(updateCb); + updateCb = nextCb; + } + else + { + updateCb = updateCb->getNestedCallback(); + } + } + + // Find UpdateBone callback and bind to just after that (order is important) + // NOTE: if it doesnt have an UpdateBone callback, we shouldnt be doing blending! + updateCb = bone->getUpdateCallback(); + while (updateCb) + { + if (updateCb->className() == std::string("UpdateBone")) + { + // Override the immediate callback after the UpdateBone + osg::ref_ptr lastCb = updateCb->getNestedCallback(); + updateCb->setNestedCallback(cb); + if (lastCb) + cb->setNestedCallback(lastCb); + break; + } + + updateCb = updateCb->getNestedCallback(); + } + } + + // Traverse childrne if this is a group + osg::Group* group = parent->asGroup(); + if (group) + for (unsigned int i = 0; i < group->getNumChildren(); ++i) + assignBoneBlendCallbackRecursive(controller, activeControllers, group->getChild(i), false); + } + class TransparencyUpdater : public SceneUtil::StateSetUpdater { public: @@ -449,6 +508,8 @@ namespace MWRender ControllerMap mControllerMap[sNumBlendMasks]; const SceneUtil::TextKeyMap& getTextKeys() const; + + osg::ref_ptr mAnimBlendRules; }; void UpdateVfxCallback::operator()(osg::Node* node, osg::NodeVisitor* nv) @@ -605,8 +666,11 @@ namespace MWRender for (const auto& name : mResourceSystem->getVFS()->getRecursiveDirectoryIterator(animationPath)) { + if (Misc::getFileExtension(name) == "kf") + { addSingleAnimSource(name, baseModel); + } } } @@ -623,17 +687,18 @@ namespace MWRender loadAllAnimationsInFolder(kfname, baseModel); } - void Animation::addSingleAnimSource(const std::string& kfname, const std::string& baseModel) + std::shared_ptr Animation::addSingleAnimSource( + const std::string& kfname, const std::string& baseModel) { if (!mResourceSystem->getVFS()->exists(kfname)) - return; + return nullptr; auto animsrc = std::make_shared(); animsrc->mKeyframes = mResourceSystem->getKeyframeManager()->get(kfname); if (!animsrc->mKeyframes || animsrc->mKeyframes->mTextKeys.empty() || animsrc->mKeyframes->mKeyframeControllers.empty()) - return; + return nullptr; const NodeMap& nodeMap = getNodeMap(); const auto& controllerMap = animsrc->mKeyframes->mKeyframeControllers; @@ -661,7 +726,7 @@ namespace MWRender animsrc->mControllerMap[blendMask].insert(std::make_pair(bonename, cloned)); } - mAnimSources.push_back(std::move(animsrc)); + mAnimSources.push_back(animsrc); for (const std::string& group : mAnimSources.back()->getTextKeys().getGroups()) mSupportedAnimations.insert(group); @@ -693,6 +758,30 @@ namespace MWRender break; } } + + // Get the blending rules + if (Settings::game().mSmoothAnimTransitions) + { + // Note, even if the actual config is .json - we should send a .yaml path to AnimBlendRulesManager, the + // manager will check for .json if it will not find a specified .yaml file. + auto yamlpath = kfname; + Misc::StringUtils::replaceLast(yamlpath, ".kf", ".yaml"); + Misc::StringUtils::replaceLast(yamlpath, ".dae", ".yaml"); + + // globalBlendConfigPath is only used with actors! Objects have no default blending. + std::string_view globalBlendConfigPath = "animations/animation-config.yaml"; + + osg::ref_ptr blendRules; + if (mPtr.getClass().isActor()) + blendRules = mResourceSystem->getAnimBlendRulesManager()->getRules(globalBlendConfigPath, yamlpath); + else + blendRules = mResourceSystem->getAnimBlendRulesManager()->getRules(yamlpath); + + // At this point blendRules will either be nullptr or an AnimBlendRules instance with > 0 rules inside. + animsrc->mAnimBlendRules = blendRules; + } + + return animsrc; } void Animation::clearAnimSources() @@ -811,25 +900,33 @@ namespace MWRender if (!mObjectRoot || mAnimSources.empty()) return; + // Log(Debug::Info) << "Please play: " << groupname << ":" << start << "..." << stop << " mask: " << blendMask; + if (groupname.empty()) { resetActiveGroups(); return; } + AnimStateMap::iterator foundstateiter = mStates.find(groupname); + if (foundstateiter != mStates.end()) + { + foundstateiter->second.mPriority = priority; + } + AnimStateMap::iterator stateiter = mStates.begin(); while (stateiter != mStates.end()) { - if (stateiter->second.mPriority == priority) + if (stateiter->second.mPriority == priority && stateiter->first != groupname) + // This MIGH be a problem since we want old states to be still running so the AnimBlendingController can + // blend them properly mStates.erase(stateiter++); else ++stateiter; } - stateiter = mStates.find(groupname); - if (stateiter != mStates.end()) + if (foundstateiter != mStates.end()) { - stateiter->second.mPriority = priority; resetActiveGroups(); return; } @@ -849,6 +946,8 @@ namespace MWRender state.mPriority = priority; state.mBlendMask = blendMask; state.mAutoDisable = autodisable; + state.mGroupname = groupname; + state.mStartKey = start; mStates[std::string{ groupname }] = state; if (state.mPlaying) @@ -1004,7 +1103,7 @@ namespace MWRender AnimStateMap::const_iterator state = mStates.begin(); for (; state != mStates.end(); ++state) { - if (!(state->second.mBlendMask & (1 << blendMask))) + if (!state->second.blendMaskContains(blendMask)) continue; if (active == mStates.end() @@ -1019,6 +1118,7 @@ namespace MWRender if (active != mStates.end()) { std::shared_ptr animsrc = active->second.mSource; + AnimBlendStateData stateData = active->second.asAnimBlendStateData(); for (AnimSource::ControllerMap::iterator it = animsrc->mControllerMap[blendMask].begin(); it != animsrc->mControllerMap[blendMask].end(); ++it) @@ -1026,7 +1126,72 @@ namespace MWRender osg::ref_ptr node = getNodeMap().at( it->first); // this should not throw, we already checked for the node existing in addAnimSource - osg::Callback* callback = it->second->getAsCallback(); + osg::Callback* callback; + const bool useSmoothAnims = Settings::game().mSmoothAnimTransitions; + if (useSmoothAnims && dynamic_cast(node.get())) + { + // Update an existing animation blending controller or create a new one for NIF animations + osg::ref_ptr animController; + + if (mAnimBlendControllers.contains(node)) + { + animController = mAnimBlendControllers[node]; + animController->setKeyframeTrack(it->second, stateData, animsrc->mAnimBlendRules); + } + else + { + animController = osg::ref_ptr( + new AnimBlendController(it->second, stateData, animsrc->mAnimBlendRules)); + + mAnimBlendControllers[node] = animController; + } + + it->second->mTime = active->second.mTime; + + callback = animController->getAsCallback(); + } + else if (useSmoothAnims && dynamic_cast(node.get())) + { + // Update an existing animation blending controller or create a new one for osgAnimation + osg::ref_ptr animController; + + if (mBoneAnimBlendControllers.contains(node)) + { + animController = mBoneAnimBlendControllers[node]; + animController->setKeyframeTrack(it->second, stateData, animsrc->mAnimBlendRules); + } + else + { + animController = osg::ref_ptr( + new BoneAnimBlendController(it->second, stateData, animsrc->mAnimBlendRules)); + + mBoneAnimBlendControllers[node] = animController; + + assignBoneBlendCallbackRecursive(animController, mActiveControllers, node, true); + } + + // IMPORTANT: we must gather all transforms at point of change before next update + // instead of at the root update callback because the root bone may need blending + if (animController->getBlendTrigger()) + animController->gatherRecursiveBoneTransforms(static_cast(node.get())); + + it->second->mTime = active->second.mTime; + + // Register blend callback after the initial animation callback + callback = animController->getAsCallback(); + + node->addUpdateCallback(callback); + mActiveControllers.emplace_back(node, callback); + + // Ensure the original animation update callback is still applied + // this is because we need this to happen first to get the latest transform to blend to + callback = it->second->getAsCallback(); + } + else + { + callback = it->second->getAsCallback(); + } + node->addUpdateCallback(callback); mActiveControllers.emplace_back(node, callback); @@ -1046,6 +1211,7 @@ namespace MWRender } } } + addControllers(); } @@ -1790,13 +1956,15 @@ namespace MWRender osg::Callback* cb = node->getUpdateCallback(); while (cb) { - if (dynamic_cast(cb)) + if (dynamic_cast(cb) || dynamic_cast(cb) + || dynamic_cast(cb)) { foundKeyframeCtrl = true; break; } cb = cb->getNestedCallback(); } + // Note: AnimBlendController also does the reset so if one is present - we should add the rotation node // Without KeyframeController the orientation will not be reseted each frame, so // RotateController shouldn't be used for such nodes. if (!foundKeyframeCtrl) diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 2d2031b13f..e40277a454 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -8,13 +8,17 @@ #include "../mwworld/movementdirection.hpp" #include "../mwworld/ptr.hpp" +#include "animblendcontroller.hpp" #include +#include #include #include #include #include +#include #include +#include #include #include #include @@ -47,6 +51,8 @@ namespace MWRender class RotateController; class TransparencyUpdater; + typedef std::vector, osg::ref_ptr>> ActiveControllersVector; + class EffectAnimationTime : public SceneUtil::ControllerSource { private: @@ -158,8 +164,17 @@ namespace MWRender int mBlendMask = 0; bool mAutoDisable = true; + std::string mGroupname; + std::string mStartKey; + float getTime() const { return *mTime; } void setTime(float time) { *mTime = time; } + bool blendMaskContains(size_t blendMask) const { return (mBlendMask & (1 << blendMask)); } + AnimBlendStateData asAnimBlendStateData() const + { + AnimBlendStateData stateData = { .mGroupname = mGroupname, .mStartKey = mStartKey }; + return stateData; + } bool shouldLoop() const { return getTime() >= mLoopStopTime && mLoopingEnabled && mLoopCount > 0; } }; @@ -189,7 +204,11 @@ namespace MWRender // Keep track of controllers that we added to our scene graph. // We may need to rebuild these controllers when the active animation groups / sources change. - std::vector, osg::ref_ptr>> mActiveControllers; + ActiveControllersVector mActiveControllers; + + // Keep track of the animation controllers for easy access + std::map, osg::ref_ptr> mAnimBlendControllers; + std::map, osg::ref_ptr> mBoneAnimBlendControllers; std::shared_ptr mAnimationTimePtr[sNumBlendMasks]; @@ -233,7 +252,9 @@ namespace MWRender const NodeMap& getNodeMap() const; - /* Sets the appropriate animations on the bone groups based on priority. + /* Sets the appropriate animations on the bone groups based on priority by finding + * the highest priority AnimationStates and linking the appropriate controllers stored + * in the AnimationState to the corresponding nodes. */ void resetActiveGroups(); @@ -275,7 +296,7 @@ namespace MWRender * @param baseModel The filename of the mObjectRoot, only used for error messages. */ void addAnimSource(std::string_view model, const std::string& baseModel); - void addSingleAnimSource(const std::string& model, const std::string& baseModel); + std::shared_ptr addSingleAnimSource(const std::string& model, const std::string& baseModel); /** Adds an additional light to the given node using the specified ESM record. */ void addExtraLight(osg::ref_ptr parent, const SceneUtil::LightCommon& light); @@ -343,6 +364,7 @@ namespace MWRender void setAccumulation(const osg::Vec3f& accum); /** Plays an animation. + * Creates or updates AnimationStates to represent and manage animation playback. * \param groupname Name of the animation group to play. * \param priority Priority of the animation. The animation will play on * bone groups that don't have another animation set of a @@ -491,6 +513,5 @@ namespace MWRender private: double mStartingTime; }; - } #endif diff --git a/apps/openmw/mwrender/animblendcontroller.cpp b/apps/openmw/mwrender/animblendcontroller.cpp new file mode 100644 index 0000000000..06c18c9ee9 --- /dev/null +++ b/apps/openmw/mwrender/animblendcontroller.cpp @@ -0,0 +1,318 @@ +#include "animblendcontroller.hpp" + +#include + +#include +#include + +namespace MWRender +{ + /// Animation Easing/Blending functions + namespace Easings + { + float linear(float x) + { + return x; + } + float sineOut(float x) + { + return sin((x * 3.14) / 2); + } + float sineIn(float x) + { + return 1 - cos((x * 3.14) / 2); + } + float sineInOut(float x) + { + return -(cos(3.14 * x) - 1) / 2; + } + float cubicOut(float t) + { + return 1 - powf(1 - t, 3); + } + float cubicIn(float x) + { + return powf(x, 3); + } + float cubicInOut(float x) + { + return x < 0.5 ? 4 * x * x * x : 1 - powf(-2 * x + 2, 3) / 2; + } + float quartOut(float t) + { + return 1 - powf(1 - t, 4); + } + float quartIn(float t) + { + return powf(t, 4); + } + float quartInOut(float x) + { + return x < 0.5 ? 8 * x * x * x * x : 1 - powf(-2 * x + 2, 4) / 2; + } + float springOutGeneric(float x, float lambda, float w) + { + // Higher lambda = lower swing amplitude. 1 = 150% swing amplitude. + // W corresponds to the amount of overswings, more = more. 4.71 = 1 overswing, 7.82 = 2 + return 1 - expf(-lambda * x) * cos(w * x); + } + float springOutWeak(float x) + { + return springOutGeneric(x, 4, 4.71); + } + float springOutMed(float x) + { + return springOutGeneric(x, 3, 4.71); + } + float springOutStrong(float x) + { + return springOutGeneric(x, 2, 4.71); + } + float springOutTooMuch(float x) + { + return springOutGeneric(x, 1, 4.71); + } + std::unordered_map easingsMap = { { "linear", Easings::linear }, + { "sineOut", Easings::sineOut }, { "sineIn", Easings::sineIn }, { "sineInOut", Easings::sineInOut }, + { "cubicOut", Easings::cubicOut }, { "cubicIn", Easings::cubicIn }, { "cubicInOut", Easings::cubicInOut }, + { "quartOut", Easings::quartOut }, { "quartIn", Easings::quartIn }, { "quartInOut", Easings::quartInOut }, + { "springOutWeak", Easings::springOutWeak }, { "springOutMed", Easings::springOutMed }, + { "springOutStrong", Easings::springOutStrong }, { "springOutTooMuch", Easings::springOutTooMuch } }; + } + + namespace + { + // Helper methods + osg::Vec3f vec3fLerp(float t, const osg::Vec3f& A, const osg::Vec3f& B) + { + return A + (B - A) * t; + } + } + + template + AnimBlendControllerBase::AnimBlendControllerBase( + osg::ref_ptr keyframeTrack, AnimBlendStateData newState, + osg::ref_ptr blendRules) + : mTimeFactor(0.0f) + , mInterpFactor(0.0f) + { + setKeyframeTrack(keyframeTrack, newState, blendRules); + } + + template + void AnimBlendControllerBase::setKeyframeTrack(osg::ref_ptr kft, + AnimBlendStateData newState, osg::ref_ptr blendRules) + { + if (newState.mGroupname != mAnimState.mGroupname || newState.mStartKey != mAnimState.mStartKey + || kft != mKeyframeTrack) + { + // Animation have changed, start blending! + // Log(Debug::Info) << "Animation change to: " << newState.mGroupname << ":" << newState.mStartKey; + + // Default blend settings + mBlendDuration = 0; + mEasingFn = &Easings::sineOut; + + if (blendRules) + { + // Finds a matching blend rule either in this or previous ruleset + auto blendRule = blendRules->findBlendingRule( + mAnimState.mGroupname, mAnimState.mStartKey, newState.mGroupname, newState.mStartKey); + // This will also check the previous ruleset, not sure it's a good idea though, commenting out + // for now. + /*if (!blendRule && mAnimBlendRules) + blendRule = mAnimBlendRules->findBlendingRule( + mAnimState.mGroupname, mAnimState.mStartKey, newState.mGroupname, newState.mStartKey);*/ + if (blendRule) + { + if (Easings::easingsMap.contains(blendRule->mEasing)) + { + mBlendDuration = blendRule->mDuration; + mEasingFn = Easings::easingsMap[blendRule->mEasing]; + } + else + { + Log(Debug::Warning) + << "Warning: animation blending rule contains invalid easing type: " << blendRule->mEasing; + } + } + } + + mAnimBlendRules = blendRules; + mKeyframeTrack = kft; + mAnimState = newState; + mBlendTrigger = true; + } + } + + template + void AnimBlendControllerBase::gatherRecursiveBoneTransforms(osgAnimation::Bone* bone, bool isRoot) + { + // Incase group traversal encountered something that isnt a bone + if (!bone) + return; + + mBlendBoneTransforms[bone] = bone->getMatrix(); + + osg::Group* group = bone->asGroup(); + if (group) + { + for (unsigned int i = 0; i < group->getNumChildren(); ++i) + gatherRecursiveBoneTransforms(dynamic_cast(group->getChild(i)), false); + } + } + + template + void AnimBlendControllerBase::applyBoneBlend(osgAnimation::Bone* bone) + { + // If we are done with interpolation then we can safely skip this as the bones are correct + if (!mInterpActive) + return; + + // Shouldnt happen, but potentially an edge case where a new bone was added + // between gatherRecursiveBoneTransforms and this update + // so far OpenMW will never do this, so this check shouldn't be needed in production + assert(mBlendBoneTransforms.find(bone) != mBlendBoneTransforms.end()); + + // every frame the osgAnimation controller updates this + // so it is ok that we update it directly below + osg::Matrixf currentSampledMatrix = bone->getMatrix(); + const osg::Matrixf& lastSampledMatrix = mBlendBoneTransforms.at(bone); + + const osg::Vec3f scale = currentSampledMatrix.getScale(); + const osg::Quat rotation = currentSampledMatrix.getRotate(); + const osg::Vec3f translation = currentSampledMatrix.getTrans(); + + const osg::Quat blendRotation = lastSampledMatrix.getRotate(); + const osg::Vec3f blendTrans = lastSampledMatrix.getTrans(); + + osg::Quat lerpedRot; + lerpedRot.slerp(mInterpFactor, blendRotation, rotation); + + osg::Matrixf lerpedMatrix; + lerpedMatrix.makeRotate(lerpedRot); + lerpedMatrix.setTrans(vec3fLerp(mInterpFactor, blendTrans, translation)); + + // Scale is not lerped based on the idea that it is much more likely that scale animation will be used to + // instantly hide/show objects in which case the scale interpolation is undesirable. + lerpedMatrix = osg::Matrixd::scale(scale) * lerpedMatrix; + + // Apply new blended matrix + osgAnimation::Bone* boneParent = bone->getBoneParent(); + bone->setMatrix(lerpedMatrix); + if (boneParent) + bone->setMatrixInSkeletonSpace(lerpedMatrix * boneParent->getMatrixInSkeletonSpace()); + else + bone->setMatrixInSkeletonSpace(lerpedMatrix); + } + + template + void AnimBlendControllerBase::calculateInterpFactor(float time) + { + if (mBlendDuration != 0) + mTimeFactor = std::min((time - mBlendStartTime) / mBlendDuration, 1.0f); + else + mTimeFactor = 1; + + mInterpActive = mTimeFactor < 1.0; + + if (mInterpActive) + mInterpFactor = mEasingFn(mTimeFactor); + else + mInterpFactor = 1.0f; + } + + template + void AnimBlendControllerBase::operator()(osgAnimation::Bone* node, osg::NodeVisitor* nv) + { + // HOW THIS WORKS: This callback method is called only for bones with attached keyframe controllers + // such as bip01, bip01 spine1 etc. The child bones of these controllers have their own callback wrapper + // which will call this instance's applyBoneBlend for each child bone. The order of update is important + // as the blending calculations expect the bone's skeleton matrix to be at the sample point + float time = nv->getFrameStamp()->getSimulationTime(); + assert(node != nullptr); + + if (mBlendTrigger) + { + mBlendTrigger = false; + mBlendStartTime = time; + } + + calculateInterpFactor(time); + + if (mInterpActive) + applyBoneBlend(node); + + SceneUtil::NodeCallback, osgAnimation::Bone*>::traverse(node, nv); + } + + template + void AnimBlendControllerBase::operator()(NifOsg::MatrixTransform* node, osg::NodeVisitor* nv) + { + // HOW THIS WORKS: The actual retrieval of the bone transformation based on animation is done by the + // KeyframeController (mKeyframeTrack). The KeyframeController retreives time data (playback position) every + // frame from controller's input (getInputValue(nv)) which is bound to an appropriate AnimationState time value + // in Animation.cpp. Animation.cpp ultimately manages animation playback via updating AnimationState objects and + // determines when and what should be playing. + // This controller exploits KeyframeController to get transformations and upon animation change blends from + // the last known position to the new animated one. + + auto [translation, rotation, scale] = mKeyframeTrack->getCurrentTransformation(nv); + + float time = nv->getFrameStamp()->getSimulationTime(); + + if (mBlendTrigger) + { + mBlendTrigger = false; + mBlendStartTime = time; + // Nif mRotation is used here because it's unaffected by the side-effects of RotationController + mBlendStartRot = node->mRotation.toOsgMatrix().getRotate(); + mBlendStartTrans = node->getMatrix().getTrans(); + mBlendStartScale = node->mScale; + } + + calculateInterpFactor(time); + + if (mInterpActive) + { + // Interpolate node's rotation + if (rotation) + { + osg::Quat lerpedRot; + lerpedRot.slerp(mInterpFactor, mBlendStartRot, *rotation); + node->setRotation(lerpedRot); + } + else + { + // This is necessary to prevent first person animation glitching out + node->setRotation(node->mRotation); + } + + // Update node's translation + if (translation) + { + osg::Vec3f lerpedTrans = vec3fLerp(mInterpFactor, mBlendStartTrans, *translation); + node->setTranslation(lerpedTrans); + } + } + else + { + // Update node's translation + if (translation) + node->setTranslation(*translation); + + if (rotation) + node->setRotation(*rotation); + else + node->setRotation(node->mRotation); + } + + // Update node's scale + if (scale) + // Scale is not lerped based on the idea that it is much more likely that scale animation will be used to + // instantly hide/show objects in which case the scale interpolation is undesirable. + node->setScale(*scale); + + SceneUtil::NodeCallback, NifOsg::MatrixTransform*>::traverse(node, nv); + } +} diff --git a/apps/openmw/mwrender/animblendcontroller.hpp b/apps/openmw/mwrender/animblendcontroller.hpp new file mode 100644 index 0000000000..0ccbc43a5c --- /dev/null +++ b/apps/openmw/mwrender/animblendcontroller.hpp @@ -0,0 +1,109 @@ +#ifndef OPENMW_MWRENDER_ANIMBLENDCONTROLLER_H +#define OPENMW_MWRENDER_ANIMBLENDCONTROLLER_H + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +namespace MWRender +{ + namespace Easings + { + typedef float (*EasingFn)(float); + } + + struct AnimBlendStateData + { + std::string mGroupname; + std::string mStartKey; + }; + + template + class AnimBlendControllerBase : public SceneUtil::NodeCallback, NodeClass*>, + public SceneUtil::Controller + { + public: + AnimBlendControllerBase(osg::ref_ptr keyframeTrack, AnimBlendStateData animState, + osg::ref_ptr blendRules); + + void operator()(NifOsg::MatrixTransform* node, osg::NodeVisitor* nv); + void operator()(osgAnimation::Bone* node, osg::NodeVisitor* nv); + + void setKeyframeTrack(osg::ref_ptr kft, AnimBlendStateData animState, + osg::ref_ptr blendRules); + + osg::Callback* getAsCallback() { return this; } + + bool getBlendTrigger() const { return mBlendTrigger; } + + void gatherRecursiveBoneTransforms(osgAnimation::Bone* parent, bool isRoot = true); + void applyBoneBlend(osgAnimation::Bone* parent); + + const char* libraryName() const override { return "openmw"; } + const char* className() const override { return "AnimBlendController"; } + + protected: + osg::ref_ptr mKeyframeTrack; + + inline void calculateInterpFactor(float time); + + private: + Easings::EasingFn mEasingFn; + float mBlendDuration; + + bool mBlendTrigger = false; + float mBlendStartTime; + osg::Quat mBlendStartRot; + osg::Vec3f mBlendStartTrans; + float mBlendStartScale; + + float mTimeFactor; + float mInterpFactor; + bool mInterpActive; + + AnimBlendStateData mAnimState; + osg::ref_ptr mAnimBlendRules; + + std::unordered_map mBlendBoneTransforms; + }; + + typedef AnimBlendControllerBase AnimBlendController; + typedef AnimBlendControllerBase BoneAnimBlendController; + + // Assigned to child bones with an instance of AnimBlendControllerBase + class BoneAnimBlendControllerWrapper : public osg::Callback + { + public: + BoneAnimBlendControllerWrapper(osg::ref_ptr rootCallback, osg::Node* node) + { + mRootCallback = rootCallback; + mNode = dynamic_cast(node); + } + + bool run(osg::Object* object, osg::Object* data) override + { + mRootCallback->applyBoneBlend(mNode); + traverse(object, data); + return true; + } + + const char* libraryName() const override { return "openmw"; } + const char* className() const override { return "AnimBlendController"; } + + private: + osg::ref_ptr mRootCallback; + osgAnimation::Bone* mNode; + }; +} + +#endif diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index fb5718a979..67cb881ee5 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -127,7 +127,7 @@ add_component_dir (vfs ) add_component_dir (resource - scenemanager keyframemanager imagemanager bulletshapemanager bulletshape niffilemanager objectcache multiobjectcache resourcesystem + scenemanager keyframemanager imagemanager animblendrulesmanager bulletshapemanager bulletshape niffilemanager objectcache multiobjectcache resourcesystem resourcemanager stats animation foreachbulletobject errormarker cachestats bgsmfilemanager ) @@ -138,7 +138,7 @@ add_component_dir (shader add_component_dir (sceneutil clone attach visitor util statesetupdater controller skeleton riggeometry morphgeometry lightcontroller lightmanager lightutil positionattitudetransform workqueue pathgridutil waterutil writescene serialize optimizer - detourdebugdraw navmesh agentpath shadow mwshadowtechnique recastmesh shadowsbin osgacontroller rtt + detourdebugdraw navmesh agentpath animblendrules shadow mwshadowtechnique recastmesh shadowsbin osgacontroller rtt screencapture depth color riggeometryosgaextension extradata unrefqueue lightcommon lightingmethod clearcolor cullsafeboundsvisitor keyframe nodecallback textkeymap glextensions ) diff --git a/components/nif/niftypes.hpp b/components/nif/niftypes.hpp index b294756f69..f1a131743d 100644 --- a/components/nif/niftypes.hpp +++ b/components/nif/niftypes.hpp @@ -52,6 +52,17 @@ namespace Nif return false; return true; } + + osg::Matrixf toOsgMatrix() const + { + osg::Matrixf osgMat; + + for (int i = 0; i < 3; ++i) + for (int j = 0; j < 3; ++j) + osgMat(i, j) = mValues[j][i]; // NB: column/row major difference + + return osgMat; + } }; struct NiTransform diff --git a/components/nifosg/controller.cpp b/components/nifosg/controller.cpp index 60f2e9c355..80fec6e39d 100644 --- a/components/nifosg/controller.cpp +++ b/components/nifosg/controller.cpp @@ -5,6 +5,8 @@ #include #include +#include + #include #include @@ -175,25 +177,48 @@ namespace NifOsg void KeyframeController::operator()(NifOsg::MatrixTransform* node, osg::NodeVisitor* nv) { + auto [translation, rotation, scale] = getCurrentTransformation(nv); + + if (rotation) + { + node->setRotation(*rotation); + } + else + { + // This is necessary to prevent first person animations glitching out due to RotationController + node->setRotation(node->mRotation); + } + + if (translation) + node->setTranslation(*translation); + + if (scale) + node->setScale(*scale); + + traverse(node, nv); + } + + KeyframeController::KfTransform KeyframeController::getCurrentTransformation(osg::NodeVisitor* nv) + { + KfTransform out; + if (hasInput()) { float time = getInputValue(nv); if (!mRotations.empty()) - node->setRotation(mRotations.interpKey(time)); + out.mRotation = mRotations.interpKey(time); else if (!mXRotations.empty() || !mYRotations.empty() || !mZRotations.empty()) - node->setRotation(getXYZRotation(time)); - else - node->setRotation(node->mRotationScale); + out.mRotation = getXYZRotation(time); if (!mTranslations.empty()) - node->setTranslation(mTranslations.interpKey(time)); + out.mTranslation = mTranslations.interpKey(time); if (!mScales.empty()) - node->setScale(mScales.interpKey(time)); + out.mScale = mScales.interpKey(time); } - traverse(node, nv); + return out; } GeomMorpherController::GeomMorpherController() {} diff --git a/components/nifosg/controller.hpp b/components/nifosg/controller.hpp index 51cf1cd428..99d3df9545 100644 --- a/components/nifosg/controller.hpp +++ b/components/nifosg/controller.hpp @@ -238,6 +238,8 @@ namespace NifOsg osg::Vec3f getTranslation(float time) const override; osg::Callback* getAsCallback() override { return this; } + KfTransform getCurrentTransformation(osg::NodeVisitor* nv) override; + void operator()(NifOsg::MatrixTransform*, osg::NodeVisitor*); private: diff --git a/components/nifosg/matrixtransform.cpp b/components/nifosg/matrixtransform.cpp index a59f10360a..caf0fa6bfb 100644 --- a/components/nifosg/matrixtransform.cpp +++ b/components/nifosg/matrixtransform.cpp @@ -5,14 +5,14 @@ namespace NifOsg MatrixTransform::MatrixTransform(const Nif::NiTransform& transform) : osg::MatrixTransform(transform.toMatrix()) , mScale(transform.mScale) - , mRotationScale(transform.mRotation) + , mRotation(transform.mRotation) { } MatrixTransform::MatrixTransform(const MatrixTransform& copy, const osg::CopyOp& copyop) : osg::MatrixTransform(copy, copyop) , mScale(copy.mScale) - , mRotationScale(copy.mRotationScale) + , mRotation(copy.mRotation) { } @@ -24,7 +24,7 @@ namespace NifOsg // Rescale the node using the known components. for (int i = 0; i < 3; ++i) for (int j = 0; j < 3; ++j) - _matrix(i, j) = mRotationScale.mValues[j][i] * mScale; // NB: column/row major difference + _matrix(i, j) = mRotation.mValues[j][i] * mScale; // NB: column/row major difference _inverseDirty = true; dirtyBound(); @@ -40,7 +40,7 @@ namespace NifOsg for (int j = 0; j < 3; ++j) { // Update the current decomposed rotation and restore the known scale. - mRotationScale.mValues[j][i] = _matrix(i, j); // NB: column/row major difference + mRotation.mValues[j][i] = _matrix(i, j); // NB: column/row major difference _matrix(i, j) *= mScale; } } @@ -52,12 +52,12 @@ namespace NifOsg void MatrixTransform::setRotation(const Nif::Matrix3& rotation) { // Update the decomposed rotation. - mRotationScale = rotation; + mRotation = rotation; // Reorient the node using the known components. for (int i = 0; i < 3; ++i) for (int j = 0; j < 3; ++j) - _matrix(i, j) = mRotationScale.mValues[j][i] * mScale; // NB: column/row major difference + _matrix(i, j) = mRotation.mValues[j][i] * mScale; // NB: column/row major difference _inverseDirty = true; dirtyBound(); diff --git a/components/nifosg/matrixtransform.hpp b/components/nifosg/matrixtransform.hpp index 4e42d00787..7f5c908156 100644 --- a/components/nifosg/matrixtransform.hpp +++ b/components/nifosg/matrixtransform.hpp @@ -23,7 +23,8 @@ namespace NifOsg // problems when a KeyframeController wants to change only one of these components. So // we store the scale and rotation components separately here. float mScale{ 0.f }; - Nif::Matrix3 mRotationScale; + + Nif::Matrix3 mRotation; // Utility methods to transform the node and keep these components up-to-date. // The matrix's components should not be overridden manually or using preMult/postMult diff --git a/components/resource/animblendrulesmanager.cpp b/components/resource/animblendrulesmanager.cpp new file mode 100644 index 0000000000..7bfea70974 --- /dev/null +++ b/components/resource/animblendrulesmanager.cpp @@ -0,0 +1,94 @@ +#include "animblendrulesmanager.hpp" + +#include + +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include + +#include "objectcache.hpp" +#include "scenemanager.hpp" + +namespace Resource +{ + using AnimBlendRules = SceneUtil::AnimBlendRules; + + AnimBlendRulesManager::AnimBlendRulesManager(const VFS::Manager* vfs, double expiryDelay) + : ResourceManager(vfs, expiryDelay) + , mVfs(vfs) + { + } + + osg::ref_ptr AnimBlendRulesManager::getRules( + std::string_view path, std::string_view overridePath) + { + // Note: Providing a non-existing path but an existing overridePath is not supported! + auto tmpl = loadRules(path); + if (!tmpl) + return nullptr; + + // Create an instance based on template and store template reference inside so the template will not be removed + // from cache + osg::ref_ptr blendRules(new AnimBlendRules(*tmpl, osg::CopyOp::SHALLOW_COPY)); + blendRules->getOrCreateUserDataContainer()->addUserObject(new Resource::TemplateRef(tmpl)); + + if (!overridePath.empty()) + { + auto blendRuleOverrides = loadRules(overridePath); + if (blendRuleOverrides) + { + blendRules->addOverrideRules(*blendRuleOverrides); + } + blendRules->getOrCreateUserDataContainer()->addUserObject(new Resource::TemplateRef(blendRuleOverrides)); + } + + return blendRules; + } + + osg::ref_ptr AnimBlendRulesManager::loadRules(std::string_view path) + { + const VFS::Path::Normalized normalizedPath = VFS::Path::Normalized(path); + std::optional> obj = mCache->getRefFromObjectCacheOrNone(normalizedPath); + if (obj.has_value()) + { + return osg::ref_ptr(static_cast(obj->get())); + } + else + { + osg::ref_ptr blendRules = AnimBlendRules::fromFile(mVfs, normalizedPath); + if (blendRules == nullptr) + { + // No blend rules were found in VFS, cache a nullptr. + osg::ref_ptr nullRules = nullptr; + mCache->addEntryToObjectCache(normalizedPath, nullRules); + // To avoid confusion - never return blend rules with 0 rules + return nullRules; + } + else + { + // Blend rules were found in VFS, cache them. + mCache->addEntryToObjectCache(normalizedPath, blendRules); + return blendRules; + } + } + + return nullptr; + } + + void AnimBlendRulesManager::reportStats(unsigned int frameNumber, osg::Stats* stats) const + { + Resource::reportStats("Blending Rules", frameNumber, mCache->getStats(), *stats); + } + +} diff --git a/components/resource/animblendrulesmanager.hpp b/components/resource/animblendrulesmanager.hpp new file mode 100644 index 0000000000..c8eeee327f --- /dev/null +++ b/components/resource/animblendrulesmanager.hpp @@ -0,0 +1,36 @@ +#ifndef OPENMW_COMPONENTS_ANIMBLENDRULESMANAGER_H +#define OPENMW_COMPONENTS_ANIMBLENDRULESMANAGER_H + +#include +#include + +#include + +#include "resourcemanager.hpp" + +namespace Resource +{ + /// @brief Managing of keyframe resources + /// @note May be used from any thread. + class AnimBlendRulesManager : public ResourceManager + { + public: + explicit AnimBlendRulesManager(const VFS::Manager* vfs, double expiryDelay); + ~AnimBlendRulesManager() = default; + + /// Retrieve a read-only keyframe resource by name (case-insensitive). + /// @note Throws an exception if the resource is not found. + osg::ref_ptr getRules( + std::string_view path, std::string_view overridePath = ""); + + void reportStats(unsigned int frameNumber, osg::Stats* stats) const override; + + private: + osg::ref_ptr loadRules(std::string_view path); + + const VFS::Manager* mVfs; + }; + +} + +#endif diff --git a/components/resource/resourcesystem.cpp b/components/resource/resourcesystem.cpp index f012627efb..f3eb835ddb 100644 --- a/components/resource/resourcesystem.cpp +++ b/components/resource/resourcesystem.cpp @@ -2,6 +2,7 @@ #include +#include "animblendrulesmanager.hpp" #include "bgsmfilemanager.hpp" #include "imagemanager.hpp" #include "keyframemanager.hpp" @@ -21,6 +22,7 @@ namespace Resource mSceneManager = std::make_unique( vfs, mImageManager.get(), mNifFileManager.get(), mBgsmFileManager.get(), expiryDelay); mKeyframeManager = std::make_unique(vfs, mSceneManager.get(), expiryDelay, encoder); + mAnimBlendRulesManager = std::make_unique(vfs, expiryDelay); addResourceManager(mNifFileManager.get()); addResourceManager(mBgsmFileManager.get()); @@ -28,6 +30,7 @@ namespace Resource // note, scene references images so add images afterwards for correct implementation of updateCache() addResourceManager(mSceneManager.get()); addResourceManager(mImageManager.get()); + addResourceManager(mAnimBlendRulesManager.get()); } ResourceSystem::~ResourceSystem() @@ -62,6 +65,11 @@ namespace Resource return mKeyframeManager.get(); } + AnimBlendRulesManager* ResourceSystem::getAnimBlendRulesManager() + { + return mAnimBlendRulesManager.get(); + } + void ResourceSystem::setExpiryDelay(double expiryDelay) { for (std::vector::iterator it = mResourceManagers.begin(); it != mResourceManagers.end(); diff --git a/components/resource/resourcesystem.hpp b/components/resource/resourcesystem.hpp index 5609176a89..abc696aae3 100644 --- a/components/resource/resourcesystem.hpp +++ b/components/resource/resourcesystem.hpp @@ -29,6 +29,7 @@ namespace Resource class NifFileManager; class KeyframeManager; class BaseResourceManager; + class AnimBlendRulesManager; /// @brief Wrapper class that constructs and provides access to the most commonly used resource subsystems. /// @par Resource subsystems can be used with multiple OpenGL contexts, just like the OSG equivalents, but @@ -45,6 +46,7 @@ namespace Resource BgsmFileManager* getBgsmFileManager(); NifFileManager* getNifFileManager(); KeyframeManager* getKeyframeManager(); + AnimBlendRulesManager* getAnimBlendRulesManager(); /// Indicates to each resource manager to clear the cache, i.e. to drop cached objects that are no longer /// referenced. @@ -79,6 +81,7 @@ namespace Resource std::unique_ptr mBgsmFileManager; std::unique_ptr mNifFileManager; std::unique_ptr mKeyframeManager; + std::unique_ptr mAnimBlendRulesManager; // Store the base classes separately to get convenient access to the common interface // Here users can register their own resourcemanager as well diff --git a/components/sceneutil/animblendrules.cpp b/components/sceneutil/animblendrules.cpp new file mode 100644 index 0000000000..8289cf0f7d --- /dev/null +++ b/components/sceneutil/animblendrules.cpp @@ -0,0 +1,177 @@ +#include "animblendrules.hpp" + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +namespace SceneUtil +{ + namespace + { + std::pair splitRuleName(std::string full) + { + std::string group; + std::string key; + size_t delimiterInd = full.find(":"); + + Misc::StringUtils::lowerCaseInPlace(full); + + if (delimiterInd == std::string::npos) + { + group = full; + Misc::StringUtils::trim(group); + } + else + { + group = full.substr(0, delimiterInd); + key = full.substr(delimiterInd + 1); + Misc::StringUtils::trim(group); + Misc::StringUtils::trim(key); + } + return std::make_pair(group, key); + } + + } + + using BlendRule = AnimBlendRules::BlendRule; + + AnimBlendRules::AnimBlendRules(const AnimBlendRules& copy, const osg::CopyOp& copyop) + : mRules(copy.mRules) + { + } + + AnimBlendRules::AnimBlendRules(const std::vector& rules) + : mRules(rules) + { + } + + osg::ref_ptr AnimBlendRules::fromFile(const VFS::Manager* vfs, VFS::Path::NormalizedView configPath) + { + Log(Debug::Debug) << "Attempting to load animation blending config '" << configPath << "'"; + + if (!vfs->exists(configPath)) + { + Log(Debug::Warning) << "Animation blending files was not found '" << configPath << "'"; + return nullptr; + } + + // Retrieving and parsing animation rules + std::string rawYaml(std::istreambuf_iterator(*vfs->get(configPath)), {}); + + std::vector rules; + + YAML::Node root = YAML::Load(rawYaml); + + if (!root.IsDefined() || root.IsNull() || root.IsScalar()) + { + Log(Debug::Error) << Misc::StringUtils::format( + "Can't parse file '%s'. Check that it's a valid YAML/JSON file.", configPath); + return nullptr; + } + + if (root["blending_rules"]) + { + for (const auto& it : root["blending_rules"]) + { + if (it["from"] && it["to"] && it["duration"] && it["easing"]) + { + auto fromNames = splitRuleName(it["from"].as()); + auto toNames = splitRuleName(it["to"].as()); + + BlendRule ruleObj = { + .mFromGroup = fromNames.first, + .mFromKey = fromNames.second, + .mToGroup = toNames.first, + .mToKey = toNames.second, + .mDuration = it["duration"].as(), + .mEasing = it["easing"].as(), + }; + + rules.emplace_back(ruleObj); + } + else + { + Log(Debug::Warning) << "Warning: Blending rule '" + << (it["from"] ? it["from"].as() : "undefined") << "->" + << (it["to"] ? it["to"].as() : "undefined") + << "' is missing some properties. File: '" << configPath << "'."; + } + } + } + else + { + throw std::domain_error( + Misc::StringUtils::format("'blending_rules' object not found in '%s' file!", configPath)); + } + + // If no rules then dont allocate any instance + if (rules.size() == 0) + return nullptr; + + return new AnimBlendRules(rules); + } + + void AnimBlendRules::addOverrideRules(const AnimBlendRules& overrideRules) + { + auto rules = overrideRules.getRules(); + // Concat the rules together, overrides added at the end since the bottom-most rule has the highest priority. + mRules.insert(mRules.end(), rules.begin(), rules.end()); + } + + inline bool AnimBlendRules::fitsRuleString(const std::string& str, const std::string& ruleStr) const + { + // A wildcard only supported in the beginning or the end of the rule string in hopes that this will be more + // performant. And most likely this kind of support is enough. + return ruleStr == "*" || str == ruleStr || (ruleStr.starts_with("*") && str.ends_with(ruleStr.substr(1))) + || (ruleStr.ends_with("*") && str.starts_with(ruleStr.substr(0, ruleStr.length() - 1))); + } + + std::optional AnimBlendRules::findBlendingRule( + std::string fromGroup, std::string fromKey, std::string toGroup, std::string toKey) const + { + Misc::StringUtils::lowerCaseInPlace(fromGroup); + Misc::StringUtils::lowerCaseInPlace(fromKey); + Misc::StringUtils::lowerCaseInPlace(toGroup); + Misc::StringUtils::lowerCaseInPlace(toKey); + for (auto rule = mRules.rbegin(); rule != mRules.rend(); ++rule) + { + // TO DO: Also allow for partial wildcards at the end of groups and keys via std::string startswith method + bool fromMatch = false; + bool toMatch = false; + + // Pseudocode: + // If not a wildcard and found a wildcard + // starts with substr(0,wildcard) + + if (fitsRuleString(fromGroup, rule->mFromGroup) + && (fitsRuleString(fromKey, rule->mFromKey) || rule->mFromKey == "")) + { + fromMatch = true; + } + + if ((fitsRuleString(toGroup, rule->mToGroup) || (rule->mToGroup == "$" && toGroup == fromGroup)) + && (fitsRuleString(toKey, rule->mToKey) || rule->mToKey == "")) + { + toMatch = true; + } + + if (fromMatch && toMatch) + return std::make_optional(*rule); + } + + return std::nullopt; + } + +} diff --git a/components/sceneutil/animblendrules.hpp b/components/sceneutil/animblendrules.hpp new file mode 100644 index 0000000000..db03c0fd0a --- /dev/null +++ b/components/sceneutil/animblendrules.hpp @@ -0,0 +1,49 @@ +#ifndef OPENMW_COMPONENTS_SCENEUTIL_ANIMBLENDRULES_HPP +#define OPENMW_COMPONENTS_SCENEUTIL_ANIMBLENDRULES_HPP + +#include +#include +#include + +#include + +#include + +namespace SceneUtil +{ + class AnimBlendRules : public osg::Object + { + public: + struct BlendRule + { + std::string mFromGroup; + std::string mFromKey; + std::string mToGroup; + std::string mToKey; + float mDuration; + std::string mEasing; + }; + + AnimBlendRules() = default; + AnimBlendRules(const std::vector& rules); + AnimBlendRules(const AnimBlendRules& copy, const osg::CopyOp& copyop); + + META_Object(SceneUtil, AnimBlendRules) + + void addOverrideRules(const AnimBlendRules& overrideRules); + + std::optional findBlendingRule( + std::string fromGroup, std::string fromKey, std::string toGroup, std::string toKey) const; + + const std::vector& getRules() const { return mRules; } + + static osg::ref_ptr fromFile(const VFS::Manager* vfs, VFS::Path::NormalizedView yamlpath); + + private: + std::vector mRules; + + inline bool fitsRuleString(const std::string& str, const std::string& ruleStr) const; + }; +} + +#endif diff --git a/components/sceneutil/keyframe.hpp b/components/sceneutil/keyframe.hpp index 3ea862d213..e8d53c1876 100644 --- a/components/sceneutil/keyframe.hpp +++ b/components/sceneutil/keyframe.hpp @@ -2,6 +2,7 @@ #define OPENMW_COMPONENTS_SCENEUTIL_KEYFRAME_HPP #include +#include #include @@ -21,8 +22,19 @@ namespace SceneUtil { } + std::shared_ptr mTime = std::make_shared(0.0f); + + struct KfTransform + { + std::optional mTranslation; + std::optional mRotation; + std::optional mScale; + }; + virtual osg::Vec3f getTranslation(float time) const { return osg::Vec3f(); } + virtual KfTransform getCurrentTransformation(osg::NodeVisitor* nv) { return KfTransform(); }; + /// @note We could drop this function in favour of osg::Object::asCallback from OSG 3.6 on. virtual osg::Callback* getAsCallback() = 0; }; diff --git a/components/settings/categories/game.hpp b/components/settings/categories/game.hpp index 4aec92d0b8..ec6f9dc206 100644 --- a/components/settings/categories/game.hpp +++ b/components/settings/categories/game.hpp @@ -39,6 +39,7 @@ namespace Settings SettingValue mCanLootDuringDeathAnimation{ mIndex, "Game", "can loot during death animation" }; SettingValue mRebalanceSoulGemValues{ mIndex, "Game", "rebalance soul gem values" }; SettingValue mUseAdditionalAnimSources{ mIndex, "Game", "use additional anim sources" }; + SettingValue mSmoothAnimTransitions{ mIndex, "Game", "smooth animation transitions" }; SettingValue mBarterDispositionChangeIsPermanent{ mIndex, "Game", "barter disposition change is permanent" }; SettingValue mStrengthInfluencesHandToHand{ mIndex, "Game", "strength influences hand to hand", diff --git a/files/data/CMakeLists.txt b/files/data/CMakeLists.txt index 2e7b98762b..d03d7e634a 100644 --- a/files/data/CMakeLists.txt +++ b/files/data/CMakeLists.txt @@ -21,6 +21,9 @@ set(BUILTIN_DATA_FILES fonts/MysticCards.omwfont fonts/MysticCardsFontLicense.txt + # Default animation blending config + animations/animation-config.yaml + # Month names and date formatting l10n/Calendar/de.yaml l10n/Calendar/en.yaml diff --git a/files/data/animations/animation-config.yaml b/files/data/animations/animation-config.yaml new file mode 100644 index 0000000000..205e4d2145 --- /dev/null +++ b/files/data/animations/animation-config.yaml @@ -0,0 +1,70 @@ +# This is the default OpenMW animation blending config file (global config) , will affect NPCs and creatures but not animated objects. +# If you want to provide an animation blending config for your modded animations - DO NOT override the global config in your mod. +# For details on how to edit and create your own blending rules, see https://openmw.readthedocs.io/en/latest/reference/modding/animation-blending.html +# Author: Maksim Eremenko (Max Yari) + +blending_rules: + # General blending rule, any transition that will not be caught by the rules below - will use this rule + - from: "*" + to: "*" + easing: "sineOut" + duration: 0.25 + # From anything to sneaking + - from: "*" + to: "idlesneak*" + easing: "springOutWeak" + duration: 0.4 + - from: "*" + to: "sneakforward*" + easing: "springOutWeak" + duration: 0.4 + # From any to preparing for an attack swing (e.g "weapononehanded: chop start"). + # Note that Rules like *:chop* will technically match any weapon attack animation with + # an animation key beginning on "chop". This includes attack preparation, attack itself and follow-through. + # Yet since rules below this block take care of more specific transitions - most likely this block will + # only affect "any animation"->"attack swing preparation". + - from: "*" + to: "*:shoot*" + easing: "sineOut" + duration: 0.1 + - from: "*" + to: "*:chop*" + easing: "sineOut" + duration: 0.1 + - from: "*" + to: "*:thrust*" + easing: "sineOut" + duration: 0.1 + - from: "*" + to: "*:slash*" + easing: "sineOut" + duration: 0.1 + # From preparing for an attack swing (e.g "weapononehanded: chop start") to an attack swing (e.g "weapononehanded: chop max attack"). + - from: "*:*start" + to: "*:*attack" + easing: "sineOut" + duration: 0.05 + # From a weapon swing to the final follow-through + - from: "*" + to: "*:*follow start" + easing: "linear" + duration: 0 + # Sharper out of jumping transition, so bunny-hopping looks similar to vanilla + - from: "jump:start" + to: "*" + easing: "sineOut" + duration: 0.1 + # Inventory doll poses don't work with transitions, so 0 duraion. + - from: "*" + to: "inventory*" + easing: "linear" + duration: 0 + - from: "inventory*" + to: "*" + easing: "linear" + duration: 0 + # Transitions from a no-state are always instant + - from: "" + to: "*" + easing: "linear" + duration: 0 diff --git a/files/settings-default.cfg b/files/settings-default.cfg index 325bc57618..70b0b133ab 100644 --- a/files/settings-default.cfg +++ b/files/settings-default.cfg @@ -284,6 +284,10 @@ rebalance soul gem values = false # Allow to load per-group KF-files from Animations folder use additional anim sources = false +# Use smooth transitions between animations making them a lot less jarring. Also allows to load modded animation blending +# configs (.yaml/.json config files). +smooth animation transitions = false + # Make the disposition change of merchants caused by barter dealings permanent barter disposition change is permanent = false From 136be62146a6038bd45964fab17e22c9be4a03f4 Mon Sep 17 00:00:00 2001 From: Max Yari Date: Thu, 4 Apr 2024 01:34:11 +0100 Subject: [PATCH 02/34] Launcher checkbox Signed-off-by: Sam Hellawell --- apps/launcher/settingspage.cpp | 2 ++ apps/launcher/ui/settingspage.ui | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/apps/launcher/settingspage.cpp b/apps/launcher/settingspage.cpp index b7f97d24fb..dfddc45bc5 100644 --- a/apps/launcher/settingspage.cpp +++ b/apps/launcher/settingspage.cpp @@ -189,6 +189,7 @@ bool Launcher::SettingsPage::loadSettings() loadSettingBool(Settings::game().mWeaponSheathing, *weaponSheathingCheckBox); loadSettingBool(Settings::game().mShieldSheathing, *shieldSheathingCheckBox); } + loadSettingBool(Settings::game().mSmoothAnimTransitions, *smoothAnimTransitionsCheckBox); loadSettingBool(Settings::game().mTurnToMovementDirection, *turnToMovementDirectionCheckBox); loadSettingBool(Settings::game().mSmoothMovement, *smoothMovementCheckBox); loadSettingBool(Settings::game().mPlayerMovementIgnoresAnimation, *playerMovementIgnoresAnimationCheckBox); @@ -394,6 +395,7 @@ void Launcher::SettingsPage::saveSettings() saveSettingBool(*weaponSheathingCheckBox, Settings::game().mWeaponSheathing); saveSettingBool(*shieldSheathingCheckBox, Settings::game().mShieldSheathing); saveSettingBool(*turnToMovementDirectionCheckBox, Settings::game().mTurnToMovementDirection); + saveSettingBool(*smoothAnimTransitionsCheckBox, Settings::game().mSmoothAnimTransitions); saveSettingBool(*smoothMovementCheckBox, Settings::game().mSmoothMovement); saveSettingBool(*playerMovementIgnoresAnimationCheckBox, Settings::game().mPlayerMovementIgnoresAnimation); diff --git a/apps/launcher/ui/settingspage.ui b/apps/launcher/ui/settingspage.ui index 9362920daf..b9fd9607e1 100644 --- a/apps/launcher/ui/settingspage.ui +++ b/apps/launcher/ui/settingspage.ui @@ -426,6 +426,16 @@ + + + + <html><head/><body><p>If enabled - makes transitions between different animations/poses much smoother. Also allows to load animation blending config .yaml files that can be bundled with the modded animations in order to customise blending styles.</p></body></html> + + + Smooth animation transitions + + + From aca091c2fcb2b93346bd7fa8f17c27a85fed0473 Mon Sep 17 00:00:00 2001 From: Abdu Sharif Date: Thu, 4 Apr 2024 01:37:46 +0100 Subject: [PATCH 03/34] Documentation Signed-off-by: Sam Hellawell --- .../reference/modding/animation-blending.rst | 90 +++++++++++++++++++ docs/source/reference/modding/extended.rst | 8 ++ docs/source/reference/modding/index.rst | 1 + 3 files changed, 99 insertions(+) create mode 100644 docs/source/reference/modding/animation-blending.rst diff --git a/docs/source/reference/modding/animation-blending.rst b/docs/source/reference/modding/animation-blending.rst new file mode 100644 index 0000000000..dec15e096d --- /dev/null +++ b/docs/source/reference/modding/animation-blending.rst @@ -0,0 +1,90 @@ +Animation blending +################## + +Animation blending introduces smooth animation transitions between essentially every animation in the game without affecting gameplay. Effective if ``smooth animation transitions`` setting is enabled in the launcher or the config files. + +Animation developers can bundle ``.yaml``/``.json`` files together with their ``.kf`` files to specify the blending style of their animations. Those settings will only affect the corresponding animation files. + +The default OpenMW animation blending config file (the global config) affects actors only, that restriction doesn't apply to other animation blending config files; they can affect animated objects too. +Do not override the global config file in your mod, instead create a ``your_modded_animation_file_name.yaml`` file and put it in the same folder as your ``.kf`` file. + +For example, if your mod includes a ``newAnimations.kf`` file, you can put a ``newAnimations.yaml`` file beside it and fill it with your blending rules. +Animation config files shipped in this fashion will only affect your modded animations and will not meddle with other animations in the game. + +Local (per-kf-file) animation rules will only affect transitions between animations provided in that file and transitions to those animations; they will not affect transitions from the file animation to some other animation. + +Editing animation config files +------------------------------ + +In examples below ``.yaml`` config file will be used. You can provide ``.json`` files instead of ``.yaml`` if you adhere to the same overall structures and field names. + +Animation blending config file is a list of blending rules that look like this: + +``` +blending_rules: + - from: "*" + to: "*" + easing: "sineOut" + duration: 0.25 + - from: "*" + to: "idlesneak*" + easing: "springOutWeak" + duration: 0.4 +``` + +See ``OpenMW\files\data\animations\animation-config.yaml`` for an example of such a file. + +Every blending rule should include a set of following fields: + +``from`` and ``to`` are rules that will attempt to match animation names; they usually look like ``animationGroupName:keyName`` where ``keyName`` is essentially the name of a specific action within the animation group. +Examples: ``"weapononehanded: chop start"``, ``"idle1h"``, ``"jump: start"`` e.t.c. + +.. note:: + + ``keyName`` is not always present and if omitted - the rule will match any ``keyName``. + The different animation names the game uses can be inspected by opening ``.kf`` animation files in Blender. + + +Both ``animationGroupName`` and ``keyName`` support wildcard characters either at the beginning, the end of the name, or instead of the name: + +- ``"*"`` will match any name. +- ``"*idle:sta*"`` will match an animationGroupName ending with ``idle`` and a keyName starting with ``sta``. +- ``"weapon*handed: chop*attack"`` will not work since we don't support wildcards in the middle. + +``easing`` is an animation blending function, i.e., a style of transition between animations, look below to see the list of possible easings. + +``duration`` is the transition duration in seconds, 0.2-0.4 are usually reasonable transition times, but this highly depends on your use case. + +.. note:: + + The bottom-most rule takes precedence in the animation config files. + + +List of possible easings +------------------------ + +- "linear" +- "sineOut" +- "sineIn" +- "sineInOut" +- "cubicOut" +- "cubicIn" +- "cubicInOut" +- "quartOut" +- "quartIn" +- "quartInOut" +- "springOutGeneric" +- "springOutWeak" +- "springOutMed" +- "springOutStrong" +- "springOutTooMuch" + +``"sineOut"`` easing is usually a safe bet. In general ``"...Out"`` easing functions will yield a transition that is fast at the beginning of the transition but slows down towards the end, that style of transitions usually looks good on organic animations e.g. humanoids and creatures. + +``"...In"`` transitions begin slow but end fast, ``"...InOut"`` begin fast, slowdown in the middle, end fast. + +Its hard to give an example of use cases for the latter 2 types of easing functions, they are there for developers to experiment. + +The possible easings are largely ported from `here `__ and have similar names. Except for the ``springOut`` family, those are similar to ``elasticOut`` from `easings.net `__, with ``springOutWeak`` being almost identical to ``elasticOut``. + +Don't be afraid to experiment with different timing and easing functions! \ No newline at end of file diff --git a/docs/source/reference/modding/extended.rst b/docs/source/reference/modding/extended.rst index db7df2e916..3243809949 100644 --- a/docs/source/reference/modding/extended.rst +++ b/docs/source/reference/modding/extended.rst @@ -276,6 +276,14 @@ Also it is possible to add a "Bip01 Arrow" bone to actor skeletons. In this case Such approach allows to implement better shooting animations (for example, beast races have tail, so quivers should be attached under different angle and default arrow fetching animation does not look good). +Animation blending +------------------ + +Animation blending introduces smooth animation transitions between essentially every animation in the game without affecting gameplay. Effective if ``smooth animation transitions`` setting is enabled in the launcher or the config files. + +Animation developers can bundle ``.yaml``/``.json`` files together with their ``.kf`` files to specify the blending style of their animations. Those settings will only affect the corresponding animation files. +For more details see :doc:`animation-blending`. + Groundcover support ------------------- diff --git a/docs/source/reference/modding/index.rst b/docs/source/reference/modding/index.rst index 33532e385b..4914a52be7 100644 --- a/docs/source/reference/modding/index.rst +++ b/docs/source/reference/modding/index.rst @@ -31,5 +31,6 @@ about creating new content for OpenMW, please refer to doors-and-teleports custom-shader-effects extended + animation-blending paths localisation From 95112f78b90a7ad84762c2c4b649aa1ff143a16c Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Thu, 4 Apr 2024 01:39:01 +0100 Subject: [PATCH 04/34] Authors and changelog --- AUTHORS.md | 2 ++ CHANGELOG.md | 1 + 2 files changed, 3 insertions(+) diff --git a/AUTHORS.md b/AUTHORS.md index 59b41e8ce6..0ca5ee68c4 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -61,6 +61,7 @@ Programmers Cory F. Cohen (cfcohen) Cris Mihalache (Mirceam) crussell187 + Sam Hellawell (cykoder) Dan Vukelich (sanchezman) darkf Dave Corley (S3ctor) @@ -144,6 +145,7 @@ Programmers Łukasz Gołębiewski (lukago) Lukasz Gromanowski (lgro) Mads Sandvei (Foal) + Maksim Eremenko (Max Yari) Marc Bouvier (CramitDeFrog) Marcin Hulist (Gohan) Mark Siewert (mark76) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0558186b3d..55f696f23c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -193,6 +193,7 @@ Feature #5492: Let rain and snow collide with statics Feature #5926: Refraction based on water depth Feature #5944: Option to use camera as sound listener + Feature #6009: Animation blending - smooth animation transitions with modding support Feature #6152: Playing music via lua scripts Feature #6188: Specular lighting from point light sources Feature #6411: Support translations in openmw-launcher From da542fcf67d4216d09b911e28e0f8787e608466e Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Thu, 4 Apr 2024 01:57:24 +0100 Subject: [PATCH 05/34] Translation updates --- files/lang/launcher_de.ts | 12 ++++++++++++ files/lang/launcher_fr.ts | 8 ++++++++ files/lang/launcher_ru.ts | 8 ++++++++ 3 files changed, 28 insertions(+) diff --git a/files/lang/launcher_de.ts b/files/lang/launcher_de.ts index 9532fcc8ec..6f111cccae 100644 --- a/files/lang/launcher_de.ts +++ b/files/lang/launcher_de.ts @@ -698,6 +698,18 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Use casting animations for magic items, just as for spells.</p></body></html> + + Use magic item animation + + + + <html><head/><body><p>If enabled - makes transitions between different animations/poses much smoother. Also allows to load animation blending config .yaml files that can be bundled with the modded animations in order to customise blending styles.</p></body></html> + + + + Smooth animation transitions + + <html><head/><body><p>Makes NPCs and player movement more smooth. Recommended to use with "turn to movement direction" enabled.</p></body></html> diff --git a/files/lang/launcher_fr.ts b/files/lang/launcher_fr.ts index 3cb015db67..31cf8bbb46 100644 --- a/files/lang/launcher_fr.ts +++ b/files/lang/launcher_fr.ts @@ -698,6 +698,14 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Use casting animations for magic items, just as for spells.</p></body></html> <html><head/><body><p>Anime l'utilisation d'objet magique, de façon similaire à l'utilisation des sorts.</p></body></html> + + <html><head/><body><p>If enabled - makes transitions between different animations/poses much smoother. Also allows to load animation blending config .yaml files that can be bundled with the modded animations in order to customise blending styles.</p></body></html> + + + + Smooth animation transitions + + <html><head/><body><p>Makes NPCs and player movement more smooth. Recommended to use with "turn to movement direction" enabled.</p></body></html> <html><head/><body><p>Cette option rend les mouvements des PNJ et du joueur plus souple. Recommandé si l'option "Se tourner en direction du mouvement" est activée.</p></body></html> diff --git a/files/lang/launcher_ru.ts b/files/lang/launcher_ru.ts index 09b2fdf0e9..382f2df52b 100644 --- a/files/lang/launcher_ru.ts +++ b/files/lang/launcher_ru.ts @@ -772,6 +772,14 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Use Magic Item Animation Анимации магических предметов + + <html><head/><body><p>If enabled - makes transitions between different animations/poses much smoother. Also allows to load animation blending config .yaml files that can be bundled with the modded animations in order to customise blending styles.</p></body></html> + + + + Smooth animation transitions + + <html><head/><body><p>Makes NPCs and player movement more smooth. Recommended to use with "turn to movement direction" enabled.</p></body></html> <html><head/><body><p>Делает перемещение персонажей более плавным. Рекомендуется использовать совместно с настройкой "Поворот в направлении движения".</p></body></html> From 1282be9d29950a03e86e67dc970a1d65d56fd852 Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Thu, 4 Apr 2024 02:40:02 +0100 Subject: [PATCH 06/34] Fix stats reporting --- components/resource/stats.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/components/resource/stats.cpp b/components/resource/stats.cpp index 34c3bc560c..c902265448 100644 --- a/components/resource/stats.cpp +++ b/components/resource/stats.cpp @@ -93,6 +93,7 @@ namespace Resource "Terrain Chunk", "Terrain Texture", "Land", + "Blending Rules", }; constexpr std::string_view cellPreloader[] = { From 07e6ebdea1c5b1cdd87ece6af1f3299f9c964c2c Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Thu, 4 Apr 2024 17:56:02 +0100 Subject: [PATCH 07/34] Update english translation, add ruski translation --- apps/launcher/ui/settingspage.ui | 2 +- files/lang/launcher_de.ts | 2 +- files/lang/launcher_fr.ts | 2 +- files/lang/launcher_ru.ts | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/apps/launcher/ui/settingspage.ui b/apps/launcher/ui/settingspage.ui index b9fd9607e1..e93debe970 100644 --- a/apps/launcher/ui/settingspage.ui +++ b/apps/launcher/ui/settingspage.ui @@ -429,7 +429,7 @@ - <html><head/><body><p>If enabled - makes transitions between different animations/poses much smoother. Also allows to load animation blending config .yaml files that can be bundled with the modded animations in order to customise blending styles.</p></body></html> + <html><head/><body><p>If enabled - makes transitions between different animations/poses much smoother. Also allows to load animation blending config YAML files that can be bundled with animations in order to customise blending styles.</p></body></html> Smooth animation transitions diff --git a/files/lang/launcher_de.ts b/files/lang/launcher_de.ts index 6f111cccae..e73f32712f 100644 --- a/files/lang/launcher_de.ts +++ b/files/lang/launcher_de.ts @@ -703,7 +703,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov - <html><head/><body><p>If enabled - makes transitions between different animations/poses much smoother. Also allows to load animation blending config .yaml files that can be bundled with the modded animations in order to customise blending styles.</p></body></html> + <html><head/><body><p>If enabled - makes transitions between different animations/poses much smoother. Also allows to load animation blending config YAML files that can be bundled with animations in order to customise blending styles.</p></body></html> diff --git a/files/lang/launcher_fr.ts b/files/lang/launcher_fr.ts index 31cf8bbb46..a4bb792323 100644 --- a/files/lang/launcher_fr.ts +++ b/files/lang/launcher_fr.ts @@ -699,7 +699,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Anime l'utilisation d'objet magique, de façon similaire à l'utilisation des sorts.</p></body></html> - <html><head/><body><p>If enabled - makes transitions between different animations/poses much smoother. Also allows to load animation blending config .yaml files that can be bundled with the modded animations in order to customise blending styles.</p></body></html> + <html><head/><body><p>If enabled - makes transitions between different animations/poses much smoother. Also allows to load animation blending config YAML files that can be bundled with animations in order to customise blending styles.</p></body></html> diff --git a/files/lang/launcher_ru.ts b/files/lang/launcher_ru.ts index 382f2df52b..bb8b372bce 100644 --- a/files/lang/launcher_ru.ts +++ b/files/lang/launcher_ru.ts @@ -773,12 +773,12 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Анимации магических предметов - <html><head/><body><p>If enabled - makes transitions between different animations/poses much smoother. Also allows to load animation blending config .yaml files that can be bundled with the modded animations in order to customise blending styles.</p></body></html> - + <html><head/><body><p>If enabled - makes transitions between different animations/poses much smoother. Also allows to load animation blending config YAML files that can be bundled with animations in order to customise blending styles.</p></body></html> + <html><head/><body><p>Если настройка включена, она делает переходы между различными анимациями/позами намного глаже. Кроме того, она позволяет загружать YAML-файлы конфигураций смешивания анимаций, которые могут быть включены с анимациями, чтобы кастомизировать стили смешивания.</p></body></html> Smooth animation transitions - + Плавные переходы между анимациями <html><head/><body><p>Makes NPCs and player movement more smooth. Recommended to use with "turn to movement direction" enabled.</p></body></html> From 28c8e6b1fef6de535395cc8cbc7c66d5b0e32d79 Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Fri, 5 Apr 2024 22:20:32 +0100 Subject: [PATCH 08/34] Cleanup loadRules method, smooth animation transitions game.rst docuemntation --- components/resource/animblendrulesmanager.cpp | 17 ++++++----------- docs/source/reference/modding/settings/game.rst | 11 +++++++++++ 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/components/resource/animblendrulesmanager.cpp b/components/resource/animblendrulesmanager.cpp index 7bfea70974..7f46079ea5 100644 --- a/components/resource/animblendrulesmanager.cpp +++ b/components/resource/animblendrulesmanager.cpp @@ -58,7 +58,7 @@ namespace Resource osg::ref_ptr AnimBlendRulesManager::loadRules(std::string_view path) { - const VFS::Path::Normalized normalizedPath = VFS::Path::Normalized(path); + const VFS::Path::Normalized normalizedPath(path); std::optional> obj = mCache->getRefFromObjectCacheOrNone(normalizedPath); if (obj.has_value()) { @@ -67,15 +67,7 @@ namespace Resource else { osg::ref_ptr blendRules = AnimBlendRules::fromFile(mVfs, normalizedPath); - if (blendRules == nullptr) - { - // No blend rules were found in VFS, cache a nullptr. - osg::ref_ptr nullRules = nullptr; - mCache->addEntryToObjectCache(normalizedPath, nullRules); - // To avoid confusion - never return blend rules with 0 rules - return nullRules; - } - else + if (blendRules) { // Blend rules were found in VFS, cache them. mCache->addEntryToObjectCache(normalizedPath, blendRules); @@ -83,7 +75,10 @@ namespace Resource } } - return nullptr; + // No blend rules were found in VFS, cache a nullptr. + osg::ref_ptr nullRules = nullptr; + mCache->addEntryToObjectCache(normalizedPath, nullRules); + return nullRules; } void AnimBlendRulesManager::reportStats(unsigned int frameNumber, osg::Stats* stats) const diff --git a/docs/source/reference/modding/settings/game.rst b/docs/source/reference/modding/settings/game.rst index 368401f5c5..a602fa99fe 100644 --- a/docs/source/reference/modding/settings/game.rst +++ b/docs/source/reference/modding/settings/game.rst @@ -541,3 +541,14 @@ In third person, the camera will sway along with the movement animations of the Enabling this option disables this swaying by having the player character move independently of its animation. This setting can be controlled in the Settings tab of the launcher. + +smooth animation transitions +--------------------------------- + +:Type: boolean +:Range: True/False +:Default: False + +Enabling this option uses smooth transitions between animations making them a lot less jarring. Also allows to load modded animation blending. + +This setting can be controlled in the Settings tab of the launcher. From a2a4d222a7d051210fc91652f09923dad75b2d9a Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Sat, 20 Apr 2024 19:39:09 +0100 Subject: [PATCH 09/34] Localization fixes --- files/lang/launcher_de.ts | 4 ---- files/lang/launcher_sv.ts | 8 ++++++++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/files/lang/launcher_de.ts b/files/lang/launcher_de.ts index e73f32712f..533207a5d3 100644 --- a/files/lang/launcher_de.ts +++ b/files/lang/launcher_de.ts @@ -698,10 +698,6 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Use casting animations for magic items, just as for spells.</p></body></html> - - Use magic item animation - - <html><head/><body><p>If enabled - makes transitions between different animations/poses much smoother. Also allows to load animation blending config YAML files that can be bundled with animations in order to customise blending styles.</p></body></html> diff --git a/files/lang/launcher_sv.ts b/files/lang/launcher_sv.ts index 2d590fea11..449ba5e197 100644 --- a/files/lang/launcher_sv.ts +++ b/files/lang/launcher_sv.ts @@ -1446,5 +1446,13 @@ de ordinarie fonterna i Morrowind. Bocka denna ruta om du ändå föredrar ordin Run Script After Startup: Kör skript efter uppstart: + + <html><head/><body><p>If enabled - makes transitions between different animations/poses much smoother. Also allows to load animation blending config YAML files that can be bundled with animations in order to customise blending styles.</p></body></html> + + + + Smooth animation transitions + + From 22229dd674339ef7eeaf479579a08f6c236ad20d Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Sat, 20 Apr 2024 20:08:50 +0100 Subject: [PATCH 10/34] Cleanup, use string_view comparison --- apps/openmw/mwrender/animation.cpp | 10 +++------- apps/openmw/mwrender/animblendcontroller.cpp | 20 ++++++-------------- 2 files changed, 9 insertions(+), 21 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 9afc5baafa..9b9236cde5 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -421,7 +421,7 @@ namespace MWRender osg::Callback* updateCb = bone->getUpdateCallback(); while (updateCb) { - if (updateCb->className() == std::string(controller->className())) + if (updateCb->className() == std::string_view(controller->className())) { osg::ref_ptr nextCb = updateCb->getNestedCallback(); bone->removeUpdateCallback(updateCb); @@ -438,7 +438,7 @@ namespace MWRender updateCb = bone->getUpdateCallback(); while (updateCb) { - if (updateCb->className() == std::string("UpdateBone")) + if (updateCb->className() == std::string_view("UpdateBone")) { // Override the immediate callback after the UpdateBone osg::ref_ptr lastCb = updateCb->getNestedCallback(); @@ -452,7 +452,7 @@ namespace MWRender } } - // Traverse childrne if this is a group + // Traverse child bones if this is a group osg::Group* group = parent->asGroup(); if (group) for (unsigned int i = 0; i < group->getNumChildren(); ++i) @@ -900,8 +900,6 @@ namespace MWRender if (!mObjectRoot || mAnimSources.empty()) return; - // Log(Debug::Info) << "Please play: " << groupname << ":" << start << "..." << stop << " mask: " << blendMask; - if (groupname.empty()) { resetActiveGroups(); @@ -918,8 +916,6 @@ namespace MWRender while (stateiter != mStates.end()) { if (stateiter->second.mPriority == priority && stateiter->first != groupname) - // This MIGH be a problem since we want old states to be still running so the AnimBlendingController can - // blend them properly mStates.erase(stateiter++); else ++stateiter; diff --git a/apps/openmw/mwrender/animblendcontroller.cpp b/apps/openmw/mwrender/animblendcontroller.cpp index 06c18c9ee9..5edd6c8e75 100644 --- a/apps/openmw/mwrender/animblendcontroller.cpp +++ b/apps/openmw/mwrender/animblendcontroller.cpp @@ -82,7 +82,6 @@ namespace MWRender namespace { - // Helper methods osg::Vec3f vec3fLerp(float t, const osg::Vec3f& A, const osg::Vec3f& B) { return A + (B - A) * t; @@ -103,11 +102,12 @@ namespace MWRender void AnimBlendControllerBase::setKeyframeTrack(osg::ref_ptr kft, AnimBlendStateData newState, osg::ref_ptr blendRules) { + // If aimation has changed, start blending if (newState.mGroupname != mAnimState.mGroupname || newState.mStartKey != mAnimState.mStartKey || kft != mKeyframeTrack) { - // Animation have changed, start blending! - // Log(Debug::Info) << "Animation change to: " << newState.mGroupname << ":" << newState.mStartKey; + // Allow logging of cahnge to aid with implementing animations for developers/modders + // Log(Debug::Verbose) << "Animation change to: " << newState.mGroupname << ":" << newState.mStartKey; // Default blend settings mBlendDuration = 0; @@ -118,11 +118,7 @@ namespace MWRender // Finds a matching blend rule either in this or previous ruleset auto blendRule = blendRules->findBlendingRule( mAnimState.mGroupname, mAnimState.mStartKey, newState.mGroupname, newState.mStartKey); - // This will also check the previous ruleset, not sure it's a good idea though, commenting out - // for now. - /*if (!blendRule && mAnimBlendRules) - blendRule = mAnimBlendRules->findBlendingRule( - mAnimState.mGroupname, mAnimState.mStartKey, newState.mGroupname, newState.mStartKey);*/ + if (blendRule) { if (Easings::easingsMap.contains(blendRule->mEasing)) @@ -171,10 +167,10 @@ namespace MWRender // Shouldnt happen, but potentially an edge case where a new bone was added // between gatherRecursiveBoneTransforms and this update - // so far OpenMW will never do this, so this check shouldn't be needed in production + // currently OpenMW will never do this, but potentially useful assert(mBlendBoneTransforms.find(bone) != mBlendBoneTransforms.end()); - // every frame the osgAnimation controller updates this + // Every frame the osgAnimation controller updates this // so it is ok that we update it directly below osg::Matrixf currentSampledMatrix = bone->getMatrix(); const osg::Matrixf& lastSampledMatrix = mBlendBoneTransforms.at(bone); @@ -275,7 +271,6 @@ namespace MWRender if (mInterpActive) { - // Interpolate node's rotation if (rotation) { osg::Quat lerpedRot; @@ -288,7 +283,6 @@ namespace MWRender node->setRotation(node->mRotation); } - // Update node's translation if (translation) { osg::Vec3f lerpedTrans = vec3fLerp(mInterpFactor, mBlendStartTrans, *translation); @@ -297,7 +291,6 @@ namespace MWRender } else { - // Update node's translation if (translation) node->setTranslation(*translation); @@ -307,7 +300,6 @@ namespace MWRender node->setRotation(node->mRotation); } - // Update node's scale if (scale) // Scale is not lerped based on the idea that it is much more likely that scale animation will be used to // instantly hide/show objects in which case the scale interpolation is undesirable. From 13e1df3bf0d6fa806639cfa0956e2d1b84781021 Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Fri, 26 Apr 2024 03:00:50 +0100 Subject: [PATCH 11/34] Grammar cleanup, code cleanup, reduce logging, revert mRotation change --- apps/openmw/mwrender/animation.cpp | 130 +++++++++--------- apps/openmw/mwrender/animation.hpp | 9 +- apps/openmw/mwrender/animblendcontroller.cpp | 15 +- apps/openmw/mwrender/animblendcontroller.hpp | 6 +- components/nifosg/controller.cpp | 2 +- components/nifosg/matrixtransform.cpp | 12 +- components/nifosg/matrixtransform.hpp | 3 +- components/resource/animblendrulesmanager.cpp | 16 +-- components/resource/animblendrulesmanager.hpp | 4 +- components/sceneutil/animblendrules.cpp | 9 +- 10 files changed, 104 insertions(+), 102 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 9b9236cde5..73c0a414dc 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -416,7 +416,7 @@ namespace MWRender // guaranteed to update before osg::ref_ptr cb = new BoneAnimBlendControllerWrapper(controller, bone); - // Ensure there is no other AnimBlendController - this can happen if using + // Ensure there is no other AnimBlendController - this can happen when using // multiple animations with different roots, such as NPC animation osg::Callback* updateCb = bone->getUpdateCallback(); while (updateCb) @@ -434,7 +434,7 @@ namespace MWRender } // Find UpdateBone callback and bind to just after that (order is important) - // NOTE: if it doesnt have an UpdateBone callback, we shouldnt be doing blending! + // NOTE: if it doesn't have an UpdateBone callback, we shouldn't be doing blending! updateCb = bone->getUpdateCallback(); while (updateCb) { @@ -666,7 +666,6 @@ namespace MWRender for (const auto& name : mResourceSystem->getVFS()->getRecursiveDirectoryIterator(animationPath)) { - if (Misc::getFileExtension(name) == "kf") { addSingleAnimSource(name, baseModel); @@ -769,13 +768,22 @@ namespace MWRender Misc::StringUtils::replaceLast(yamlpath, ".dae", ".yaml"); // globalBlendConfigPath is only used with actors! Objects have no default blending. - std::string_view globalBlendConfigPath = "animations/animation-config.yaml"; + const VFS::Path::NormalizedView globalBlendConfigPath("animations/animation-config.yaml"); + const VFS::Path::NormalizedView blendConfigPath(yamlpath); osg::ref_ptr blendRules; if (mPtr.getClass().isActor()) - blendRules = mResourceSystem->getAnimBlendRulesManager()->getRules(globalBlendConfigPath, yamlpath); + { + blendRules + = mResourceSystem->getAnimBlendRulesManager()->getRules(globalBlendConfigPath, blendConfigPath); + if (blendRules == nullptr) + Log(Debug::Warning) << "Animation blending files were not found '" << blendConfigPath.value() + << "' or '" << globalBlendConfigPath.value() << "'"; + } else - blendRules = mResourceSystem->getAnimBlendRulesManager()->getRules(yamlpath); + { + blendRules = mResourceSystem->getAnimBlendRulesManager()->getRules(blendConfigPath); + } // At this point blendRules will either be nullptr or an AnimBlendRules instance with > 0 rules inside. animsrc->mAnimBlendRules = blendRules; @@ -1076,6 +1084,48 @@ namespace MWRender return mNodeMap; } + template + inline osg::Callback* Animation::handleBlendTransform(osg::ref_ptr node, + osg::ref_ptr keyframeController, + std::map, osg::ref_ptr>>& blendControllers, + const AnimBlendStateData& stateData, const osg::ref_ptr& blendRules, + const AnimState& active) + { + osg::ref_ptr animController; + + if (blendControllers.contains(node)) + { + animController = blendControllers[node]; + animController->setKeyframeTrack(keyframeController, stateData, blendRules); + } + else + { + animController = new ControllerType(keyframeController, stateData, blendRules); + blendControllers[node] = animController; + + if constexpr (std::is_same_v) + assignBoneBlendCallbackRecursive(animController, mActiveControllers, node, true); + } + + keyframeController->mTime = active.mTime; + + if constexpr (std::is_same_v) + { + // IMPORTANT: we must gather all transforms at point of change before next update + // instead of at the root update callback because the root bone may require blending. + if (animController->getBlendTrigger()) + animController->gatherRecursiveBoneTransforms(static_cast(node.get())); + + // Register blend callback after the initial animation callback + node->addUpdateCallback(animController->getAsCallback()); + mActiveControllers.emplace_back(node, animController->getAsCallback()); + + return keyframeController->getAsCallback(); + } + + return animController->getAsCallback(); + } + void Animation::resetActiveGroups() { // remove all previous external controllers from the scene graph @@ -1122,70 +1172,24 @@ namespace MWRender osg::ref_ptr node = getNodeMap().at( it->first); // this should not throw, we already checked for the node existing in addAnimSource - osg::Callback* callback; const bool useSmoothAnims = Settings::game().mSmoothAnimTransitions; - if (useSmoothAnims && dynamic_cast(node.get())) - { - // Update an existing animation blending controller or create a new one for NIF animations - osg::ref_ptr animController; - - if (mAnimBlendControllers.contains(node)) - { - animController = mAnimBlendControllers[node]; - animController->setKeyframeTrack(it->second, stateData, animsrc->mAnimBlendRules); - } - else - { - animController = osg::ref_ptr( - new AnimBlendController(it->second, stateData, animsrc->mAnimBlendRules)); + const bool isNifTransform = dynamic_cast(node.get()) != nullptr; + const bool isBoneTransform = dynamic_cast(node.get()) != nullptr; - mAnimBlendControllers[node] = animController; - } - - it->second->mTime = active->second.mTime; - - callback = animController->getAsCallback(); - } - else if (useSmoothAnims && dynamic_cast(node.get())) + osg::Callback* callback = it->second->getAsCallback(); + if (useSmoothAnims) { - // Update an existing animation blending controller or create a new one for osgAnimation - osg::ref_ptr animController; - - if (mBoneAnimBlendControllers.contains(node)) + if (isNifTransform) { - animController = mBoneAnimBlendControllers[node]; - animController->setKeyframeTrack(it->second, stateData, animsrc->mAnimBlendRules); + callback = handleBlendTransform(node, + it->second, mAnimBlendControllers, stateData, animsrc->mAnimBlendRules, active->second); } - else + else if (isBoneTransform) { - animController = osg::ref_ptr( - new BoneAnimBlendController(it->second, stateData, animsrc->mAnimBlendRules)); - - mBoneAnimBlendControllers[node] = animController; - - assignBoneBlendCallbackRecursive(animController, mActiveControllers, node, true); + callback + = handleBlendTransform(node, it->second, + mBoneAnimBlendControllers, stateData, animsrc->mAnimBlendRules, active->second); } - - // IMPORTANT: we must gather all transforms at point of change before next update - // instead of at the root update callback because the root bone may need blending - if (animController->getBlendTrigger()) - animController->gatherRecursiveBoneTransforms(static_cast(node.get())); - - it->second->mTime = active->second.mTime; - - // Register blend callback after the initial animation callback - callback = animController->getAsCallback(); - - node->addUpdateCallback(callback); - mActiveControllers.emplace_back(node, callback); - - // Ensure the original animation update callback is still applied - // this is because we need this to happen first to get the latest transform to blend to - callback = it->second->getAsCallback(); - } - else - { - callback = it->second->getAsCallback(); } node->addUpdateCallback(callback); diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index e40277a454..82d7d98eba 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -51,7 +51,7 @@ namespace MWRender class RotateController; class TransparencyUpdater; - typedef std::vector, osg::ref_ptr>> ActiveControllersVector; + using ActiveControllersVector = std::vector, osg::ref_ptr>>; class EffectAnimationTime : public SceneUtil::ControllerSource { @@ -312,6 +312,13 @@ namespace MWRender void removeFromSceneImpl(); + template + inline osg::Callback* handleBlendTransform(osg::ref_ptr node, + osg::ref_ptr keyframeController, + std::map, osg::ref_ptr>>& blendControllers, + const AnimBlendStateData& stateData, const osg::ref_ptr& blendRules, + const AnimState& active); + public: Animation( const MWWorld::Ptr& ptr, osg::ref_ptr parentNode, Resource::ResourceSystem* resourceSystem); diff --git a/apps/openmw/mwrender/animblendcontroller.cpp b/apps/openmw/mwrender/animblendcontroller.cpp index 5edd6c8e75..e22e2ccfa3 100644 --- a/apps/openmw/mwrender/animblendcontroller.cpp +++ b/apps/openmw/mwrender/animblendcontroller.cpp @@ -102,13 +102,10 @@ namespace MWRender void AnimBlendControllerBase::setKeyframeTrack(osg::ref_ptr kft, AnimBlendStateData newState, osg::ref_ptr blendRules) { - // If aimation has changed, start blending + // If animation has changed then start blending if (newState.mGroupname != mAnimState.mGroupname || newState.mStartKey != mAnimState.mStartKey || kft != mKeyframeTrack) { - // Allow logging of cahnge to aid with implementing animations for developers/modders - // Log(Debug::Verbose) << "Animation change to: " << newState.mGroupname << ":" << newState.mStartKey; - // Default blend settings mBlendDuration = 0; mEasingFn = &Easings::sineOut; @@ -167,12 +164,12 @@ namespace MWRender // Shouldnt happen, but potentially an edge case where a new bone was added // between gatherRecursiveBoneTransforms and this update - // currently OpenMW will never do this, but potentially useful + // currently OpenMW will never do this assert(mBlendBoneTransforms.find(bone) != mBlendBoneTransforms.end()); // Every frame the osgAnimation controller updates this // so it is ok that we update it directly below - osg::Matrixf currentSampledMatrix = bone->getMatrix(); + const osg::Matrixf& currentSampledMatrix = bone->getMatrix(); const osg::Matrixf& lastSampledMatrix = mBlendBoneTransforms.at(bone); const osg::Vec3f scale = currentSampledMatrix.getScale(); @@ -262,7 +259,7 @@ namespace MWRender mBlendTrigger = false; mBlendStartTime = time; // Nif mRotation is used here because it's unaffected by the side-effects of RotationController - mBlendStartRot = node->mRotation.toOsgMatrix().getRotate(); + mBlendStartRot = node->mRotationScale.toOsgMatrix().getRotate(); mBlendStartTrans = node->getMatrix().getTrans(); mBlendStartScale = node->mScale; } @@ -280,7 +277,7 @@ namespace MWRender else { // This is necessary to prevent first person animation glitching out - node->setRotation(node->mRotation); + node->setRotation(node->mRotationScale); } if (translation) @@ -297,7 +294,7 @@ namespace MWRender if (rotation) node->setRotation(*rotation); else - node->setRotation(node->mRotation); + node->setRotation(node->mRotationScale); } if (scale) diff --git a/apps/openmw/mwrender/animblendcontroller.hpp b/apps/openmw/mwrender/animblendcontroller.hpp index 0ccbc43a5c..2f1f66d51d 100644 --- a/apps/openmw/mwrender/animblendcontroller.hpp +++ b/apps/openmw/mwrender/animblendcontroller.hpp @@ -49,7 +49,7 @@ namespace MWRender void gatherRecursiveBoneTransforms(osgAnimation::Bone* parent, bool isRoot = true); void applyBoneBlend(osgAnimation::Bone* parent); - const char* libraryName() const override { return "openmw"; } + const char* libraryName() const override { return "MWRender"; } const char* className() const override { return "AnimBlendController"; } protected: @@ -77,8 +77,8 @@ namespace MWRender std::unordered_map mBlendBoneTransforms; }; - typedef AnimBlendControllerBase AnimBlendController; - typedef AnimBlendControllerBase BoneAnimBlendController; + using AnimBlendController = AnimBlendControllerBase; + using BoneAnimBlendController = AnimBlendControllerBase; // Assigned to child bones with an instance of AnimBlendControllerBase class BoneAnimBlendControllerWrapper : public osg::Callback diff --git a/components/nifosg/controller.cpp b/components/nifosg/controller.cpp index 80fec6e39d..7e4c5da7a0 100644 --- a/components/nifosg/controller.cpp +++ b/components/nifosg/controller.cpp @@ -186,7 +186,7 @@ namespace NifOsg else { // This is necessary to prevent first person animations glitching out due to RotationController - node->setRotation(node->mRotation); + node->setRotation(node->mRotationScale); } if (translation) diff --git a/components/nifosg/matrixtransform.cpp b/components/nifosg/matrixtransform.cpp index caf0fa6bfb..a59f10360a 100644 --- a/components/nifosg/matrixtransform.cpp +++ b/components/nifosg/matrixtransform.cpp @@ -5,14 +5,14 @@ namespace NifOsg MatrixTransform::MatrixTransform(const Nif::NiTransform& transform) : osg::MatrixTransform(transform.toMatrix()) , mScale(transform.mScale) - , mRotation(transform.mRotation) + , mRotationScale(transform.mRotation) { } MatrixTransform::MatrixTransform(const MatrixTransform& copy, const osg::CopyOp& copyop) : osg::MatrixTransform(copy, copyop) , mScale(copy.mScale) - , mRotation(copy.mRotation) + , mRotationScale(copy.mRotationScale) { } @@ -24,7 +24,7 @@ namespace NifOsg // Rescale the node using the known components. for (int i = 0; i < 3; ++i) for (int j = 0; j < 3; ++j) - _matrix(i, j) = mRotation.mValues[j][i] * mScale; // NB: column/row major difference + _matrix(i, j) = mRotationScale.mValues[j][i] * mScale; // NB: column/row major difference _inverseDirty = true; dirtyBound(); @@ -40,7 +40,7 @@ namespace NifOsg for (int j = 0; j < 3; ++j) { // Update the current decomposed rotation and restore the known scale. - mRotation.mValues[j][i] = _matrix(i, j); // NB: column/row major difference + mRotationScale.mValues[j][i] = _matrix(i, j); // NB: column/row major difference _matrix(i, j) *= mScale; } } @@ -52,12 +52,12 @@ namespace NifOsg void MatrixTransform::setRotation(const Nif::Matrix3& rotation) { // Update the decomposed rotation. - mRotation = rotation; + mRotationScale = rotation; // Reorient the node using the known components. for (int i = 0; i < 3; ++i) for (int j = 0; j < 3; ++j) - _matrix(i, j) = mRotation.mValues[j][i] * mScale; // NB: column/row major difference + _matrix(i, j) = mRotationScale.mValues[j][i] * mScale; // NB: column/row major difference _inverseDirty = true; dirtyBound(); diff --git a/components/nifosg/matrixtransform.hpp b/components/nifosg/matrixtransform.hpp index 7f5c908156..4e42d00787 100644 --- a/components/nifosg/matrixtransform.hpp +++ b/components/nifosg/matrixtransform.hpp @@ -23,8 +23,7 @@ namespace NifOsg // problems when a KeyframeController wants to change only one of these components. So // we store the scale and rotation components separately here. float mScale{ 0.f }; - - Nif::Matrix3 mRotation; + Nif::Matrix3 mRotationScale; // Utility methods to transform the node and keep these components up-to-date. // The matrix's components should not be overridden manually or using preMult/postMult diff --git a/components/resource/animblendrulesmanager.cpp b/components/resource/animblendrulesmanager.cpp index 7f46079ea5..68560c3ed1 100644 --- a/components/resource/animblendrulesmanager.cpp +++ b/components/resource/animblendrulesmanager.cpp @@ -31,7 +31,7 @@ namespace Resource } osg::ref_ptr AnimBlendRulesManager::getRules( - std::string_view path, std::string_view overridePath) + const VFS::Path::NormalizedView path, const VFS::Path::NormalizedView overridePath) { // Note: Providing a non-existing path but an existing overridePath is not supported! auto tmpl = loadRules(path); @@ -43,7 +43,7 @@ namespace Resource osg::ref_ptr blendRules(new AnimBlendRules(*tmpl, osg::CopyOp::SHALLOW_COPY)); blendRules->getOrCreateUserDataContainer()->addUserObject(new Resource::TemplateRef(tmpl)); - if (!overridePath.empty()) + if (!overridePath.value().empty()) { auto blendRuleOverrides = loadRules(overridePath); if (blendRuleOverrides) @@ -56,28 +56,28 @@ namespace Resource return blendRules; } - osg::ref_ptr AnimBlendRulesManager::loadRules(std::string_view path) + osg::ref_ptr AnimBlendRulesManager::loadRules(VFS::Path::NormalizedView path) { - const VFS::Path::Normalized normalizedPath(path); - std::optional> obj = mCache->getRefFromObjectCacheOrNone(normalizedPath); + const std::string normalized = VFS::Path::normalizeFilename(path.value()); + std::optional> obj = mCache->getRefFromObjectCacheOrNone(normalized); if (obj.has_value()) { return osg::ref_ptr(static_cast(obj->get())); } else { - osg::ref_ptr blendRules = AnimBlendRules::fromFile(mVfs, normalizedPath); + osg::ref_ptr blendRules = AnimBlendRules::fromFile(mVfs, path); if (blendRules) { // Blend rules were found in VFS, cache them. - mCache->addEntryToObjectCache(normalizedPath, blendRules); + mCache->addEntryToObjectCache(normalized, blendRules); return blendRules; } } // No blend rules were found in VFS, cache a nullptr. osg::ref_ptr nullRules = nullptr; - mCache->addEntryToObjectCache(normalizedPath, nullRules); + mCache->addEntryToObjectCache(normalized, nullRules); return nullRules; } diff --git a/components/resource/animblendrulesmanager.hpp b/components/resource/animblendrulesmanager.hpp index c8eeee327f..cab8f01985 100644 --- a/components/resource/animblendrulesmanager.hpp +++ b/components/resource/animblendrulesmanager.hpp @@ -21,12 +21,12 @@ namespace Resource /// Retrieve a read-only keyframe resource by name (case-insensitive). /// @note Throws an exception if the resource is not found. osg::ref_ptr getRules( - std::string_view path, std::string_view overridePath = ""); + const VFS::Path::NormalizedView path, const VFS::Path::NormalizedView overridePath = ""); void reportStats(unsigned int frameNumber, osg::Stats* stats) const override; private: - osg::ref_ptr loadRules(std::string_view path); + osg::ref_ptr loadRules(VFS::Path::NormalizedView path); const VFS::Manager* mVfs; }; diff --git a/components/sceneutil/animblendrules.cpp b/components/sceneutil/animblendrules.cpp index 8289cf0f7d..716eb62374 100644 --- a/components/sceneutil/animblendrules.cpp +++ b/components/sceneutil/animblendrules.cpp @@ -62,10 +62,7 @@ namespace SceneUtil Log(Debug::Debug) << "Attempting to load animation blending config '" << configPath << "'"; if (!vfs->exists(configPath)) - { - Log(Debug::Warning) << "Animation blending files was not found '" << configPath << "'"; return nullptr; - } // Retrieving and parsing animation rules std::string rawYaml(std::istreambuf_iterator(*vfs->get(configPath)), {}); @@ -147,22 +144,20 @@ namespace SceneUtil Misc::StringUtils::lowerCaseInPlace(toKey); for (auto rule = mRules.rbegin(); rule != mRules.rend(); ++rule) { - // TO DO: Also allow for partial wildcards at the end of groups and keys via std::string startswith method bool fromMatch = false; bool toMatch = false; // Pseudocode: // If not a wildcard and found a wildcard // starts with substr(0,wildcard) - if (fitsRuleString(fromGroup, rule->mFromGroup) - && (fitsRuleString(fromKey, rule->mFromKey) || rule->mFromKey == "")) + && (rule->mFromKey.empty() || fitsRuleString(fromKey, rule->mFromKey))) { fromMatch = true; } if ((fitsRuleString(toGroup, rule->mToGroup) || (rule->mToGroup == "$" && toGroup == fromGroup)) - && (fitsRuleString(toKey, rule->mToKey) || rule->mToKey == "")) + && (rule->mToKey.empty() || fitsRuleString(toKey, rule->mToKey))) { toMatch = true; } From 42406ed0af7578cd4171a1516a94c11aec050a63 Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Fri, 26 Apr 2024 23:54:21 +0100 Subject: [PATCH 12/34] Use META_Object, ignore for serialize --- apps/openmw/mwrender/animblendcontroller.hpp | 24 ++++++++++++----- components/sceneutil/serialize.cpp | 27 ++++++++++---------- 2 files changed, 31 insertions(+), 20 deletions(-) diff --git a/apps/openmw/mwrender/animblendcontroller.hpp b/apps/openmw/mwrender/animblendcontroller.hpp index 2f1f66d51d..59ab2cbcd5 100644 --- a/apps/openmw/mwrender/animblendcontroller.hpp +++ b/apps/openmw/mwrender/animblendcontroller.hpp @@ -36,6 +36,17 @@ namespace MWRender AnimBlendControllerBase(osg::ref_ptr keyframeTrack, AnimBlendStateData animState, osg::ref_ptr blendRules); + AnimBlendControllerBase() {} + + AnimBlendControllerBase(const AnimBlendControllerBase& copy, const osg::CopyOp&) + : mTimeFactor(0.0f) + , mInterpFactor(0.0f) + { + setKeyframeTrack(copy.getKeyframeTrack(), copy.getAnimState(), copy.getBlendRules()); + } + + META_Object(MWRender, AnimBlendControllerBase) + void operator()(NifOsg::MatrixTransform* node, osg::NodeVisitor* nv); void operator()(osgAnimation::Bone* node, osg::NodeVisitor* nv); @@ -49,13 +60,9 @@ namespace MWRender void gatherRecursiveBoneTransforms(osgAnimation::Bone* parent, bool isRoot = true); void applyBoneBlend(osgAnimation::Bone* parent); - const char* libraryName() const override { return "MWRender"; } - const char* className() const override { return "AnimBlendController"; } - - protected: - osg::ref_ptr mKeyframeTrack; - - inline void calculateInterpFactor(float time); + osg::ref_ptr getKeyframeTrack() const { return mKeyframeTrack; } + osg::ref_ptr getBlendRules() const { return mAnimBlendRules; } + AnimBlendStateData getAnimState() const { return mAnimState; } private: Easings::EasingFn mEasingFn; @@ -73,8 +80,11 @@ namespace MWRender AnimBlendStateData mAnimState; osg::ref_ptr mAnimBlendRules; + osg::ref_ptr mKeyframeTrack; std::unordered_map mBlendBoneTransforms; + + inline void calculateInterpFactor(float time); }; using AnimBlendController = AnimBlendControllerBase; diff --git a/components/sceneutil/serialize.cpp b/components/sceneutil/serialize.cpp index 93237dc02d..73be49929c 100644 --- a/components/sceneutil/serialize.cpp +++ b/components/sceneutil/serialize.cpp @@ -187,19 +187,20 @@ namespace SceneUtil mgr->addWrapper(new GeometrySerializer); // ignore the below for now to avoid warning spam - const char* ignore[] - = { "Debug::DebugDrawer", "MWRender::PtrHolder", "Resource::TemplateRef", "Resource::TemplateMultiRef", - "SceneUtil::CompositeStateSetUpdater", "SceneUtil::UBOManager", "SceneUtil::LightListCallback", - "SceneUtil::LightManagerUpdateCallback", "SceneUtil::FFPLightStateAttribute", - "SceneUtil::UpdateRigBounds", "SceneUtil::UpdateRigGeometry", "SceneUtil::LightSource", - "SceneUtil::DisableLight", "SceneUtil::MWShadowTechnique", "SceneUtil::TextKeyMapHolder", - "Shader::AddedState", "Shader::RemovedAlphaFunc", "NifOsg::FlipController", - "NifOsg::KeyframeController", "NifOsg::Emitter", "NifOsg::ParticleColorAffector", - "NifOsg::ParticleSystem", "NifOsg::GravityAffector", "NifOsg::ParticleBomb", - "NifOsg::GrowFadeAffector", "NifOsg::InverseWorldMatrix", "NifOsg::StaticBoundingBoxCallback", - "NifOsg::GeomMorpherController", "NifOsg::UpdateMorphGeometry", "NifOsg::UVController", - "NifOsg::VisController", "osgMyGUI::Drawable", "osg::DrawCallback", "osg::UniformBufferObject", - "osgOQ::ClearQueriesCallback", "osgOQ::RetrieveQueriesCallback", "osg::DummyObject" }; + const char* ignore[] = { "Debug::DebugDrawer", "AnimBlendControllerBase", + "AnimBlendControllerBase", "MWRender::PtrHolder", "Resource::TemplateRef", + "Resource::TemplateMultiRef", "SceneUtil::CompositeStateSetUpdater", "SceneUtil::UBOManager", + "SceneUtil::LightListCallback", "SceneUtil::LightManagerUpdateCallback", + "SceneUtil::FFPLightStateAttribute", "SceneUtil::UpdateRigBounds", "SceneUtil::UpdateRigGeometry", + "SceneUtil::LightSource", "SceneUtil::DisableLight", "SceneUtil::MWShadowTechnique", + "SceneUtil::TextKeyMapHolder", "Shader::AddedState", "Shader::RemovedAlphaFunc", + "NifOsg::FlipController", "NifOsg::KeyframeController", "NifOsg::Emitter", + "NifOsg::ParticleColorAffector", "NifOsg::ParticleSystem", "NifOsg::GravityAffector", + "NifOsg::ParticleBomb", "NifOsg::GrowFadeAffector", "NifOsg::InverseWorldMatrix", + "NifOsg::StaticBoundingBoxCallback", "NifOsg::GeomMorpherController", "NifOsg::UpdateMorphGeometry", + "NifOsg::UVController", "NifOsg::VisController", "osgMyGUI::Drawable", "osg::DrawCallback", + "osg::UniformBufferObject", "osgOQ::ClearQueriesCallback", "osgOQ::RetrieveQueriesCallback", + "osg::DummyObject" }; for (size_t i = 0; i < sizeof(ignore) / sizeof(ignore[0]); ++i) { mgr->addWrapper(makeDummySerializer(ignore[i])); From 00a7d0281fa78ef7e48c1980b5609c28337940aa Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Sat, 27 Apr 2024 15:19:39 +0100 Subject: [PATCH 13/34] Cleanup, refactor, rename AnimBlendControllerBase -> AnimBlendController --- apps/openmw/mwrender/animation.cpp | 38 +++++++------- apps/openmw/mwrender/animation.hpp | 12 ++--- apps/openmw/mwrender/animblendcontroller.cpp | 25 +++++----- apps/openmw/mwrender/animblendcontroller.hpp | 50 +++++++++---------- components/resource/animblendrulesmanager.cpp | 21 ++------ components/resource/animblendrulesmanager.hpp | 2 - components/sceneutil/animblendrules.cpp | 2 +- components/sceneutil/animblendrules.hpp | 2 +- components/sceneutil/serialize.cpp | 28 +++++------ 9 files changed, 80 insertions(+), 100 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 73c0a414dc..04d524c70f 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -14,6 +14,7 @@ #include #include +#include #include @@ -400,28 +401,25 @@ namespace return lightModel; } -} -namespace MWRender -{ - void assignBoneBlendCallbackRecursive( - BoneAnimBlendController* controller, ActiveControllersVector& activeControllers, osg::Node* parent, bool isRoot) + void assignBoneBlendCallbackRecursive(MWRender::BoneAnimBlendController* controller, + MWRender::ActiveControllersVector& activeControllers, osg::Node* parent, bool isRoot) { // Attempt to cast node to an osgAnimation::Bone - osgAnimation::Bone* bone = dynamic_cast(parent); - if (!isRoot && bone) + if (!isRoot && dynamic_cast(parent)) { // Wrapping in a custom callback object allows for nested callback chaining, otherwise it has link to self // issues we need to share the base BoneAnimBlendController as that contains blending information and is // guaranteed to update before - osg::ref_ptr cb = new BoneAnimBlendControllerWrapper(controller, bone); + osgAnimation::Bone* bone = static_cast(parent); + osg::ref_ptr cb = new MWRender::BoneAnimBlendControllerWrapper(controller, bone); // Ensure there is no other AnimBlendController - this can happen when using // multiple animations with different roots, such as NPC animation osg::Callback* updateCb = bone->getUpdateCallback(); while (updateCb) { - if (updateCb->className() == std::string_view(controller->className())) + if (dynamic_cast(updateCb)) { osg::ref_ptr nextCb = updateCb->getNestedCallback(); bone->removeUpdateCallback(updateCb); @@ -438,7 +436,7 @@ namespace MWRender updateCb = bone->getUpdateCallback(); while (updateCb) { - if (updateCb->className() == std::string_view("UpdateBone")) + if (dynamic_cast(updateCb)) { // Override the immediate callback after the UpdateBone osg::ref_ptr lastCb = updateCb->getNestedCallback(); @@ -458,7 +456,10 @@ namespace MWRender for (unsigned int i = 0; i < group->getNumChildren(); ++i) assignBoneBlendCallbackRecursive(controller, activeControllers, group->getChild(i), false); } +} +namespace MWRender +{ class TransparencyUpdater : public SceneUtil::StateSetUpdater { public: @@ -777,7 +778,7 @@ namespace MWRender blendRules = mResourceSystem->getAnimBlendRulesManager()->getRules(globalBlendConfigPath, blendConfigPath); if (blendRules == nullptr) - Log(Debug::Warning) << "Animation blending files were not found '" << blendConfigPath.value() + Log(Debug::Warning) << "Unable to find animation blending rules: '" << blendConfigPath.value() << "' or '" << globalBlendConfigPath.value() << "'"; } else @@ -1087,7 +1088,7 @@ namespace MWRender template inline osg::Callback* Animation::handleBlendTransform(osg::ref_ptr node, osg::ref_ptr keyframeController, - std::map, osg::ref_ptr>>& blendControllers, + std::map, osg::ref_ptr>>& blendControllers, const AnimBlendStateData& stateData, const osg::ref_ptr& blendRules, const AnimState& active) { @@ -1164,7 +1165,8 @@ namespace MWRender if (active != mStates.end()) { std::shared_ptr animsrc = active->second.mSource; - AnimBlendStateData stateData = active->second.asAnimBlendStateData(); + const AnimBlendStateData stateData + = { .mGroupname = active->second.mGroupname, .mStartKey = active->second.mStartKey }; for (AnimSource::ControllerMap::iterator it = animsrc->mControllerMap[blendMask].begin(); it != animsrc->mControllerMap[blendMask].end(); ++it) @@ -1173,18 +1175,16 @@ namespace MWRender it->first); // this should not throw, we already checked for the node existing in addAnimSource const bool useSmoothAnims = Settings::game().mSmoothAnimTransitions; - const bool isNifTransform = dynamic_cast(node.get()) != nullptr; - const bool isBoneTransform = dynamic_cast(node.get()) != nullptr; osg::Callback* callback = it->second->getAsCallback(); if (useSmoothAnims) { - if (isNifTransform) + if (dynamic_cast(node.get())) { - callback = handleBlendTransform(node, + callback = handleBlendTransform(node, it->second, mAnimBlendControllers, stateData, animsrc->mAnimBlendRules, active->second); } - else if (isBoneTransform) + else if (dynamic_cast(node.get())) { callback = handleBlendTransform(node, it->second, @@ -1956,7 +1956,7 @@ namespace MWRender osg::Callback* cb = node->getUpdateCallback(); while (cb) { - if (dynamic_cast(cb) || dynamic_cast(cb) + if (dynamic_cast(cb) || dynamic_cast(cb) || dynamic_cast(cb)) { foundKeyframeCtrl = true; diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index 82d7d98eba..b11639b21f 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -2,13 +2,13 @@ #define GAME_RENDER_ANIMATION_H #include "animationpriority.hpp" +#include "animblendcontroller.hpp" #include "blendmask.hpp" #include "bonegroup.hpp" #include "../mwworld/movementdirection.hpp" #include "../mwworld/ptr.hpp" -#include "animblendcontroller.hpp" #include #include #include @@ -170,12 +170,6 @@ namespace MWRender float getTime() const { return *mTime; } void setTime(float time) { *mTime = time; } bool blendMaskContains(size_t blendMask) const { return (mBlendMask & (1 << blendMask)); } - AnimBlendStateData asAnimBlendStateData() const - { - AnimBlendStateData stateData = { .mGroupname = mGroupname, .mStartKey = mStartKey }; - return stateData; - } - bool shouldLoop() const { return getTime() >= mLoopStopTime && mLoopingEnabled && mLoopCount > 0; } }; @@ -207,7 +201,7 @@ namespace MWRender ActiveControllersVector mActiveControllers; // Keep track of the animation controllers for easy access - std::map, osg::ref_ptr> mAnimBlendControllers; + std::map, osg::ref_ptr> mAnimBlendControllers; std::map, osg::ref_ptr> mBoneAnimBlendControllers; std::shared_ptr mAnimationTimePtr[sNumBlendMasks]; @@ -315,7 +309,7 @@ namespace MWRender template inline osg::Callback* handleBlendTransform(osg::ref_ptr node, osg::ref_ptr keyframeController, - std::map, osg::ref_ptr>>& blendControllers, + std::map, osg::ref_ptr>>& blendControllers, const AnimBlendStateData& stateData, const osg::ref_ptr& blendRules, const AnimState& active); diff --git a/apps/openmw/mwrender/animblendcontroller.cpp b/apps/openmw/mwrender/animblendcontroller.cpp index e22e2ccfa3..ed3d3e955b 100644 --- a/apps/openmw/mwrender/animblendcontroller.cpp +++ b/apps/openmw/mwrender/animblendcontroller.cpp @@ -1,5 +1,7 @@ #include "animblendcontroller.hpp" +#include + #include #include @@ -89,9 +91,8 @@ namespace MWRender } template - AnimBlendControllerBase::AnimBlendControllerBase( - osg::ref_ptr keyframeTrack, AnimBlendStateData newState, - osg::ref_ptr blendRules) + AnimBlendController::AnimBlendController(osg::ref_ptr keyframeTrack, + const AnimBlendStateData& newState, osg::ref_ptr blendRules) : mTimeFactor(0.0f) , mInterpFactor(0.0f) { @@ -99,8 +100,8 @@ namespace MWRender } template - void AnimBlendControllerBase::setKeyframeTrack(osg::ref_ptr kft, - AnimBlendStateData newState, osg::ref_ptr blendRules) + void AnimBlendController::setKeyframeTrack(osg::ref_ptr kft, + const AnimBlendStateData& newState, osg::ref_ptr blendRules) { // If animation has changed then start blending if (newState.mGroupname != mAnimState.mGroupname || newState.mStartKey != mAnimState.mStartKey @@ -139,7 +140,7 @@ namespace MWRender } template - void AnimBlendControllerBase::gatherRecursiveBoneTransforms(osgAnimation::Bone* bone, bool isRoot) + void AnimBlendController::gatherRecursiveBoneTransforms(osgAnimation::Bone* bone, bool isRoot) { // Incase group traversal encountered something that isnt a bone if (!bone) @@ -156,7 +157,7 @@ namespace MWRender } template - void AnimBlendControllerBase::applyBoneBlend(osgAnimation::Bone* bone) + void AnimBlendController::applyBoneBlend(osgAnimation::Bone* bone) { // If we are done with interpolation then we can safely skip this as the bones are correct if (!mInterpActive) @@ -200,7 +201,7 @@ namespace MWRender } template - void AnimBlendControllerBase::calculateInterpFactor(float time) + void AnimBlendController::calculateInterpFactor(float time) { if (mBlendDuration != 0) mTimeFactor = std::min((time - mBlendStartTime) / mBlendDuration, 1.0f); @@ -216,7 +217,7 @@ namespace MWRender } template - void AnimBlendControllerBase::operator()(osgAnimation::Bone* node, osg::NodeVisitor* nv) + void AnimBlendController::operator()(osgAnimation::Bone* node, osg::NodeVisitor* nv) { // HOW THIS WORKS: This callback method is called only for bones with attached keyframe controllers // such as bip01, bip01 spine1 etc. The child bones of these controllers have their own callback wrapper @@ -236,11 +237,11 @@ namespace MWRender if (mInterpActive) applyBoneBlend(node); - SceneUtil::NodeCallback, osgAnimation::Bone*>::traverse(node, nv); + SceneUtil::NodeCallback, osgAnimation::Bone*>::traverse(node, nv); } template - void AnimBlendControllerBase::operator()(NifOsg::MatrixTransform* node, osg::NodeVisitor* nv) + void AnimBlendController::operator()(NifOsg::MatrixTransform* node, osg::NodeVisitor* nv) { // HOW THIS WORKS: The actual retrieval of the bone transformation based on animation is done by the // KeyframeController (mKeyframeTrack). The KeyframeController retreives time data (playback position) every @@ -302,6 +303,6 @@ namespace MWRender // instantly hide/show objects in which case the scale interpolation is undesirable. node->setScale(*scale); - SceneUtil::NodeCallback, NifOsg::MatrixTransform*>::traverse(node, nv); + SceneUtil::NodeCallback, NifOsg::MatrixTransform*>::traverse(node, nv); } } diff --git a/apps/openmw/mwrender/animblendcontroller.hpp b/apps/openmw/mwrender/animblendcontroller.hpp index 59ab2cbcd5..d08db5d643 100644 --- a/apps/openmw/mwrender/animblendcontroller.hpp +++ b/apps/openmw/mwrender/animblendcontroller.hpp @@ -8,7 +8,6 @@ #include -#include #include #include #include @@ -29,28 +28,26 @@ namespace MWRender }; template - class AnimBlendControllerBase : public SceneUtil::NodeCallback, NodeClass*>, - public SceneUtil::Controller + class AnimBlendController : public SceneUtil::NodeCallback, NodeClass*>, + public SceneUtil::Controller { public: - AnimBlendControllerBase(osg::ref_ptr keyframeTrack, AnimBlendStateData animState, - osg::ref_ptr blendRules); + AnimBlendController(osg::ref_ptr keyframeTrack, + const AnimBlendStateData& animState, osg::ref_ptr blendRules); - AnimBlendControllerBase() {} + AnimBlendController() {} - AnimBlendControllerBase(const AnimBlendControllerBase& copy, const osg::CopyOp&) - : mTimeFactor(0.0f) - , mInterpFactor(0.0f) + AnimBlendController(const AnimBlendController& other, const osg::CopyOp&) + : AnimBlendController(other.mKeyframeTrack, other.mAnimState, other.mAnimBlendRules) { - setKeyframeTrack(copy.getKeyframeTrack(), copy.getAnimState(), copy.getBlendRules()); } - META_Object(MWRender, AnimBlendControllerBase) + META_Object(MWRender, AnimBlendController) void operator()(NifOsg::MatrixTransform* node, osg::NodeVisitor* nv); void operator()(osgAnimation::Bone* node, osg::NodeVisitor* nv); - void setKeyframeTrack(osg::ref_ptr kft, AnimBlendStateData animState, + void setKeyframeTrack(osg::ref_ptr kft, const AnimBlendStateData& animState, osg::ref_ptr blendRules); osg::Callback* getAsCallback() { return this; } @@ -60,10 +57,6 @@ namespace MWRender void gatherRecursiveBoneTransforms(osgAnimation::Bone* parent, bool isRoot = true); void applyBoneBlend(osgAnimation::Bone* parent); - osg::ref_ptr getKeyframeTrack() const { return mKeyframeTrack; } - osg::ref_ptr getBlendRules() const { return mAnimBlendRules; } - AnimBlendStateData getAnimState() const { return mAnimState; } - private: Easings::EasingFn mEasingFn; float mBlendDuration; @@ -87,19 +80,29 @@ namespace MWRender inline void calculateInterpFactor(float time); }; - using AnimBlendController = AnimBlendControllerBase; - using BoneAnimBlendController = AnimBlendControllerBase; + using NifAnimBlendController = AnimBlendController; + using BoneAnimBlendController = AnimBlendController; - // Assigned to child bones with an instance of AnimBlendControllerBase + // Assigned to child bones with an instance of AnimBlendController class BoneAnimBlendControllerWrapper : public osg::Callback { public: - BoneAnimBlendControllerWrapper(osg::ref_ptr rootCallback, osg::Node* node) + BoneAnimBlendControllerWrapper(osg::ref_ptr rootCallback, osgAnimation::Bone* node) + : mRootCallback(rootCallback) + , mNode(node) + { + } + + BoneAnimBlendControllerWrapper() {} + + BoneAnimBlendControllerWrapper(const BoneAnimBlendControllerWrapper& copy, const osg::CopyOp&) + : mRootCallback(copy.mRootCallback) + , mNode(copy.mNode) { - mRootCallback = rootCallback; - mNode = dynamic_cast(node); } + META_Object(MWRender, BoneAnimBlendControllerWrapper) + bool run(osg::Object* object, osg::Object* data) override { mRootCallback->applyBoneBlend(mNode); @@ -107,9 +110,6 @@ namespace MWRender return true; } - const char* libraryName() const override { return "openmw"; } - const char* className() const override { return "AnimBlendController"; } - private: osg::ref_ptr mRootCallback; osgAnimation::Bone* mNode; diff --git a/components/resource/animblendrulesmanager.cpp b/components/resource/animblendrulesmanager.cpp index 68560c3ed1..550315cace 100644 --- a/components/resource/animblendrulesmanager.cpp +++ b/components/resource/animblendrulesmanager.cpp @@ -26,7 +26,6 @@ namespace Resource AnimBlendRulesManager::AnimBlendRulesManager(const VFS::Manager* vfs, double expiryDelay) : ResourceManager(vfs, expiryDelay) - , mVfs(vfs) { } @@ -58,27 +57,15 @@ namespace Resource osg::ref_ptr AnimBlendRulesManager::loadRules(VFS::Path::NormalizedView path) { - const std::string normalized = VFS::Path::normalizeFilename(path.value()); - std::optional> obj = mCache->getRefFromObjectCacheOrNone(normalized); + std::optional> obj = mCache->getRefFromObjectCacheOrNone(path); if (obj.has_value()) { return osg::ref_ptr(static_cast(obj->get())); } - else - { - osg::ref_ptr blendRules = AnimBlendRules::fromFile(mVfs, path); - if (blendRules) - { - // Blend rules were found in VFS, cache them. - mCache->addEntryToObjectCache(normalized, blendRules); - return blendRules; - } - } - // No blend rules were found in VFS, cache a nullptr. - osg::ref_ptr nullRules = nullptr; - mCache->addEntryToObjectCache(normalized, nullRules); - return nullRules; + osg::ref_ptr blendRules = AnimBlendRules::fromFile(mVFS, path); + mCache->addEntryToObjectCache(path.value(), blendRules); + return blendRules; } void AnimBlendRulesManager::reportStats(unsigned int frameNumber, osg::Stats* stats) const diff --git a/components/resource/animblendrulesmanager.hpp b/components/resource/animblendrulesmanager.hpp index cab8f01985..79fd869954 100644 --- a/components/resource/animblendrulesmanager.hpp +++ b/components/resource/animblendrulesmanager.hpp @@ -27,8 +27,6 @@ namespace Resource private: osg::ref_ptr loadRules(VFS::Path::NormalizedView path); - - const VFS::Manager* mVfs; }; } diff --git a/components/sceneutil/animblendrules.cpp b/components/sceneutil/animblendrules.cpp index 716eb62374..2f760f41f3 100644 --- a/components/sceneutil/animblendrules.cpp +++ b/components/sceneutil/animblendrules.cpp @@ -127,7 +127,7 @@ namespace SceneUtil mRules.insert(mRules.end(), rules.begin(), rules.end()); } - inline bool AnimBlendRules::fitsRuleString(const std::string& str, const std::string& ruleStr) const + inline bool AnimBlendRules::fitsRuleString(const std::string_view& str, const std::string_view& ruleStr) const { // A wildcard only supported in the beginning or the end of the rule string in hopes that this will be more // performant. And most likely this kind of support is enough. diff --git a/components/sceneutil/animblendrules.hpp b/components/sceneutil/animblendrules.hpp index db03c0fd0a..913d3531c0 100644 --- a/components/sceneutil/animblendrules.hpp +++ b/components/sceneutil/animblendrules.hpp @@ -42,7 +42,7 @@ namespace SceneUtil private: std::vector mRules; - inline bool fitsRuleString(const std::string& str, const std::string& ruleStr) const; + inline bool fitsRuleString(const std::string_view& str, const std::string_view& ruleStr) const; }; } diff --git a/components/sceneutil/serialize.cpp b/components/sceneutil/serialize.cpp index 73be49929c..4909fe4f89 100644 --- a/components/sceneutil/serialize.cpp +++ b/components/sceneutil/serialize.cpp @@ -187,20 +187,20 @@ namespace SceneUtil mgr->addWrapper(new GeometrySerializer); // ignore the below for now to avoid warning spam - const char* ignore[] = { "Debug::DebugDrawer", "AnimBlendControllerBase", - "AnimBlendControllerBase", "MWRender::PtrHolder", "Resource::TemplateRef", - "Resource::TemplateMultiRef", "SceneUtil::CompositeStateSetUpdater", "SceneUtil::UBOManager", - "SceneUtil::LightListCallback", "SceneUtil::LightManagerUpdateCallback", - "SceneUtil::FFPLightStateAttribute", "SceneUtil::UpdateRigBounds", "SceneUtil::UpdateRigGeometry", - "SceneUtil::LightSource", "SceneUtil::DisableLight", "SceneUtil::MWShadowTechnique", - "SceneUtil::TextKeyMapHolder", "Shader::AddedState", "Shader::RemovedAlphaFunc", - "NifOsg::FlipController", "NifOsg::KeyframeController", "NifOsg::Emitter", - "NifOsg::ParticleColorAffector", "NifOsg::ParticleSystem", "NifOsg::GravityAffector", - "NifOsg::ParticleBomb", "NifOsg::GrowFadeAffector", "NifOsg::InverseWorldMatrix", - "NifOsg::StaticBoundingBoxCallback", "NifOsg::GeomMorpherController", "NifOsg::UpdateMorphGeometry", - "NifOsg::UVController", "NifOsg::VisController", "osgMyGUI::Drawable", "osg::DrawCallback", - "osg::UniformBufferObject", "osgOQ::ClearQueriesCallback", "osgOQ::RetrieveQueriesCallback", - "osg::DummyObject" }; + const char* ignore[] = { "Debug::DebugDrawer", "MWRender::AnimBlendController", + "MWRender::AnimBlendController", "MWRender::BoneAnimBlendControllerWrapper", + "MWRender::PtrHolder", "Resource::TemplateRef", "Resource::TemplateMultiRef", + "SceneUtil::CompositeStateSetUpdater", "SceneUtil::UBOManager", "SceneUtil::LightListCallback", + "SceneUtil::LightManagerUpdateCallback", "SceneUtil::FFPLightStateAttribute", + "SceneUtil::UpdateRigBounds", "SceneUtil::UpdateRigGeometry", "SceneUtil::LightSource", + "SceneUtil::DisableLight", "SceneUtil::MWShadowTechnique", "SceneUtil::TextKeyMapHolder", + "Shader::AddedState", "Shader::RemovedAlphaFunc", "NifOsg::FlipController", + "NifOsg::KeyframeController", "NifOsg::Emitter", "NifOsg::ParticleColorAffector", + "NifOsg::ParticleSystem", "NifOsg::GravityAffector", "NifOsg::ParticleBomb", "NifOsg::GrowFadeAffector", + "NifOsg::InverseWorldMatrix", "NifOsg::StaticBoundingBoxCallback", "NifOsg::GeomMorpherController", + "NifOsg::UpdateMorphGeometry", "NifOsg::UVController", "NifOsg::VisController", "osgMyGUI::Drawable", + "osg::DrawCallback", "osg::UniformBufferObject", "osgOQ::ClearQueriesCallback", + "osgOQ::RetrieveQueriesCallback", "osg::DummyObject" }; for (size_t i = 0; i < sizeof(ignore) / sizeof(ignore[0]); ++i) { mgr->addWrapper(makeDummySerializer(ignore[i])); From b1b2cceec6eb7feea975a586a75f1fb11cfdd8cd Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Sat, 27 Apr 2024 17:36:01 +0100 Subject: [PATCH 14/34] Update translation capitalization --- apps/launcher/ui/settingspage.ui | 2 +- files/lang/launcher_de.ts | 2 +- files/lang/launcher_fr.ts | 2 +- files/lang/launcher_ru.ts | 2 +- files/lang/launcher_sv.ts | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/apps/launcher/ui/settingspage.ui b/apps/launcher/ui/settingspage.ui index e93debe970..e5ff1d5c7f 100644 --- a/apps/launcher/ui/settingspage.ui +++ b/apps/launcher/ui/settingspage.ui @@ -432,7 +432,7 @@ <html><head/><body><p>If enabled - makes transitions between different animations/poses much smoother. Also allows to load animation blending config YAML files that can be bundled with animations in order to customise blending styles.</p></body></html> - Smooth animation transitions + Smooth Animation Transitions diff --git a/files/lang/launcher_de.ts b/files/lang/launcher_de.ts index 533207a5d3..c91b75bbc7 100644 --- a/files/lang/launcher_de.ts +++ b/files/lang/launcher_de.ts @@ -703,7 +703,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov - Smooth animation transitions + Smooth Animation Transitions diff --git a/files/lang/launcher_fr.ts b/files/lang/launcher_fr.ts index a4bb792323..492121d7af 100644 --- a/files/lang/launcher_fr.ts +++ b/files/lang/launcher_fr.ts @@ -703,7 +703,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov - Smooth animation transitions + Smooth Animation Transitions diff --git a/files/lang/launcher_ru.ts b/files/lang/launcher_ru.ts index bb8b372bce..42d7e2f143 100644 --- a/files/lang/launcher_ru.ts +++ b/files/lang/launcher_ru.ts @@ -777,7 +777,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>Если настройка включена, она делает переходы между различными анимациями/позами намного глаже. Кроме того, она позволяет загружать YAML-файлы конфигураций смешивания анимаций, которые могут быть включены с анимациями, чтобы кастомизировать стили смешивания.</p></body></html> - Smooth animation transitions + Smooth Animation Transitions Плавные переходы между анимациями diff --git a/files/lang/launcher_sv.ts b/files/lang/launcher_sv.ts index 449ba5e197..e02ee472a6 100644 --- a/files/lang/launcher_sv.ts +++ b/files/lang/launcher_sv.ts @@ -1451,7 +1451,7 @@ de ordinarie fonterna i Morrowind. Bocka denna ruta om du ändå föredrar ordin - Smooth animation transitions + Smooth Animation Transitions From 4040bd9231cd28b2019657498117291aed25d869 Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Sat, 27 Apr 2024 17:51:28 +0100 Subject: [PATCH 15/34] FIx GCC debug build error --- apps/openmw/mwrender/animation.cpp | 2 +- components/resource/animblendrulesmanager.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 04d524c70f..2f8e8c2c73 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -783,7 +783,7 @@ namespace MWRender } else { - blendRules = mResourceSystem->getAnimBlendRulesManager()->getRules(blendConfigPath); + blendRules = mResourceSystem->getAnimBlendRulesManager()->getRules(blendConfigPath, blendConfigPath); } // At this point blendRules will either be nullptr or an AnimBlendRules instance with > 0 rules inside. diff --git a/components/resource/animblendrulesmanager.hpp b/components/resource/animblendrulesmanager.hpp index 79fd869954..5b167f932d 100644 --- a/components/resource/animblendrulesmanager.hpp +++ b/components/resource/animblendrulesmanager.hpp @@ -21,7 +21,7 @@ namespace Resource /// Retrieve a read-only keyframe resource by name (case-insensitive). /// @note Throws an exception if the resource is not found. osg::ref_ptr getRules( - const VFS::Path::NormalizedView path, const VFS::Path::NormalizedView overridePath = ""); + const VFS::Path::NormalizedView path, const VFS::Path::NormalizedView overridePath); void reportStats(unsigned int frameNumber, osg::Stats* stats) const override; From eb290bebbb64bfe998e1646b61ccf11e60d3be91 Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Sat, 27 Apr 2024 23:31:28 +0100 Subject: [PATCH 16/34] Refactor AnimBlendControllers --- apps/openmw/CMakeLists.txt | 2 +- apps/openmw/mwrender/animation.cpp | 35 ++++---- apps/openmw/mwrender/animation.hpp | 6 +- apps/openmw/mwrender/animblendcontroller.cpp | 72 ++++++++------- apps/openmw/mwrender/animblendcontroller.hpp | 94 +++++++++++++------- components/sceneutil/serialize.cpp | 28 +++--- 6 files changed, 132 insertions(+), 105 deletions(-) diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 3c5a6423fd..835bd53d1d 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -27,7 +27,7 @@ add_openmw_dir (mwrender bulletdebugdraw globalmap characterpreview camera localmap water terrainstorage ripplesimulation renderbin actoranimation landmanager navmesh actorspaths recastmesh fogmanager objectpaging groundcover postprocessor pingpongcull luminancecalculator pingpongcanvas transparentpass precipitationocclusion ripples - actorutil distortion animationpriority bonegroup blendmask + actorutil distortion animationpriority bonegroup blendmask animblendcontroller ) add_openmw_dir (mwinput diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 2f8e8c2c73..3dbe5b40af 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -60,7 +60,6 @@ #include "../mwmechanics/weapontype.hpp" #include "actorutil.hpp" -#include "animblendcontroller.cpp" #include "rotatecontroller.hpp" #include "util.hpp" #include "vismask.hpp" @@ -402,8 +401,7 @@ namespace return lightModel; } - void assignBoneBlendCallbackRecursive(MWRender::BoneAnimBlendController* controller, - MWRender::ActiveControllersVector& activeControllers, osg::Node* parent, bool isRoot) + void assignBoneBlendCallbackRecursive(MWRender::BoneAnimBlendController* controller, osg::Node* parent, bool isRoot) { // Attempt to cast node to an osgAnimation::Bone if (!isRoot && dynamic_cast(parent)) @@ -454,7 +452,7 @@ namespace osg::Group* group = parent->asGroup(); if (group) for (unsigned int i = 0; i < group->getNumChildren(); ++i) - assignBoneBlendCallbackRecursive(controller, activeControllers, group->getChild(i), false); + assignBoneBlendCallbackRecursive(controller, group->getChild(i), false); } } @@ -1085,31 +1083,31 @@ namespace MWRender return mNodeMap; } - template - inline osg::Callback* Animation::handleBlendTransform(osg::ref_ptr node, + template + inline osg::Callback* Animation::handleBlendTransform(const osg::ref_ptr& node, osg::ref_ptr keyframeController, - std::map, osg::ref_ptr>>& blendControllers, + std::map, osg::ref_ptr>& blendControllers, const AnimBlendStateData& stateData, const osg::ref_ptr& blendRules, const AnimState& active) { osg::ref_ptr animController; - if (blendControllers.contains(node)) { - animController = blendControllers[node]; + animController = blendControllers.at(node); animController->setKeyframeTrack(keyframeController, stateData, blendRules); } else { animController = new ControllerType(keyframeController, stateData, blendRules); - blendControllers[node] = animController; + blendControllers.emplace(node, animController); if constexpr (std::is_same_v) - assignBoneBlendCallbackRecursive(animController, mActiveControllers, node, true); + assignBoneBlendCallbackRecursive(animController, node, true); } keyframeController->mTime = active.mTime; + osg::Callback* asCallback = animController->getAsCallback(); if constexpr (std::is_same_v) { // IMPORTANT: we must gather all transforms at point of change before next update @@ -1118,13 +1116,13 @@ namespace MWRender animController->gatherRecursiveBoneTransforms(static_cast(node.get())); // Register blend callback after the initial animation callback - node->addUpdateCallback(animController->getAsCallback()); - mActiveControllers.emplace_back(node, animController->getAsCallback()); + node->addUpdateCallback(asCallback); + mActiveControllers.emplace_back(node, asCallback); return keyframeController->getAsCallback(); } - return animController->getAsCallback(); + return asCallback; } void Animation::resetActiveGroups() @@ -1181,14 +1179,13 @@ namespace MWRender { if (dynamic_cast(node.get())) { - callback = handleBlendTransform(node, - it->second, mAnimBlendControllers, stateData, animsrc->mAnimBlendRules, active->second); + callback = handleBlendTransform(node, it->second, + mAnimBlendControllers, stateData, animsrc->mAnimBlendRules, active->second); } else if (dynamic_cast(node.get())) { - callback - = handleBlendTransform(node, it->second, - mBoneAnimBlendControllers, stateData, animsrc->mAnimBlendRules, active->second); + callback = handleBlendTransform(node, it->second, + mBoneAnimBlendControllers, stateData, animsrc->mAnimBlendRules, active->second); } } diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index b11639b21f..36a84ba2ab 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -306,10 +306,10 @@ namespace MWRender void removeFromSceneImpl(); - template - inline osg::Callback* handleBlendTransform(osg::ref_ptr node, + template + inline osg::Callback* handleBlendTransform(const osg::ref_ptr& node, osg::ref_ptr keyframeController, - std::map, osg::ref_ptr>>& blendControllers, + std::map, osg::ref_ptr>& blendControllers, const AnimBlendStateData& stateData, const osg::ref_ptr& blendRules, const AnimState& active); diff --git a/apps/openmw/mwrender/animblendcontroller.cpp b/apps/openmw/mwrender/animblendcontroller.cpp index ed3d3e955b..59149e45e4 100644 --- a/apps/openmw/mwrender/animblendcontroller.cpp +++ b/apps/openmw/mwrender/animblendcontroller.cpp @@ -4,6 +4,7 @@ #include +#include #include #include @@ -90,18 +91,26 @@ namespace MWRender } } - template - AnimBlendController::AnimBlendController(osg::ref_ptr keyframeTrack, - const AnimBlendStateData& newState, osg::ref_ptr blendRules) - : mTimeFactor(0.0f) - , mInterpFactor(0.0f) + AnimBlendController::AnimBlendController(const osg::ref_ptr& keyframeTrack, + const AnimBlendStateData& newState, const osg::ref_ptr& blendRules) { setKeyframeTrack(keyframeTrack, newState, blendRules); } - template - void AnimBlendController::setKeyframeTrack(osg::ref_ptr kft, - const AnimBlendStateData& newState, osg::ref_ptr blendRules) + NifAnimBlendController::NifAnimBlendController(const osg::ref_ptr& keyframeTrack, + const AnimBlendStateData& newState, const osg::ref_ptr& blendRules) + : AnimBlendController(keyframeTrack, newState, blendRules) + { + } + + BoneAnimBlendController::BoneAnimBlendController(const osg::ref_ptr& keyframeTrack, + const AnimBlendStateData& newState, const osg::ref_ptr& blendRules) + : AnimBlendController(keyframeTrack, newState, blendRules) + { + } + + void AnimBlendController::setKeyframeTrack(const osg::ref_ptr& kft, + const AnimBlendStateData& newState, const osg::ref_ptr& blendRules) { // If animation has changed then start blending if (newState.mGroupname != mAnimState.mGroupname || newState.mStartKey != mAnimState.mStartKey @@ -139,8 +148,22 @@ namespace MWRender } } - template - void AnimBlendController::gatherRecursiveBoneTransforms(osgAnimation::Bone* bone, bool isRoot) + void AnimBlendController::calculateInterpFactor(float time) + { + if (mBlendDuration != 0) + mTimeFactor = std::min((time - mBlendStartTime) / mBlendDuration, 1.0f); + else + mTimeFactor = 1; + + mInterpActive = mTimeFactor < 1.0; + + if (mInterpActive) + mInterpFactor = mEasingFn(mTimeFactor); + else + mInterpFactor = 1.0f; + } + + void BoneAnimBlendController::gatherRecursiveBoneTransforms(osgAnimation::Bone* bone, bool isRoot) { // Incase group traversal encountered something that isnt a bone if (!bone) @@ -156,8 +179,7 @@ namespace MWRender } } - template - void AnimBlendController::applyBoneBlend(osgAnimation::Bone* bone) + void BoneAnimBlendController::applyBoneBlend(osgAnimation::Bone* bone) { // If we are done with interpolation then we can safely skip this as the bones are correct if (!mInterpActive) @@ -200,24 +222,7 @@ namespace MWRender bone->setMatrixInSkeletonSpace(lerpedMatrix); } - template - void AnimBlendController::calculateInterpFactor(float time) - { - if (mBlendDuration != 0) - mTimeFactor = std::min((time - mBlendStartTime) / mBlendDuration, 1.0f); - else - mTimeFactor = 1; - - mInterpActive = mTimeFactor < 1.0; - - if (mInterpActive) - mInterpFactor = mEasingFn(mTimeFactor); - else - mInterpFactor = 1.0f; - } - - template - void AnimBlendController::operator()(osgAnimation::Bone* node, osg::NodeVisitor* nv) + void BoneAnimBlendController::operator()(osgAnimation::Bone* node, osg::NodeVisitor* nv) { // HOW THIS WORKS: This callback method is called only for bones with attached keyframe controllers // such as bip01, bip01 spine1 etc. The child bones of these controllers have their own callback wrapper @@ -237,11 +242,10 @@ namespace MWRender if (mInterpActive) applyBoneBlend(node); - SceneUtil::NodeCallback, osgAnimation::Bone*>::traverse(node, nv); + SceneUtil::NodeCallback::traverse(node, nv); } - template - void AnimBlendController::operator()(NifOsg::MatrixTransform* node, osg::NodeVisitor* nv) + void NifAnimBlendController::operator()(NifOsg::MatrixTransform* node, osg::NodeVisitor* nv) { // HOW THIS WORKS: The actual retrieval of the bone transformation based on animation is done by the // KeyframeController (mKeyframeTrack). The KeyframeController retreives time data (playback position) every @@ -303,6 +307,6 @@ namespace MWRender // instantly hide/show objects in which case the scale interpolation is undesirable. node->setScale(*scale); - SceneUtil::NodeCallback, NifOsg::MatrixTransform*>::traverse(node, nv); + SceneUtil::NodeCallback::traverse(node, nv); } } diff --git a/apps/openmw/mwrender/animblendcontroller.hpp b/apps/openmw/mwrender/animblendcontroller.hpp index d08db5d643..38d83af7cd 100644 --- a/apps/openmw/mwrender/animblendcontroller.hpp +++ b/apps/openmw/mwrender/animblendcontroller.hpp @@ -27,49 +27,28 @@ namespace MWRender std::string mStartKey; }; - template - class AnimBlendController : public SceneUtil::NodeCallback, NodeClass*>, - public SceneUtil::Controller + class AnimBlendController : public SceneUtil::Controller { public: - AnimBlendController(osg::ref_ptr keyframeTrack, - const AnimBlendStateData& animState, osg::ref_ptr blendRules); + AnimBlendController(const osg::ref_ptr& keyframeTrack, + const AnimBlendStateData& animState, const osg::ref_ptr& blendRules); AnimBlendController() {} - AnimBlendController(const AnimBlendController& other, const osg::CopyOp&) - : AnimBlendController(other.mKeyframeTrack, other.mAnimState, other.mAnimBlendRules) - { - } - - META_Object(MWRender, AnimBlendController) - - void operator()(NifOsg::MatrixTransform* node, osg::NodeVisitor* nv); - void operator()(osgAnimation::Bone* node, osg::NodeVisitor* nv); - - void setKeyframeTrack(osg::ref_ptr kft, const AnimBlendStateData& animState, - osg::ref_ptr blendRules); - - osg::Callback* getAsCallback() { return this; } + void setKeyframeTrack(const osg::ref_ptr& kft, + const AnimBlendStateData& animState, const osg::ref_ptr& blendRules); bool getBlendTrigger() const { return mBlendTrigger; } - void gatherRecursiveBoneTransforms(osgAnimation::Bone* parent, bool isRoot = true); - void applyBoneBlend(osgAnimation::Bone* parent); - - private: + protected: Easings::EasingFn mEasingFn; - float mBlendDuration; + float mBlendDuration = 0.0f; + float mBlendStartTime = 0.0f; + float mTimeFactor = 0.0f; + float mInterpFactor = 0.0f; bool mBlendTrigger = false; - float mBlendStartTime; - osg::Quat mBlendStartRot; - osg::Vec3f mBlendStartTrans; - float mBlendStartScale; - - float mTimeFactor; - float mInterpFactor; - bool mInterpActive; + bool mInterpActive = false; AnimBlendStateData mAnimState; osg::ref_ptr mAnimBlendRules; @@ -80,8 +59,55 @@ namespace MWRender inline void calculateInterpFactor(float time); }; - using NifAnimBlendController = AnimBlendController; - using BoneAnimBlendController = AnimBlendController; + class NifAnimBlendController : public SceneUtil::NodeCallback, + public AnimBlendController + { + public: + NifAnimBlendController(const osg::ref_ptr& keyframeTrack, + const AnimBlendStateData& animState, const osg::ref_ptr& blendRules); + + NifAnimBlendController() {} + + NifAnimBlendController(const NifAnimBlendController& other, const osg::CopyOp&) + : NifAnimBlendController(other.mKeyframeTrack, other.mAnimState, other.mAnimBlendRules) + { + } + + META_Object(MWRender, NifAnimBlendController) + + void operator()(NifOsg::MatrixTransform* node, osg::NodeVisitor* nv); + + osg::Callback* getAsCallback() { return this; } + + private: + osg::Quat mBlendStartRot; + osg::Vec3f mBlendStartTrans; + float mBlendStartScale = 0.0f; + }; + + class BoneAnimBlendController : public SceneUtil::NodeCallback, + public AnimBlendController + { + public: + BoneAnimBlendController(const osg::ref_ptr& keyframeTrack, + const AnimBlendStateData& animState, const osg::ref_ptr& blendRules); + + BoneAnimBlendController() {} + + BoneAnimBlendController(const BoneAnimBlendController& other, const osg::CopyOp&) + : BoneAnimBlendController(other.mKeyframeTrack, other.mAnimState, other.mAnimBlendRules) + { + } + + void gatherRecursiveBoneTransforms(osgAnimation::Bone* parent, bool isRoot = true); + void applyBoneBlend(osgAnimation::Bone* parent); + + META_Object(MWRender, BoneAnimBlendController) + + void operator()(osgAnimation::Bone* node, osg::NodeVisitor* nv); + + osg::Callback* getAsCallback() { return this; } + }; // Assigned to child bones with an instance of AnimBlendController class BoneAnimBlendControllerWrapper : public osg::Callback diff --git a/components/sceneutil/serialize.cpp b/components/sceneutil/serialize.cpp index 4909fe4f89..480d1abf2f 100644 --- a/components/sceneutil/serialize.cpp +++ b/components/sceneutil/serialize.cpp @@ -187,20 +187,20 @@ namespace SceneUtil mgr->addWrapper(new GeometrySerializer); // ignore the below for now to avoid warning spam - const char* ignore[] = { "Debug::DebugDrawer", "MWRender::AnimBlendController", - "MWRender::AnimBlendController", "MWRender::BoneAnimBlendControllerWrapper", - "MWRender::PtrHolder", "Resource::TemplateRef", "Resource::TemplateMultiRef", - "SceneUtil::CompositeStateSetUpdater", "SceneUtil::UBOManager", "SceneUtil::LightListCallback", - "SceneUtil::LightManagerUpdateCallback", "SceneUtil::FFPLightStateAttribute", - "SceneUtil::UpdateRigBounds", "SceneUtil::UpdateRigGeometry", "SceneUtil::LightSource", - "SceneUtil::DisableLight", "SceneUtil::MWShadowTechnique", "SceneUtil::TextKeyMapHolder", - "Shader::AddedState", "Shader::RemovedAlphaFunc", "NifOsg::FlipController", - "NifOsg::KeyframeController", "NifOsg::Emitter", "NifOsg::ParticleColorAffector", - "NifOsg::ParticleSystem", "NifOsg::GravityAffector", "NifOsg::ParticleBomb", "NifOsg::GrowFadeAffector", - "NifOsg::InverseWorldMatrix", "NifOsg::StaticBoundingBoxCallback", "NifOsg::GeomMorpherController", - "NifOsg::UpdateMorphGeometry", "NifOsg::UVController", "NifOsg::VisController", "osgMyGUI::Drawable", - "osg::DrawCallback", "osg::UniformBufferObject", "osgOQ::ClearQueriesCallback", - "osgOQ::RetrieveQueriesCallback", "osg::DummyObject" }; + const char* ignore[] = { "Debug::DebugDrawer", "MWRender::NifAnimBlendController", + "MWRender::BoneAnimBlendController", "MWRender::BoneAnimBlendControllerWrapper", "MWRender::PtrHolder", + "Resource::TemplateRef", "Resource::TemplateMultiRef", "SceneUtil::CompositeStateSetUpdater", + "SceneUtil::UBOManager", "SceneUtil::LightListCallback", "SceneUtil::LightManagerUpdateCallback", + "SceneUtil::FFPLightStateAttribute", "SceneUtil::UpdateRigBounds", "SceneUtil::UpdateRigGeometry", + "SceneUtil::LightSource", "SceneUtil::DisableLight", "SceneUtil::MWShadowTechnique", + "SceneUtil::TextKeyMapHolder", "Shader::AddedState", "Shader::RemovedAlphaFunc", + "NifOsg::FlipController", "NifOsg::KeyframeController", "NifOsg::Emitter", + "NifOsg::ParticleColorAffector", "NifOsg::ParticleSystem", "NifOsg::GravityAffector", + "NifOsg::ParticleBomb", "NifOsg::GrowFadeAffector", "NifOsg::InverseWorldMatrix", + "NifOsg::StaticBoundingBoxCallback", "NifOsg::GeomMorpherController", "NifOsg::UpdateMorphGeometry", + "NifOsg::UVController", "NifOsg::VisController", "osgMyGUI::Drawable", "osg::DrawCallback", + "osg::UniformBufferObject", "osgOQ::ClearQueriesCallback", "osgOQ::RetrieveQueriesCallback", + "osg::DummyObject" }; for (size_t i = 0; i < sizeof(ignore) / sizeof(ignore[0]); ++i) { mgr->addWrapper(makeDummySerializer(ignore[i])); From 231af7b1ae211f8c2d0b3b7f8d21e31a8ec9bb5c Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Sun, 28 Apr 2024 23:24:13 +0100 Subject: [PATCH 17/34] fr/sv translations --- files/lang/launcher_fr.ts | 4 ++-- files/lang/launcher_sv.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/files/lang/launcher_fr.ts b/files/lang/launcher_fr.ts index 492121d7af..4ae2b3d015 100644 --- a/files/lang/launcher_fr.ts +++ b/files/lang/launcher_fr.ts @@ -700,11 +700,11 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>If enabled - makes transitions between different animations/poses much smoother. Also allows to load animation blending config YAML files that can be bundled with animations in order to customise blending styles.</p></body></html> - + <html><body><p>Lorsque cette option est désactivée, le moteur de jeu n'effectue aucune transition entre les différentes poses/animations.</p><p>Lorsque cette option est activée, le moteur de jeu adoucit la transition entre les différentes poses/animations.</p><p>Cette option prend en charge les fichiers de configuration YAML pour les transitions entre animations, ceux-ci peuvent être inclus avec les lots d'animations afin de configurer le type de transition entre les diverses animations fournies.</p></body></html> Smooth Animation Transitions - + Adoucir la transition entre animations <html><head/><body><p>Makes NPCs and player movement more smooth. Recommended to use with "turn to movement direction" enabled.</p></body></html> diff --git a/files/lang/launcher_sv.ts b/files/lang/launcher_sv.ts index e02ee472a6..55f29241dd 100644 --- a/files/lang/launcher_sv.ts +++ b/files/lang/launcher_sv.ts @@ -1448,11 +1448,11 @@ de ordinarie fonterna i Morrowind. Bocka denna ruta om du ändå föredrar ordin <html><head/><body><p>If enabled - makes transitions between different animations/poses much smoother. Also allows to load animation blending config YAML files that can be bundled with animations in order to customise blending styles.</p></body></html> - + <html><head/><body><p>Vid aktivering gör denna funktion att övergångarna mellan olika animationer och poser blir mycket mjukare. Funktionen gör det också möjligt att konfigurera animationsövergångarna i YAML-filer. Dessa filer kan buntas ihop tillsammans med nya animationsfiler.</p></body></html> Smooth Animation Transitions - + Mjuka animationsövergångar From 72c95a51df21ad8d65f07612e101e4c732decc3c Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Sun, 28 Apr 2024 23:40:45 +0100 Subject: [PATCH 18/34] Default mEasingFn in constructor --- apps/openmw/mwrender/animblendcontroller.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/openmw/mwrender/animblendcontroller.cpp b/apps/openmw/mwrender/animblendcontroller.cpp index 59149e45e4..79f7c23069 100644 --- a/apps/openmw/mwrender/animblendcontroller.cpp +++ b/apps/openmw/mwrender/animblendcontroller.cpp @@ -93,6 +93,7 @@ namespace MWRender AnimBlendController::AnimBlendController(const osg::ref_ptr& keyframeTrack, const AnimBlendStateData& newState, const osg::ref_ptr& blendRules) + : mEasingFn(&Easings::sineOut) { setKeyframeTrack(keyframeTrack, newState, blendRules); } From 014cba807b1caf247ec88e2c2f3fbf1fa5e34498 Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Wed, 1 May 2024 01:41:08 +0100 Subject: [PATCH 19/34] Refactor easings, serialize per-line items, constexpr path --- apps/openmw/mwrender/animation.cpp | 2 +- apps/openmw/mwrender/animblendcontroller.cpp | 193 ++++++++++++------- apps/openmw/mwrender/animblendcontroller.hpp | 7 +- components/sceneutil/serialize.cpp | 56 ++++-- 4 files changed, 163 insertions(+), 95 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 3dbe5b40af..ae0d8a489d 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -767,7 +767,7 @@ namespace MWRender Misc::StringUtils::replaceLast(yamlpath, ".dae", ".yaml"); // globalBlendConfigPath is only used with actors! Objects have no default blending. - const VFS::Path::NormalizedView globalBlendConfigPath("animations/animation-config.yaml"); + constexpr VFS::Path::NormalizedView globalBlendConfigPath("animations/animation-config.yaml"); const VFS::Path::NormalizedView blendConfigPath(yamlpath); osg::ref_ptr blendRules; diff --git a/apps/openmw/mwrender/animblendcontroller.cpp b/apps/openmw/mwrender/animblendcontroller.cpp index 79f7c23069..2aace7a36d 100644 --- a/apps/openmw/mwrender/animblendcontroller.cpp +++ b/apps/openmw/mwrender/animblendcontroller.cpp @@ -10,84 +10,127 @@ namespace MWRender { - /// Animation Easing/Blending functions - namespace Easings + namespace { - float linear(float x) - { - return x; - } - float sineOut(float x) - { - return sin((x * 3.14) / 2); - } - float sineIn(float x) - { - return 1 - cos((x * 3.14) / 2); - } - float sineInOut(float x) - { - return -(cos(3.14 * x) - 1) / 2; - } - float cubicOut(float t) - { - return 1 - powf(1 - t, 3); - } - float cubicIn(float x) + // Animation Easing/Blending functions + namespace Easings { - return powf(x, 3); - } - float cubicInOut(float x) - { - return x < 0.5 ? 4 * x * x * x : 1 - powf(-2 * x + 2, 3) / 2; - } - float quartOut(float t) - { - return 1 - powf(1 - t, 4); - } - float quartIn(float t) - { - return powf(t, 4); - } - float quartInOut(float x) - { - return x < 0.5 ? 8 * x * x * x * x : 1 - powf(-2 * x + 2, 4) / 2; - } - float springOutGeneric(float x, float lambda, float w) - { - // Higher lambda = lower swing amplitude. 1 = 150% swing amplitude. - // W corresponds to the amount of overswings, more = more. 4.71 = 1 overswing, 7.82 = 2 - return 1 - expf(-lambda * x) * cos(w * x); - } - float springOutWeak(float x) - { - return springOutGeneric(x, 4, 4.71); - } - float springOutMed(float x) - { - return springOutGeneric(x, 3, 4.71); - } - float springOutStrong(float x) - { - return springOutGeneric(x, 2, 4.71); - } - float springOutTooMuch(float x) - { - return springOutGeneric(x, 1, 4.71); + float linear(float x) + { + return x; + } + + float sineOut(float x) + { + return sin((x * osg::PIf) / 2); + } + + float sineIn(float x) + { + return 1 - cos((x * osg::PIf) / 2); + } + + float sineInOut(float x) + { + return -(cos(osg::PIf * x) - 1) / 2; + } + + float cubicOut(float t) + { + float t1 = 1 - t; + return 1 - (t1 * t1 * t1); // (1-t)^3 + } + + float cubicIn(float x) + { + return x * x * x; // x^3 + } + + float cubicInOut(float x) + { + if (x < 0.5) + { + return 4 * x * x * x; // 4x^3 + } + else + { + float x2 = -2 * x + 2; + return 1 - (x2 * x2 * x2) / 2; // (1 - (-2x + 2)^3)/2 + } + } + + float quartOut(float t) + { + float t1 = 1 - t; + return 1 - (t1 * t1 * t1 * t1); // (1-t)^4 + } + + float quartIn(float t) + { + return t * t * t * t; // t^4 + } + + float quartInOut(float x) + { + if (x < 0.5) + { + return 8 * x * x * x * x; // 8x^4 + } + else + { + float x2 = -2 * x + 2; + return 1 - (x2 * x2 * x2 * x2) / 2; // 1 - ((-2x + 2)^4)/2 + } + } + + float springOutGeneric(float x, float lambda, float w) + { + // Higher lambda = lower swing amplitude. 1 = 150% swing amplitude. + // W corresponds to the amount of overswings, more = more. 4.71 = 1 overswing, 7.82 = 2 + return 1 - expf(-lambda * x) * cos(w * x); + } + + float springOutWeak(float x) + { + return springOutGeneric(x, 4, 4.71); + } + + float springOutMed(float x) + { + return springOutGeneric(x, 3, 4.71); + } + + float springOutStrong(float x) + { + return springOutGeneric(x, 2, 4.71); + } + + float springOutTooMuch(float x) + { + return springOutGeneric(x, 1, 4.71); + } + + const std::unordered_map easingsMap = { + { "linear", Easings::linear }, + { "sineOut", Easings::sineOut }, + { "sineIn", Easings::sineIn }, + { "sineInOut", Easings::sineInOut }, + { "cubicOut", Easings::cubicOut }, + { "cubicIn", Easings::cubicIn }, + { "cubicInOut", Easings::cubicInOut }, + { "quartOut", Easings::quartOut }, + { "quartIn", Easings::quartIn }, + { "quartInOut", Easings::quartInOut }, + { "springOutWeak", Easings::springOutWeak }, + { "springOutMed", Easings::springOutMed }, + { "springOutStrong", Easings::springOutStrong }, + { "springOutTooMuch", Easings::springOutTooMuch }, + }; } - std::unordered_map easingsMap = { { "linear", Easings::linear }, - { "sineOut", Easings::sineOut }, { "sineIn", Easings::sineIn }, { "sineInOut", Easings::sineInOut }, - { "cubicOut", Easings::cubicOut }, { "cubicIn", Easings::cubicIn }, { "cubicInOut", Easings::cubicInOut }, - { "quartOut", Easings::quartOut }, { "quartIn", Easings::quartIn }, { "quartInOut", Easings::quartInOut }, - { "springOutWeak", Easings::springOutWeak }, { "springOutMed", Easings::springOutMed }, - { "springOutStrong", Easings::springOutStrong }, { "springOutTooMuch", Easings::springOutTooMuch } }; - } - namespace - { - osg::Vec3f vec3fLerp(float t, const osg::Vec3f& A, const osg::Vec3f& B) + osg::Vec3f vec3fLerp(float t, const osg::Vec3f& start, const osg::Vec3f& end) { - return A + (B - A) * t; + return start + (end - start) * t; } } @@ -129,10 +172,10 @@ namespace MWRender if (blendRule) { - if (Easings::easingsMap.contains(blendRule->mEasing)) + if (const auto it = Easings::easingsMap.find(blendRule->mEasing); it != Easings::easingsMap.end()) { + mEasingFn = it->second; mBlendDuration = blendRule->mDuration; - mEasingFn = Easings::easingsMap[blendRule->mEasing]; } else { diff --git a/apps/openmw/mwrender/animblendcontroller.hpp b/apps/openmw/mwrender/animblendcontroller.hpp index 38d83af7cd..8f8ac03ae0 100644 --- a/apps/openmw/mwrender/animblendcontroller.hpp +++ b/apps/openmw/mwrender/animblendcontroller.hpp @@ -16,10 +16,7 @@ namespace MWRender { - namespace Easings - { - typedef float (*EasingFn)(float); - } + typedef float (*EasingFn)(float); struct AnimBlendStateData { @@ -41,7 +38,7 @@ namespace MWRender bool getBlendTrigger() const { return mBlendTrigger; } protected: - Easings::EasingFn mEasingFn; + EasingFn mEasingFn; float mBlendDuration = 0.0f; float mBlendStartTime = 0.0f; float mTimeFactor = 0.0f; diff --git a/components/sceneutil/serialize.cpp b/components/sceneutil/serialize.cpp index 480d1abf2f..81053aa476 100644 --- a/components/sceneutil/serialize.cpp +++ b/components/sceneutil/serialize.cpp @@ -187,20 +187,48 @@ namespace SceneUtil mgr->addWrapper(new GeometrySerializer); // ignore the below for now to avoid warning spam - const char* ignore[] = { "Debug::DebugDrawer", "MWRender::NifAnimBlendController", - "MWRender::BoneAnimBlendController", "MWRender::BoneAnimBlendControllerWrapper", "MWRender::PtrHolder", - "Resource::TemplateRef", "Resource::TemplateMultiRef", "SceneUtil::CompositeStateSetUpdater", - "SceneUtil::UBOManager", "SceneUtil::LightListCallback", "SceneUtil::LightManagerUpdateCallback", - "SceneUtil::FFPLightStateAttribute", "SceneUtil::UpdateRigBounds", "SceneUtil::UpdateRigGeometry", - "SceneUtil::LightSource", "SceneUtil::DisableLight", "SceneUtil::MWShadowTechnique", - "SceneUtil::TextKeyMapHolder", "Shader::AddedState", "Shader::RemovedAlphaFunc", - "NifOsg::FlipController", "NifOsg::KeyframeController", "NifOsg::Emitter", - "NifOsg::ParticleColorAffector", "NifOsg::ParticleSystem", "NifOsg::GravityAffector", - "NifOsg::ParticleBomb", "NifOsg::GrowFadeAffector", "NifOsg::InverseWorldMatrix", - "NifOsg::StaticBoundingBoxCallback", "NifOsg::GeomMorpherController", "NifOsg::UpdateMorphGeometry", - "NifOsg::UVController", "NifOsg::VisController", "osgMyGUI::Drawable", "osg::DrawCallback", - "osg::UniformBufferObject", "osgOQ::ClearQueriesCallback", "osgOQ::RetrieveQueriesCallback", - "osg::DummyObject" }; + const char* ignore[] = { + "Debug::DebugDrawer", + "MWRender::NifAnimBlendController", + "MWRender::BoneAnimBlendController", + "MWRender::BoneAnimBlendControllerWrapper", + "MWRender::PtrHolder", + "Resource::TemplateRef", + "Resource::TemplateMultiRef", + "SceneUtil::CompositeStateSetUpdater", + "SceneUtil::UBOManager", + "SceneUtil::LightListCallback", + "SceneUtil::LightManagerUpdateCallback", + "SceneUtil::FFPLightStateAttribute", + "SceneUtil::UpdateRigBounds", + "SceneUtil::UpdateRigGeometry", + "SceneUtil::LightSource", + "SceneUtil::DisableLight", + "SceneUtil::MWShadowTechnique", + "SceneUtil::TextKeyMapHolder", + "Shader::AddedState", + "Shader::RemovedAlphaFunc", + "NifOsg::FlipController", + "NifOsg::KeyframeController", + "NifOsg::Emitter", + "NifOsg::ParticleColorAffector", + "NifOsg::ParticleSystem", + "NifOsg::GravityAffector", + "NifOsg::ParticleBomb", + "NifOsg::GrowFadeAffector", + "NifOsg::InverseWorldMatrix", + "NifOsg::StaticBoundingBoxCallback", + "NifOsg::GeomMorpherController", + "NifOsg::UpdateMorphGeometry", + "NifOsg::UVController", + "NifOsg::VisController", + "osgMyGUI::Drawable", + "osg::DrawCallback", + "osg::UniformBufferObject", + "osgOQ::ClearQueriesCallback", + "osgOQ::RetrieveQueriesCallback", + "osg::DummyObject", + }; for (size_t i = 0; i < sizeof(ignore) / sizeof(ignore[0]); ++i) { mgr->addWrapper(makeDummySerializer(ignore[i])); From 340252007b1d9c01cfa7def84228363562ae307e Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Fri, 3 May 2024 01:50:14 +0100 Subject: [PATCH 20/34] fitsRuleString dont reference string_view --- components/sceneutil/animblendrules.cpp | 2 +- components/sceneutil/animblendrules.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/sceneutil/animblendrules.cpp b/components/sceneutil/animblendrules.cpp index 2f760f41f3..130c285689 100644 --- a/components/sceneutil/animblendrules.cpp +++ b/components/sceneutil/animblendrules.cpp @@ -127,7 +127,7 @@ namespace SceneUtil mRules.insert(mRules.end(), rules.begin(), rules.end()); } - inline bool AnimBlendRules::fitsRuleString(const std::string_view& str, const std::string_view& ruleStr) const + inline bool AnimBlendRules::fitsRuleString(const std::string_view str, const std::string_view ruleStr) const { // A wildcard only supported in the beginning or the end of the rule string in hopes that this will be more // performant. And most likely this kind of support is enough. diff --git a/components/sceneutil/animblendrules.hpp b/components/sceneutil/animblendrules.hpp index 913d3531c0..57e67164b9 100644 --- a/components/sceneutil/animblendrules.hpp +++ b/components/sceneutil/animblendrules.hpp @@ -42,7 +42,7 @@ namespace SceneUtil private: std::vector mRules; - inline bool fitsRuleString(const std::string_view& str, const std::string_view& ruleStr) const; + inline bool fitsRuleString(const std::string_view str, const std::string_view ruleStr) const; }; } From bce97f49b486c36b8ce41ab355f46bae3b1e5cd7 Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Fri, 10 May 2024 17:57:11 +0100 Subject: [PATCH 21/34] spring function clarification --- apps/openmw/mwrender/animblendcontroller.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwrender/animblendcontroller.cpp b/apps/openmw/mwrender/animblendcontroller.cpp index 2aace7a36d..9e446f3a4b 100644 --- a/apps/openmw/mwrender/animblendcontroller.cpp +++ b/apps/openmw/mwrender/animblendcontroller.cpp @@ -83,31 +83,32 @@ namespace MWRender } } - float springOutGeneric(float x, float lambda, float w) + float springOutGeneric(float x, float lambda) { // Higher lambda = lower swing amplitude. 1 = 150% swing amplitude. - // W corresponds to the amount of overswings, more = more. 4.71 = 1 overswing, 7.82 = 2 + // w corresponds to the frequency of oscillation in the easing function, controlling the amount of overswing + constexpr float w = 1.5f * osg::PIf; // 4.71238 return 1 - expf(-lambda * x) * cos(w * x); } float springOutWeak(float x) { - return springOutGeneric(x, 4, 4.71); + return springOutGeneric(x, 4); } float springOutMed(float x) { - return springOutGeneric(x, 3, 4.71); + return springOutGeneric(x, 3); } float springOutStrong(float x) { - return springOutGeneric(x, 2, 4.71); + return springOutGeneric(x, 2); } float springOutTooMuch(float x) { - return springOutGeneric(x, 1, 4.71); + return springOutGeneric(x, 1); } const std::unordered_map easingsMap = { From 1317434ac00a000ebf0eae023f5bfbb53485689f Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Fri, 17 May 2024 00:53:32 +0100 Subject: [PATCH 22/34] CI fix --- apps/openmw/mwrender/animblendcontroller.cpp | 2 +- files/lang/launcher_en.ts | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/animblendcontroller.cpp b/apps/openmw/mwrender/animblendcontroller.cpp index 9e446f3a4b..477d7174f1 100644 --- a/apps/openmw/mwrender/animblendcontroller.cpp +++ b/apps/openmw/mwrender/animblendcontroller.cpp @@ -86,7 +86,7 @@ namespace MWRender float springOutGeneric(float x, float lambda) { // Higher lambda = lower swing amplitude. 1 = 150% swing amplitude. - // w corresponds to the frequency of oscillation in the easing function, controlling the amount of overswing + // w is the frequency of oscillation in the easing func, controls the amount of overswing constexpr float w = 1.5f * osg::PIf; // 4.71238 return 1 - expf(-lambda * x) * cos(w * x); } diff --git a/files/lang/launcher_en.ts b/files/lang/launcher_en.ts index 36506bfb50..88498cb84f 100644 --- a/files/lang/launcher_en.ts +++ b/files/lang/launcher_en.ts @@ -1427,5 +1427,13 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov Browse… + + <html><head/><body><p>If enabled - makes transitions between different animations/poses much smoother. Also allows to load animation blending config YAML files that can be bundled with animations in order to customise blending styles.</p></body></html> + + + + Smooth Animation Transitions + + From 5deacb82ffc0655ffcf7f460c50915fddea59513 Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Sat, 18 May 2024 06:38:41 +0100 Subject: [PATCH 23/34] Use M_PI as osg::PIf is not constant --- apps/openmw/mwrender/animblendcontroller.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwrender/animblendcontroller.cpp b/apps/openmw/mwrender/animblendcontroller.cpp index 477d7174f1..61abfdd191 100644 --- a/apps/openmw/mwrender/animblendcontroller.cpp +++ b/apps/openmw/mwrender/animblendcontroller.cpp @@ -4,7 +4,10 @@ #include +#define _USE_MATH_DEFINES + #include +#include #include #include @@ -22,17 +25,17 @@ namespace MWRender float sineOut(float x) { - return sin((x * osg::PIf) / 2); + return sin((x * M_PI) / 2); } float sineIn(float x) { - return 1 - cos((x * osg::PIf) / 2); + return 1 - cos((x * M_PI) / 2); } float sineInOut(float x) { - return -(cos(osg::PIf * x) - 1) / 2; + return -(cos(M_PI * x) - 1) / 2; } float cubicOut(float t) @@ -87,7 +90,7 @@ namespace MWRender { // Higher lambda = lower swing amplitude. 1 = 150% swing amplitude. // w is the frequency of oscillation in the easing func, controls the amount of overswing - constexpr float w = 1.5f * osg::PIf; // 4.71238 + constexpr float w = 1.5f * M_PI; // 4.71238 return 1 - expf(-lambda * x) * cos(w * x); } From 9beb380c7d2a2113e3a6b106044451e07beb7aab Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Wed, 29 May 2024 13:05:23 +0100 Subject: [PATCH 24/34] Remove M_PI, documenation cleanup --- apps/openmw/mwrender/animblendcontroller.cpp | 11 ++++------- components/sceneutil/animblendrules.cpp | 2 -- docs/source/reference/modding/animation-blending.rst | 12 ++++++------ docs/source/reference/modding/settings/game.rst | 2 +- files/data/animations/animation-config.yaml | 1 - 5 files changed, 11 insertions(+), 17 deletions(-) diff --git a/apps/openmw/mwrender/animblendcontroller.cpp b/apps/openmw/mwrender/animblendcontroller.cpp index 61abfdd191..412c83d89a 100644 --- a/apps/openmw/mwrender/animblendcontroller.cpp +++ b/apps/openmw/mwrender/animblendcontroller.cpp @@ -4,10 +4,7 @@ #include -#define _USE_MATH_DEFINES - #include -#include #include #include @@ -25,17 +22,17 @@ namespace MWRender float sineOut(float x) { - return sin((x * M_PI) / 2); + return sin((x * osg::PIf) / 2); } float sineIn(float x) { - return 1 - cos((x * M_PI) / 2); + return 1 - cos((x * osg::PIf) / 2); } float sineInOut(float x) { - return -(cos(M_PI * x) - 1) / 2; + return -(cos(osg::PIf * x) - 1) / 2; } float cubicOut(float t) @@ -90,7 +87,7 @@ namespace MWRender { // Higher lambda = lower swing amplitude. 1 = 150% swing amplitude. // w is the frequency of oscillation in the easing func, controls the amount of overswing - constexpr float w = 1.5f * M_PI; // 4.71238 + const float w = 1.5f * osg::PIf; // 4.71238 return 1 - expf(-lambda * x) * cos(w * x); } diff --git a/components/sceneutil/animblendrules.cpp b/components/sceneutil/animblendrules.cpp index 130c285689..6e53d4bc5e 100644 --- a/components/sceneutil/animblendrules.cpp +++ b/components/sceneutil/animblendrules.cpp @@ -42,7 +42,6 @@ namespace SceneUtil } return std::make_pair(group, key); } - } using BlendRule = AnimBlendRules::BlendRule; @@ -168,5 +167,4 @@ namespace SceneUtil return std::nullopt; } - } diff --git a/docs/source/reference/modding/animation-blending.rst b/docs/source/reference/modding/animation-blending.rst index dec15e096d..1cc10b8863 100644 --- a/docs/source/reference/modding/animation-blending.rst +++ b/docs/source/reference/modding/animation-blending.rst @@ -20,8 +20,9 @@ In examples below ``.yaml`` config file will be used. You can provide ``.json`` Animation blending config file is a list of blending rules that look like this: -``` -blending_rules: +:: + + blending_rules: - from: "*" to: "*" easing: "sineOut" @@ -30,9 +31,8 @@ blending_rules: to: "idlesneak*" easing: "springOutWeak" duration: 0.4 -``` -See ``OpenMW\files\data\animations\animation-config.yaml`` for an example of such a file. +See `files/data/animations/animation-config.yaml `__ for an example of such a file. Every blending rule should include a set of following fields: @@ -85,6 +85,6 @@ List of possible easings Its hard to give an example of use cases for the latter 2 types of easing functions, they are there for developers to experiment. -The possible easings are largely ported from `here `__ and have similar names. Except for the ``springOut`` family, those are similar to ``elasticOut`` from `easings.net `__, with ``springOutWeak`` being almost identical to ``elasticOut``. +The possible easings are largely ported from `easings.net `__ and have similar names. Except for the ``springOut`` family, those are similar to ``elasticOut``, with ``springOutWeak`` being almost identical to ``elasticOut``. -Don't be afraid to experiment with different timing and easing functions! \ No newline at end of file +Don't be afraid to experiment with different timing and easing functions! diff --git a/docs/source/reference/modding/settings/game.rst b/docs/source/reference/modding/settings/game.rst index a602fa99fe..e4146f7322 100644 --- a/docs/source/reference/modding/settings/game.rst +++ b/docs/source/reference/modding/settings/game.rst @@ -543,7 +543,7 @@ Enabling this option disables this swaying by having the player character move i This setting can be controlled in the Settings tab of the launcher. smooth animation transitions ---------------------------------- +---------------------------- :Type: boolean :Range: True/False diff --git a/files/data/animations/animation-config.yaml b/files/data/animations/animation-config.yaml index 205e4d2145..1dd8c1da04 100644 --- a/files/data/animations/animation-config.yaml +++ b/files/data/animations/animation-config.yaml @@ -1,7 +1,6 @@ # This is the default OpenMW animation blending config file (global config) , will affect NPCs and creatures but not animated objects. # If you want to provide an animation blending config for your modded animations - DO NOT override the global config in your mod. # For details on how to edit and create your own blending rules, see https://openmw.readthedocs.io/en/latest/reference/modding/animation-blending.html -# Author: Maksim Eremenko (Max Yari) blending_rules: # General blending rule, any transition that will not be caught by the rules below - will use this rule From ddbd87e2a1af1b0f4a13c3c91acb558193cd8494 Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Thu, 4 Jul 2024 03:12:06 +0100 Subject: [PATCH 25/34] Fix blending with additional offsets (sneaking issue) --- apps/openmw/mwrender/animblendcontroller.cpp | 24 +++++++++++++++++++- apps/openmw/mwrender/rotatecontroller.hpp | 10 ++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/apps/openmw/mwrender/animblendcontroller.cpp b/apps/openmw/mwrender/animblendcontroller.cpp index 412c83d89a..8c40cd441b 100644 --- a/apps/openmw/mwrender/animblendcontroller.cpp +++ b/apps/openmw/mwrender/animblendcontroller.cpp @@ -1,4 +1,5 @@ #include "animblendcontroller.hpp" +#include "rotatecontroller.hpp" #include @@ -308,10 +309,31 @@ namespace MWRender { mBlendTrigger = false; mBlendStartTime = time; - // Nif mRotation is used here because it's unaffected by the side-effects of RotationController + + // Nif mRotationScale is used here because it's unaffected by the side-effects of RotationController mBlendStartRot = node->mRotationScale.toOsgMatrix().getRotate(); mBlendStartTrans = node->getMatrix().getTrans(); mBlendStartScale = node->mScale; + + // Subtract any rotate controller's offset from start transform (if it appears after this callback) + // this is required otherwise the blend start will be with an offset, then offset could be applied again + // fixes an issue with camera jumping during first person sneak jumping camera + osg::Callback* updateCb = node->getUpdateCallback()->getNestedCallback(); + while (updateCb) + { + MWRender::RotateController* rotateController = dynamic_cast(updateCb); + if (rotateController) + { + const osg::Quat rotate = rotateController->getRotate(); + const osg::Vec3f offset = rotateController->getOffset(); + const osg::Quat worldOrient = rotateController->getWorldOrientation(node) * rotate.inverse(); + const osg::Quat worldOrientInverse = worldOrient.inverse(); + + mBlendStartTrans -= worldOrientInverse * offset; + } + + updateCb = updateCb->getNestedCallback(); + } } calculateInterpFactor(time); diff --git a/apps/openmw/mwrender/rotatecontroller.hpp b/apps/openmw/mwrender/rotatecontroller.hpp index 3c7e130d98..143585d039 100644 --- a/apps/openmw/mwrender/rotatecontroller.hpp +++ b/apps/openmw/mwrender/rotatecontroller.hpp @@ -20,10 +20,20 @@ namespace MWRender public: RotateController(osg::Node* relativeTo); + osg::Quat getWorldOrientation(osg::Node* node); + void setEnabled(bool enabled); void setOffset(const osg::Vec3f& offset); void setRotate(const osg::Quat& rotate); + const osg::Vec3f getOffset() const { + return mOffset; + } + + const osg::Quat getRotate() const { + return mRotate; + } + void operator()(osg::MatrixTransform* node, osg::NodeVisitor* nv); protected: From 0bcd872561b238e146ef3efd8d11e588b2c43b00 Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Thu, 4 Jul 2024 03:15:09 +0100 Subject: [PATCH 26/34] Clang format --- apps/openmw/mwrender/rotatecontroller.hpp | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwrender/rotatecontroller.hpp b/apps/openmw/mwrender/rotatecontroller.hpp index 143585d039..b409f6e4d3 100644 --- a/apps/openmw/mwrender/rotatecontroller.hpp +++ b/apps/openmw/mwrender/rotatecontroller.hpp @@ -26,13 +26,9 @@ namespace MWRender void setOffset(const osg::Vec3f& offset); void setRotate(const osg::Quat& rotate); - const osg::Vec3f getOffset() const { - return mOffset; - } + const osg::Vec3f getOffset() const { return mOffset; } - const osg::Quat getRotate() const { - return mRotate; - } + const osg::Quat getRotate() const { return mRotate; } void operator()(osg::MatrixTransform* node, osg::NodeVisitor* nv); From 0b3ab23e507a0a93a357310d62d1acb94062413c Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Thu, 4 Jul 2024 03:19:29 +0100 Subject: [PATCH 27/34] Russian update suggested by Andrei --- files/lang/launcher_ru.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/files/lang/launcher_ru.ts b/files/lang/launcher_ru.ts index 42d7e2f143..cf1d054a5b 100644 --- a/files/lang/launcher_ru.ts +++ b/files/lang/launcher_ru.ts @@ -774,7 +774,7 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov <html><head/><body><p>If enabled - makes transitions between different animations/poses much smoother. Also allows to load animation blending config YAML files that can be bundled with animations in order to customise blending styles.</p></body></html> - <html><head/><body><p>Если настройка включена, она делает переходы между различными анимациями/позами намного глаже. Кроме того, она позволяет загружать YAML-файлы конфигураций смешивания анимаций, которые могут быть включены с анимациями, чтобы кастомизировать стили смешивания.</p></body></html> + <html><head/><body><p>Если настройка включена, она делает переходы между различными анимациями/позами намного глаже. Кроме того, она позволяет загружать YAML-файлы конфигураций смешивания анимаций, которые могут быть включены с анимациями, чтобы настроить стили смешивания.</p></body></html> Smooth Animation Transitions From 277c179fe958241c292fce0b792445be036e1429 Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Thu, 4 Jul 2024 03:22:57 +0100 Subject: [PATCH 28/34] Translation fix --- files/lang/launcher_en.ts | 4 ++-- files/lang/launcher_sv.ts | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/files/lang/launcher_en.ts b/files/lang/launcher_en.ts index 88498cb84f..96167e6596 100644 --- a/files/lang/launcher_en.ts +++ b/files/lang/launcher_en.ts @@ -1428,11 +1428,11 @@ to default Morrowind fonts. Check this box if you still prefer original fonts ov - <html><head/><body><p>If enabled - makes transitions between different animations/poses much smoother. Also allows to load animation blending config YAML files that can be bundled with animations in order to customise blending styles.</p></body></html> + Smooth Animation Transitions - Smooth Animation Transitions + <html><head/><body><p>If enabled - makes transitions between different animations/poses much smoother. Also allows to load animation blending config YAML files that can be bundled with animations in order to customise blending styles.</p></body></html> diff --git a/files/lang/launcher_sv.ts b/files/lang/launcher_sv.ts index 55f29241dd..477fa991f5 100644 --- a/files/lang/launcher_sv.ts +++ b/files/lang/launcher_sv.ts @@ -1446,13 +1446,13 @@ de ordinarie fonterna i Morrowind. Bocka denna ruta om du ändå föredrar ordin Run Script After Startup: Kör skript efter uppstart: - - <html><head/><body><p>If enabled - makes transitions between different animations/poses much smoother. Also allows to load animation blending config YAML files that can be bundled with animations in order to customise blending styles.</p></body></html> - <html><head/><body><p>Vid aktivering gör denna funktion att övergångarna mellan olika animationer och poser blir mycket mjukare. Funktionen gör det också möjligt att konfigurera animationsövergångarna i YAML-filer. Dessa filer kan buntas ihop tillsammans med nya animationsfiler.</p></body></html> - Smooth Animation Transitions Mjuka animationsövergångar + + <html><head/><body><p>If enabled - makes transitions between different animations/poses much smoother. Also allows to load animation blending config YAML files that can be bundled with animations in order to customise blending styles.</p></body></html> + <html><head/><body><p>Vid aktivering gör denna funktion att övergångarna mellan olika animationer och poser blir mycket mjukare. Funktionen gör det också möjligt att konfigurera animationsövergångarna i YAML-filer. Dessa filer kan buntas ihop tillsammans med nya animationsfiler.</p></body></html> + From 380d357d02ff3a5e8575e95eb6290daa95d54027 Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Thu, 4 Jul 2024 03:48:59 +0100 Subject: [PATCH 29/34] Fix compilation after master merge --- apps/openmw/mwrender/animblendcontroller.cpp | 11 ++++++++++- apps/openmw/mwrender/rotatecontroller.hpp | 4 ++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/apps/openmw/mwrender/animblendcontroller.cpp b/apps/openmw/mwrender/animblendcontroller.cpp index 8c40cd441b..00cc2900b2 100644 --- a/apps/openmw/mwrender/animblendcontroller.cpp +++ b/apps/openmw/mwrender/animblendcontroller.cpp @@ -326,7 +326,16 @@ namespace MWRender { const osg::Quat rotate = rotateController->getRotate(); const osg::Vec3f offset = rotateController->getOffset(); - const osg::Quat worldOrient = rotateController->getWorldOrientation(node) * rotate.inverse(); + + osg::NodePathList nodepaths = node->getParentalNodePaths(rotateController->getRelativeTo()); + osg::Quat worldOrient; + if (!nodepaths.empty()) + { + osg::Matrixf worldMat = osg::computeLocalToWorld(nodepaths[0]); + worldOrient = worldMat.getRotate(); + } + + worldOrient = worldOrient * rotate.inverse(); const osg::Quat worldOrientInverse = worldOrient.inverse(); mBlendStartTrans -= worldOrientInverse * offset; diff --git a/apps/openmw/mwrender/rotatecontroller.hpp b/apps/openmw/mwrender/rotatecontroller.hpp index b409f6e4d3..8c37345b02 100644 --- a/apps/openmw/mwrender/rotatecontroller.hpp +++ b/apps/openmw/mwrender/rotatecontroller.hpp @@ -20,8 +20,6 @@ namespace MWRender public: RotateController(osg::Node* relativeTo); - osg::Quat getWorldOrientation(osg::Node* node); - void setEnabled(bool enabled); void setOffset(const osg::Vec3f& offset); void setRotate(const osg::Quat& rotate); @@ -30,6 +28,8 @@ namespace MWRender const osg::Quat getRotate() const { return mRotate; } + osg::Node* getRelativeTo() const { return mRelativeTo; } + void operator()(osg::MatrixTransform* node, osg::NodeVisitor* nv); protected: From ecb5616b3627c0b7f37751ae0060d3f3d1ccf9de Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Fri, 19 Jul 2024 01:29:15 +0100 Subject: [PATCH 30/34] Use std::sin/std::cos, clarify floating point types, update comment --- apps/openmw/mwrender/animblendcontroller.cpp | 42 ++++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/apps/openmw/mwrender/animblendcontroller.cpp b/apps/openmw/mwrender/animblendcontroller.cpp index 00cc2900b2..6006250af9 100644 --- a/apps/openmw/mwrender/animblendcontroller.cpp +++ b/apps/openmw/mwrender/animblendcontroller.cpp @@ -23,23 +23,23 @@ namespace MWRender float sineOut(float x) { - return sin((x * osg::PIf) / 2); + return std::sin((x * osg::PIf) / 2.f); } float sineIn(float x) { - return 1 - cos((x * osg::PIf) / 2); + return 1.f - std::cos((x * osg::PIf) / 2.f); } float sineInOut(float x) { - return -(cos(osg::PIf * x) - 1) / 2; + return -(std::cos(osg::PIf * x) - 1.f) / 2.f; } float cubicOut(float t) { - float t1 = 1 - t; - return 1 - (t1 * t1 * t1); // (1-t)^3 + float t1 = 1.f - t; + return 1.f - (t1 * t1 * t1); // (1-t)^3 } float cubicIn(float x) @@ -49,21 +49,21 @@ namespace MWRender float cubicInOut(float x) { - if (x < 0.5) + if (x < 0.5f) { - return 4 * x * x * x; // 4x^3 + return 4.f * x * x * x; // 4x^3 } else { - float x2 = -2 * x + 2; - return 1 - (x2 * x2 * x2) / 2; // (1 - (-2x + 2)^3)/2 + float x2 = -2.f * x + 2.f; + return 1.f - (x2 * x2 * x2) / 2.f; // (1 - (-2x + 2)^3)/2 } } float quartOut(float t) { - float t1 = 1 - t; - return 1 - (t1 * t1 * t1 * t1); // (1-t)^4 + float t1 = 1.f - t; + return 1.f - (t1 * t1 * t1 * t1); // (1-t)^4 } float quartIn(float t) @@ -73,14 +73,14 @@ namespace MWRender float quartInOut(float x) { - if (x < 0.5) + if (x < 0.5f) { - return 8 * x * x * x * x; // 8x^4 + return 8.f * x * x * x * x; // 8x^4 } else { - float x2 = -2 * x + 2; - return 1 - (x2 * x2 * x2 * x2) / 2; // 1 - ((-2x + 2)^4)/2 + float x2 = -2.f * x + 2.f; + return 1.f - (x2 * x2 * x2 * x2) / 2.f; // 1 - ((-2x + 2)^4)/2 } } @@ -89,27 +89,27 @@ namespace MWRender // Higher lambda = lower swing amplitude. 1 = 150% swing amplitude. // w is the frequency of oscillation in the easing func, controls the amount of overswing const float w = 1.5f * osg::PIf; // 4.71238 - return 1 - expf(-lambda * x) * cos(w * x); + return 1.f - expf(-lambda * x) * std::cos(w * x); } float springOutWeak(float x) { - return springOutGeneric(x, 4); + return springOutGeneric(x, 4.f); } float springOutMed(float x) { - return springOutGeneric(x, 3); + return springOutGeneric(x, 3.f); } float springOutStrong(float x) { - return springOutGeneric(x, 2); + return springOutGeneric(x, 2.f); } float springOutTooMuch(float x) { - return springOutGeneric(x, 1); + return springOutGeneric(x, 1.f); } const std::unordered_map easingsMap = { @@ -231,7 +231,7 @@ namespace MWRender if (!mInterpActive) return; - // Shouldnt happen, but potentially an edge case where a new bone was added + // Shouldn't happen, but potentially an edge case where a new bone was added // between gatherRecursiveBoneTransforms and this update // currently OpenMW will never do this assert(mBlendBoneTransforms.find(bone) != mBlendBoneTransforms.end()); From 4bd078a6db76e7c8a734b1bd3894d4eb8a580f50 Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Fri, 19 Jul 2024 01:29:24 +0100 Subject: [PATCH 31/34] Return offset/angle by reference --- apps/openmw/mwrender/rotatecontroller.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/openmw/mwrender/rotatecontroller.hpp b/apps/openmw/mwrender/rotatecontroller.hpp index 8c37345b02..3d9f44c6a3 100644 --- a/apps/openmw/mwrender/rotatecontroller.hpp +++ b/apps/openmw/mwrender/rotatecontroller.hpp @@ -24,9 +24,9 @@ namespace MWRender void setOffset(const osg::Vec3f& offset); void setRotate(const osg::Quat& rotate); - const osg::Vec3f getOffset() const { return mOffset; } + const osg::Vec3f& getOffset() const { return mOffset; } - const osg::Quat getRotate() const { return mRotate; } + const osg::Quat& getRotate() const { return mRotate; } osg::Node* getRelativeTo() const { return mRelativeTo; } From a7a105dfdd64e84dceb9e50bd5faea4585203284 Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Sat, 20 Jul 2024 03:06:24 +0100 Subject: [PATCH 32/34] Copy by ref, fix unable to find rules warning garbage --- apps/openmw/mwrender/animation.cpp | 4 ++-- apps/openmw/mwrender/animblendcontroller.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index ae0d8a489d..0a4a1cace5 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -776,8 +776,8 @@ namespace MWRender blendRules = mResourceSystem->getAnimBlendRulesManager()->getRules(globalBlendConfigPath, blendConfigPath); if (blendRules == nullptr) - Log(Debug::Warning) << "Unable to find animation blending rules: '" << blendConfigPath.value() - << "' or '" << globalBlendConfigPath.value() << "'"; + Log(Debug::Warning) << "Unable to find animation blending rules: '" << yamlpath << "' or '" + << globalBlendConfigPath.value() << "'"; } else { diff --git a/apps/openmw/mwrender/animblendcontroller.cpp b/apps/openmw/mwrender/animblendcontroller.cpp index 6006250af9..684183e230 100644 --- a/apps/openmw/mwrender/animblendcontroller.cpp +++ b/apps/openmw/mwrender/animblendcontroller.cpp @@ -324,8 +324,8 @@ namespace MWRender MWRender::RotateController* rotateController = dynamic_cast(updateCb); if (rotateController) { - const osg::Quat rotate = rotateController->getRotate(); - const osg::Vec3f offset = rotateController->getOffset(); + const osg::Quat& rotate = rotateController->getRotate(); + const osg::Vec3f& offset = rotateController->getOffset(); osg::NodePathList nodepaths = node->getParentalNodePaths(rotateController->getRelativeTo()); osg::Quat worldOrient; From b9a7bdc0c7ab9e9454ba496e5ff7a0aacdd5c638 Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Sat, 20 Jul 2024 20:15:17 +0100 Subject: [PATCH 33/34] Dont use NormalizedView for yamlpath, use changeExtension, add setting to tests script --- apps/openmw/mwrender/animation.cpp | 10 ++++------ scripts/integration_tests.py | 1 + 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index 0a4a1cace5..32d775a9bd 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -762,13 +762,11 @@ namespace MWRender { // Note, even if the actual config is .json - we should send a .yaml path to AnimBlendRulesManager, the // manager will check for .json if it will not find a specified .yaml file. - auto yamlpath = kfname; - Misc::StringUtils::replaceLast(yamlpath, ".kf", ".yaml"); - Misc::StringUtils::replaceLast(yamlpath, ".dae", ".yaml"); + VFS::Path::Normalized blendConfigPath(kfname); + blendConfigPath.changeExtension("yaml"); // globalBlendConfigPath is only used with actors! Objects have no default blending. constexpr VFS::Path::NormalizedView globalBlendConfigPath("animations/animation-config.yaml"); - const VFS::Path::NormalizedView blendConfigPath(yamlpath); osg::ref_ptr blendRules; if (mPtr.getClass().isActor()) @@ -776,8 +774,8 @@ namespace MWRender blendRules = mResourceSystem->getAnimBlendRulesManager()->getRules(globalBlendConfigPath, blendConfigPath); if (blendRules == nullptr) - Log(Debug::Warning) << "Unable to find animation blending rules: '" << yamlpath << "' or '" - << globalBlendConfigPath.value() << "'"; + Log(Debug::Warning) << "Unable to find animation blending rules: '" << blendConfigPath << "' or '" + << globalBlendConfigPath << "'"; } else { diff --git a/scripts/integration_tests.py b/scripts/integration_tests.py index 41c6c3a5a2..17c86c508b 100755 --- a/scripts/integration_tests.py +++ b/scripts/integration_tests.py @@ -62,6 +62,7 @@ def runTest(name): "resolution x = 640\n" "resolution y = 480\n" "framerate limit = 60\n" + "smooth animation transitions = true\n" ) stdout_lines = list() exit_ok = True From 96db8e94bdb3cb9d59643d11394e3988f2494a9b Mon Sep 17 00:00:00 2001 From: Sam Hellawell Date: Sat, 20 Jul 2024 22:56:55 +0100 Subject: [PATCH 34/34] Fix forgot game section for test settings --- scripts/integration_tests.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/integration_tests.py b/scripts/integration_tests.py index 17c86c508b..028b55d7da 100755 --- a/scripts/integration_tests.py +++ b/scripts/integration_tests.py @@ -62,6 +62,7 @@ def runTest(name): "resolution x = 640\n" "resolution y = 480\n" "framerate limit = 60\n" + "[Game]\n" "smooth animation transitions = true\n" ) stdout_lines = list()