310 lines
7.0 KiB
C++
310 lines
7.0 KiB
C++
#include "verus.h"
|
|
|
|
using namespace verus;
|
|
using namespace verus::Anim;
|
|
|
|
// Collection:
|
|
|
|
Collection::Collection()
|
|
{
|
|
}
|
|
|
|
Collection::~Collection()
|
|
{
|
|
DeleteAll();
|
|
}
|
|
|
|
void Collection::Async_Run(CSZ url, RcBlob blob)
|
|
{
|
|
IO::StreamPtr sp(blob);
|
|
CSZ niceName = strrchr(url, '/');
|
|
niceName = niceName ? niceName + 1 : url;
|
|
RMotionData md = _map[niceName];
|
|
md._motion.Init();
|
|
md._motion.Deserialize(sp);
|
|
if (md._duration)
|
|
md._motion.ComputePlaybackSpeed(md._duration);
|
|
}
|
|
|
|
void Collection::AddMotion(CSZ name, bool loop, float duration)
|
|
{
|
|
CSZ niceName = strrchr(name, '/');
|
|
niceName = niceName ? niceName + 1 : name;
|
|
RMotionData md = _map[niceName];
|
|
md._duration = duration;
|
|
md._loop = loop;
|
|
IO::Async::I().Load(name, this);
|
|
}
|
|
|
|
void Collection::DeleteAll()
|
|
{
|
|
IO::Async::Cancel(this);
|
|
_map.clear();
|
|
}
|
|
|
|
PMotionData Collection::Find(CSZ name)
|
|
{
|
|
return TStoreMotions::Find(name);
|
|
}
|
|
|
|
int Collection::GetMaxBones()
|
|
{
|
|
int num = 0;
|
|
VERUS_FOREACH(TStoreMotions::TMap, _map, it)
|
|
num = Math::Max(num, it->second._motion.GetNumBones());
|
|
return num;
|
|
}
|
|
|
|
// Animation:
|
|
|
|
Animation::Animation()
|
|
{
|
|
}
|
|
|
|
Animation::~Animation()
|
|
{
|
|
}
|
|
|
|
void Animation::Update(int numAlphaTracks, PAlphaTrack pAlphaTracks)
|
|
{
|
|
VERUS_QREF_TIMER;
|
|
|
|
// Async BindCollection():
|
|
if (_vTriggerStates.size() != _pCollection->GetMaxBones())
|
|
_vTriggerStates.resize(_pCollection->GetMaxBones());
|
|
|
|
// Async BindSkeleton():
|
|
if (!_blendMotion.GetNumBones() && _pSkeleton->GetNumBones())
|
|
_pSkeleton->InsertBonesIntoMotion(_blendMotion);
|
|
|
|
if (_playing)
|
|
{
|
|
if (_blending)
|
|
{
|
|
_blendTimer += dt;
|
|
if (_blendTimer >= _blendDuration)
|
|
_blending = false;
|
|
}
|
|
else if (!_currentMotion.empty())
|
|
{
|
|
RMotionData md = *_pCollection->Find(_C(_currentMotion));
|
|
const float len = md._motion.GetDuration();
|
|
if (_time < len + 0.5f)
|
|
{
|
|
_time += dt;
|
|
md._motion.ProcessTriggers(_time, this, GetTriggerStatesArray());
|
|
if (_time >= len)
|
|
{
|
|
if (_pDelegate)
|
|
_pDelegate->Animation_OnEnd(_C(_currentMotion));
|
|
_time = md._loop ? fmod(_time, len) : len + 1;
|
|
md._motion.ResetTriggers(GetTriggerStatesArray()); // New loop, reset triggers.
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!_currentMotion.empty() && numAlphaTracks >= 0) // Alpha track (-1) should not modify the skeleton.
|
|
{
|
|
RMotionData md = *_pCollection->Find(_C(_currentMotion));
|
|
|
|
int numAlphaMotions = 0;
|
|
Skeleton::AlphaMotion alphaMotions[4];
|
|
|
|
for (int i = 0; i < numAlphaTracks && i < 4; ++i)
|
|
{
|
|
alphaMotions[i]._pMotion = pAlphaTracks[i]._pAnimation->GetMotion(); // Can be in blend state.
|
|
alphaMotions[i]._rootBone = pAlphaTracks[i]._rootBone;
|
|
alphaMotions[i]._alpha = pAlphaTracks[i]._pAnimation->GetAlpha();
|
|
alphaMotions[i]._time = pAlphaTracks[i]._pAnimation->GetTime();
|
|
numAlphaMotions++;
|
|
}
|
|
|
|
if (_blending)
|
|
{
|
|
md._motion.BindBlendMotion(&_blendMotion, _blendTimer / _blendDuration);
|
|
_pSkeleton->ApplyMotion(md._motion, _time, numAlphaMotions, alphaMotions);
|
|
}
|
|
else
|
|
{
|
|
#ifdef VERUS_COMPARE_MODE
|
|
_pSkeleton->ApplyMotion(md._motion, 0, numAlphaMotions, alphaMotions);
|
|
#else
|
|
_pSkeleton->ApplyMotion(md._motion, _time, numAlphaMotions, alphaMotions);
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
|
|
void Animation::BindCollection(PCollection p)
|
|
{
|
|
_pCollection = p;
|
|
}
|
|
|
|
void Animation::BindSkeleton(PSkeleton p)
|
|
{
|
|
_pSkeleton = p;
|
|
}
|
|
|
|
void Animation::SetCurrentMotion(CSZ name)
|
|
{
|
|
// Reset triggers:
|
|
if (!_currentMotion.empty())
|
|
_pCollection->Find(_C(_currentMotion))->_motion.ResetTriggers(GetTriggerStatesArray());
|
|
if (name)
|
|
{
|
|
_pCollection->Find(name)->_motion.ResetTriggers(GetTriggerStatesArray());
|
|
_currentMotion = name;
|
|
}
|
|
else
|
|
_currentMotion.clear();
|
|
}
|
|
|
|
void Animation::Play()
|
|
{
|
|
_playing = true;
|
|
}
|
|
|
|
void Animation::Stop()
|
|
{
|
|
_playing = false;
|
|
_time = 0;
|
|
}
|
|
|
|
void Animation::Pause()
|
|
{
|
|
_playing = false;
|
|
}
|
|
|
|
void Animation::BlendTo(CSZ name, Range<float> duration, int randTime, PMotion pMotionFrom)
|
|
{
|
|
VERUS_QREF_UTILS;
|
|
|
|
PMotion pMotion = pMotionFrom;
|
|
if (!_currentMotion.empty() || pMotionFrom)
|
|
{
|
|
if (!pMotionFrom)
|
|
pMotion = &_pCollection->Find(_C(_currentMotion))->_motion;
|
|
if (_blending) // Already blending?
|
|
pMotion->BindBlendMotion(&_blendMotion, _blendTimer / _blendDuration);
|
|
pMotion->BakeMotionAt(_time, _blendMotion); // Capture current pose.
|
|
pMotion->BindBlendMotion(nullptr, 0);
|
|
}
|
|
|
|
if ((0 == duration._max) || (_currentMotion.empty() && name && _prevMotion == name && _blendTimer / _blendDuration < 0.5f))
|
|
{
|
|
_blending = false; // Special case for alpha tracks.
|
|
}
|
|
else
|
|
{
|
|
_blending = true;
|
|
_blendDuration = (_currentMotion == (name ? name : "")) ? duration._min : duration._max;
|
|
_blendTimer = 0;
|
|
}
|
|
|
|
_prevMotion = _currentMotion;
|
|
_currentMotion = name ? name : "";
|
|
|
|
// Reset triggers:
|
|
if (pMotion)
|
|
pMotion->ResetTriggers(GetTriggerStatesArray());
|
|
if (name)
|
|
{
|
|
pMotion = &_pCollection->Find(name)->_motion;
|
|
pMotion->ResetTriggers(GetTriggerStatesArray());
|
|
}
|
|
|
|
// Reset time:
|
|
_time = (randTime >= 0 && pMotion) ? (randTime / 255.f)*pMotion->GetDuration() : 0;
|
|
if (_time)
|
|
pMotion->SkipTriggers(_time, GetTriggerStatesArray());
|
|
}
|
|
|
|
bool Animation::BlendToNew(std::initializer_list<CSZ> names, Range<float> duration, int randTime, PMotion pMotionFrom)
|
|
{
|
|
for (auto name : names)
|
|
{
|
|
if (_currentMotion == name)
|
|
return false;
|
|
}
|
|
BlendTo(*names.begin(), duration, randTime, pMotionFrom);
|
|
return true;
|
|
}
|
|
|
|
void Animation::Motion_OnTrigger(CSZ name, int state)
|
|
{
|
|
if (_pDelegate)
|
|
_pDelegate->Animation_OnTrigger(name, state);
|
|
}
|
|
|
|
PMotion Animation::GetMotion()
|
|
{
|
|
PMotion p = nullptr;
|
|
if (_currentMotion.empty())
|
|
{
|
|
if (!_prevMotion.empty())
|
|
p = &_pCollection->Find(_C(_prevMotion))->_motion;
|
|
}
|
|
else
|
|
{
|
|
p = &_pCollection->Find(_C(_currentMotion))->_motion;
|
|
}
|
|
if (p && _blending)
|
|
p->BindBlendMotion(&_blendMotion, _blendTimer / _blendDuration);
|
|
return p;
|
|
}
|
|
|
|
float Animation::GetAlpha(CSZ name) const
|
|
{
|
|
bool matchCurrentMotion = false;
|
|
bool matchPrevMotion = false;
|
|
if (name)
|
|
{
|
|
matchCurrentMotion = Str::StartsWith(_C(_currentMotion), name);
|
|
matchPrevMotion = Str::StartsWith(_C(_prevMotion), name);
|
|
}
|
|
const bool doBlend = name ? (matchCurrentMotion || matchPrevMotion) : true;
|
|
if (_blending && doBlend)
|
|
{
|
|
bool fadeIn = _prevMotion.empty() && !_currentMotion.empty();
|
|
bool fadeOut = !_prevMotion.empty() && _currentMotion.empty();
|
|
if (name)
|
|
{
|
|
fadeIn = !matchPrevMotion && matchCurrentMotion;
|
|
fadeOut = matchPrevMotion && !matchCurrentMotion;
|
|
}
|
|
if (fadeIn)
|
|
return Math::SmoothStep(0, 1, _blendTimer / _blendDuration);
|
|
if (fadeOut)
|
|
return Math::SmoothStep(0, 1, 1 - _blendTimer / _blendDuration);
|
|
}
|
|
if (name)
|
|
return matchCurrentMotion ? 1.f : 0.f;
|
|
else
|
|
return _currentMotion.empty() ? 0.f : 1.f;
|
|
}
|
|
|
|
float Animation::GetTime()
|
|
{
|
|
if (_currentMotion.empty())
|
|
{
|
|
if (_prevMotion.empty())
|
|
return _time;
|
|
else // When alpha goes to zero, use last motion frame:
|
|
return _pCollection->Find(_C(_prevMotion))->_motion.GetDuration();
|
|
}
|
|
else
|
|
{
|
|
return _time;
|
|
}
|
|
}
|
|
|
|
bool Animation::IsNearEdge(float t, Edge edge)
|
|
{
|
|
PMotion pMotion = GetMotion();
|
|
if (!pMotion)
|
|
return false;
|
|
const float at = _time / pMotion->GetDuration();
|
|
return ((edge&Edge::begin) && at < t) || ((edge&Edge::end) && at >= 1 - t);
|
|
}
|