anim-clip -- sequence selection by afx effects

This commit is contained in:
Marc Chapman 2017-07-27 00:31:43 +01:00
parent 8c65467697
commit ab88b8f489
6 changed files with 511 additions and 14 deletions

View file

@ -20,6 +20,11 @@
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
// Copyright (C) 2015 Faust Logic, Inc.
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
#include "platform/platform.h"
#include "T3D/aiPlayer.h"
@ -97,6 +102,9 @@ AIPlayer::AIPlayer()
mMoveSlowdown = true;
mMoveState = ModeStop;
// This new member saves the movement state of the AI so that
// it can be restored after a substituted animation is finished.
mMoveState_saved = -1;
mAimObject = 0;
mAimLocationSet = false;
mTargetInLOS = false;
@ -547,23 +555,27 @@ bool AIPlayer::getAIMove(Move *movePtr)
mMoveState = ModeMove;
}
if (mMoveStuckTestCountdown > 0)
--mMoveStuckTestCountdown;
else
{
// We should check to see if we are stuck...
F32 locationDelta = (location - mLastLocation).len();
// Don't check for ai stuckness if animation during
// an anim-clip effect override.
if (mDamageState == Enabled && !(anim_clip_flags & ANIM_OVERRIDDEN) && !isAnimationLocked()) {
if (mMoveStuckTestCountdown > 0)
--mMoveStuckTestCountdown;
else
{
// We should check to see if we are stuck...
F32 locationDelta = (location - mLastLocation).len();
if (locationDelta < mMoveStuckTolerance && mDamageState == Enabled)
{
// If we are slowing down, then it's likely that our location delta will be less than
// our move stuck tolerance. Because we can be both slowing and stuck
// we should TRY to check if we've moved. This could use better detection.
if ( mMoveState != ModeSlowing || locationDelta == 0 )
{
mMoveState = ModeStuck;
onStuck();
}
}
{
mMoveState = ModeStuck;
onStuck();
}
}
}
}
}
}
@ -626,6 +638,7 @@ bool AIPlayer::getAIMove(Move *movePtr)
}
#endif // TORQUE_NAVIGATION_ENABLED
if (!(anim_clip_flags & ANIM_OVERRIDDEN) && !isAnimationLocked())
mLastLocation = location;
return true;
@ -1415,6 +1428,47 @@ DefineEngineMethod( AIPlayer, clearMoveTriggers, void, ( ),,
object->clearMoveTriggers();
}
// These changes coordinate with anim-clip mods to parent class, Player.
// New method, restartMove(), restores the AIPlayer to its normal move-state
// following animation overrides from AFX. The tag argument is used to match
// the latest override and prevents interruption of overlapping animation
// overrides. See related anim-clip changes in Player.[h,cc].
void AIPlayer::restartMove(U32 tag)
{
if (tag != 0 && tag == last_anim_tag)
{
if (mMoveState_saved != -1)
{
mMoveState = (MoveState) mMoveState_saved;
mMoveState_saved = -1;
}
bool is_death_anim = ((anim_clip_flags & IS_DEATH_ANIM) != 0);
last_anim_tag = 0;
anim_clip_flags &= ~(ANIM_OVERRIDDEN | IS_DEATH_ANIM);
if (mDamageState != Enabled)
{
if (!is_death_anim)
{
// this is a bit hardwired and desperate,
// but if he's dead he needs to look like it.
setActionThread("death10", false, false, false);
}
}
}
}
// New method, saveMoveState(), stores the current movement state
// so that it can be restored when restartMove() is called.
void AIPlayer::saveMoveState()
{
if (mMoveState_saved == -1)
mMoveState_saved = (S32) mMoveState;
}
F32 AIPlayer::getTargetDistance(GameBase* target, bool _checkEnabled)
{
if (!isServerObject()) return false;

View file

@ -20,6 +20,11 @@
// IN THE SOFTWARE.
//-----------------------------------------------------------------------------
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
// Arcane-FX for MIT Licensed Open Source version of Torque 3D from GarageGames
// Copyright (C) 2015 Faust Logic, Inc.
//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~//~~~~~~~~~~~~~~~~~~~~~//
#ifndef _AIPLAYER_H_
#define _AIPLAYER_H_
@ -225,6 +230,18 @@ public:
/// @}
#endif // TORQUE_NAVIGATION_ENABLED
// New method, restartMove(), restores the AIPlayer to its normal move-state
// following animation overrides from AFX. The tag argument is used to match
// the latest override and prevents interruption of overlapping animation
// overrides.
// New method, saveMoveState(), stores the current movement state
// so that it can be restored when restartMove() is called.
// See related anim-clip changes in Player.[h,cc].
private:
S32 mMoveState_saved;
public:
void restartMove(U32 tag);
void saveMoveState();
};
#endif

