Skip to content

Commit

Permalink
Merge remote-tracking branch 'remotes/maxyari/animationblending' into…
Browse files Browse the repository at this point in the history
… animationblending

# Conflicts:
#	apps/launcher/ui/settingspage.ui
  • Loading branch information
dwing4g committed Dec 28, 2023
2 parents 7298ec2 + 6721e93 commit 5866947
Show file tree
Hide file tree
Showing 32 changed files with 1,057 additions and 50 deletions.
1 change: 1 addition & 0 deletions AUTHORS.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,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)
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@
Feature #3537: Shader-based water ripples
Feature #5173: Support for NiFogProperty
Feature #5492: Let rain and snow collide with statics
Feature #6009: Animation blending - smooth animation transitions with modding support
Feature #6149: Dehardcode Lua API_REVISION
Feature #6152: Playing music via lua scripts
Feature #6188: Specular lighting from point light sources
Expand Down
2 changes: 2 additions & 0 deletions apps/launcher/settingspage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -338,6 +339,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);

Expand Down
53 changes: 35 additions & 18 deletions apps/launcher/ui/settingspage.ui
Original file line number Diff line number Diff line change
Expand Up @@ -293,8 +293,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>671</width>
<height>774</height>
<width>676</width>
<height>914</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
Expand Down Expand Up @@ -334,7 +334,7 @@
</property>
</widget>
</item>
<item row="1" column="1">
<item row="2" column="1">
<widget class="QCheckBox" name="turnToMovementDirectionCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;影响左右和斜向的移动。启用这个设置可以使运动更加真实。&lt;/p&gt;&lt;p&gt;如果禁用,那么整个角色的身体就会始终面向镜头的方向。斜向运动也没有特殊的动画,会导致在地上滑行。&lt;/p&gt;&lt;p&gt;如果启用,则角色将下半身转向运动方向。上半身部分转动。头部总是面向镜头的方向。在战斗模式下,它只适用于斜向运动。在非战斗模式下,它也能改变左右运动。在游泳时也会根据运动方向将整个身体向上或向下转动。&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
Expand All @@ -344,8 +344,18 @@
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QCheckBox" name="smoothAnimTransitionsCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;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.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Smooth animation transitions</string>
</property>
</widget>
</item>
<item row="2" column="0">
<layout class="QVBoxLayout" name="sheathingLayout">
<layout class="QVBoxLayout" name="weaponSheathingLayout">
<property name="leftMargin">
<number>20</number>
</property>
Expand All @@ -362,22 +372,29 @@
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="shieldSheathingCheckBox">
<property name="enabled">
<bool>false</bool>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;渲染带护套的盾牌,需要额外的mod资源。&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>盾牌护套</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="1">
<item row="3" column="0">
<layout class="QVBoxLayout" name="shieldSheathingLayout">
<property name="leftMargin">
<number>20</number>
</property>
<item>
<widget class="QCheckBox" name="shieldSheathingCheckBox">
<property name="enabled">
<bool>false</bool>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;渲染带护套的盾牌,需要额外的mod资源。&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>盾牌护套</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="3" column="1">
<widget class="QCheckBox" name="playerMovementIgnoresAnimationCheckBox">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;In third person, the camera will sway along with the movement animations of the player. Enabling this option disables this swaying by having the player character move independently of its animation. This was the default behavior of OpenMW 0.48 and earlier.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
Expand Down
2 changes: 1 addition & 1 deletion apps/openmw/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ set(GAME_HEADER
source_group(game FILES ${GAME} ${GAME_HEADER})

add_openmw_dir (mwrender
actors objects renderingmanager animation rotatecontroller sky skyutil npcanimation esm4npcanimation vismask
actors objects renderingmanager animation animblendcontroller rotatecontroller sky skyutil npcanimation esm4npcanimation vismask
creatureanimation effectmanager util renderinginterface pathgrid rendermode weaponanimation screenshotmanager
bulletdebugdraw globalmap characterpreview camera localmap water terrainstorage ripplesimulation
renderbin actoranimation landmanager navmesh actorspaths recastmesh fogmanager objectpaging groundcover
Expand Down
105 changes: 93 additions & 12 deletions apps/openmw/mwrender/animation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

#include <components/debug/debuglog.hpp>

#include <components/resource/animblendrulesmanager.hpp>
#include <components/resource/keyframemanager.hpp>
#include <components/resource/scenemanager.hpp>

Expand Down Expand Up @@ -449,6 +450,8 @@ namespace MWRender
ControllerMap mControllerMap[Animation::sNumBlendMasks];

const SceneUtil::TextKeyMap& getTextKeys() const;

osg::ref_ptr<const SceneUtil::AnimBlendRules> mAnimBlendRules;
};

void UpdateVfxCallback::operator()(osg::Node* node, osg::NodeVisitor* nv)
Expand Down Expand Up @@ -604,8 +607,11 @@ namespace MWRender

for (const auto& name : mResourceSystem->getVFS()->getRecursiveDirectoryIterator(animationPath))
{

if (Misc::getFileExtension(name) == "kf")
{
addSingleAnimSource(name, baseModel);
}
}
}

