[LYN-3344] EMotionFX: Data integrity error, missing keyframe at the end of the animation (#418)

Animation tracks in the DCC tool formats are often stored individually, each having its own duration. For EMotion FX motion data, it is required to have tracks with the same duration and e.g. a position track has to match the duration of a morph track. This will be automatically fixed by adding missing keyframes at the end of the tracks to match the animation's global duration. The value of these are the same as the last one of the given track so that they freeze at that value.

* Added function that adds missing keyframes to match the individual tracks' duration for the non-uniform motion data in emfx.
* Added data integrity checks for various stages of the motion data builder / motion exporter.
* Checking for data integrity issues with an assert in the emfx importer. This is just a safety check and we don't need that for release builds as they should be captured at asset processing time already.
This commit is contained in:
Benjamin Jillich
2021-04-30 07:21:52 +02:00
committed by GitHub
parent 2aa8e08b12
commit aa5ddbf532
5 changed files with 74 additions and 6 deletions
@@ -414,9 +414,17 @@ namespace EMotionFX
}
}
// Add missing keyframes at the end of the animation to match all keytracks' duration.
motionData->FixMissingEndKeyframes();
// Let's prepare the motion data in the type we want.
// This can later be extended with other types of motion data like least square fit curves etc.
motionData->UpdateDuration();
if (!motionData->VerifyIntegrity())
{
AZ_Error(SceneUtil::ErrorWindow, false, "Data integrity issue in '%s'.", motionGroup.GetName().c_str());
return SceneEvents::ProcessingResult::Failure;
}
// Get the sample rate we have setup or that we have used.
// Also make sure we don't sample at higher rate than we want.
@@ -435,6 +443,11 @@ namespace EMotionFX
}
AZ_TracePrintf("EMotionFX", "Motion sample rate = %f", sampleRate);
motionData->RemoveRedundantKeyframes(samplingRule ? !samplingRule->GetKeepDuration() : false); // Clear any tracks of non-animated parts.
if (!motionData->VerifyIntegrity())
{
AZ_Error(SceneUtil::ErrorWindow, false, "Data integrity issue after removing redundant keyframes for '%s'.", motionGroup.GetName().c_str());
return SceneEvents::ProcessingResult::Failure;
}
// Create the desired type of motion data, based on what is selected in the motion sampling rule.
MotionData* finalMotionData = nullptr;
@@ -473,11 +486,16 @@ namespace EMotionFX
InitAndOptimizeMotionData(finalMotionData, motionData, sampleRate, samplingRule.get(), rootJoints);
}
if (!finalMotionData->VerifyIntegrity())
{
AZ_Error(SceneUtil::ErrorWindow, false, "Data integrity issue in the final animation for '%s'.", motionGroup.GetName().c_str());
return SceneEvents::ProcessingResult::Failure;
}
// Delete the data that we created out of the Scene API as it is no longer needed as we already extracted all the data from it
// into our finalMotionData.
delete motionData;
context.m_motion.SetMotionData(finalMotionData);
return SceneEvents::ProcessingResult::Success;
}
} // namespace Pipeline
@@ -775,7 +775,7 @@ namespace EMotionFX
} // for all submotions
motion->UpdateDuration();
AZ_Assert(motion->GetMotionData()->VerifyIntegrity(), "Data integrity issue in animation '%s'.", motion->GetName());
return true;
}
@@ -1572,7 +1572,7 @@ namespace EMotionFX
} // for all submotions
motion->UpdateDuration();
AZ_Assert(motion->GetMotionData()->VerifyIntegrity(), "Data integrity issue in animation '%s'.", motion->GetName());
return true;
}
@@ -195,6 +195,7 @@ namespace EMotionFX
virtual bool IsMorphAnimated(size_t morphDataIndex) const = 0;
virtual bool IsFloatAnimated(size_t floatDataIndex) const = 0;
virtual void UpdateDuration() {}
virtual bool VerifyIntegrity() const { return true; }
void Resize(size_t numJoints, size_t numMorphs, size_t numFloats);
void Clear();
@@ -375,10 +375,48 @@ namespace EMotionFX
return true;
}
template<class KeyTrackType>
void NonUniformMotionData::FixMissingEndKeyframes(KeyTrackType& keytrack, float endTimeToMatch)
{
if (keytrack.m_times.empty() || keytrack.m_values.empty())
{
return;
}
if (!AZ::IsClose(keytrack.m_times.back(), endTimeToMatch, AZ::Constants::FloatEpsilon))
{
keytrack.m_times.emplace_back(endTimeToMatch);
keytrack.m_values.emplace_back(keytrack.m_values.back());
}
}
void NonUniformMotionData::FixMissingEndKeyframes()
{
UpdateDuration();
for (JointData& jointData : m_jointData)
{
FixMissingEndKeyframes(jointData.m_positionTrack, m_duration);
FixMissingEndKeyframes(jointData.m_rotationTrack, m_duration);
#ifndef EMFX_SCALE_DISABLED
FixMissingEndKeyframes(jointData.m_scaleTrack, m_duration);
#endif
}
for (FloatData& morphData : m_morphData)
{
FixMissingEndKeyframes(morphData.m_track, m_duration);
}
for (FloatData& floatData : m_floatData)
{
FixMissingEndKeyframes(floatData.m_track, m_duration);
}
}
void NonUniformMotionData::UpdateDuration()
{
AZ_Assert(VerifyIntegrity(), "Data integrity issue!");
for (const JointData& jointData : m_jointData)
{
if (!jointData.m_positionTrack.m_times.empty())
@@ -82,7 +82,15 @@ namespace EMotionFX
void ClearMorphSamples(size_t morphDataIndex) override;
void ClearFloatSamples(size_t floatDataIndex) override;
bool VerifyIntegrity() const;
bool VerifyIntegrity() const override;
//! Animation tracks in the DCC tool formats are often stored individually, each having its own duration.
//! For the motion data, it is required to have tracks with the same duration and e.g. a position track
//! has to match the duration of a morph track. This will be automatically fixed by adding missing
//! keyframes at the end of the tracks to match the animation's global duration. The value of these
//! are the same as the last one of the given track so that they freeze at that value.
void FixMissingEndKeyframes();
void ScaleData(float scaleFactor) override;
void UpdateDuration() override;
@@ -155,6 +163,9 @@ namespace EMotionFX
void RemoveMorphSampleData(size_t morphDataIndex) override;
void RemoveFloatSampleData(size_t floatDataIndex) override;
template<class KeyTrackType>
void FixMissingEndKeyframes(KeyTrackType& keytrack, float endTimeToMatch);
private:
AZStd::vector<JointData> m_jointData;
AZStd::vector<FloatData> m_morphData;