View file

@ -2746,7 +2746,12 @@ void Player::updateMove(const Move* move)
// Desired move direction & speed
VectorF moveVec;
F32 moveSpeed;
if ((mState == MoveState || (mState == RecoverState && mDataBlock->recoverRunForceScale > 0.0f)) && mDamageState == Enabled)
// If BLOCK_USER_CONTROL is set in anim_clip_flags, the user won't be able to
// resume control over the player character. This generally happens for
// short periods of time synchronized with script driven animation at places
// where it makes sense that user motion is prohibited, such as when the
// player is lifted off the ground or knocked down.
if ((mState == MoveState || (mState == RecoverState && mDataBlock->recoverRunForceScale > 0.0f)) && mDamageState == Enabled && !isAnimationLocked())
{
zRot.getColumn(0,&moveVec);
moveVec *= (move->x * (mPose == SprintPose ? mDataBlock->sprintStrafeScale : 1.0f));
@ -3031,7 +3036,9 @@ void Player::updateMove(const Move* move)
mContactTimer++;
// Acceleration from Jumping
if (move->trigger[sJumpTrigger] && canJump())// !isMounted() &&
// While BLOCK_USER_CONTROL is set in anim_clip_flags, the user won't be able to
// make the player character jump.
if (move->trigger[sJumpTrigger] && canJump() && !isAnimationLocked())
{
// Scale the jump impulse base on maxJumpSpeed
F32 zSpeedScale = mVelocity.z;
@ -3539,6 +3546,8 @@ void Player::updateLookAnimation(F32 dt)
bool Player::inDeathAnim()
{
if ((anim_clip_flags & ANIM_OVERRIDDEN) != 0 && (anim_clip_flags & IS_DEATH_ANIM) == 0)
return false;
if (mActionAnimation.thread && mActionAnimation.action >= 0)
if (mActionAnimation.action < mDataBlock->actionCount)
return mDataBlock->actionList[mActionAnimation.action].death;
@ -3748,6 +3757,8 @@ bool Player::setArmThread(U32 action)
bool Player::setActionThread(const char* sequence,bool hold,bool wait,bool fsp)
{
if (anim_clip_flags & ANIM_OVERRIDDEN)
return false;
for (U32 i = 1; i < mDataBlock->actionCount; i++)
{
PlayerData::ActionAnimation &anim = mDataBlock->actionList[i];
@ -3947,8 +3958,10 @@ void Player::updateActionThread()
pickActionAnimation();
}
// prevent scaling of AFX picked actions
if ( (mActionAnimation.action != PlayerData::LandAnim) &&
(mActionAnimation.action != PlayerData::NullAnimation) )
(mActionAnimation.action != PlayerData::NullAnimation) &&
!(anim_clip_flags & ANIM_OVERRIDDEN))
{
// Update action animation time scale to match ground velocity
PlayerData::ActionAnimation &anim =
@ -4566,6 +4579,10 @@ void Player::updateAnimation(F32 dt)
if (mImageStateThread)
mShapeInstance->advanceTime(dt,mImageStateThread);
// update any active blend clips
if (isGhost())
for (S32 i = 0; i < blend_clips.size(); i++)
mShapeInstance->advanceTime(dt, blend_clips[i].thread);
// If we are the client's player on this machine, then we need
// to make sure the transforms are up to date as they are used
// to setup the camera.
@ -4579,6 +4596,11 @@ void Player::updateAnimation(F32 dt)
else
{
updateAnimationTree(false);
// This addition forces recently visible players to animate their
// skeleton now rather than in pre-render so that constrained effects
// get up-to-date node transforms.
if (didRenderLastRender())
mShapeInstance->animate();
}
}
}
@ -7249,6 +7271,165 @@ void Player::afx_unpackUpdate(NetConnection* con, BitStream* stream)
mark_fx_c_triggers = mask;
}
}
// Code for overriding player's animation with sequences selected by the
// anim-clip component effect.
void Player::restoreAnimation(U32 tag)
{
// check if this is a blended clip
if ((tag & BLENDED_CLIP) != 0)
{
restoreBlendAnimation(tag);
return;
}
if (tag != 0 && tag == last_anim_tag)
{
bool is_death_anim = ((anim_clip_flags & IS_DEATH_ANIM) != 0);
anim_clip_flags &= ~(ANIM_OVERRIDDEN | IS_DEATH_ANIM);
if (isClientObject())
{
if (mDamageState != Enabled)
{
if (!is_death_anim)
{
// this is a bit hardwired and desperate,
// but if he's dead he needs to look like it.
setActionThread("death10", false, false, false);
}
}
else if (mState != MoveState)
{
// not sure what happens here
}
else
{
pickActionAnimation();
}
}
last_anim_tag = 0;
last_anim_id = -1;
}
}
U32 Player::getAnimationID(const char* name)
{
for (U32 i = 0; i < mDataBlock->actionCount; i++)
{
PlayerData::ActionAnimation &anim = mDataBlock->actionList[i];
if (dStricmp(anim.name, name) == 0)
return i;
}
Con::errorf("Player::getAnimationID() -- Player does not contain a sequence that matches the name, %s.", name);
return BAD_ANIM_ID;
}
U32 Player::playAnimationByID(U32 anim_id, F32 pos, F32 rate, F32 trans, bool hold, bool wait, bool is_death_anim)
{
if (anim_id == BAD_ANIM_ID)
return 0;
S32 seq_id = mDataBlock->actionList[anim_id].sequence;
if (seq_id == -1)
{
Con::errorf("Player::playAnimation() problem. BAD_SEQ_ID");
return 0;
}
if (mShapeInstance->getShape()->sequences[seq_id].isBlend())
return playBlendAnimation(seq_id, pos, rate);
if (isClientObject())
{
PlayerData::ActionAnimation &anim = mDataBlock->actionList[anim_id];
if (anim.sequence != -1)
{
mActionAnimation.action = anim_id;
mActionAnimation.forward = (rate >= 0);
mActionAnimation.firstPerson = false;
mActionAnimation.holdAtEnd = hold;
mActionAnimation.waitForEnd = hold? true: wait;
mActionAnimation.animateOnServer = false;
mActionAnimation.atEnd = false;
mActionAnimation.delayTicks = (S32)sNewAnimationTickTime;
F32 transTime = (trans < 0) ? sAnimationTransitionTime : trans;
mShapeInstance->setTimeScale(mActionAnimation.thread, rate);
mShapeInstance->transitionToSequence(mActionAnimation.thread,anim.sequence,
pos, transTime, true);
}
}
if (is_death_anim)
anim_clip_flags |= IS_DEATH_ANIM;
else
anim_clip_flags &= ~IS_DEATH_ANIM;
anim_clip_flags |= ANIM_OVERRIDDEN;
last_anim_tag = unique_anim_tag_counter++;
last_anim_id = anim_id;
return last_anim_tag;
}
F32 Player::getAnimationDurationByID(U32 anim_id)
{
if (anim_id == BAD_ANIM_ID)
return 0.0f;
S32 seq_id = mDataBlock->actionList[anim_id].sequence;
if (seq_id >= 0 && seq_id < mDataBlock->mShape->sequences.size())
return mDataBlock->mShape->sequences[seq_id].duration;
return 0.0f;
}
bool Player::isBlendAnimation(const char* name)
{
U32 anim_id = getAnimationID(name);
if (anim_id == BAD_ANIM_ID)
return false;
S32 seq_id = mDataBlock->actionList[anim_id].sequence;
if (seq_id >= 0 && seq_id < mDataBlock->mShape->sequences.size())
return mDataBlock->mShape->sequences[seq_id].isBlend();
return false;
}
const char* Player::getLastClipName(U32 clip_tag)
{
if (clip_tag != last_anim_tag || last_anim_id >= PlayerData::NumActionAnims)
return "";
return mDataBlock->actionList[last_anim_id].name;
}
void Player::unlockAnimation(U32 tag, bool force)
{
if ((tag != 0 && tag == last_anim_lock_tag) || force)
anim_clip_flags &= ~BLOCK_USER_CONTROL;
}
U32 Player::lockAnimation()
{
anim_clip_flags |= BLOCK_USER_CONTROL;
last_anim_lock_tag = unique_anim_tag_counter++;
return last_anim_lock_tag;
}
ConsoleMethod(Player, isAnimationLocked, bool, 2, 2, "isAnimationLocked()")
{
return object->isAnimationLocked();
}
#ifdef TORQUE_OPENVR
void Player::setControllers(Vector<OpenVRTrackedObject*> controllerList)
{

View file

@ -793,6 +793,17 @@ private:
private:
static bool sCorpsesHiddenFromRayCast;
public:
virtual void restoreAnimation(U32 tag);
virtual U32 getAnimationID(const char* name);
virtual U32 playAnimationByID(U32 anim_id, F32 pos, F32 rate, F32 trans, bool hold, bool wait, bool is_death_anim);
virtual F32 getAnimationDurationByID(U32 anim_id);
virtual bool isBlendAnimation(const char* name);
virtual const char* getLastClipName(U32 clip_tag);
virtual void unlockAnimation(U32 tag, bool force=false);
virtual U32 lockAnimation();
virtual bool isAnimationLocked() const { return ((anim_clip_flags & BLOCK_USER_CONTROL) != 0); }
};
typedef Player::Pose PlayerPose;

