Merge pull request #666 from scrawl/animation

Animation refactoring & fixes
sceneinput
scrawl 10 years ago
commit 111cf5462b

@ -43,6 +43,7 @@
#include "../mwworld/class.hpp"
#include "../mwworld/inventorystore.hpp"
#include "../mwworld/esmstore.hpp"
#include "../mwworld/player.hpp"
namespace
{
@ -236,7 +237,7 @@ std::string CharacterController::chooseRandomGroup (const std::string& prefix, i
return prefix + toString(roll);
}
void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterState movement, bool force)
void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterState movement, JumpingState jump, bool force)
{
// hit recoils/knockdown animations handling
if(mPtr.getClass().isActor())
@ -251,26 +252,28 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat
{
mHitState = CharState_KnockOut;
mCurrentHit = "knockout";
mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::Group_All, false, 1, "start", "stop", 0.0f, ~0ul);
mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, false, 1, "start", "stop", 0.0f, ~0ul);
mPtr.getClass().getCreatureStats(mPtr).setKnockedDown(true);
}
else if(knockdown)
{
mHitState = CharState_KnockDown;
mCurrentHit = "knockdown";
mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::Group_All, true, 1, "start", "stop", 0.0f, 0);
mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, true, 1, "start", "stop", 0.0f, 0);
}
else if (recovery)
{
mHitState = CharState_Hit;
mCurrentHit = chooseRandomGroup("hit");
mAnimation->play(mCurrentHit, Priority_Hit, MWRender::Animation::Group_All, true, 1, "start", "stop", 0.0f, 0);
mAnimation->play(mCurrentHit, Priority_Hit, MWRender::Animation::BlendMask_All, true, 1, "start", "stop", 0.0f, 0);
}
else if (block)
{
mHitState = CharState_Block;
mCurrentHit = "shield";
mAnimation->play(mCurrentHit, Priority_Hit, MWRender::Animation::Group_All, true, 1, "block start", "block stop", 0.0f, 0);
MWRender::Animation::AnimPriority priorityBlock (Priority_Hit);
priorityBlock.mPriority[MWRender::Animation::BoneGroup_LeftArm] = Priority_Block;
mAnimation->play(mCurrentHit, priorityBlock, MWRender::Animation::BlendMask_All, true, 1, "block start", "block stop", 0.0f, 0);
}
// Cancel upper body animations
@ -303,7 +306,7 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat
{
mHitState = CharState_KnockDown;
mAnimation->disable(mCurrentHit);
mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::Group_All, true, 1, "loop stop", "stop", 0.0f, 0);
mAnimation->play(mCurrentHit, Priority_Knockdown, MWRender::Animation::BlendMask_All, true, 1, "loop stop", "stop", 0.0f, 0);
}
}
@ -311,40 +314,41 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat
if (!mPtr.getClass().isBipedal(mPtr))
weap = sWeaponTypeListEnd;
if(force && mJumpState != JumpState_None)
if(force || jump != mJumpState)
{
std::string jump;
MWRender::Animation::Group jumpgroup = MWRender::Animation::Group_All;
bool startAtLoop = (jump == mJumpState);
mJumpState = jump;
std::string jumpAnimName;
MWRender::Animation::BlendMask jumpmask = MWRender::Animation::BlendMask_All;
if(mJumpState != JumpState_None)
{
jump = "jump";
jumpAnimName = "jump";
if(weap != sWeaponTypeListEnd)
{
jump += weap->shortgroup;
if(!mAnimation->hasAnimation(jump))
jumpAnimName += weap->shortgroup;
if(!mAnimation->hasAnimation(jumpAnimName))
{
jumpgroup = MWRender::Animation::Group_LowerBody;
jump = "jump";
jumpmask = MWRender::Animation::BlendMask_LowerBody;
jumpAnimName = "jump";
}
}
}
if(mJumpState == JumpState_InAir)
{
int mode = ((jump == mCurrentJump) ? 2 : 1);
mAnimation->disable(mCurrentJump);
mCurrentJump = jump;
mCurrentJump = jumpAnimName;
if (mAnimation->hasAnimation("jump"))
mAnimation->play(mCurrentJump, Priority_Jump, jumpgroup, false,
1.0f, ((mode!=2)?"start":"loop start"), "stop", 0.0f, ~0ul);
mAnimation->play(mCurrentJump, Priority_Jump, jumpmask, false,
1.0f, (startAtLoop?"loop start":"start"), "stop", 0.0f, ~0ul);
}
else
{
mAnimation->disable(mCurrentJump);
mCurrentJump.clear();
if (mAnimation->hasAnimation("jump"))
mAnimation->play(jump, Priority_Jump, jumpgroup, true,
mAnimation->play(jumpAnimName, Priority_Jump, jumpmask, true,
1.0f, "loop stop", "stop", 0.0f, 0);
}
}
@ -353,55 +357,55 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat
{
mMovementState = movement;
std::string movement;
MWRender::Animation::Group movegroup = MWRender::Animation::Group_All;
std::string movementAnimName;
MWRender::Animation::BlendMask movemask = MWRender::Animation::BlendMask_All;
const StateInfo *movestate = std::find_if(sMovementList, sMovementListEnd, FindCharState(mMovementState));
if(movestate != sMovementListEnd)
{
movement = movestate->groupname;
if(weap != sWeaponTypeListEnd && movement.find("swim") == std::string::npos)
movementAnimName = movestate->groupname;
if(weap != sWeaponTypeListEnd && movementAnimName.find("swim") == std::string::npos)
{
movement += weap->shortgroup;
if(!mAnimation->hasAnimation(movement))
movementAnimName += weap->shortgroup;
if(!mAnimation->hasAnimation(movementAnimName))
{
movegroup = MWRender::Animation::Group_LowerBody;
movement = movestate->groupname;
movemask = MWRender::Animation::BlendMask_LowerBody;
movementAnimName = movestate->groupname;
}
}
if(!mAnimation->hasAnimation(movement))
if(!mAnimation->hasAnimation(movementAnimName))
{
std::string::size_type swimpos = movement.find("swim");
std::string::size_type swimpos = movementAnimName.find("swim");
if(swimpos == std::string::npos)
{
std::string::size_type runpos = movement.find("run");
std::string::size_type runpos = movementAnimName.find("run");
if (runpos != std::string::npos)
{
movement.replace(runpos, runpos+3, "walk");
if (!mAnimation->hasAnimation(movement))
movement.clear();
movementAnimName.replace(runpos, runpos+3, "walk");
if (!mAnimation->hasAnimation(movementAnimName))
movementAnimName.clear();
}
else
movement.clear();
movementAnimName.clear();
}
else
{
movegroup = MWRender::Animation::Group_LowerBody;
movement.erase(swimpos, 4);
if(!mAnimation->hasAnimation(movement))
movement.clear();
movemask = MWRender::Animation::BlendMask_LowerBody;
movementAnimName.erase(swimpos, 4);
if(!mAnimation->hasAnimation(movementAnimName))
movementAnimName.clear();
}
}
}
/* If we're playing the same animation, restart from the loop start instead of the
* beginning. */
int mode = ((movement == mCurrentMovement) ? 2 : 1);
int mode = ((movementAnimName == mCurrentMovement) ? 2 : 1);
mMovementAnimationControlled = true;
mAnimation->disable(mCurrentMovement);
mCurrentMovement = movement;
mCurrentMovement = movementAnimName;
if(!mCurrentMovement.empty())
{
float vel, speedmult = 1.0f;
@ -447,7 +451,17 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat
}
}
mAnimation->play(mCurrentMovement, Priority_Movement, movegroup, false,
MWRender::Animation::AnimPriority priorityMovement (Priority_Movement);
if ((movement == CharState_TurnLeft || movement == CharState_TurnRight)
&& mPtr == MWBase::Environment::get().getWorld()->getPlayerPtr()
&& MWBase::Environment::get().getWorld()->isFirstPerson())
{
priorityMovement.mPriority[MWRender::Animation::BoneGroup_Torso] = 0;
priorityMovement.mPriority[MWRender::Animation::BoneGroup_LeftArm] = 0;
priorityMovement.mPriority[MWRender::Animation::BoneGroup_RightArm] = 0;
}
mAnimation->play(mCurrentMovement, priorityMovement, movemask, false,
speedmult, ((mode!=2)?"start":"loop start"), "stop", 0.0f, ~0ul);
}
}
@ -456,7 +470,7 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat
// FIXME: if one of the below states is close to their last animation frame (i.e. will be disabled in the coming update),
// the idle animation should be displayed
if ((mUpperBodyState != UpperCharState_Nothing
|| mMovementState != CharState_None
|| (mMovementState != CharState_None && mMovementState != CharState_TurnLeft && mMovementState != CharState_TurnRight)
|| mHitState != CharState_None)
&& !mPtr.getClass().isBipedal(mPtr))
idle = CharState_None;
@ -486,7 +500,7 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat
mAnimation->disable(mCurrentIdle);
mCurrentIdle = idle;
if(!mCurrentIdle.empty())
mAnimation->play(mCurrentIdle, Priority_Default, MWRender::Animation::Group_All, false,
mAnimation->play(mCurrentIdle, Priority_Default, MWRender::Animation::BlendMask_All, false,
1.0f, "start", "stop", 0.0f, ~0ul, true);
}
@ -602,7 +616,7 @@ void CharacterController::playDeath(float startpoint, CharacterState death)
mCurrentJump = "";
mMovementAnimationControlled = true;
mAnimation->play(mCurrentDeath, Priority_Death, MWRender::Animation::Group_All,
mAnimation->play(mCurrentDeath, Priority_Death, MWRender::Animation::BlendMask_All,
false, 1.0f, "start", "stop", startpoint, 0);
}
@ -704,7 +718,7 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim
if(mDeathState == CharState_None)
refreshCurrentAnims(mIdleState, mMovementState, true);
refreshCurrentAnims(mIdleState, mMovementState, mJumpState, true);
mAnimation->runAnimation(0.f);
}
@ -868,10 +882,10 @@ void CharacterController::updateIdleStormState()
mAnimation->getInfo("idlestorm", &complete);
if (complete == 0)
mAnimation->play("idlestorm", Priority_Storm, MWRender::Animation::Group_RightArm, false,
mAnimation->play("idlestorm", Priority_Storm, MWRender::Animation::BlendMask_RightArm, false,
1.0f, "start", "loop start", 0.0f, 0);
else if (complete == 1)
mAnimation->play("idlestorm", Priority_Storm, MWRender::Animation::Group_RightArm, false,
mAnimation->play("idlestorm", Priority_Storm, MWRender::Animation::BlendMask_RightArm, false,
1.0f, "loop start", "loop stop", 0.0f, ~0ul);
}
else
@ -882,7 +896,7 @@ void CharacterController::updateIdleStormState()
{
if (mAnimation->getCurrentTime("idlestorm") < mAnimation->getTextKeyTime("idlestorm: loop stop"))
{
mAnimation->play("idlestorm", Priority_Storm, MWRender::Animation::Group_RightArm, true,
mAnimation->play("idlestorm", Priority_Storm, MWRender::Animation::BlendMask_RightArm, true,
1.0f, "loop stop", "stop", 0.0f, 0);
}
}
@ -989,7 +1003,7 @@ bool CharacterController::updateCreatureState()
if (!mCurrentWeapon.empty())
{
mAnimation->play(mCurrentWeapon, Priority_Weapon,
MWRender::Animation::Group_All, true,
MWRender::Animation::BlendMask_All, true,
1, startKey, stopKey,
0.0f, 0);
mUpperBodyState = UpperCharState_StartToMinAttack;
@ -1051,6 +1065,9 @@ bool CharacterController::updateWeaponState()
}
}
MWRender::Animation::AnimPriority priorityWeapon(Priority_Weapon);
priorityWeapon.mPriority[MWRender::Animation::BoneGroup_LowerBody] = 0;
bool forcestateupdate = false;
if(weaptype != mWeaponType && mHitState != CharState_KnockDown && mHitState != CharState_KnockOut
&& mHitState != CharState_Hit)
@ -1063,8 +1080,8 @@ bool CharacterController::updateWeaponState()
if(weaptype == WeapType_None)
{
getWeaponGroup(mWeaponType, weapgroup);
mAnimation->play(weapgroup, Priority_Weapon,
MWRender::Animation::Group_UpperBody, true,
mAnimation->play(weapgroup, priorityWeapon,
MWRender::Animation::BlendMask_All, true,
1.0f, "unequip start", "unequip stop", 0.0f, 0);
mUpperBodyState = UpperCharState_UnEquipingWeap;
}
@ -1074,8 +1091,8 @@ bool CharacterController::updateWeaponState()
mAnimation->showWeapons(false);
mAnimation->setWeaponGroup(weapgroup);
mAnimation->play(weapgroup, Priority_Weapon,
MWRender::Animation::Group_UpperBody, true,
mAnimation->play(weapgroup, priorityWeapon,
MWRender::Animation::BlendMask_All, true,
1.0f, "equip start", "equip stop", 0.0f, 0);
mUpperBodyState = UpperCharState_EquipingWeap;
@ -1145,7 +1162,7 @@ bool CharacterController::updateWeaponState()
bool animPlaying;
if(mAttackingOrSpell)
{
if(mUpperBodyState == UpperCharState_WeapEquiped && mHitState == CharState_None)
if(mUpperBodyState == UpperCharState_WeapEquiped && (mHitState == CharState_None || mHitState == CharState_Block))
{
MWBase::Environment::get().getWorld()->breakInvisibility(mPtr);
mAttackType.clear();
@ -1154,6 +1171,10 @@ bool CharacterController::updateWeaponState()
// Unset casting flag, otherwise pressing the mouse button down would
// continue casting every frame if there is no animation
mAttackingOrSpell = false;
if (mPtr == MWBase::Environment::get().getWorld()->getPlayerPtr())
{
MWBase::Environment::get().getWorld()->getPlayer().setAttackingOrSpell(false);
}
const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore();
@ -1191,8 +1212,8 @@ bool CharacterController::updateWeaponState()
case 2: mAttackType = "target"; break;
}
mAnimation->play(mCurrentWeapon, Priority_Weapon,
MWRender::Animation::Group_UpperBody, true,
mAnimation->play(mCurrentWeapon, priorityWeapon,
MWRender::Animation::BlendMask_All, true,
weapSpeed, mAttackType+" start", mAttackType+" stop",
0.0f, 0);
mUpperBodyState = UpperCharState_CastingSpell;
@ -1223,8 +1244,8 @@ bool CharacterController::updateWeaponState()
else if(item.getTypeName() == typeid(ESM::Probe).name())
Security(mPtr).probeTrap(target, item, resultMessage, resultSound);
}
mAnimation->play(mCurrentWeapon, Priority_Weapon,
MWRender::Animation::Group_UpperBody, true,
mAnimation->play(mCurrentWeapon, priorityWeapon,
MWRender::Animation::BlendMask_All, true,
1.0f, "start", "stop", 0.0, 0);
mUpperBodyState = UpperCharState_FollowStartToFollowStop;
@ -1250,8 +1271,8 @@ bool CharacterController::updateWeaponState()
determineAttackType();
}
mAnimation->play(mCurrentWeapon, Priority_Weapon,
MWRender::Animation::Group_UpperBody, false,
mAnimation->play(mCurrentWeapon, priorityWeapon,
MWRender::Animation::BlendMask_All, false,
weapSpeed, mAttackType+" start", mAttackType+" min attack",
0.0f, 0);
mUpperBodyState = UpperCharState_StartToMinAttack;
@ -1301,8 +1322,8 @@ bool CharacterController::updateWeaponState()
mAttackStrength = attackStrength;
mAnimation->disable(mCurrentWeapon);
mAnimation->play(mCurrentWeapon, Priority_Weapon,
MWRender::Animation::Group_UpperBody, false,
mAnimation->play(mCurrentWeapon, priorityWeapon,
MWRender::Animation::BlendMask_All, false,
weapSpeed, mAttackType+" max attack", mAttackType+" min hit",
1.0f-complete, 0);
@ -1371,15 +1392,6 @@ bool CharacterController::updateWeaponState()
mAnimation->attachArrow();
mUpperBodyState = UpperCharState_WeapEquiped;
//don't allow to continue playing hit animation on UpperBody after actor had attacked during it
if(mHitState == CharState_Hit)
{
mAnimation->changeGroups(mCurrentHit, MWRender::Animation::Group_LowerBody);
//commenting out following 2 lines will give a bit different combat dynamics(slower)
mHitState = CharState_None;
mCurrentHit.clear();
mPtr.getClass().getCreatureStats(mPtr).setHitRecovery(false);
}
}
else if(mUpperBodyState == UpperCharState_UnEquipingWeap)
mUpperBodyState = UpperCharState_Nothing;
@ -1397,8 +1409,8 @@ bool CharacterController::updateWeaponState()
case UpperCharState_MinAttackToMaxAttack:
//hack to avoid body pos desync when jumping/sneaking in 'max attack' state
if(!mAnimation->isPlaying(mCurrentWeapon))
mAnimation->play(mCurrentWeapon, Priority_Weapon,
MWRender::Animation::Group_UpperBody, false,
mAnimation->play(mCurrentWeapon, priorityWeapon,
MWRender::Animation::BlendMask_All, false,
0, mAttackType+" min attack", mAttackType+" max attack", 0.999f, 0);
break;
case UpperCharState_MaxAttackToMinHit:
@ -1440,33 +1452,16 @@ bool CharacterController::updateWeaponState()
{
mAnimation->disable(mCurrentWeapon);
if (mUpperBodyState == UpperCharState_FollowStartToFollowStop)
mAnimation->play(mCurrentWeapon, Priority_Weapon,
MWRender::Animation::Group_UpperBody, true,
mAnimation->play(mCurrentWeapon, priorityWeapon,
MWRender::Animation::BlendMask_All, true,
weapSpeed, start, stop, 0.0f, 0);
else
mAnimation->play(mCurrentWeapon, Priority_Weapon,
MWRender::Animation::Group_UpperBody, false,
mAnimation->play(mCurrentWeapon, priorityWeapon,
MWRender::Animation::BlendMask_All, false,
weapSpeed, start, stop, 0.0f, 0);
}
}
//if playing combat animation and lowerbody is not busy switch to whole body animation
if((weaptype != WeapType_None || mUpperBodyState == UpperCharState_UnEquipingWeap) && animPlaying)
{
if( mMovementState != CharState_None ||
mJumpState != JumpState_None ||
mHitState != CharState_None ||
MWBase::Environment::get().getWorld()->isSwimming(mPtr) ||
cls.getCreatureStats(mPtr).getMovementFlag(CreatureStats::Flag_Sneak))
{
mAnimation->changeGroups(mCurrentWeapon, MWRender::Animation::Group_UpperBody);
}
else
{
mAnimation->changeGroups(mCurrentWeapon, MWRender::Animation::Group_All);
}
}
if (mPtr.getClass().hasInventoryStore(mPtr))
{
MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr);
@ -1475,7 +1470,7 @@ bool CharacterController::updateWeaponState()
&& updateCarriedLeftVisible(mWeaponType))
{
mAnimation->play("torch", Priority_Torch, MWRender::Animation::Group_LeftArm,
mAnimation->play("torch", Priority_Torch, MWRender::Animation::BlendMask_LeftArm,
false, 1.0f, "start", "stop", 0.0f, (~(size_t)0), true);
}
else if (mAnimation->isPlaying("torch"))
@ -1505,7 +1500,7 @@ void CharacterController::update(float duration)
mAnimQueue.pop_front();
mAnimation->play(mAnimQueue.front().first, Priority_Default,
MWRender::Animation::Group_All, false,
MWRender::Animation::BlendMask_All, false,
1.0f, "start", "stop", 0.0f, mAnimQueue.front().second);
}
}
@ -1562,6 +1557,8 @@ void CharacterController::update(float duration)
CharacterState movestate = CharState_None;
CharacterState idlestate = CharState_SpecialIdle;
JumpingState jumpstate = JumpState_None;
bool forcestateupdate = false;
mHasMovedInXY = std::abs(vec.x())+std::abs(vec.y()) > 0.0f;
@ -1644,7 +1641,7 @@ void CharacterController::update(float duration)
}
forcestateupdate = (mJumpState != JumpState_InAir);
mJumpState = JumpState_InAir;
jumpstate = JumpState_InAir;
static const float fJumpMoveBase = gmst.find("fJumpMoveBase")->getFloat();
static const float fJumpMoveMult = gmst.find("fJumpMoveMult")->getFloat();
@ -1689,7 +1686,7 @@ void CharacterController::update(float duration)
else if(mJumpState == JumpState_InAir)
{
forcestateupdate = true;
mJumpState = JumpState_Landing;
jumpstate = JumpState_Landing;
vec.z() = 0.0f;
float height = cls.getCreatureStats(mPtr).land();
@ -1720,7 +1717,7 @@ void CharacterController::update(float duration)
}
else
{
mJumpState = JumpState_None;
jumpstate = JumpState_None;
vec.z() = 0.0f;
inJump = false;
@ -1786,19 +1783,22 @@ void CharacterController::update(float duration)
mAnimQueue.pop_front();
mAnimation->play(mAnimQueue.front().first, Priority_Default,
MWRender::Animation::Group_All, false,
MWRender::Animation::BlendMask_All, false,
1.0f, "start", "stop", 0.0f, mAnimQueue.front().second);
}
}
// bipedal means hand-to-hand could be used (which is handled in updateWeaponState). an existing InventoryStore means an actual weapon could be used.
if(cls.isBipedal(mPtr) || cls.hasInventoryStore(mPtr))
forcestateupdate = updateWeaponState() || forcestateupdate;
else
forcestateupdate = updateCreatureState() || forcestateupdate;
if (!mSkipAnim)
refreshCurrentAnims(idlestate, movestate, forcestateupdate);
{
// bipedal means hand-to-hand could be used (which is handled in updateWeaponState). an existing InventoryStore means an actual weapon could be used.
if(cls.isBipedal(mPtr) || cls.hasInventoryStore(mPtr))
forcestateupdate = updateWeaponState() || forcestateupdate;
else
forcestateupdate = updateCreatureState() || forcestateupdate;
refreshCurrentAnims(idlestate, movestate, jumpstate, forcestateupdate);
}
if (inJump)
mMovementAnimationControlled = false;
@ -1894,7 +1894,7 @@ void CharacterController::playGroup(const std::string &groupname, int mode, int
mIdleState = CharState_SpecialIdle;
mAnimation->play(groupname, Priority_Default,
MWRender::Animation::Group_All, false, 1.0f,
MWRender::Animation::BlendMask_All, false, 1.0f,
((mode==2) ? "loop start" : "start"), "stop", 0.0f, count-1);
}
else if(mode == 0)
@ -1934,7 +1934,7 @@ void CharacterController::forceStateUpdate()
return;
clearAnimQueue();
refreshCurrentAnims(mIdleState, mMovementState, true);
refreshCurrentAnims(mIdleState, mMovementState, mJumpState, true);
if(mDeathState != CharState_None)
{
playRandomDeath();
@ -2052,12 +2052,13 @@ void CharacterController::setAttackingOrSpell(bool attackingOrSpell)
bool CharacterController::readyToPrepareAttack() const
{
return mHitState == CharState_None && mUpperBodyState <= UpperCharState_WeapEquiped;
return (mHitState == CharState_None || mHitState == CharState_Block)
&& mUpperBodyState <= UpperCharState_WeapEquiped;
}
bool CharacterController::readyToStartAttack() const
{
if (mHitState != CharState_None)
if (mHitState != CharState_None && mHitState != CharState_Block)
return false;
if (mPtr.getClass().hasInventoryStore(mPtr) || mPtr.getClass().isBipedal(mPtr))

@ -32,6 +32,7 @@ enum Priority {
Priority_Movement,
Priority_Hit,
Priority_Weapon,
Priority_Block,
Priority_Knockdown,
Priority_Torch,
Priority_Storm,
@ -185,7 +186,7 @@ class CharacterController : public MWRender::Animation::TextKeyListener
void determineAttackType();
void refreshCurrentAnims(CharacterState idle, CharacterState movement, bool force=false);
void refreshCurrentAnims(CharacterState idle, CharacterState movement, JumpingState jump, bool force=false);
void clearAnimQueue();

@ -219,7 +219,7 @@ namespace MWRender
typedef std::map<std::string, osg::ref_ptr<NifOsg::KeyframeController> > ControllerMap;
ControllerMap mControllerMap[Animation::sNumGroups];
ControllerMap mControllerMap[Animation::sNumBlendMasks];
const std::multimap<float, std::string>& getTextKeys();
};
@ -261,7 +261,7 @@ namespace MWRender
, mHeadYawRadians(0.f)
, mHeadPitchRadians(0.f)
{
for(size_t i = 0;i < sNumGroups;i++)
for(size_t i = 0;i < sNumBlendMasks;i++)
mAnimationTimePtr[i].reset(new AnimationTime);
}
@ -299,9 +299,9 @@ namespace MWRender
mResetAccumRootCallback->setAccumulate(mAccumulate);
}
size_t Animation::detectAnimGroup(osg::Node* node)
size_t Animation::detectBlendMask(osg::Node* node)
{
static const char sGroupRoots[sNumGroups][32] = {
static const char sBlendMaskRoots[sNumBlendMasks][32] = {
"", /* Lower body / character root */
"Bip01 Spine1", /* Torso */
"Bip01 L Clavicle", /* Left arm */
@ -311,9 +311,9 @@ namespace MWRender
while(node != mObjectRoot)
{
const std::string &name = node->getName();
for(size_t i = 1;i < sNumGroups;i++)
for(size_t i = 1;i < sNumBlendMasks;i++)
{
if(name == sGroupRoots[i])
if(name == sBlendMaskRoots[i])
return i;
}
@ -361,13 +361,13 @@ namespace MWRender
osg::Node* node = found->second;
size_t group = detectAnimGroup(node);
size_t blendMask = detectBlendMask(node);
// clone the controller, because each Animation needs its own ControllerSource
osg::ref_ptr<NifOsg::KeyframeController> cloned = osg::clone(it->second.get(), osg::CopyOp::DEEP_COPY_ALL);
cloned->setSource(mAnimationTimePtr[group]);
cloned->setSource(mAnimationTimePtr[blendMask]);
animsrc->mControllerMap[group].insert(std::make_pair(bonename, cloned));
animsrc->mControllerMap[blendMask].insert(std::make_pair(bonename, cloned));
}
mAnimSources.push_back(animsrc);
@ -390,7 +390,7 @@ namespace MWRender
{
mStates.clear();
for(size_t i = 0;i < sNumGroups;i++)
for(size_t i = 0;i < sNumBlendMasks;i++)
mAnimationTimePtr[i]->setTimePtr(boost::shared_ptr<float>());
mAccumCtrl = NULL;
@ -461,7 +461,7 @@ namespace MWRender
mTextKeyListener->handleTextKey(groupname, key, map);
}
void Animation::play(const std::string &groupname, int priority, int groups, bool autodisable, float speedmult,
void Animation::play(const std::string &groupname, const AnimPriority& priority, int blendMask, bool autodisable, float speedmult,
const std::string &start, const std::string &stop, float startpoint, size_t loops, bool loopfallback)
{
if(!mObjectRoot || mAnimSources.empty())
@ -473,8 +473,6 @@ namespace MWRender
return;
}
priority = std::max(0, priority);
AnimStateMap::iterator stateiter = mStates.begin();
while(stateiter != mStates.end())
{
@ -505,7 +503,7 @@ namespace MWRender
state.mLoopCount = loops;
state.mPlaying = (state.getTime() < state.mStopTime);
state.mPriority = priority;
state.mGroups = groups;
state.mBlendMask = blendMask;
state.mAutoDisable = autodisable;
mStates[groupname] = state;
@ -600,23 +598,20 @@ namespace MWRender
state.setTime(state.mStartTime + ((state.mStopTime - state.mStartTime) * startpoint));
// mLoopStartTime and mLoopStopTime normally get assigned when encountering these keys while playing the animation
// (see handleTextKey). But if startpoint is already past these keys, we need to assign them now.
if(state.getTime() > state.mStartTime)
{
const std::string loopstarttag = groupname+": loop start";
const std::string loopstoptag = groupname+": loop stop";
// (see handleTextKey). But if startpoint is already past these keys, or start time is == stop time, we need to assign them now.
const std::string loopstarttag = groupname+": loop start";
const std::string loopstoptag = groupname+": loop stop";
NifOsg::TextKeyMap::const_reverse_iterator key(groupend);
for (; key != startkey && key != keys.rend(); ++key)
{
if (key->first > state.getTime())
continue;
NifOsg::TextKeyMap::const_reverse_iterator key(groupend);
for (; key != startkey && key != keys.rend(); ++key)
{
if (key->first > state.getTime())
continue;
if (key->second == loopstarttag)
state.mLoopStartTime = key->first;
else if (key->second == loopstoptag)
state.mLoopStopTime = key->first;
}
if (key->second == loopstarttag)
state.mLoopStartTime = key->first;
else if (key->second == loopstoptag)
state.mLoopStopTime = key->first;
}
return true;
@ -643,35 +638,35 @@ namespace MWRender
mAccumCtrl = NULL;
for(size_t grp = 0;grp < sNumGroups;grp++)
for(size_t blendMask = 0;blendMask < sNumBlendMasks;blendMask++)
{
AnimStateMap::const_iterator active = mStates.end();
AnimStateMap::const_iterator state = mStates.begin();
for(;state != mStates.end();++state)
{
if(!(state->second.mGroups&(1<<grp)))
if(!(state->second.mBlendMask&(1<<blendMask)))
continue;
if(active == mStates.end() || active->second.mPriority < state->second.mPriority)
if(active == mStates.end() || active->second.mPriority.mPriority[blendMask] < state->second.mPriority.mPriority[blendMask])
active = state;
}
mAnimationTimePtr[grp]->setTimePtr(active == mStates.end() ? boost::shared_ptr<float>() : active->second.mTime);
mAnimationTimePtr[blendMask]->setTimePtr(active == mStates.end() ? boost::shared_ptr<float>() : active->second.mTime);
// add external controllers for the AnimSource active in this group
// add external controllers for the AnimSource active in this blend mask
if (active != mStates.end())
{
boost::shared_ptr<AnimSource> animsrc = active->second.mSource;
for (AnimSource::ControllerMap::iterator it = animsrc->mControllerMap[grp].begin(); it != animsrc->mControllerMap[grp].end(); ++it)
for (AnimSource::ControllerMap::iterator it = animsrc->mControllerMap[blendMask].begin(); it != animsrc->mControllerMap[blendMask].end(); ++it)
{
osg::ref_ptr<osg::Node> node = mNodeMap.at(it->first); // this should not throw, we already checked for the node existing in addAnimSource
node->addUpdateCallback(it->second);
mActiveControllers.insert(std::make_pair(node, it->second));
if (grp == 0 && node == mAccumRoot)
if (blendMask == 0 && node == mAccumRoot)
{
mAccumCtrl = it->second;
@ -690,20 +685,6 @@ namespace MWRender
addControllers();
}
void Animation::changeGroups(const std::string &groupname, int groups)
{
AnimStateMap::iterator stateiter = mStates.find(groupname);
if(stateiter != mStates.end())
{
if(stateiter->second.mGroups != groups)
{
stateiter->second.mGroups = groups;
resetActiveGroups();
}
return;
}
}
void Animation::stopLooping(const std::string& groupname)
{
AnimStateMap::iterator stateiter = mStates.find(groupname);
@ -1208,9 +1189,10 @@ namespace MWRender
{
for (AnimStateMap::const_iterator stateiter = mStates.begin(); stateiter != mStates.end(); ++stateiter)
{
if((stateiter->second.mPriority > MWMechanics::Priority_Movement
&& stateiter->second.mPriority < MWMechanics::Priority_Torch)
|| stateiter->second.mPriority == MWMechanics::Priority_Death)
if (stateiter->second.mPriority.contains(int(MWMechanics::Priority_Hit))
|| stateiter->second.mPriority.contains(int(MWMechanics::Priority_Weapon))
|| stateiter->second.mPriority.contains(int(MWMechanics::Priority_Knockdown))
|| stateiter->second.mPriority.contains(int(MWMechanics::Priority_Death)))
return false;
}
return true;

@ -67,16 +67,53 @@ typedef boost::shared_ptr<PartHolder> PartHolderPtr;
class Animation
{
public:
enum Group {
Group_LowerBody = 1<<0,
enum BoneGroup {
BoneGroup_LowerBody = 0,
BoneGroup_Torso,
BoneGroup_LeftArm,
BoneGroup_RightArm
};
enum BlendMask {
BlendMask_LowerBody = 1<<0,
BlendMask_Torso = 1<<1,
BlendMask_LeftArm = 1<<2,
BlendMask_RightArm = 1<<3,
BlendMask_UpperBody = BlendMask_Torso | BlendMask_LeftArm | BlendMask_RightArm,
BlendMask_All = BlendMask_LowerBody | BlendMask_UpperBody
};
/* This is the number of *discrete* blend masks. */
static const size_t sNumBlendMasks = 4;
/// Holds an animation priority value for each BoneGroup.
struct AnimPriority
{
/// Convenience constructor, initialises all priorities to the same value.
AnimPriority(int priority)
{
for (unsigned int i=0; i<sNumBlendMasks; ++i)
mPriority[i] = priority;
}
Group_Torso = 1<<1,
Group_LeftArm = 1<<2,
Group_RightArm = 1<<3,
bool operator == (const AnimPriority& other) const
{
for (unsigned int i=0; i<sNumBlendMasks; ++i)
if (other.mPriority[i] != mPriority[i])
return false;
return true;
}
Group_UpperBody = Group_Torso | Group_LeftArm | Group_RightArm,
bool contains(int priority) const
{
for (unsigned int i=0; i<sNumBlendMasks; ++i)
if (priority == mPriority[i])
return true;
return false;
}
Group_All = Group_LowerBody | Group_UpperBody
int mPriority[sNumBlendMasks];
};
class TextKeyListener
@ -89,9 +126,6 @@ public:
void setTextKeyListener(TextKeyListener* listener);
protected:
/* This is the number of *discrete* groups. */
static const size_t sNumGroups = 4;
class AnimationTime : public SceneUtil::ControllerSource
{
private:
@ -132,13 +166,13 @@ protected:
bool mPlaying;
size_t mLoopCount;
int mPriority;
int mGroups;
AnimPriority mPriority;
int mBlendMask;
bool mAutoDisable;
AnimState() : mStartTime(0.0f), mLoopStartTime(0.0f), mLoopStopTime(0.0f), mStopTime(0.0f),
mTime(new float), mSpeedMult(1.0f), mPlaying(false), mLoopCount(0),
mPriority(0), mGroups(0), mAutoDisable(true)
mPriority(0), mBlendMask(0), mAutoDisable(true)
{
}
~AnimState();
@ -176,7 +210,7 @@ protected:
typedef std::multimap<osg::ref_ptr<osg::Node>, osg::ref_ptr<osg::NodeCallback> > ControllerMap;
ControllerMap mActiveControllers;
boost::shared_ptr<AnimationTime> mAnimationTimePtr[sNumGroups];
boost::shared_ptr<AnimationTime> mAnimationTimePtr[sNumBlendMasks];
// Stored in all lowercase for a case-insensitive lookup
typedef std::map<std::string, osg::ref_ptr<osg::MatrixTransform> > NodeMap;
@ -213,7 +247,7 @@ protected:
*/
void resetActiveGroups();
size_t detectAnimGroup(osg::Node* node);
size_t detectBlendMask(osg::Node* node);
/* Updates the position of the accum root node for the given time, and
* returns the wanted movement vector from the previous time. */
@ -304,7 +338,7 @@ public:
* \param priority Priority of the animation. The animation will play on
* bone groups that don't have another animation set of a
* higher priority.
* \param groups Bone groups to play the animation on.
* \param blendMask Bone groups to play the animation on.
* \param autodisable Automatically disable the animation when it stops
* playing.
* \param speedmult Speed multiplier for the animation.
@ -319,7 +353,7 @@ public:
* \param loopFallback Allow looping an animation that has no loop keys, i.e. fall back to use
* the "start" and "stop" keys for looping?
*/
void play(const std::string &groupname, int priority, int groups, bool autodisable,
void play(const std::string &groupname, const AnimPriority& priority, int blendMask, bool autodisable,
float speedmult, const std::string &start, const std::string &stop,
float startpoint, size_t loops, bool loopfallback=false);
@ -358,7 +392,6 @@ public:
* \param groupname Animation group to disable.
*/
void disable(const std::string &groupname);
void changeGroups(const std::string &groupname, int group);
/** Retrieves the velocity (in units per second) that the animation will move. */
float getVelocity(const std::string &groupname) const;

@ -241,13 +241,13 @@ namespace MWRender
mAnimation->showCarriedLeft(showCarriedLeft);
mCurrentAnimGroup = groupname;
mAnimation->play(mCurrentAnimGroup, 1, Animation::Group_All, false, 1.0f, "start", "stop", 0.0f, 0);
mAnimation->play(mCurrentAnimGroup, 1, Animation::BlendMask_All, false, 1.0f, "start", "stop", 0.0f, 0);
MWWorld::ContainerStoreIterator torch = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft);
if(torch != inv.end() && torch->getTypeName() == typeid(ESM::Light).name() && showCarriedLeft)
{
if(!mAnimation->getInfo("torch"))
mAnimation->play("torch", 2, MWRender::Animation::Group_LeftArm, false,
mAnimation->play("torch", 2, Animation::BlendMask_LeftArm, false,
1.0f, "start", "stop", 0.0f, ~0ul, true);
}
else if(mAnimation->getInfo("torch"))
@ -357,7 +357,7 @@ namespace MWRender
void RaceSelectionPreview::onSetup ()
{
mAnimation->play("idle", 1, Animation::Group_All, false, 1.0f, "start", "stop", 0.0f, 0);
mAnimation->play("idle", 1, Animation::BlendMask_All, false, 1.0f, "start", "stop", 0.0f, 0);
mAnimation->runAnimation(0.f);
// attach camera to follow the head node

Loading…
Cancel
Save