verus/Verus/src/Anim/Motion.cpp

1285 lines
28 KiB
C++

// Copyright (C) 2021-2022, Dmitry Maluev (dmaluev@gmail.com). All rights reserved.
#include "verus.h"
using namespace verus;
using namespace verus::Anim;
// Motion::Bone::Rotation:
Motion::Bone::Rotation::Rotation()
{
}
Motion::Bone::Rotation::Rotation(RcQuat q)
{
_q = q;
}
Motion::Bone::Rotation::Rotation(RcVector3 euler)
{
euler.EulerToQuaternion(_q);
}
// Motion::Bone:
const float Motion::Bone::s_magicValueForCircle = 0.825f * 0.7071f * 4; // Octagon vertices will form a perfect circle.
Motion::Bone::Bone(PMotion pMotion) :
_pMotion(pMotion)
{
}
Motion::Bone::~Bone()
{
DeleteAll();
}
void Motion::Bone::DeleteAll()
{
_mapRot.clear();
_mapPos.clear();
_mapScale.clear();
_mapTrigger.clear();
}
// Insert:
void Motion::Bone::InsertKeyframeRotation(int frame, RcQuat q)
{
_mapRot[frame] = Rotation(q);
}
void Motion::Bone::InsertKeyframeRotation(int frame, RcVector3 euler)
{
_mapRot[frame] = Rotation(euler);
}
void Motion::Bone::InsertKeyframePosition(int frame, RcVector3 pos)
{
_mapPos[frame] = pos;
}
void Motion::Bone::InsertKeyframeScale(int frame, RcVector3 scale)
{
_mapScale[frame] = scale;
}
void Motion::Bone::InsertKeyframeTrigger(int frame, int state)
{
_mapTrigger[frame] = state;
}
// Delete:
void Motion::Bone::DeleteKeyframeRotation(int frame)
{
VERUS_IF_FOUND_IN(TMapRot, _mapRot, frame, it)
_mapRot.erase(it);
}
void Motion::Bone::DeleteKeyframePosition(int frame)
{
VERUS_IF_FOUND_IN(TMapPos, _mapPos, frame, it)
_mapPos.erase(it);
}
void Motion::Bone::DeleteKeyframeScale(int frame)
{
VERUS_IF_FOUND_IN(TMapScale, _mapScale, frame, it)
_mapScale.erase(it);
}
void Motion::Bone::DeleteKeyframeTrigger(int frame)
{
VERUS_IF_FOUND_IN(TMapTrigger, _mapTrigger, frame, it)
_mapTrigger.erase(it);
}
// Find:
bool Motion::Bone::FindKeyframeRotation(int frame, RVector3 euler, RQuat q) const
{
euler = Vector3(0);
q = Quat::identity();
VERUS_IF_FOUND_IN(TMapRot, _mapRot, frame, it)
{
euler.EulerFromQuaternion(it->second._q);
q = it->second._q;
return true;
}
return false;
}
bool Motion::Bone::FindKeyframePosition(int frame, RVector3 pos) const
{
pos = Vector3(0);
VERUS_IF_FOUND_IN(TMapPos, _mapPos, frame, it)
{
pos = it->second;
return true;
}
return false;
}
bool Motion::Bone::FindKeyframeScale(int frame, RVector3 scale) const
{
scale = Vector3(1, 1, 1);
VERUS_IF_FOUND_IN(TMapScale, _mapScale, frame, it)
{
scale = it->second;
return true;
}
return false;
}
bool Motion::Bone::FindKeyframeTrigger(int frame, int& state) const
{
state = 0;
VERUS_IF_FOUND_IN(TMapTrigger, _mapTrigger, frame, it)
{
state = it->second;
return true;
}
return false;
}
void Motion::Bone::ComputeRotationAt(float time, RVector3 euler, RQuat q) const
{
int frames[4];
Rotation keys[4];
const float alpha = FindControlPoints(_mapRot, frames, keys, time);
if (_flags & Flags::slerpRot)
{
const Rotation null(Quat(0));
RcRotation prev = (frames[1] == -1) ? null : keys[1];
RcRotation next = (frames[2] == -1) ? null : keys[2];
q = VMath::slerp(alpha, prev._q, next._q);
}
else
{
const Rotation null(Quat(0));
RcRotation prev = (frames[1] == -1) ? null : keys[1];
RcRotation next = (frames[2] == -1) ? null : keys[2];
q = Math::NLerp(alpha, prev._q, next._q);
}
PMotion pBlendMotion = _pMotion->GetBlendMotion();
if (pBlendMotion)
{
PBone pBone = pBlendMotion->FindBone(_C(_name));
if (pBone)
{
Vector3 eulerBlend;
Quat qBlend;
if (pBone->FindKeyframeRotation(0, eulerBlend, qBlend))
{
if (_flags & Flags::slerpRot)
q = VMath::slerp(_pMotion->GetBlendAlpha(), qBlend, q);
else
q = Math::NLerp(_pMotion->GetBlendAlpha(), qBlend, q);
}
}
}
euler.EulerFromQuaternion(q);
}
void Motion::Bone::ComputePositionAt(float time, RVector3 pos) const
{
int frames[4];
Vector3 keys[4];
const float alpha = FindControlPoints(_mapPos, frames, keys, time);
if (_flags & Flags::splinePos)
{
const Vector3 null(0);
if (frames[1] == -1) { frames[1] = 0; keys[1] = null; }
if (frames[2] == -1) { frames[2] = 0; keys[2] = null; }
// Extrapolate:
if (frames[0] == -1) { frames[0] = frames[1] * 2 - frames[2]; keys[0] = keys[1] * 2 - keys[2]; }
if (frames[3] == -1) { frames[3] = frames[2] * 2 - frames[1]; keys[3] = keys[2] * 2 - keys[1]; }
// Ratio controls the direction and length of tangents. One keyframe can have different tangent lengths to match speed.
const int intervals[3] =
{
frames[1] - frames[0],
frames[2] - frames[1],
frames[3] - frames[2]
};
const float ratioA = static_cast<float>(intervals[0]) / (intervals[0] + intervals[1]);
const float ratioB = static_cast<float>(intervals[1]) / (intervals[1] + intervals[2]);
const Vector3 tanA = VMath::lerp(ratioA, keys[1] - keys[0], keys[2] - keys[1]) * s_magicValueForCircle * (1 - ratioA);
const Vector3 tanB = VMath::lerp(ratioB, keys[2] - keys[1], keys[3] - keys[2]) * s_magicValueForCircle * ratioB;
pos = glm::hermite(keys[1].GLM(), tanA.GLM(), keys[2].GLM(), tanB.GLM(), alpha);
}
else
{
const Vector3 null(0);
RcVector3 prev = (frames[1] == -1) ? null : keys[1];
RcVector3 next = (frames[2] == -1) ? null : keys[2];
pos = VMath::lerp(alpha, prev, next);
}
PMotion pBlendMotion = _pMotion->GetBlendMotion();
if (pBlendMotion)
{
PBone pBone = pBlendMotion->FindBone(_C(_name));
if (pBone)
{
Vector3 posBlend;
if (pBone->FindKeyframePosition(0, posBlend))
pos = VMath::lerp(_pMotion->GetBlendAlpha(), posBlend, pos);
}
}
}
void Motion::Bone::ComputeScaleAt(float time, RVector3 scale) const
{
int frames[4];
Vector3 keys[4];
const float alpha = FindControlPoints(_mapScale, frames, keys, time);
if (_flags & Flags::splineScale)
{
const Vector3 null(1, 1, 1);
if (frames[1] == -1) { frames[1] = 0; keys[1] = null; }
if (frames[2] == -1) { frames[2] = 0; keys[2] = null; }
// Extrapolate:
if (frames[0] == -1) { frames[0] = frames[1] * 2 - frames[2]; keys[0] = keys[1] * 2 - keys[2]; }
if (frames[3] == -1) { frames[3] = frames[2] * 2 - frames[1]; keys[3] = keys[2] * 2 - keys[1]; }
// Ratio controls the direction and length of tangents. One keyframe can have different tangent lengths to match speed.
const int intervals[3] =
{
frames[1] - frames[0],
frames[2] - frames[1],
frames[3] - frames[2]
};
const float ratioA = static_cast<float>(intervals[0]) / (intervals[0] + intervals[1]);
const float ratioB = static_cast<float>(intervals[1]) / (intervals[1] + intervals[2]);
const Vector3 tanA = VMath::lerp(ratioA, keys[1] - keys[0], keys[2] - keys[1]) * s_magicValueForCircle * (1 - ratioA);
const Vector3 tanB = VMath::lerp(ratioB, keys[2] - keys[1], keys[3] - keys[2]) * s_magicValueForCircle * ratioB;
scale = glm::hermite(keys[1].GLM(), tanA.GLM(), keys[2].GLM(), tanB.GLM(), alpha);
}
else
{
const Vector3 null(1, 1, 1);
RcVector3 prev = (frames[1] == -1) ? null : keys[1];
RcVector3 next = (frames[2] == -1) ? null : keys[2];
scale = VMath::lerp(alpha, prev, next);
}
PMotion pBlendMotion = _pMotion->GetBlendMotion();
if (pBlendMotion)
{
PBone pBone = pBlendMotion->FindBone(_C(_name));
if (pBone)
{
Vector3 scaleBlend;
if (pBone->FindKeyframeScale(0, scaleBlend))
scale = VMath::lerp(_pMotion->GetBlendAlpha(), scaleBlend, scale);
}
}
}
void Motion::Bone::ComputeTriggerAt(float time, int& state) const
{
if (_mapTrigger.empty())
{
state = 0;
return;
}
const int frame = static_cast<int>(_pMotion->GetFps() * time);
TMapTrigger::const_iterator it = _mapTrigger.upper_bound(frame); // Find frame after 'time'.
if (it != _mapTrigger.begin())
{
it--;
state = it->second;
}
else // All keyframes are after 'time':
{
state = 0;
}
}
void Motion::Bone::ComputeMatrixAt(float time, RTransform3 mat)
{
Quat q;
Vector3 euler, pos, scale;
ComputeRotationAt(time, euler, q);
ComputePositionAt(time, pos);
ComputeScaleAt(time, scale);
mat = VMath::appendScale(Transform3(q, pos), scale);
}
void Motion::Bone::MoveKeyframe(int direction, Channel channel, int frame)
{
const int frameDest = (direction >= 0) ? frame + 1 : frame - 1;
if (frameDest < 0 || frameDest >= _pMotion->GetFrameCount())
return;
switch (channel)
{
case Channel::rotation:
{
VERUS_IF_FOUND_IN(TMapRot, _mapRot, frame, it)
{
if (_mapRot.find(frameDest) == _mapRot.end())
{
_mapRot[frameDest] = it->second;
_mapRot.erase(it);
}
}
}
break;
case Channel::position:
{
VERUS_IF_FOUND_IN(TMapPos, _mapPos, frame, it)
{
if (_mapPos.find(frameDest) == _mapPos.end())
{
_mapPos[frameDest] = it->second;
_mapPos.erase(it);
}
}
}
break;
case Channel::scale:
{
VERUS_IF_FOUND_IN(TMapScale, _mapScale, frame, it)
{
if (_mapScale.find(frameDest) == _mapScale.end())
{
_mapScale[frameDest] = it->second;
_mapScale.erase(it);
}
}
}
break;
case Channel::trigger:
{
VERUS_IF_FOUND_IN(TMapTrigger, _mapTrigger, frame, it)
{
if (_mapTrigger.find(frameDest) == _mapTrigger.end())
{
_mapTrigger[frameDest] = it->second;
_mapTrigger.erase(it);
}
}
}
break;
}
}
void Motion::Bone::Serialize(IO::RStream stream, UINT16 version)
{
stream << _flags;
const int rotKeyframeCount = Utils::Cast32(_mapRot.size());
const int posKeyframeCount = Utils::Cast32(_mapPos.size());
const int scaleKeyframeCount = Utils::Cast32(_mapScale.size());
const int triggerKeyframeCount = Utils::Cast32(_mapTrigger.size());
stream << rotKeyframeCount;
VERUS_FOREACH_CONST(TMapRot, _mapRot, it)
{
stream << it->first;
stream.Write(&it->second._q, 16);
}
stream << posKeyframeCount;
VERUS_FOREACH_CONST(TMapPos, _mapPos, it)
{
stream << it->first;
stream.Write(&it->second, 12);
}
stream << scaleKeyframeCount;
VERUS_FOREACH_CONST(TMapScale, _mapScale, it)
{
stream << it->first;
stream.Write(&it->second, 12);
}
stream << triggerKeyframeCount;
VERUS_FOREACH_CONST(TMapTrigger, _mapTrigger, it)
{
stream << it->first;
stream << it->second;
}
}
void Motion::Bone::Deserialize(IO::RStream stream, UINT16 version)
{
_flags = Flags::none;
if (version >= 0x0102)
stream >> _flags;
int rotKeyframeCount, posKeyframeCount, scaleKeyframeCount, triggerKeyframeCount, frame, state;
Vector3 temp;
Quat q;
stream >> rotKeyframeCount;
VERUS_FOR(i, rotKeyframeCount)
{
stream >> frame;
stream.Read(&q, 16);
InsertKeyframeRotation(frame, q);
}
stream >> posKeyframeCount;
VERUS_FOR(i, posKeyframeCount)
{
stream >> frame;
stream.Read(&temp, 12);
InsertKeyframePosition(frame, temp);
}
stream >> scaleKeyframeCount;
VERUS_FOR(i, scaleKeyframeCount)
{
stream >> frame;
stream.Read(&temp, 12);
InsertKeyframeScale(frame, temp);
}
stream >> triggerKeyframeCount;
VERUS_FOR(i, triggerKeyframeCount)
{
stream >> frame;
stream >> state;
InsertKeyframeTrigger(frame, state);
}
}
void Motion::Bone::DeleteRedundantKeyframes(float boneAccLength)
{
// Long bones should have smaller error threshold because their influence is bigger.
const float thresholdScale = 1.f / boneAccLength;
// Rotation:
{
const float threshold = 0.0000025f * thresholdScale;
TMapRot mapReference; // Ideal case.
VERUS_FOR(i, _pMotion->GetFrameCount())
{
Quat q;
Vector3 euler;
ComputeRotationAt(i * _pMotion->GetFpsInv(), euler, q);
mapReference[i] = q;
}
TMapRot mapSaved = _mapRot;
do
{
// Which keyframe can be removed with the least error?
float leastError = threshold;
int frameWithLeastError = -1;
for (const auto& [key, value] : mapSaved)
{
const int excludeFrame = key;
if (!excludeFrame || _pMotion->GetFrameCount() == excludeFrame)
continue;
_mapRot.clear();
for (const auto& [key2, value2] : mapSaved)
{
if (key2 != excludeFrame)
_mapRot[key2] = value2;
}
float accError = 0;
VERUS_FOR(i, _pMotion->GetFrameCount())
{
Quat q;
Vector3 euler;
ComputeRotationAt(i * _pMotion->GetFpsInv(), euler, q);
const float dot = VMath::dot(mapReference[i]._q, q);
accError += Math::Clamp(1 - dot, 0.f, 1.f);
if (accError >= leastError)
break;
}
if (leastError > accError)
{
leastError = accError;
frameWithLeastError = excludeFrame;
}
}
if (leastError < threshold && frameWithLeastError >= 0)
{
mapSaved.erase(frameWithLeastError);
_mapRot = mapSaved;
}
else
{
_mapRot = mapSaved;
break;
}
} while (true);
}
// Position:
{
const float threshold = 0.00025f * thresholdScale;
TMapPos mapReference; // Ideal case.
VERUS_FOR(i, _pMotion->GetFrameCount())
{
Vector3 pos;
ComputePositionAt(i * _pMotion->GetFpsInv(), pos);
mapReference[i] = pos;
}
TMapPos mapSaved = _mapPos;
do
{
// Which keyframe can be removed with the least error?
float leastError = threshold;
int frameWithLeastError = -1;
for (const auto& [key, value] : mapSaved)
{
const int excludeFrame = key;
if (!excludeFrame || _pMotion->GetFrameCount() == excludeFrame)
continue;
_mapPos.clear();
for (const auto& [key2, value2] : mapSaved)
{
if (key2 != excludeFrame)
_mapPos[key2] = value2;
}
float accError = 0;
VERUS_FOR(i, _pMotion->GetFrameCount())
{
Vector3 pos;
ComputePositionAt(i * _pMotion->GetFpsInv(), pos);
const Vector3 diff = mapReference[i] - pos;
const float len = VMath::length(diff);
accError += len + len * len;
if (accError >= leastError)
break;
}
if (leastError > accError)
{
leastError = accError;
frameWithLeastError = excludeFrame;
}
}
if (leastError < threshold && frameWithLeastError >= 0)
{
mapSaved.erase(frameWithLeastError);
_mapPos = mapSaved;
}
else
{
_mapPos = mapSaved;
break;
}
} while (true);
}
// Scale:
{
const float threshold = 0.00025f * thresholdScale;
TMapScale mapReference; // Ideal case.
VERUS_FOR(i, _pMotion->GetFrameCount())
{
Vector3 scale;
ComputeScaleAt(i * _pMotion->GetFpsInv(), scale);
mapReference[i] = scale;
}
TMapScale mapSaved = _mapScale;
do
{
// Which keyframe can be removed with the least error?
float leastError = threshold;
int frameWithLeastError = -1;
for (const auto& [key, value] : mapSaved)
{
const int excludeFrame = key;
if (!excludeFrame || _pMotion->GetFrameCount() == excludeFrame)
continue;
_mapScale.clear();
for (const auto& [key2, value2] : mapSaved)
{
if (key2 != excludeFrame)
_mapScale[key2] = value2;
}
float accError = 0;
VERUS_FOR(i, _pMotion->GetFrameCount())
{
Vector3 scale;
ComputeScaleAt(i * _pMotion->GetFpsInv(), scale);
const Vector3 diff = mapReference[i] - scale;
const float len = VMath::length(diff);
accError += len + len * len;
if (accError >= leastError)
break;
}
if (leastError > accError)
{
leastError = accError;
frameWithLeastError = excludeFrame;
}
}
if (leastError < threshold && frameWithLeastError >= 0)
{
mapSaved.erase(frameWithLeastError);
_mapScale = mapSaved;
}
else
{
_mapScale = mapSaved;
break;
}
} while (true);
}
if (2 == _mapRot.size())
{
if (!memcmp(
&_mapRot.begin()->second,
&_mapRot.rbegin()->second,
16))
_mapRot.erase(--_mapRot.end());
}
if (1 == _mapRot.size())
{
RQuat q = _mapRot.begin()->second._q;
if (q.IsIdentity())
_mapRot.clear();
}
if (2 == _mapPos.size())
{
if (!memcmp(
&_mapPos.begin()->second,
&_mapPos.rbegin()->second,
12))
_mapPos.erase(--_mapPos.end());
}
if (2 == _mapScale.size())
{
if (!memcmp(
&_mapScale.begin()->second,
&_mapScale.rbegin()->second,
12))
_mapScale.erase(--_mapScale.end());
}
}
void Motion::Bone::DeleteOddKeyframes()
{
{
TMapRot::iterator it = _mapRot.begin();
while (it != _mapRot.end())
{
if (it->first & 0x1)
_mapRot.erase(it++);
else
++it;
}
}
{
TMapPos::iterator it = _mapPos.begin();
while (it != _mapPos.end())
{
if (it->first & 0x1)
_mapPos.erase(it++);
else
++it;
}
}
{
TMapScale::iterator it = _mapScale.begin();
while (it != _mapScale.end())
{
if (it->first & 0x1)
_mapScale.erase(it++);
else
++it;
}
}
}
void Motion::Bone::InsertLoopKeyframes()
{
if (!_mapRot.empty())
_mapRot[_pMotion->GetFrameCount()] = _mapRot.begin()->second;
if (!_mapPos.empty())
_mapPos[_pMotion->GetFrameCount()] = _mapPos.begin()->second;
if (!_mapScale.empty())
_mapScale[_pMotion->GetFrameCount()] = _mapScale.begin()->second;
}
void Motion::Bone::Cut(int frame, bool before)
{
if (before && !frame)
return;
{
TMapRot::iterator it = _mapRot.begin();
while (it != _mapRot.end())
{
if (before)
{
if (it->first >= frame)
_mapRot[it->first - frame] = it->second;
_mapRot.erase(it++);
}
else
{
if (it->first > frame)
_mapRot.erase(it++);
else
++it;
}
}
}
{
TMapPos::iterator it = _mapPos.begin();
while (it != _mapPos.end())
{
if (before)
{
if (it->first >= frame)
_mapPos[it->first - frame] = it->second;
_mapPos.erase(it++);
}
else
{
if (it->first > frame)
_mapPos.erase(it++);
else
++it;
}
}
}
{
TMapScale::iterator it = _mapScale.begin();
while (it != _mapScale.end())
{
if (before)
{
if (it->first >= frame)
_mapScale[it->first - frame] = it->second;
_mapScale.erase(it++);
}
else
{
if (it->first > frame)
_mapScale.erase(it++);
else
++it;
}
}
}
}
void Motion::Bone::Fix(bool speedLimit)
{
const float e = 0.02f;
// Remove rotation keyframes:
if (true)
{
Quat qBase(0);
TMapRot::iterator it = _mapRot.begin();
bool hasBroken = false;
while (it != _mapRot.end())
{
RcQuat q = it->second._q;
const bool broken =
Math::IsNaN(q.getX()) ||
Math::IsNaN(q.getY()) ||
Math::IsNaN(q.getZ()) ||
Math::IsNaN(q.getW());
if (broken)
hasBroken = true;
const bool same = qBase.IsEqual(q, e);
if (same || broken)
_mapRot.erase(it++);
else
{
qBase = q;
++it;
}
}
}
// Remove position keyframes:
if (true)
{
Vector3 base(0);
TMapPos::iterator it = _mapPos.begin();
while (it != _mapPos.end())
{
RcVector3 pos = it->second;
const bool broken =
Math::IsNaN(pos.getX()) ||
Math::IsNaN(pos.getY()) ||
Math::IsNaN(pos.getZ());
const bool same = base.IsEqual(pos, e);
if (same || broken)
_mapPos.erase(it++);
else
{
base = pos;
++it;
}
}
}
// Limit rotation speed:
if (speedLimit && Skeleton::IsKinectBone(_C(_name)))
{
VERUS_FOREACH(TMapRot, _mapRot, it)
{
RQuat q = it->second._q;
if (it->first >= 1)
{
Quat qPrev;
Vector3 euler;
ComputeRotationAt((it->first - 1) * _pMotion->GetFpsInv(), euler, qPrev);
const float threshold = 0.5f;
const Quat qPrevInv = VMath::inverse(Matrix3(qPrev));
const Quat qD = q * qPrevInv;
const float a = abs(glm::angle(qD.GLM()));
if (a > threshold)
{
const float back = Math::Clamp((a - threshold) / threshold, 0.f, 0.75f);
q = VMath::slerp(back, q, qPrev);
}
}
}
}
}
void Motion::Bone::ApplyScaleBias(RcVector3 scale, RcVector3 bias)
{
VERUS_FOREACH(TMapPos, _mapPos, it)
it->second = VMath::mulPerElem(it->second, scale) + bias;
}
void Motion::Bone::Scatter(int srcFrom, int srcTo, int dMin, int dMax)
{
VERUS_QREF_UTILS;
int start = srcFrom;
const int range = dMax - dMin;
while (true)
{
start += dMin + utils.GetRandom().Next() % range;
if (start >= _pMotion->GetFrameCount())
break;
for (int i = srcFrom; i < srcTo; ++i)
{
Quat q;
Vector3 euler, pos;
if (FindKeyframeRotation(i, euler, q))
InsertKeyframeRotation(i + start, q);
if (FindKeyframePosition(i, pos))
InsertKeyframePosition(i + start, pos);
}
}
}
bool Motion::Bone::SpaceTimeSync(Channel channel, int fromFrame, int toFrame)
{
if (fromFrame == toFrame) // Endpoints must not match.
return false;
switch (channel)
{
case Channel::position: return SpaceTimeSyncTemplate(_mapPos, fromFrame, toFrame);
case Channel::scale: return SpaceTimeSyncTemplate(_mapScale, fromFrame, toFrame);
}
return false;
}
int Motion::Bone::GetLastKeyframe() const
{
int frame = -1;
if (!_mapRot.empty())
frame = Math::Max(frame, _mapRot.rbegin()->first);
if (!_mapPos.empty())
frame = Math::Max(frame, _mapPos.rbegin()->first);
if (!_mapScale.empty())
frame = Math::Max(frame, _mapScale.rbegin()->first);
if (!_mapTrigger.empty())
frame = Math::Max(frame, _mapTrigger.rbegin()->first);
return frame;
}
// Motion:
Motion::Motion()
{
}
Motion::~Motion()
{
Done();
}
void Motion::Init()
{
VERUS_INIT();
}
void Motion::Done()
{
DeleteAllBones();
VERUS_DONE(Motion);
}
Motion::PBone Motion::GetBoneByIndex(int index)
{
int i = 0;
for (auto& [key, value] : _mapBones)
{
if (i == index)
return &value;
i++;
}
return nullptr;
}
int Motion::GetBoneIndex(CSZ name) const
{
int i = 0;
VERUS_FOREACH_CONST(TMapBones, _mapBones, it)
{
if (it->first == name)
return i;
i++;
}
return -1;
}
Motion::PBone Motion::InsertBone(CSZ name)
{
PBone pBone = FindBone(name);
if (pBone)
return pBone;
Bone bone(this);
bone.Rename(name);
_mapBones[name] = bone;
return &_mapBones[name];
}
void Motion::DeleteBone(CSZ name)
{
VERUS_IF_FOUND_IN(TMapBones, _mapBones, name, it)
_mapBones.erase(it);
}
void Motion::DeleteAllBones()
{
_mapBones.clear();
}
Motion::PBone Motion::FindBone(CSZ name)
{
VERUS_IF_FOUND_IN(TMapBones, _mapBones, name, it)
return &it->second;
return nullptr;
}
void Motion::Serialize(IO::RStream stream)
{
const UINT32 magic = '2NAX';
stream << magic;
const UINT16 version = s_xanVersion;
stream << version;
stream << _frameCount;
stream << _fps;
stream << GetBoneCount();
for (auto& [key, value] : _mapBones)
{
RBone bone = value;
stream.WriteString(_C(bone.GetName()));
bone.Serialize(stream, version);
}
}
void Motion::Deserialize(IO::RStream stream)
{
UINT32 magic = 0;
stream >> magic;
if ('2NAX' != magic)
throw VERUS_RECOVERABLE << "Deserialize(); Invalid XAN format";
UINT16 version = 0;
stream >> version;
if (s_xanVersion < version)
throw VERUS_RECOVERABLE << "Deserialize(); Invalid XAN version";
stream >> _frameCount;
stream >> _fps;
if (_frameCount < 0 || _frameCount > s_maxFrames)
throw VERUS_RECOVERABLE << "Deserialize(); Invalid number of frames in XAN";
if (_fps <= 0 || _fps > s_maxFps)
throw VERUS_RECOVERABLE << "Deserialize(); Invalid FPS in XAN";
SetFps(_fps);
int boneCount = 0;
stream >> boneCount;
if (boneCount < 0 || boneCount > s_maxBones)
throw VERUS_RECOVERABLE << "Deserialize(); Invalid number of bones in XAN";
char buffer[IO::Stream::s_bufferSize] = {};
VERUS_FOR(i, boneCount)
{
stream.ReadString(buffer);
PBone pBone = InsertBone(buffer);
pBone->Deserialize(stream, version);
}
}
void Motion::BakeMotionAt(float time, Motion& dest) const
{
float nativeTime = time * _playbackSpeed;
if (_reversed)
nativeTime = GetNativeDuration() - nativeTime;
VERUS_FOREACH_CONST(TMapBones, _mapBones, it)
{
PcBone pBone = &it->second;
PBone pBoneDest = dest.FindBone(_C(it->first));
if (pBoneDest)
{
Vector3 rot, pos, scale;
Quat q;
pBone->ComputeRotationAt(nativeTime, rot, q);
pBone->ComputePositionAt(nativeTime, pos);
pBone->ComputeScaleAt(nativeTime, scale);
pBoneDest->InsertKeyframeRotation(0, q);
pBoneDest->InsertKeyframePosition(0, pos);
pBoneDest->InsertKeyframeScale(0, scale);
}
}
}
void Motion::BindBlendMotion(PMotion p, float alpha)
{
_pBlendMotion = p;
_blendAlpha = alpha;
}
void Motion::DeleteRedundantKeyframes(Map<String, float>& mapBoneAccLengths)
{
VERUS_P_FOR(i, static_cast<int>(_mapBones.size()))
{
auto it = _mapBones.begin();
std::advance(it, i);
auto itBoneAccLength = mapBoneAccLengths.find(_C(it->second.GetName()));
const float leafBoneLength = 0.02f;
it->second.DeleteRedundantKeyframes(mapBoneAccLengths.end() != itBoneAccLength ? itBoneAccLength->second : leafBoneLength);
});
}
void Motion::DeleteOddKeyframes()
{
for (auto& [key, value] : _mapBones)
value.DeleteOddKeyframes();
}
void Motion::InsertLoopKeyframes()
{
for (auto& [key, value] : _mapBones)
value.InsertLoopKeyframes();
}
void Motion::Cut(int frame, bool before)
{
for (auto& [key, value] : _mapBones)
value.Cut(frame, before);
_frameCount = before ? _frameCount - frame : frame + 1;
}
void Motion::Fix(bool speedLimit)
{
for (auto& [key, value] : _mapBones)
value.Fix(speedLimit);
}
void Motion::ProcessTriggers(float time, PMotionDelegate p, int* pUserTriggerStates)
{
VERUS_RT_ASSERT(p);
float nativeTime = time * _playbackSpeed;
if (_reversed)
nativeTime = GetNativeDuration() - nativeTime;
int i = 0;
for (auto& [key, value] : _mapBones)
{
PBone pBone = &value;
int state = 0;
if (!(_reversed && nativeTime < _fpsInv * 0.5f)) // Avoid edge case for the first frame in reverse.
pBone->ComputeTriggerAt(nativeTime, state);
const int last = pUserTriggerStates ? pUserTriggerStates[i] : pBone->GetLastTriggerState();
if (state != last)
{
p->Motion_OnTrigger(_C(pBone->GetName()), state);
if (pUserTriggerStates)
pUserTriggerStates[i] = state;
else
pBone->SetLastTriggerState(state);
}
i++;
}
}
void Motion::ResetTriggers(int* pUserTriggerStates)
{
int i = 0;
for (auto& [key, value] : _mapBones)
{
PBone pBone = &value;
int state = 0;
if (_reversed)
pBone->ComputeTriggerAt(GetNativeDuration(), state);
if (pUserTriggerStates)
pUserTriggerStates[i] = state;
else
pBone->SetLastTriggerState(state);
i++;
}
}
void Motion::SkipTriggers(float time, int* pUserTriggerStates)
{
float nativeTime = time * _playbackSpeed;
if (_reversed)
nativeTime = GetNativeDuration() - nativeTime;
int i = 0;
for (auto& [key, value] : _mapBones)
{
PBone pBone = &value;
int state;
pBone->ComputeTriggerAt(nativeTime, state);
if (pUserTriggerStates)
pUserTriggerStates[i] = state;
else
pBone->SetLastTriggerState(state);
i++;
}
}
void Motion::ApplyScaleBias(CSZ name, RcVector3 scale, RcVector3 bias)
{
PBone pBone = FindBone(name);
if (pBone)
pBone->ApplyScaleBias(scale, bias);
}
void Motion::SetPlaybackSpeed(float x)
{
VERUS_RT_ASSERT(x != 0);
_playbackSpeed = x;
_reversed = x < 0;
if (_reversed)
_playbackSpeed = -_playbackSpeed;
_playbackSpeedInv = 1 / _playbackSpeed;
}
void Motion::ComputePlaybackSpeed(float duration)
{
VERUS_RT_ASSERT(duration != 0);
_playbackSpeed = GetNativeDuration() / duration;
_reversed = duration < 0;
if (_reversed)
_playbackSpeed = -_playbackSpeed;
_playbackSpeedInv = 1 / _playbackSpeed;
}
void Motion::Exec(CSZ code, PBone pBone, Bone::Channel channel)
{
if (Str::StartsWith(code, "copy "))
{
char boneSrc[80] = {};
char boneDst[80] = {};
sscanf(code, "%*s %s %s", boneSrc, boneDst);
PBone pSrc = FindBone(boneSrc);
PBone pDst = FindBone(boneDst);
pDst->_mapRot = pSrc->_mapRot;
pDst->_mapPos = pSrc->_mapPos;
}
if (Str::StartsWith(code, "scatter "))
{
int srcFrom = 0, srcTo = 0;
int dMin = 0, dMax = 0;
char bone[80] = {};
sscanf(code, "%*s %d %d %d %d %s", &srcFrom, &srcTo, &dMin, &dMax, bone);
const int srcSize = srcTo - srcFrom;
if (dMin < srcSize)
{
const int d = srcSize - dMin;
dMin += d;
dMax += d;
}
PBone p = FindBone(bone + 5);
if (p)
p->Scatter(srcFrom, srcTo, dMin, dMax);
}
if (Str::StartsWith(code, "delete "))
{
char what[80] = {};
char name[80] = {};
sscanf(code, "%*s %s %s", what, name);
if (strlen(name) > 5)
{
PBone p = FindBone(name + 5);
if (p)
{
if (!strcmp(what, "rot"))
p->_mapRot.clear();
if (!strcmp(what, "pos"))
p->_mapPos.clear();
if (!strcmp(what, "scale"))
p->_mapScale.clear();
}
}
else
{
for (auto& [key, value] : _mapBones)
{
if (!strcmp(what, "rot"))
value._mapRot.clear();
if (!strcmp(what, "pos"))
value._mapPos.clear();
if (!strcmp(what, "scale"))
value._mapScale.clear();
}
}
}
if (pBone && Str::StartsWith(code, "sts "))
{
int from = 0, to = 0;
sscanf(code, "%*s %d %d", &from, &to);
pBone->SpaceTimeSync(channel, from, to);
}
}
int Motion::GetLastKeyframe() const
{
int frame = -1;
VERUS_FOREACH_CONST(TMapBones, _mapBones, it)
frame = Math::Max(frame, it->second.GetLastKeyframe());
return frame;
}
bool Motion::ExtractNestBone(CSZ name, SZ nestBone)
{
CSZ pBegin = strstr(name, "[");
CSZ pEnd = strstr(name, "]:");
if (pBegin && pEnd && pBegin < pEnd)
{
const size_t count = pEnd - pBegin - 1;
strncpy(nestBone, pBegin + 1, count);
nestBone[count] = 0;
return true;
}
return false;
}