View file

@ -1045,6 +1045,13 @@ ShapeBase::ShapeBase()
for (i = 0; i < MaxTriggerKeys; i++)
mTrigger[i] = false;
anim_clip_flags = 0;
last_anim_id = -1;
last_anim_tag = 0;
last_anim_lock_tag = 0;
saved_seq_id = -1;
saved_pos = 0.0f;
saved_rate = 1.0f;
}
@ -1183,6 +1190,16 @@ void ShapeBase::onSceneRemove()
bool ShapeBase::onNewDataBlock( GameBaseData *dptr, bool reload )
{
// need to destroy blend-clips or we crash
if (isGhost())
{
for (S32 i = 0; i < blend_clips.size(); i++)
{
if (blend_clips[i].thread)
mShapeInstance->destroyThread(blend_clips[i].thread);
blend_clips.erase_fast(i);
}
}
ShapeBaseData *prevDB = dynamic_cast<ShapeBaseData*>( mDataBlock );
bool isInitialDataBlock = ( mDataBlock == 0 );
@ -5101,6 +5118,186 @@ DefineEngineMethod( ShapeBase, getModelFile, const char *, (),,
return datablock->getDataField( fieldName, NULL );
}
U32 ShapeBase::unique_anim_tag_counter = 1;
U32 ShapeBase::playBlendAnimation(S32 seq_id, F32 pos, F32 rate)
{
BlendThread blend_clip;
blend_clip.tag = ((unique_anim_tag_counter++) | BLENDED_CLIP);
blend_clip.thread = 0;
if (isClientObject())
{
blend_clip.thread = mShapeInstance->addThread();
mShapeInstance->setSequence(blend_clip.thread, seq_id, pos);
mShapeInstance->setTimeScale(blend_clip.thread, rate);
}
blend_clips.push_back(blend_clip);
return blend_clip.tag;
}
void ShapeBase::restoreBlendAnimation(U32 tag)
{
for (S32 i = 0; i < blend_clips.size(); i++)
{
if (blend_clips[i].tag == tag)
{
if (blend_clips[i].thread)
{
mShapeInstance->destroyThread(blend_clips[i].thread);
}
blend_clips.erase_fast(i);
break;
}
}
}
//
void ShapeBase::restoreAnimation(U32 tag)
{
if (!isClientObject())
return;
// check if this is a blended clip
if ((tag & BLENDED_CLIP) != 0)
{
restoreBlendAnimation(tag);
return;
}
if (tag != 0 && tag == last_anim_tag)
{
anim_clip_flags &= ~(ANIM_OVERRIDDEN | IS_DEATH_ANIM);
stopThread(0);
if (saved_seq_id != -1)
{
setThreadSequence(0, saved_seq_id);
setThreadPosition(0, saved_pos);
setThreadTimeScale(0, saved_rate);
setThreadDir(0, (saved_rate >= 0));
playThread(0);
saved_seq_id = -1;
saved_pos = 0.0f;
saved_rate = 1.0f;
}
last_anim_tag = 0;
last_anim_id = -1;
}
}
U32 ShapeBase::getAnimationID(const char* name)
{
const TSShape* ts_shape = getShape();
S32 seq_id = (ts_shape) ? ts_shape->findSequence(name) : -1;
return (seq_id >= 0) ? (U32) seq_id : BAD_ANIM_ID;
}
U32 ShapeBase::playAnimationByID(U32 anim_id, F32 pos, F32 rate, F32 trans, bool hold, bool wait, bool is_death_anim)
{
if (!isClientObject())
return 0;
if (anim_id == BAD_ANIM_ID)
return 0;
const TSShape* ts_shape = getShape();
if (!ts_shape)
return 0;
S32 seq_id = (S32) anim_id;
if (mShapeInstance->getShape()->sequences[seq_id].isBlend())
return playBlendAnimation(seq_id, pos, rate);
if (last_anim_tag == 0)
{
// try to save state of playing animation
Thread& st = mScriptThread[0];
if (st.sequence != -1)
{
saved_seq_id = st.sequence;
saved_pos = st.position;
saved_rate = st.timescale;
}
}
// START OR TRANSITION TO SEQUENCE HERE
setThreadSequence(0, seq_id);
setThreadPosition(0, pos);
setThreadTimeScale(0, rate);
setThreadDir(0, (rate >= 0));
playThread(0);
if (is_death_anim)
anim_clip_flags |= IS_DEATH_ANIM;
else
anim_clip_flags &= ~IS_DEATH_ANIM;
anim_clip_flags |= ANIM_OVERRIDDEN;
last_anim_tag = unique_anim_tag_counter++;
last_anim_id = anim_id;
return last_anim_tag;
}
F32 ShapeBase::getAnimationDurationByID(U32 anim_id)
{
if (anim_id == BAD_ANIM_ID)
return 0.0f;
S32 seq_id = (S32) anim_id;
if (seq_id >= 0 && seq_id < mDataBlock->mShape->sequences.size())
return mDataBlock->mShape->sequences[seq_id].duration;
return 0.0f;
}
bool ShapeBase::isBlendAnimation(const char* name)
{
U32 anim_id = getAnimationID(name);
if (anim_id == BAD_ANIM_ID)
return false;
S32 seq_id = (S32) anim_id;
if (seq_id >= 0 && seq_id < mDataBlock->mShape->sequences.size())
return mDataBlock->mShape->sequences[seq_id].isBlend();
return false;
}
const char* ShapeBase::getLastClipName(U32 clip_tag)
{
if (clip_tag != last_anim_tag)
return "";
S32 seq_id = (S32) last_anim_id;
S32 idx = mDataBlock->mShape->sequences[seq_id].nameIndex;
if (idx < 0 || idx >= mDataBlock->mShape->names.size())
return 0;
return mDataBlock->mShape->names[idx];
}
//
U32 ShapeBase::playAnimation(const char* name, F32 pos, F32 rate, F32 trans, bool hold, bool wait, bool is_death_anim)
{
return playAnimationByID(getAnimationID(name), pos, rate, trans, hold, wait, is_death_anim);
}
F32 ShapeBase::getAnimationDuration(const char* name)
{
return getAnimationDurationByID(getAnimationID(name));
}
void ShapeBase::setSelectionFlags(U8 flags)
{
Parent::setSelectionFlags(flags);

View file

@ -1861,6 +1861,43 @@ public:
protected:
DECLARE_CALLBACK( F32, validateCameraFov, (F32 fov) );
protected:
enum {
ANIM_OVERRIDDEN = BIT(0),
BLOCK_USER_CONTROL = BIT(1),
IS_DEATH_ANIM = BIT(2),
BAD_ANIM_ID = 999999999,
BLENDED_CLIP = 0x80000000,
};
struct BlendThread
{
TSThread* thread;
U32 tag;
};
Vector<BlendThread> blend_clips;
static U32 unique_anim_tag_counter;
U8 anim_clip_flags;
S32 last_anim_id;
U32 last_anim_tag;
U32 last_anim_lock_tag;
S32 saved_seq_id;
F32 saved_pos;
F32 saved_rate;
U32 playBlendAnimation(S32 seq_id, F32 pos, F32 rate);
void restoreBlendAnimation(U32 tag);
public:
U32 playAnimation(const char* name, F32 pos, F32 rate, F32 trans, bool hold, bool wait, bool is_death_anim);
F32 getAnimationDuration(const char* name);
virtual void restoreAnimation(U32 tag);
virtual U32 getAnimationID(const char* name);
virtual U32 playAnimationByID(U32 anim_id, F32 pos, F32 rate, F32 trans, bool hold, bool wait, bool is_death_anim);
virtual F32 getAnimationDurationByID(U32 anim_id);
virtual bool isBlendAnimation(const char* name);
virtual const char* getLastClipName(U32 clip_tag);
virtual void unlockAnimation(U32 tag, bool force=false) { }
virtual U32 lockAnimation() { return 0; }
virtual bool isAnimationLocked() const { return false; }
virtual void setSelectionFlags(U8 flags);
};