verus/Verus/src/Anim/Motion.h

340 lines
9.6 KiB
C++

// Copyright (C) 2021-2022, Dmitry Maluev (dmaluev@gmail.com). All rights reserved.
#pragma once
namespace verus::Anim
{
struct MotionDelegate
{
virtual void Motion_OnTrigger(CSZ name, int state) = 0;
};
VERUS_TYPEDEFS(MotionDelegate);
// Motion holds a series of keyframes and provides the ability to interpolate between them.
// Motion can be stored in XAN format.
// Motion's rate can be scaled and even reversed.
// Animation object can be used to handle multiple motion objects.
class Motion : public Object
{
public:
class Bone : public AllocatorAware
{
public:
enum class Channel : int
{
rotation,
position,
scale,
trigger
};
enum class Flags : UINT32
{
none = 0,
slerpRot = (1 << 0),
splinePos = (1 << 1),
splineScale = (1 << 2)
};
private:
friend class Motion;
static const float s_magicValueForCircle;
class Rotation
{
public:
Quat _q;
Rotation();
Rotation(RcQuat q);
Rotation(RcVector3 euler);
};
VERUS_TYPEDEFS(Rotation);
typedef Map<int, Rotation> TMapRot;
typedef Map<int, Vector3> TMapPos;
typedef Map<int, Vector3> TMapScale;
typedef Map<int, int> TMapTrigger;
String _name;
Motion* _pMotion = nullptr;
TMapRot _mapRot; // Rotation keyframes.
TMapPos _mapPos; // Position keyframes.
TMapScale _mapScale; // Scaling keyframes.
TMapTrigger _mapTrigger; // Trigger keyframes.
int _lastTriggerState = 0;
Flags _flags = Flags::none;
template<typename TMap, typename T>
float FindControlPoints(const TMap& m, int frames[4], T keys[4], float time) const
{
frames[0] = frames[1] = frames[2] = frames[3] = -1;
if (m.empty()) // No frames at all, so return null.
return 0;
time = Math::Max(0.f, time); // Negative time is not allowed.
float alpha;
const int frame = static_cast<int>(_pMotion->GetFps() * time); // Frame is before or at 'time'.
typename TMap::const_iterator it = m.upper_bound(frame); // Find frame after 'time'.
if (it != m.cend()) // There are frames greater (after 'time'):
{
if (it != m.cbegin()) // And there are less than (before 'time'), full interpolation:
{
typename TMap::const_iterator itPrev = it;
itPrev--;
frames[1] = itPrev->first;
keys[1] = itPrev->second;
if (itPrev != m.cbegin())
{
itPrev--;
frames[0] = itPrev->first;
keys[0] = itPrev->second;
}
frames[2] = it->first;
keys[2] = it->second;
it++;
if (it != m.cend())
{
frames[3] = it->first;
keys[3] = it->second;
}
alpha = (time - (frames[1] * _pMotion->GetFpsInv())) / ((frames[2] - frames[1]) * _pMotion->GetFpsInv());
}
else // But there are no less than:
{
frames[2] = it->first;
keys[2] = it->second;
it++;
if (it != m.cend())
{
frames[3] = it->first;
keys[3] = it->second;
}
alpha = time / (frames[2] * _pMotion->GetFpsInv());
}
}
else // There are no frames greater, but there are less than:
{
it--;
frames[1] = it->first;
keys[1] = it->second;
if (it != m.cbegin())
{
it--;
frames[0] = it->first;
keys[0] = it->second;
}
alpha = 0;
}
return alpha;
}
template<typename TMap>
static bool SpaceTimeSyncTemplate(TMap& m, int fromFrame, int toFrame)
{
const auto itFrom = m.find(fromFrame);
const auto itTo = m.find(toFrame);
if (m.end() == itFrom || m.end() == itTo) // Endpoints must exist.
return false;
const int totalFrames = toFrame - fromFrame;
auto it = itFrom;
Vector<float> vSegments;
vSegments.reserve(8);
float totalDist = 0;
while (it != itTo)
{
const auto& posA = it->second;
it++;
const auto& posB = it->second;
const float d = VMath::dist(Point3(posA), Point3(posB));
vSegments.push_back(d);
totalDist += d;
}
if (totalDist < 1e-4f) // Distance is too small.
return false;
const float invTotalDist = 1.f / totalDist;
TMap tempMap;
it = itFrom;
it++;
while (it != itTo) // Copy all keys in-between, delete original keys.
{
tempMap[it->first] = std::move(it->second);
it = m.erase(it);
}
it = tempMap.begin();
float distAcc = 0;
int i = 0;
while (it != tempMap.end())
{
distAcc += vSegments[i++];
int syncedFrame = fromFrame + static_cast<int>(totalFrames * (distAcc * invTotalDist) + 0.5f);
while (m.end() != m.find(syncedFrame)) // Frame already occupied?
syncedFrame++;
m[syncedFrame] = it->second;
it++;
}
return true;
}
public:
Bone(Motion* pMotion = nullptr);
~Bone();
Str GetName() const { return _C(_name); }
void Rename(CSZ name) { _name = name; }
int GetLastTriggerState() const { return _lastTriggerState; }
void SetLastTriggerState(int state) { _lastTriggerState = state; }
Flags GetFlags() const { return _flags; }
void SetFlags(Flags flags) { _flags = flags; }
void DeleteAll();
// Insert:
void InsertKeyframeRotation(int frame, RcQuat q);
void InsertKeyframeRotation(int frame, RcVector3 euler);
void InsertKeyframePosition(int frame, RcVector3 pos);
void InsertKeyframeScale(int frame, RcVector3 scale);
void InsertKeyframeTrigger(int frame, int state);
// Delete:
void DeleteKeyframeRotation(int frame);
void DeleteKeyframePosition(int frame);
void DeleteKeyframeScale(int frame);
void DeleteKeyframeTrigger(int frame);
// Find:
bool FindKeyframeRotation(int frame, RVector3 euler, RQuat q) const;
bool FindKeyframePosition(int frame, RVector3 pos) const;
bool FindKeyframeScale(int frame, RVector3 scale) const;
bool FindKeyframeTrigger(int frame, int& state) const;
// Compute:
void ComputeRotationAt(float time, RVector3 euler, RQuat q) const;
void ComputePositionAt(float time, RVector3 pos) const;
void ComputeScaleAt(float time, RVector3 scale) const;
void ComputeTriggerAt(float time, int& state) const;
void ComputeMatrixAt(float time, RTransform3 mat);
void MoveKeyframe(int direction, Channel channel, int frame);
int GetRotationKeyCount() const { return Utils::Cast32(_mapRot.size()); }
int GetPositionKeyCount() const { return Utils::Cast32(_mapPos.size()); }
int GetScaleKeyCount() const { return Utils::Cast32(_mapScale.size()); }
int GetTriggerKeyCount() const { return Utils::Cast32(_mapTrigger.size()); }
VERUS_P(void Serialize(IO::RStream stream, UINT16 version));
VERUS_P(void Deserialize(IO::RStream stream, UINT16 version));
void DeleteRedundantKeyframes(float boneAccLength);
void DeleteOddKeyframes();
void InsertLoopKeyframes();
void Cut(int frame, bool before);
void Fix(bool speedLimit);
void ApplyScaleBias(RcVector3 scale, RcVector3 bias);
void Scatter(int srcFrom, int srcTo, int dMin, int dMax);
bool SpaceTimeSync(Channel channel, int fromFrame, int toFrame);
int GetLastKeyframe() const;
};
VERUS_TYPEDEFS(Bone);
private:
static const int s_xanVersion = 0x0102;
static const int s_maxFps = 10000;
static const int s_maxBones = 10000;
static const int s_maxFrames = 32 * 1024 * 1024;
typedef Map<String, Bone> TMapBones;
TMapBones _mapBones;
Motion* _pBlendMotion = nullptr;
int _frameCount = 60;
int _fps = 12;
float _fpsInv = 1 / 12.f;
float _blendAlpha = 0;
float _playbackSpeed = 1;
float _playbackSpeedInv = 1;
bool _reversed = false;
public:
Motion();
~Motion();
void Init();
void Done();
int GetFps() const { return _fps; }
float GetFpsInv() const { return _fpsInv; }
void SetFps(int fps) { _fps = fps; _fpsInv = 1.f / _fps; }
int GetFrameCount() const { return _frameCount; }
void SetFrameCount(int count) { _frameCount = count; }
int GetBoneCount() const { return Utils::Cast32(_mapBones.size()); }
float GetDuration() const { return GetNativeDuration() * _playbackSpeedInv; }
float GetNativeDuration() const { return _frameCount * _fpsInv; }
PBone GetBoneByIndex(int index);
int GetBoneIndex(CSZ name) const;
PBone InsertBone(CSZ name);
void DeleteBone(CSZ name);
void DeleteAllBones();
PBone FindBone(CSZ name);
template<typename T>
void ForEachBone(const T& fn)
{
for (auto& [key, value] : _mapBones)
{
if (Continue::no == fn(value))
break;
}
}
void Serialize(IO::RStream stream);
void Deserialize(IO::RStream stream);
void BakeMotionAt(float time, Motion& dest) const;
void BindBlendMotion(Motion* p, float alpha);
Motion* GetBlendMotion() const { return _pBlendMotion; }
float GetBlendAlpha() const { return _blendAlpha; }
void DeleteRedundantKeyframes(Map<String, float>& mapBoneAccLengths);
void DeleteOddKeyframes();
void InsertLoopKeyframes();
void Cut(int frame, bool before = true);
void Fix(bool speedLimit);
// Triggers:
void ProcessTriggers(float time, PMotionDelegate p, int* pUserTriggerStates = nullptr);
void ResetTriggers(int* pUserTriggerStates = nullptr);
void SkipTriggers(float time, int* pUserTriggerStates = nullptr);
void ApplyScaleBias(CSZ name, RcVector3 scale, RcVector3 bias);
float GetPlaybackSpeed() const { return _playbackSpeed; }
float GetPlaybackSpeedInv() const { return _playbackSpeedInv; }
void SetPlaybackSpeed(float x);
void ComputePlaybackSpeed(float duration);
bool IsReversed() const { return _reversed; }
void Exec(CSZ code, PBone pBone = nullptr, Bone::Channel channel = Bone::Channel::rotation);
int GetLastKeyframe() const;
static bool ExtractNestBone(CSZ name, SZ nestBone);
};
VERUS_TYPEDEFS(Motion);
}