Expand All @@ -622,17 +628,18 @@ namespace MWRender
loadAllAnimationsInFolder(kfname, baseModel);
}

void Animation::addSingleAnimSource(const std::string& kfname, const std::string& baseModel)
std::shared_ptr<Animation::AnimSource> Animation::addSingleAnimSource(
const std::string& kfname, const std::string& baseModel)
{
if (!mResourceSystem->getVFS()->exists(kfname))
return;
return nullptr;

auto animsrc = std::make_shared<AnimSource>();
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;
Expand Down Expand Up @@ -660,7 +667,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);
Expand Down Expand Up @@ -692,6 +699,29 @@ 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");

// globalBlendConfigPath is only used with actors! Objects have no default blending.
std::string globalBlendConfigPath = "animations/animation-config.yaml";

osg::ref_ptr<const SceneUtil::AnimBlendRules> blendRules;
if (mPtr.getClass().isActor())
blendRules = mResourceSystem->getAnimBlendRulesManager()->getInstance(globalBlendConfigPath, yamlpath);
else
blendRules = mResourceSystem->getAnimBlendRulesManager()->getInstance(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()
Expand Down Expand Up @@ -777,31 +807,40 @@ 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;
}

/* Look in reverse; last-inserted source has priority. */
AnimState state;

/* Look in reverse; last-inserted source has priority. */
AnimSourceList::reverse_iterator iter(mAnimSources.rbegin());
for (; iter != mAnimSources.rend(); ++iter)
{
Expand All @@ -815,6 +854,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)
Expand Down Expand Up @@ -961,7 +1002,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()
Expand All @@ -976,14 +1017,53 @@ namespace MWRender
if (active != mStates.end())
{
std::shared_ptr<AnimSource> animsrc = active->second.mSource;
AnimBlendController::AnimStateData stateData = active->second.asAnimStateData();

for (AnimSource::ControllerMap::iterator it = animsrc->mControllerMap[blendMask].begin();
it != animsrc->mControllerMap[blendMask].end(); ++it)
{
osg::ref_ptr<osg::Node> node = getNodeMap().at(
it->first); // this should not throw, we already checked for the node existing in addAnimSource

osg::Callback* callback = it->second->getAsCallback();
auto mtx = dynamic_cast<NifOsg::MatrixTransform*>(node.get());
osg::Callback* callback;

if (mtx && Settings::game().mSmoothAnimTransitions)
{
// Note: AnimBlendController currently works only with nifOsg::MatrixTransform. Due to
// the side-effect of RotationController (and probably RollController) applying undesired
// rotations to the osg::MatrixTransofrm - the AnimBlendController or KeyframeController need
// to have access to mRotation of NifOsg::MatrixTransform (which is unaffected by the
// side-effect) so they can use it to override the undesired rotations.
//
// For now if the node is not a NifOsg::MatrixTransform - animation will fallback
// to KeyframeControllers and won't do any blending

// Update an existing animation blending controller or create a new one
osg::ref_ptr<AnimBlendController> animController;

if (mAnimBlendControllers.contains(node))
{
animController = mAnimBlendControllers[node];
animController->setKeyframeTrack(it->second, stateData, animsrc->mAnimBlendRules);
}
else
{
animController = osg::ref_ptr<AnimBlendController>(
new AnimBlendController(it->second, stateData, animsrc->mAnimBlendRules));

mAnimBlendControllers[node] = animController;
}

it->second->mTime = active->second.mTime;

callback = animController->getAsCallback();
}
else
{
callback = it->second->getAsCallback();
}

node->addUpdateCallback(callback);
mActiveControllers.emplace_back(node, callback);

Expand Down Expand Up @@ -1733,13 +1813,14 @@ namespace MWRender
osg::Callback* cb = node->getUpdateCallback();
while (cb)
{
if (dynamic_cast<SceneUtil::KeyframeController*>(cb))
if (dynamic_cast<AnimBlendController*>(cb) || dynamic_cast<SceneUtil::KeyframeController*>(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)
Expand Down
Loading

0 comments on commit 5866947

Please sign in to comment.