59f3813b10
* [LYN-3481] Added new actor instance request and notification buses * [LYN-3481] Actor instance notifies bus about it being created or destroyed * [LYN-3481] Selection lists are now automatically removing destroyed actor instances * [LYN-3481] Morph targets window plugin reinitializing when used actor instance got destroyed * [LYN-3481] Removing the OnDeleteActorInstance() from the emfx event handler/manager and porting the recorder to the new actor instance bus * [LYN-3481] Removed the create actor instance calls from the event handler/manager * [LYN-3481] Fixing automated tests
1613 lines
58 KiB
C++
1613 lines
58 KiB
C++
/*
|
|
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
|
|
* its licensors.
|
|
*
|
|
* For complete copyright and license terms please see the LICENSE at the root of this
|
|
* distribution (the "License"). All use of this software is governed by the License,
|
|
* or, if provided, by the license below or the license accompanying this file. Do not
|
|
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
*
|
|
*/
|
|
|
|
#include <AzCore/Serialization/SerializeContext.h>
|
|
#include "Recorder.h"
|
|
#include "RecorderBus.h"
|
|
#include "ActorInstance.h"
|
|
#include "TransformData.h"
|
|
#include "AnimGraphManager.h"
|
|
#include "AnimGraphInstance.h"
|
|
#include "AnimGraph.h"
|
|
#include "AnimGraphMotionNode.h"
|
|
#include "AnimGraphStateMachine.h"
|
|
#include "EventManager.h"
|
|
#include "EMotionFXManager.h"
|
|
#include "ActorManager.h"
|
|
#include "MorphSetupInstance.h"
|
|
#include "MotionEvent.h"
|
|
#include <EMotionFX/Source/Allocators.h>
|
|
#include <MCore/Source/AzCoreConversions.h>
|
|
#include <MCore/Source/Compare.h>
|
|
#include <MCore/Source/DiskFile.h>
|
|
#include <MCore/Source/Compare.h>
|
|
#include <MCore/Source/LogManager.h>
|
|
#include <MCore/Source/ReflectionSerializer.h>
|
|
|
|
|
|
namespace EMotionFX
|
|
{
|
|
AZ_CLASS_ALLOCATOR_IMPL(Recorder, RecorderAllocator, 0)
|
|
AZ_CLASS_ALLOCATOR_IMPL(Recorder::ActorInstanceData, RecorderAllocator, 0)
|
|
AZ_CLASS_ALLOCATOR_IMPL(Recorder::RecordSettings, RecorderAllocator, 0)
|
|
|
|
|
|
void Recorder::TransformTracks::Reflect(AZ::ReflectContext* context)
|
|
{
|
|
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
|
|
if (!serializeContext)
|
|
{
|
|
return;
|
|
}
|
|
|
|
serializeContext->Class<Recorder::TransformTracks>()
|
|
->Version(1)
|
|
->Field("positions", &Recorder::TransformTracks::mPositions)
|
|
->Field("rotations", &Recorder::TransformTracks::mRotations)
|
|
#ifndef EMFX_SCALE_DISABLED
|
|
->Field("scales", &Recorder::TransformTracks::mScales)
|
|
#endif
|
|
;
|
|
}
|
|
|
|
void Recorder::ActorInstanceData::Reflect(AZ::ReflectContext* context)
|
|
{
|
|
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
|
|
if (!serializeContext)
|
|
{
|
|
return;
|
|
}
|
|
|
|
serializeContext->Class<Recorder::ActorInstanceData>()
|
|
->Version(1)
|
|
->Field("transformTracks", &ActorInstanceData::m_transformTracks)
|
|
;
|
|
}
|
|
|
|
void Recorder::RecordSettings::Reflect(AZ::ReflectContext* context)
|
|
{
|
|
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
|
|
if (!serializeContext)
|
|
{
|
|
return;
|
|
}
|
|
|
|
serializeContext->Class<Recorder::RecordSettings>()
|
|
->Version(1)
|
|
->Field("fps", &Recorder::RecordSettings::mFPS)
|
|
->Field("recordTransforms", &Recorder::RecordSettings::mRecordTransforms)
|
|
->Field("recordNodeHistory", &Recorder::RecordSettings::mRecordNodeHistory)
|
|
->Field("historyStatesOnly", &Recorder::RecordSettings::mHistoryStatesOnly)
|
|
->Field("recordAnimGraphStates", &Recorder::RecordSettings::mRecordAnimGraphStates)
|
|
->Field("recordEvents", &Recorder::RecordSettings::mRecordEvents)
|
|
->Field("recordScale", &Recorder::RecordSettings::mRecordScale)
|
|
->Field("recordMorphs", &Recorder::RecordSettings::mRecordMorphs)
|
|
->Field("interpolate", &Recorder::RecordSettings::mInterpolate)
|
|
;
|
|
}
|
|
|
|
Recorder::Recorder()
|
|
: BaseObject()
|
|
{
|
|
mIsInPlayMode = false;
|
|
mIsRecording = false;
|
|
mAutoPlay = false;
|
|
mRecordTime = 0.0f;
|
|
mLastRecordTime = 0.0f;
|
|
mCurrentPlayTime = 0.0f;
|
|
|
|
mObjects.SetMemoryCategory(EMFX_MEMCATEGORY_RECORDER);
|
|
EMotionFX::ActorInstanceNotificationBus::Handler::BusConnect();
|
|
}
|
|
|
|
Recorder::~Recorder()
|
|
{
|
|
EMotionFX::ActorInstanceNotificationBus::Handler::BusDisconnect();
|
|
Clear();
|
|
}
|
|
|
|
// create a new recorder
|
|
Recorder* Recorder::Create()
|
|
{
|
|
return aznew Recorder();
|
|
}
|
|
|
|
void Recorder::Reflect(AZ::ReflectContext* context)
|
|
{
|
|
Recorder::ActorInstanceData::Reflect(context);
|
|
Recorder::TransformTracks::Reflect(context);
|
|
Recorder::RecordSettings::Reflect(context);
|
|
|
|
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
|
|
if (!serializeContext)
|
|
{
|
|
return;
|
|
}
|
|
|
|
serializeContext->Class<Recorder>()
|
|
->Version(1)
|
|
->Field("actorInstanceDatas", &Recorder::m_actorInstanceDatas)
|
|
->Field("timeDeltas", &Recorder::m_timeDeltas)
|
|
->Field("settings", &Recorder::mRecordSettings)
|
|
;
|
|
}
|
|
|
|
// enable or disable auto play mode
|
|
void Recorder::SetAutoPlay(bool enabled)
|
|
{
|
|
mAutoPlay = enabled;
|
|
}
|
|
|
|
|
|
// set the current play time
|
|
void Recorder::SetCurrentPlayTime(float timeInSeconds)
|
|
{
|
|
mCurrentPlayTime = timeInSeconds;
|
|
|
|
if (mCurrentPlayTime < 0.0f)
|
|
{
|
|
mCurrentPlayTime = 0.0f;
|
|
}
|
|
|
|
if (mCurrentPlayTime > mRecordTime)
|
|
{
|
|
mCurrentPlayTime = mRecordTime;
|
|
}
|
|
}
|
|
|
|
|
|
// start playback mode
|
|
void Recorder::StartPlayBack()
|
|
{
|
|
StopRecording();
|
|
mIsInPlayMode = true;
|
|
}
|
|
|
|
|
|
// stop playback mode
|
|
void Recorder::StopPlayBack()
|
|
{
|
|
mIsInPlayMode = false;
|
|
}
|
|
|
|
|
|
// rewind the playback
|
|
void Recorder::Rewind()
|
|
{
|
|
mCurrentPlayTime = 0.0f;
|
|
}
|
|
|
|
bool Recorder::HasRecording() const
|
|
{
|
|
return (GetRecordTime() > AZ::Constants::FloatEpsilon && !m_actorInstanceDatas.empty());
|
|
}
|
|
|
|
// clear any currently recorded data
|
|
void Recorder::Clear()
|
|
{
|
|
Lock();
|
|
|
|
mIsInPlayMode = false;
|
|
mIsRecording = false;
|
|
mAutoPlay = false;
|
|
mRecordTime = 0.0f;
|
|
mLastRecordTime = 0.0f;
|
|
mCurrentPlayTime = 0.0f;
|
|
mRecordSettings.m_actorInstances.clear();
|
|
m_timeDeltas.clear();
|
|
|
|
// delete all actor instance datas
|
|
for (const ActorInstanceData* actorInstanceData : m_actorInstanceDatas)
|
|
{
|
|
delete actorInstanceData;
|
|
}
|
|
m_actorInstanceDatas.clear();
|
|
|
|
Unlock();
|
|
}
|
|
|
|
|
|
// start recording
|
|
void Recorder::StartRecording(const RecordSettings& settings)
|
|
{
|
|
// clear any previous recorded data
|
|
Clear();
|
|
StopRecording();
|
|
|
|
Lock();
|
|
|
|
// Generate a new random UUID for this recording session.
|
|
m_sessionUuid = AZ::Uuid::Create();
|
|
|
|
// we are recording again
|
|
mRecordSettings = settings;
|
|
mIsRecording = true;
|
|
|
|
// Add all actor instances if we did not specify them explicitly.
|
|
if (mRecordSettings.m_actorInstances.empty())
|
|
{
|
|
const uint32 numActorInstances = GetActorManager().GetNumActorInstances();
|
|
mRecordSettings.m_actorInstances.resize(numActorInstances);
|
|
for (uint32 i = 0; i < numActorInstances; ++i)
|
|
{
|
|
ActorInstance* actorInstance = GetActorManager().GetActorInstance(i);
|
|
mRecordSettings.m_actorInstances[i] = actorInstance;
|
|
}
|
|
}
|
|
|
|
// prepare for recording
|
|
// this resizes arrays and allocates buffers upfront
|
|
PrepareForRecording();
|
|
|
|
// record the initial frame
|
|
RecordCurrentFrame(0.0f);
|
|
|
|
Unlock();
|
|
}
|
|
|
|
|
|
// update (when playback)
|
|
void Recorder::UpdatePlayMode(float timeDelta)
|
|
{
|
|
// increase the playtime if we are in automatic play mode and playback is enabled
|
|
if (mIsInPlayMode && mAutoPlay)
|
|
{
|
|
SetCurrentPlayTime(mCurrentPlayTime + timeDelta);
|
|
}
|
|
}
|
|
|
|
|
|
// update the recorder
|
|
void Recorder::Update(float timeDelta)
|
|
{
|
|
Lock();
|
|
|
|
// if we are not recording there is nothing to do
|
|
if (mIsRecording == false)
|
|
{
|
|
Unlock();
|
|
return;
|
|
}
|
|
|
|
// increase the time we record
|
|
mRecordTime += timeDelta;
|
|
|
|
// save a sample when more time passed than the desired sample rate
|
|
const float sampleRate = 1.0f / (float)mRecordSettings.mFPS;
|
|
if (mRecordTime - mLastRecordTime >= sampleRate)
|
|
{
|
|
RecordCurrentFrame(timeDelta);
|
|
}
|
|
|
|
Unlock();
|
|
}
|
|
|
|
|
|
// stop recording
|
|
void Recorder::StopRecording(bool lock)
|
|
{
|
|
if (lock)
|
|
{
|
|
Lock();
|
|
}
|
|
|
|
mIsRecording = false;
|
|
FinalizeAllNodeHistoryItems();
|
|
|
|
if (lock)
|
|
{
|
|
Unlock();
|
|
}
|
|
}
|
|
|
|
|
|
// prepare for recording by resizing and preallocating space/arrays
|
|
void Recorder::PrepareForRecording()
|
|
{
|
|
const size_t numActorInstances = mRecordSettings.m_actorInstances.size();
|
|
m_actorInstanceDatas.resize(numActorInstances);
|
|
for (size_t i = 0; i < numActorInstances; ++i)
|
|
{
|
|
m_actorInstanceDatas[i] = aznew ActorInstanceData();
|
|
ActorInstanceData& actorInstanceData = *m_actorInstanceDatas[i];
|
|
|
|
// link it to the right actor instance
|
|
ActorInstance* actorInstance = mRecordSettings.m_actorInstances[i];
|
|
actorInstanceData.mActorInstance = actorInstance;
|
|
|
|
// add the transform tracks
|
|
if (mRecordSettings.mRecordTransforms)
|
|
{
|
|
// for all nodes in the actor instance
|
|
const uint32 numNodes = actorInstance->GetNumNodes();
|
|
actorInstanceData.m_transformTracks.resize(numNodes);
|
|
for (uint32 n = 0; n < numNodes; ++n)
|
|
{
|
|
actorInstanceData.m_transformTracks[n].mPositions.Reserve(mRecordSettings.mNumPreAllocTransformKeys);
|
|
actorInstanceData.m_transformTracks[n].mRotations.Reserve(mRecordSettings.mNumPreAllocTransformKeys);
|
|
|
|
EMFX_SCALECODE
|
|
(
|
|
if (mRecordSettings.mRecordScale)
|
|
{
|
|
actorInstanceData.m_transformTracks[n].mScales.Reserve(mRecordSettings.mNumPreAllocTransformKeys);
|
|
}
|
|
)
|
|
}
|
|
} // if recording transforms
|
|
|
|
// if recording morph targets, resize the morphs array
|
|
if (mRecordSettings.mRecordMorphs)
|
|
{
|
|
const uint32 numMorphs = actorInstance->GetMorphSetupInstance()->GetNumMorphTargets();
|
|
actorInstanceData.mMorphTracks.Resize(numMorphs);
|
|
for (uint32 m = 0; m < numMorphs; ++m)
|
|
{
|
|
actorInstanceData.mMorphTracks[m].Reserve(256);
|
|
}
|
|
}
|
|
|
|
// add the animgraph data
|
|
if (mRecordSettings.mRecordAnimGraphStates)
|
|
{
|
|
if (actorInstance->GetAnimGraphInstance())
|
|
{
|
|
actorInstanceData.mAnimGraphData = new AnimGraphInstanceData();
|
|
actorInstanceData.mAnimGraphData->mAnimGraphInstance = actorInstance->GetAnimGraphInstance();
|
|
if (mRecordSettings.mInitialAnimGraphAnimBytes > 0)
|
|
{
|
|
actorInstanceData.mAnimGraphData->mDataBuffer = (uint8*)MCore::Allocate(mRecordSettings.mInitialAnimGraphAnimBytes, EMFX_MEMCATEGORY_RECORDER);
|
|
}
|
|
|
|
actorInstanceData.mAnimGraphData->mDataBufferSize = mRecordSettings.mInitialAnimGraphAnimBytes;
|
|
}
|
|
} // if recording animgraphs
|
|
} // for all actor instances
|
|
}
|
|
|
|
|
|
void Recorder::OptimizeRecording()
|
|
{
|
|
ShrinkTransformTracks();
|
|
}
|
|
|
|
|
|
void Recorder::ShrinkTransformTracks()
|
|
{
|
|
for (ActorInstanceData* actorInstanceData : m_actorInstanceDatas)
|
|
{
|
|
const ActorInstance* actorInstance = actorInstanceData->mActorInstance;
|
|
if (actorInstanceData->m_transformTracks.empty())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const uint32 numNodes = actorInstance->GetNumNodes();
|
|
for (uint32 n = 0; n < numNodes; ++n)
|
|
{
|
|
actorInstanceData->m_transformTracks[n].mPositions.Shrink();
|
|
actorInstanceData->m_transformTracks[n].mRotations.Shrink();
|
|
|
|
EMFX_SCALECODE
|
|
(
|
|
actorInstanceData->m_transformTracks[n].mScales.Shrink();
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// save to a file
|
|
bool Recorder::SaveToFile(const char* outFile)
|
|
{
|
|
// The template types used by the recorder result in an extremely
|
|
// verbose serialized object stream. Use the binary format to attempt
|
|
// to optimize the file size.
|
|
return AZ::Utils::SaveObjectToFile(outFile, AZ::ObjectStream::ST_BINARY, this);
|
|
}
|
|
|
|
|
|
Recorder* Recorder::LoadFromFile(const char* filename)
|
|
{
|
|
return AZ::Utils::LoadObjectFromFile<Recorder>(filename);
|
|
}
|
|
|
|
|
|
// record the current frame
|
|
void Recorder::RecordCurrentFrame(float timeDelta)
|
|
{
|
|
m_timeDeltas.emplace_back(timeDelta);
|
|
|
|
// record the current transforms
|
|
if (mRecordSettings.mRecordTransforms)
|
|
{
|
|
RecordCurrentTransforms();
|
|
}
|
|
|
|
// record the current anim graph states
|
|
if (mRecordSettings.mRecordAnimGraphStates)
|
|
{
|
|
if (!RecordCurrentAnimGraphStates())
|
|
{
|
|
StopRecording();
|
|
Clear();
|
|
return;
|
|
}
|
|
}
|
|
|
|
// always record the main transforms
|
|
RecordMainLocalTransforms();
|
|
|
|
// record morphs
|
|
if (mRecordSettings.mRecordMorphs)
|
|
{
|
|
RecordMorphs();
|
|
}
|
|
|
|
// update (while recording) the node history items
|
|
if (mRecordSettings.mRecordNodeHistory)
|
|
{
|
|
UpdateNodeHistoryItems();
|
|
}
|
|
|
|
// recordo the events
|
|
if (mRecordSettings.mRecordEvents)
|
|
{
|
|
RecordEvents();
|
|
}
|
|
|
|
// update the last record time
|
|
mLastRecordTime = mRecordTime;
|
|
}
|
|
|
|
|
|
// record the morph weights
|
|
void Recorder::RecordMorphs()
|
|
{
|
|
// for all actor instances
|
|
const uint32 numActorInstances = static_cast<uint32>(m_actorInstanceDatas.size());
|
|
for (uint32 i = 0; i < numActorInstances; ++i)
|
|
{
|
|
ActorInstanceData& actorInstanceData = *m_actorInstanceDatas[i];
|
|
ActorInstance* actorInstance = actorInstanceData.mActorInstance;
|
|
|
|
const uint32 numMorphs = actorInstance->GetMorphSetupInstance()->GetNumMorphTargets();
|
|
for (uint32 m = 0; m < numMorphs; ++m)
|
|
{
|
|
KeyTrackLinearDynamic<float, float>& morphTrack = actorInstanceData.mMorphTracks[i]; // morph animation data
|
|
morphTrack.AddKey(mRecordTime, actorInstance->GetMorphSetupInstance()->GetMorphTarget(i)->GetWeight());
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// record all actor instance main transformations, so not of the nodes, but of the actor instance itself
|
|
void Recorder::RecordMainLocalTransforms()
|
|
{
|
|
// for all actor instances
|
|
for (ActorInstanceData* actorInstanceData : m_actorInstanceDatas)
|
|
{
|
|
ActorInstance* actorInstance = actorInstanceData->mActorInstance;
|
|
const Transform& transform = actorInstance->GetLocalSpaceTransform();
|
|
|
|
#ifndef EMFX_SCALE_DISABLED
|
|
AddTransformKey(actorInstanceData->mActorLocalTransform, transform.mPosition, transform.mRotation, transform.mScale);
|
|
#else
|
|
AddTransformKey(actorInstanceData->mActorLocalTransform, transform.mPosition, transform.mRotation, AZ::Vector3(1.0f, 1.0f, 1.0f));
|
|
#endif
|
|
}
|
|
}
|
|
|
|
|
|
// record current transforms
|
|
void Recorder::RecordCurrentTransforms()
|
|
{
|
|
for (ActorInstanceData* actorInstanceData : m_actorInstanceDatas)
|
|
{
|
|
ActorInstance* actorInstance = actorInstanceData->mActorInstance;
|
|
|
|
const TransformData* transformData = actorInstance->GetTransformData();
|
|
{
|
|
const uint32 numNodes = actorInstance->GetNumNodes();
|
|
for (uint32 n = 0; n < numNodes; ++n)
|
|
{
|
|
const Transform& localTransform = transformData->GetCurrentPose()->GetLocalSpaceTransform(n);
|
|
|
|
#ifndef EMFX_SCALE_DISABLED
|
|
AddTransformKey(actorInstanceData->m_transformTracks[n], localTransform.mPosition, localTransform.mRotation, localTransform.mScale);
|
|
#else
|
|
AddTransformKey(actorInstanceData->m_transformTracks[n], localTransform.mPosition, localTransform.mRotation, AZ::Vector3(1.0f, 1.0f, 1.0f));
|
|
#endif
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// record current animgraph states
|
|
bool Recorder::RecordCurrentAnimGraphStates()
|
|
{
|
|
// for all actor instances
|
|
for (const ActorInstanceData* actorInstanceData : m_actorInstanceDatas)
|
|
{
|
|
if (actorInstanceData->mAnimGraphData == nullptr)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
{
|
|
// get some shortcuts
|
|
AnimGraphInstanceData& animGraphInstanceData = *actorInstanceData->mAnimGraphData;
|
|
AnimGraphInstance* animGraphInstance = animGraphInstanceData.mAnimGraphInstance;
|
|
const AnimGraph* animGraph = animGraphInstance->GetAnimGraph();
|
|
|
|
// add a new frame
|
|
MCore::Array<AnimGraphAnimFrame>& frames = animGraphInstanceData.mFrames;
|
|
if (frames.GetLength() > 0)
|
|
{
|
|
const uint32 byteOffset = frames.GetLast().mByteOffset + frames.GetLast().mNumBytes;
|
|
frames.AddEmpty();
|
|
frames.GetLast().mByteOffset = byteOffset;
|
|
frames.GetLast().mNumBytes = 0;
|
|
}
|
|
else
|
|
{
|
|
frames.AddEmpty();
|
|
frames.GetLast().mByteOffset = 0;
|
|
frames.GetLast().mNumBytes = 0;
|
|
}
|
|
|
|
// get the current frame
|
|
AnimGraphAnimFrame& currentFrame = frames.GetLast();
|
|
currentFrame.mTimeValue = mRecordTime;
|
|
|
|
// save the parameter values
|
|
const uint32 numParams = static_cast<uint32>(animGraphInstance->GetAnimGraph()->GetNumValueParameters());
|
|
currentFrame.mParameterValues.SetMemoryCategory(EMFX_MEMCATEGORY_RECORDER);
|
|
currentFrame.mParameterValues.Resize(numParams);
|
|
for (uint32 p = 0; p < numParams; ++p)
|
|
{
|
|
currentFrame.mParameterValues[p] = animGraphInstance->GetParameterValue(p)->Clone();
|
|
}
|
|
|
|
// recursively save all unique datas
|
|
if (!SaveUniqueData(animGraphInstance, animGraph->GetRootStateMachine(), animGraphInstanceData))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// increase the frames counter
|
|
animGraphInstanceData.mNumFrames++;
|
|
|
|
//MCore::LogInfo("Frame %d = %d bytes (offset=%d), with %d objects - dataBuffer = %d kb", animGraphInstanceData.mNumFrames, frames.GetLast().mNumBytes, frames.GetLast().mByteOffset, frames.GetLast().mObjectInfos.GetLength(), animGraphInstanceData.mDataBufferSize / 1024);
|
|
}
|
|
} // for all actor instances
|
|
return true;
|
|
}
|
|
|
|
|
|
// recursively save the node's unique data
|
|
bool Recorder::SaveUniqueData(AnimGraphInstance* animGraphInstance, AnimGraphObject* object, AnimGraphInstanceData& animGraphInstanceData)
|
|
{
|
|
// get the current frame's data pointer
|
|
AnimGraphAnimFrame& currentFrame = animGraphInstanceData.mFrames.GetLast();
|
|
const uint32 frameOffset = currentFrame.mByteOffset;
|
|
|
|
// prepare the objects array
|
|
mObjects.Clear(false);
|
|
mObjects.Reserve(1024);
|
|
|
|
// collect the objects we are going to save for this frame
|
|
object->RecursiveCollectObjects(mObjects);
|
|
|
|
// resize the object infos array
|
|
const uint32 numObjects = mObjects.GetLength();
|
|
currentFrame.mObjectInfos.Resize(numObjects);
|
|
|
|
// calculate how much memory we need for this frame
|
|
uint32 requiredFrameBytes = 0;
|
|
for (uint32 i = 0; i < numObjects; ++i)
|
|
{
|
|
requiredFrameBytes += mObjects[i]->SaveUniqueData(animGraphInstance, nullptr);
|
|
}
|
|
|
|
// make sure we have at least the given amount of space in the buffer we are going to write the frame data to
|
|
if (!AssureAnimGraphBufferSize(animGraphInstanceData, requiredFrameBytes + currentFrame.mByteOffset))
|
|
{
|
|
return false;
|
|
}
|
|
uint8* dataPointer = &animGraphInstanceData.mDataBuffer[frameOffset];
|
|
|
|
// save all the unique datas for the objects
|
|
for (uint32 i = 0; i < numObjects; ++i)
|
|
{
|
|
// store the object info
|
|
AnimGraphObject* curObject = mObjects[i];
|
|
currentFrame.mObjectInfos[i].mObject = curObject;
|
|
currentFrame.mObjectInfos[i].mFrameByteOffset = currentFrame.mNumBytes;
|
|
|
|
// write the unique data
|
|
const uint32 numBytesWritten = curObject->SaveUniqueData(animGraphInstance, dataPointer);
|
|
|
|
// increase some offsets/pointers
|
|
currentFrame.mNumBytes += numBytesWritten;
|
|
dataPointer += numBytesWritten;
|
|
}
|
|
|
|
// make sure we have a match here, otherwise some of the object->SaveUniqueData(dataPointer) returns different values than object->SaveUniqueData(nullptr)
|
|
MCORE_ASSERT(requiredFrameBytes == currentFrame.mNumBytes);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
// make sure our anim graph anim buffer is big enough to hold a specified amount of bytes
|
|
bool Recorder::AssureAnimGraphBufferSize(AnimGraphInstanceData& animGraphInstanceData, uint32 numBytes)
|
|
{
|
|
// if the buffer is big enough, do nothing
|
|
if (animGraphInstanceData.mDataBufferSize >= numBytes)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// we need to reallocate to grow the buffer
|
|
const uint32 newNumBytes = animGraphInstanceData.mDataBufferSize + (numBytes - animGraphInstanceData.mDataBufferSize) * 100; // allocate 100 frames ahead
|
|
void* newBuffer = MCore::Realloc(animGraphInstanceData.mDataBuffer, newNumBytes, EMFX_MEMCATEGORY_RECORDER);
|
|
MCORE_ASSERT(newBuffer);
|
|
if (newBuffer)
|
|
{
|
|
animGraphInstanceData.mDataBuffer = static_cast<uint8*>(newBuffer);
|
|
animGraphInstanceData.mDataBufferSize = newNumBytes;
|
|
return true;
|
|
}
|
|
RecorderNotificationBus::Broadcast(&RecorderNotificationBus::Events::OnRecordingFailed,
|
|
"There is not enough memory to continue the current EMotionFX "
|
|
"recording. It was deleted to free memory in order to keep the "
|
|
"editor stable."
|
|
);
|
|
return false;
|
|
}
|
|
|
|
|
|
// add a transform key
|
|
void Recorder::AddTransformKey(TransformTracks& track, const AZ::Vector3& pos, const AZ::Quaternion& rot, const AZ::Vector3& scale)
|
|
{
|
|
#ifdef EMFX_SCALE_DISABLED
|
|
MCORE_UNUSED(scale);
|
|
#endif
|
|
|
|
// check if we need to add a position key at all
|
|
if (track.mPositions.GetNumKeys() > 0)
|
|
{
|
|
const AZ::Vector3 lastPos = track.mPositions.GetLastKey()->GetValue();
|
|
if (!pos.IsClose(lastPos, 0.0001f))
|
|
{
|
|
track.mPositions.AddKey(mRecordTime, pos);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
track.mPositions.AddKey(mRecordTime, pos);
|
|
}
|
|
|
|
|
|
// check if we need to add a rotation key at all
|
|
if (track.mRotations.GetNumKeys() > 0)
|
|
{
|
|
const AZ::Quaternion lastRot = track.mRotations.GetLastKey()->GetValue();
|
|
if (!rot.IsClose(lastRot, 0.0001f))
|
|
{
|
|
track.mRotations.AddKey(mRecordTime, rot);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
track.mRotations.AddKey(mRecordTime, rot);
|
|
}
|
|
|
|
|
|
EMFX_SCALECODE
|
|
(
|
|
if (mRecordSettings.mRecordScale)
|
|
{
|
|
// check if we need to add a scale key
|
|
if (track.mScales.GetNumKeys() > 0)
|
|
{
|
|
const AZ::Vector3 lastScale = track.mScales.GetLastKey()->GetValue();
|
|
if (!scale.IsClose(lastScale, 0.0001f))
|
|
{
|
|
track.mScales.AddKey(mRecordTime, scale);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
track.mScales.AddKey(mRecordTime, scale);
|
|
}
|
|
}
|
|
)
|
|
}
|
|
|
|
void Recorder::SampleAndApplyTransforms(float timeInSeconds, ActorInstance* actorInstance) const
|
|
{
|
|
const AZStd::vector<ActorInstance*>& recordedActorInstances = mRecordSettings.m_actorInstances;
|
|
const auto iterator = AZStd::find(recordedActorInstances.begin(), recordedActorInstances.end(), actorInstance);
|
|
if (iterator != recordedActorInstances.end())
|
|
{
|
|
const size_t index = iterator - recordedActorInstances.begin();
|
|
SampleAndApplyTransforms(timeInSeconds, index);
|
|
}
|
|
}
|
|
|
|
void Recorder::SampleAndApplyAnimGraphs(float timeInSeconds) const
|
|
{
|
|
for (const ActorInstanceData* actorInstanceData : m_actorInstanceDatas)
|
|
{
|
|
if(actorInstanceData->mAnimGraphData)
|
|
{
|
|
SampleAndApplyAnimGraphStates(timeInSeconds, *actorInstanceData->mAnimGraphData);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Recorder::SampleAndApplyMainTransform(float timeInSeconds, ActorInstance* actorInstance) const
|
|
{
|
|
const AZStd::vector<ActorInstance*>& recordedActorInstances = mRecordSettings.m_actorInstances;
|
|
const auto iterator = AZStd::find(recordedActorInstances.begin(), recordedActorInstances.end(), actorInstance);
|
|
if (iterator != recordedActorInstances.end())
|
|
{
|
|
const size_t index = iterator - recordedActorInstances.begin();
|
|
SampleAndApplyMainTransform(timeInSeconds, index);
|
|
}
|
|
}
|
|
|
|
void Recorder::SampleAndApplyMorphs(float timeInSeconds, ActorInstance* actorInstance) const
|
|
{
|
|
const AZStd::vector<ActorInstance*>& recordedActorInstances = mRecordSettings.m_actorInstances;
|
|
const auto iterator = AZStd::find(recordedActorInstances.begin(), recordedActorInstances.end(), actorInstance);
|
|
if (iterator != recordedActorInstances.end())
|
|
{
|
|
const size_t index = iterator - recordedActorInstances.begin();
|
|
const ActorInstanceData& actorInstanceData = *m_actorInstanceDatas[index];
|
|
const uint32 numMorphs = actorInstanceData.mMorphTracks.GetLength();
|
|
if (numMorphs == actorInstance->GetMorphSetupInstance()->GetNumMorphTargets())
|
|
{
|
|
for (uint32 i = 0; i < numMorphs; ++i)
|
|
{
|
|
actorInstance->GetMorphSetupInstance()->GetMorphTarget(i)->SetWeight(actorInstanceData.mMorphTracks[i].GetValueAtTime(timeInSeconds));
|
|
// actorInstance->GetMorphSetupInstance()->GetMorphTarget(i)->SetManualMode(true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Recorder::SampleAndApplyMainTransform(float timeInSeconds, size_t actorInstanceIndex) const
|
|
{
|
|
// get the actor instance
|
|
const ActorInstanceData& actorInstanceData = *m_actorInstanceDatas[actorInstanceIndex];
|
|
ActorInstance* actorInstance = actorInstanceData.mActorInstance;
|
|
|
|
// sample and apply
|
|
const TransformTracks& track = actorInstanceData.mActorLocalTransform;
|
|
actorInstance->SetLocalSpacePosition(track.mPositions.GetValueAtTime(timeInSeconds, nullptr, nullptr, mRecordSettings.mInterpolate));
|
|
actorInstance->SetLocalSpaceRotation(track.mRotations.GetValueAtTime(timeInSeconds, nullptr, nullptr, mRecordSettings.mInterpolate));
|
|
EMFX_SCALECODE
|
|
(
|
|
if (mRecordSettings.mRecordScale)
|
|
{
|
|
actorInstance->SetLocalSpaceScale(track.mScales.GetValueAtTime(timeInSeconds, nullptr, nullptr, mRecordSettings.mInterpolate));
|
|
}
|
|
)
|
|
}
|
|
|
|
void Recorder::SampleAndApplyTransforms(float timeInSeconds, size_t actorInstanceIndex) const
|
|
{
|
|
const ActorInstanceData& actorInstanceData = *m_actorInstanceDatas[actorInstanceIndex];
|
|
ActorInstance* actorInstance = actorInstanceData.mActorInstance;
|
|
TransformData* transformData = actorInstance->GetTransformData();
|
|
|
|
// for all nodes in the actor instance
|
|
Transform outTransform;
|
|
const uint32 numNodes = actorInstance->GetNumNodes();
|
|
for (uint32 n = 0; n < numNodes; ++n)
|
|
{
|
|
outTransform = transformData->GetCurrentPose()->GetLocalSpaceTransform(n);
|
|
const TransformTracks& track = actorInstanceData.m_transformTracks[n];
|
|
|
|
// build the output transform by sampling the keytracks
|
|
outTransform.mPosition = track.mPositions.GetValueAtTime(timeInSeconds, nullptr, nullptr, mRecordSettings.mInterpolate);
|
|
outTransform.mRotation = track.mRotations.GetValueAtTime(timeInSeconds, nullptr, nullptr, mRecordSettings.mInterpolate);
|
|
|
|
EMFX_SCALECODE
|
|
(
|
|
if (mRecordSettings.mRecordScale)
|
|
{
|
|
outTransform.mScale = track.mScales.GetValueAtTime(timeInSeconds, nullptr, nullptr, mRecordSettings.mInterpolate);
|
|
}
|
|
)
|
|
|
|
// set the transform
|
|
transformData->GetCurrentPose()->SetLocalSpaceTransform(n, outTransform);
|
|
}
|
|
}
|
|
|
|
void Recorder::SampleAndApplyAnimGraphStates(float timeInSeconds, const AnimGraphInstanceData& animGraphInstanceData) const
|
|
{
|
|
// find out the frame number
|
|
const uint32 frameNumber = FindAnimGraphDataFrameNumber(timeInSeconds);
|
|
if (frameNumber == MCORE_INVALIDINDEX32)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// for all animgraph instances that we recorded, restore their internal states
|
|
AnimGraphInstance* animGraphInstance = animGraphInstanceData.mAnimGraphInstance;
|
|
|
|
// get the real frame number (clamped)
|
|
const uint32 realFrameNumber = MCore::Min<uint32>(frameNumber, animGraphInstanceData.mFrames.GetLength() - 1);
|
|
const AnimGraphAnimFrame& currentFrame = animGraphInstanceData.mFrames[realFrameNumber];
|
|
|
|
// get the data and objects buffers
|
|
const uint32 byteOffset = currentFrame.mByteOffset;
|
|
const uint8* frameDataBuffer = &animGraphInstanceData.mDataBuffer[byteOffset];
|
|
const MCore::Array<AnimGraphAnimObjectInfo>& frameObjects = currentFrame.mObjectInfos;
|
|
|
|
// first lets update all parameter values
|
|
MCORE_ASSERT(currentFrame.mParameterValues.GetLength() == animGraphInstance->GetAnimGraph()->GetNumParameters());
|
|
const uint32 numParameters = currentFrame.mParameterValues.GetLength();
|
|
for (uint32 p = 0; p < numParameters; ++p)
|
|
{
|
|
// make sure the parameters are of the same type
|
|
MCORE_ASSERT(animGraphInstance->GetParameterValue(p)->GetType() == currentFrame.mParameterValues[p]->GetType());
|
|
animGraphInstance->GetParameterValue(p)->InitFrom(currentFrame.mParameterValues[p]);
|
|
}
|
|
|
|
// process all objects for this frame
|
|
uint32 totalBytesRead = 0;
|
|
const uint32 numObjects = frameObjects.GetLength();
|
|
for (uint32 a = 0; a < numObjects; ++a)
|
|
{
|
|
const AnimGraphAnimObjectInfo& objectInfo = frameObjects[a];
|
|
const uint32 numBytesRead = objectInfo.mObject->LoadUniqueData(animGraphInstance, &frameDataBuffer[objectInfo.mFrameByteOffset]);
|
|
totalBytesRead += numBytesRead;
|
|
}
|
|
|
|
// make sure this matches, otherwise the data read is not the same as we have written
|
|
MCORE_ASSERT(totalBytesRead == currentFrame.mNumBytes);
|
|
}
|
|
|
|
bool Recorder::GetHasRecorded(ActorInstance* actorInstance) const
|
|
{
|
|
return AZStd::find(mRecordSettings.m_actorInstances.begin(), mRecordSettings.m_actorInstances.end(), actorInstance)
|
|
!= mRecordSettings.m_actorInstances.end();
|
|
}
|
|
|
|
uint32 Recorder::FindActorInstanceDataIndex(ActorInstance* actorInstance) const
|
|
{
|
|
// for all actor instances
|
|
const uint32 numActorInstances = static_cast<uint32>(m_actorInstanceDatas.size());
|
|
for (uint32 a = 0; a < numActorInstances; ++a)
|
|
{
|
|
if (m_actorInstanceDatas[a]->mActorInstance == actorInstance)
|
|
{
|
|
return a;
|
|
}
|
|
}
|
|
|
|
return MCORE_INVALIDINDEX32;
|
|
}
|
|
|
|
void Recorder::UpdateNodeHistoryItems()
|
|
{
|
|
// for all actor instances
|
|
for (ActorInstanceData* actorInstanceData : m_actorInstanceDatas)
|
|
{
|
|
// get the animgraph instance
|
|
AnimGraphInstance* animGraphInstance = actorInstanceData->mActorInstance->GetAnimGraphInstance();
|
|
if (animGraphInstance == nullptr)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// collect all active motion nodes
|
|
animGraphInstance->CollectActiveAnimGraphNodes(&mActiveNodes);
|
|
|
|
// get the history items as shortcut
|
|
MCore::Array<NodeHistoryItem*>& historyItems = actorInstanceData->mNodeHistoryItems;
|
|
|
|
// finalize items
|
|
const size_t numActiveNodes = mActiveNodes.size();
|
|
const uint32 numHistoryItems = historyItems.GetLength();
|
|
for (uint32 h = 0; h < numHistoryItems; ++h)
|
|
{
|
|
NodeHistoryItem* curItem = historyItems[h];
|
|
if (curItem->mIsFinalized)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// check if we have an active node for the given item
|
|
size_t index = MCORE_INVALIDINDEX32;
|
|
for (size_t x = 0; x < numActiveNodes; ++x)
|
|
{
|
|
if (mActiveNodes[x]->GetId() == curItem->mNodeId)
|
|
{
|
|
index = x;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// the node got deactivated, finalize the item
|
|
if (index == MCORE_INVALIDINDEX32)
|
|
{
|
|
curItem->mGlobalWeights.Optimize(0.0001f);
|
|
curItem->mLocalWeights.Optimize(0.0001f);
|
|
curItem->mPlayTimes.Optimize(0.0001f);
|
|
curItem->mIsFinalized = true;
|
|
curItem->mEndTime = mRecordTime;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// iterate over all active nodes
|
|
for (size_t i = 0; i < numActiveNodes; ++i)
|
|
{
|
|
AnimGraphNode* activeNode = mActiveNodes[i];
|
|
if (activeNode == animGraphInstance->GetRootNode()) // skip the root node
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const AZ::TypeId typeID = azrtti_typeid(activeNode);
|
|
|
|
// if the parent isn't a state machine then it isn't a state
|
|
if (mRecordSettings.mHistoryStatesOnly)
|
|
{
|
|
if (azrtti_typeid(activeNode->GetParentNode()) != azrtti_typeid<AnimGraphStateMachine>())
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// make sure this node is on our capture list
|
|
if (!mRecordSettings.mNodeHistoryTypes.empty())
|
|
{
|
|
if (mRecordSettings.mNodeHistoryTypes.find(typeID) == mRecordSettings.mNodeHistoryTypes.end())
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// skip node types we do not want to capture
|
|
if (!mRecordSettings.mNodeHistoryTypesToIgnore.empty())
|
|
{
|
|
if (mRecordSettings.mNodeHistoryTypesToIgnore.find(typeID) != mRecordSettings.mNodeHistoryTypesToIgnore.end())
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// try to locate an existing item
|
|
NodeHistoryItem* item = FindNodeHistoryItem(*actorInstanceData, activeNode, mRecordTime);
|
|
if (item == nullptr)
|
|
{
|
|
item = new NodeHistoryItem();
|
|
item->mName = activeNode->GetName();
|
|
item->mAnimGraphID = animGraphInstance->GetAnimGraph()->GetID();
|
|
item->mStartTime = mRecordTime;
|
|
item->mIsFinalized = false;
|
|
item->mTrackIndex = FindFreeNodeHistoryItemTrack(*actorInstanceData, item);
|
|
item->mNodeId = activeNode->GetId();
|
|
item->mColor = activeNode->GetVisualizeColor();
|
|
item->mTypeColor = activeNode->GetVisualColor();
|
|
item->mCategoryID = (uint32)activeNode->GetPaletteCategory();
|
|
item->mNodeType = typeID;
|
|
item->mAnimGraphInstance = animGraphInstance;
|
|
item->mGlobalWeights.Reserve(1024);
|
|
item->mLocalWeights.Reserve(1024);
|
|
item->mPlayTimes.Reserve(1024);
|
|
|
|
// get the motion instance
|
|
if (typeID == azrtti_typeid<AnimGraphMotionNode>())
|
|
{
|
|
AnimGraphMotionNode* motionNode = static_cast<AnimGraphMotionNode*>(activeNode);
|
|
MotionInstance* motionInstance = motionNode->FindMotionInstance(animGraphInstance);
|
|
if (motionInstance)
|
|
{
|
|
item->mMotionID = motionInstance->GetMotion()->GetID();
|
|
AzFramework::StringFunc::Path::GetFileName(item->mMotionFileName.c_str(), item->mMotionFileName);
|
|
}
|
|
}
|
|
|
|
historyItems.Add(item);
|
|
}
|
|
|
|
// add the weight key and update infos
|
|
const AnimGraphNodeData* uniqueData = activeNode->FindOrCreateUniqueNodeData(animGraphInstance);
|
|
const float keyTime = mRecordTime - item->mStartTime;
|
|
item->mGlobalWeights.AddKey(keyTime, uniqueData->GetGlobalWeight());
|
|
item->mLocalWeights.AddKey(keyTime, uniqueData->GetLocalWeight());
|
|
|
|
float normalizedTime = uniqueData->GetCurrentPlayTime();
|
|
const float duration = uniqueData->GetDuration();
|
|
if (duration > MCore::Math::epsilon)
|
|
{
|
|
normalizedTime /= duration;
|
|
}
|
|
else
|
|
{
|
|
normalizedTime = 0.0f;
|
|
}
|
|
|
|
item->mPlayTimes.AddKey(keyTime, normalizedTime);
|
|
item->mEndTime = mRecordTime;
|
|
}
|
|
} // for all actor instances
|
|
}
|
|
|
|
|
|
// try to find a given node history item
|
|
Recorder::NodeHistoryItem* Recorder::FindNodeHistoryItem(const ActorInstanceData& actorInstanceData, AnimGraphNode* node, float recordTime) const
|
|
{
|
|
const MCore::Array<NodeHistoryItem*>& historyItems = actorInstanceData.mNodeHistoryItems;
|
|
const uint32 numItems = historyItems.GetLength();
|
|
for (uint32 i = 0; i < numItems; ++i)
|
|
{
|
|
NodeHistoryItem* curItem = historyItems[i];
|
|
if (curItem->mNodeId == node->GetId() && curItem->mStartTime <= recordTime && curItem->mIsFinalized == false)
|
|
{
|
|
return curItem;
|
|
}
|
|
|
|
if (curItem->mNodeId == node->GetId() && curItem->mStartTime <= recordTime && curItem->mEndTime >= recordTime && curItem->mIsFinalized)
|
|
{
|
|
return curItem;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
|
|
// find a free track
|
|
uint32 Recorder::FindFreeNodeHistoryItemTrack(const ActorInstanceData& actorInstanceData, NodeHistoryItem* item) const
|
|
{
|
|
const MCore::Array<NodeHistoryItem*>& historyItems = actorInstanceData.mNodeHistoryItems;
|
|
const uint32 numItems = historyItems.GetLength();
|
|
|
|
bool found = false;
|
|
uint32 trackIndex = 0;
|
|
while (found == false)
|
|
{
|
|
bool hasCollision = false;
|
|
|
|
for (uint32 i = 0; i < numItems; ++i)
|
|
{
|
|
NodeHistoryItem* curItem = historyItems[i];
|
|
if (curItem->mTrackIndex != trackIndex)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// if the current item is not active anymore
|
|
if (curItem->mIsFinalized)
|
|
{
|
|
// if the start time of the item we try to insert is within the range of this item
|
|
if (item->mStartTime > curItem->mStartTime && item->mStartTime < curItem->mEndTime)
|
|
{
|
|
hasCollision = true;
|
|
break;
|
|
}
|
|
|
|
// if the end time of this item is within the range of this item
|
|
if (item->mEndTime > curItem->mStartTime && item->mEndTime < curItem->mEndTime)
|
|
{
|
|
hasCollision = true;
|
|
break;
|
|
}
|
|
/*
|
|
if (MCore::Compare<float>::CheckIfIsClose(item->mStartTime, curItem->mStartTime, 0.001f) || MCore::Compare<float>::CheckIfIsClose(item->mStartTime, curItem->mEndTime, 0.001f))
|
|
{
|
|
hasCollision = true;
|
|
break;
|
|
}*/
|
|
}
|
|
else // if the current item is still active and has no real end time yet
|
|
{
|
|
if (item->mStartTime >= curItem->mStartTime)
|
|
{
|
|
hasCollision = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (hasCollision)
|
|
{
|
|
trackIndex++;
|
|
}
|
|
else
|
|
{
|
|
found = true;
|
|
}
|
|
}
|
|
|
|
return trackIndex;
|
|
}
|
|
|
|
|
|
// find the maximum track index
|
|
uint32 Recorder::CalcMaxNodeHistoryTrackIndex(const ActorInstanceData& actorInstanceData) const
|
|
{
|
|
uint32 result = 0;
|
|
const MCore::Array<NodeHistoryItem*>& historyItems = actorInstanceData.mNodeHistoryItems;
|
|
const uint32 numItems = historyItems.GetLength();
|
|
for (uint32 i = 0; i < numItems; ++i)
|
|
{
|
|
NodeHistoryItem* curItem = historyItems[i];
|
|
if (curItem->mTrackIndex > result)
|
|
{
|
|
result = curItem->mTrackIndex;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
// find the maximum event track index
|
|
uint32 Recorder::CalcMaxEventHistoryTrackIndex(const ActorInstanceData& actorInstanceData) const
|
|
{
|
|
uint32 result = 0;
|
|
const MCore::Array<EventHistoryItem*>& historyItems = actorInstanceData.mEventHistoryItems;
|
|
const uint32 numItems = historyItems.GetLength();
|
|
for (uint32 i = 0; i < numItems; ++i)
|
|
{
|
|
EventHistoryItem* curItem = historyItems[i];
|
|
if (curItem->mTrackIndex > result)
|
|
{
|
|
result = curItem->mTrackIndex;
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
// find the maximum track index
|
|
uint32 Recorder::CalcMaxNodeHistoryTrackIndex() const
|
|
{
|
|
uint32 result = 0;
|
|
|
|
// for all actor instances
|
|
for (const ActorInstanceData* actorInstanceData : m_actorInstanceDatas)
|
|
{
|
|
result = MCore::Max<uint32>(result, CalcMaxNodeHistoryTrackIndex(*actorInstanceData));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
// finalize all items
|
|
void Recorder::FinalizeAllNodeHistoryItems()
|
|
{
|
|
// for all actor instances
|
|
for (const ActorInstanceData* actorInstanceData : m_actorInstanceDatas)
|
|
{
|
|
// get the animgraph instance
|
|
AnimGraphInstance* animGraphInstance = actorInstanceData->mActorInstance->GetAnimGraphInstance();
|
|
if (animGraphInstance == nullptr)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// collect all active motion nodes
|
|
animGraphInstance->CollectActiveAnimGraphNodes(&mActiveNodes);
|
|
|
|
// get the history items as shortcut
|
|
const MCore::Array<NodeHistoryItem*>& historyItems = actorInstanceData->mNodeHistoryItems;
|
|
|
|
// finalize all items
|
|
const uint32 numItems = historyItems.GetLength();
|
|
for (uint32 i = 0; i < numItems; ++i)
|
|
{
|
|
// remove unneeded key frames
|
|
if (historyItems[i]->mIsFinalized == false)
|
|
{
|
|
historyItems[i]->mGlobalWeights.Optimize(0.0001f);
|
|
historyItems[i]->mLocalWeights.Optimize(0.0001f);
|
|
historyItems[i]->mPlayTimes.Optimize(0.0001f);
|
|
historyItems[i]->mIsFinalized = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// record events
|
|
void Recorder::RecordEvents()
|
|
{
|
|
// for all actor instances
|
|
for (ActorInstanceData* actorInstanceData : m_actorInstanceDatas)
|
|
{
|
|
// get the animgraph instance
|
|
AnimGraphInstance* animGraphInstance = actorInstanceData->mActorInstance->GetAnimGraphInstance();
|
|
if (animGraphInstance == nullptr)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// get the event buffer
|
|
const AnimGraphEventBuffer& eventBuffer = animGraphInstance->GetEventBuffer();
|
|
|
|
// iterate over all events
|
|
MCore::Array<EventHistoryItem*>& historyItems = actorInstanceData->mEventHistoryItems;
|
|
const uint32 numEvents = eventBuffer.GetNumEvents();
|
|
for (uint32 i = 0; i < numEvents; ++i)
|
|
{
|
|
const EventInfo& eventInfo = eventBuffer.GetEvent(i);
|
|
if (eventInfo.m_eventState == EventInfo::EventInfo::ACTIVE)
|
|
{
|
|
continue;
|
|
}
|
|
EventHistoryItem* item = FindEventHistoryItem(*actorInstanceData, eventInfo, mRecordTime);
|
|
if (item == nullptr) // create a new one
|
|
{
|
|
item = new EventHistoryItem();
|
|
|
|
// TODO
|
|
//item->mEventIndex = GetEventManager().FindEventTypeIndex(eventInfo.mTypeID);
|
|
item->mEventInfo = eventInfo;
|
|
item->mIsTickEvent = eventInfo.mEvent->GetIsTickEvent();
|
|
item->mStartTime = mRecordTime;
|
|
item->mAnimGraphID = animGraphInstance->GetAnimGraph()->GetID();
|
|
item->mEmitterNodeId= eventInfo.mEmitter->GetId();
|
|
item->mColor = eventInfo.mEmitter->GetVisualizeColor();
|
|
|
|
item->mTrackIndex = FindFreeEventHistoryItemTrack(*actorInstanceData, item);
|
|
|
|
historyItems.Add(item);
|
|
}
|
|
|
|
item->mEndTime = mRecordTime;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// find a given event
|
|
Recorder::EventHistoryItem* Recorder::FindEventHistoryItem(const ActorInstanceData& actorInstanceData, const EventInfo& eventInfo, float recordTime)
|
|
{
|
|
MCORE_UNUSED(recordTime);
|
|
const MCore::Array<EventHistoryItem*>& historyItems = actorInstanceData.mEventHistoryItems;
|
|
const uint32 numItems = historyItems.GetLength();
|
|
for (uint32 i = 0; i < numItems; ++i)
|
|
{
|
|
EventHistoryItem* curItem = historyItems[i];
|
|
|
|
if (curItem->mStartTime < eventInfo.mTimeValue)
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
|
|
// find a free event track index
|
|
uint32 Recorder::FindFreeEventHistoryItemTrack(const ActorInstanceData& actorInstanceData, EventHistoryItem* item) const
|
|
{
|
|
const MCore::Array<EventHistoryItem*>& historyItems = actorInstanceData.mEventHistoryItems;
|
|
const uint32 numItems = historyItems.GetLength();
|
|
bool found = false;
|
|
uint32 trackIndex = 0;
|
|
while (found == false)
|
|
{
|
|
bool hasCollision = false;
|
|
|
|
for (uint32 i = 0; i < numItems; ++i)
|
|
{
|
|
EventHistoryItem* curItem = historyItems[i];
|
|
if (curItem->mTrackIndex != trackIndex)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (MCore::Compare<float>::CheckIfIsClose(curItem->mStartTime, item->mStartTime, 0.01f))
|
|
{
|
|
hasCollision = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (hasCollision)
|
|
{
|
|
trackIndex++;
|
|
}
|
|
else
|
|
{
|
|
found = true;
|
|
}
|
|
}
|
|
|
|
return trackIndex;
|
|
}
|
|
|
|
|
|
// find the frame number for a time value
|
|
uint32 Recorder::FindAnimGraphDataFrameNumber(float timeValue) const
|
|
{
|
|
// check if we recorded any actor instances at all
|
|
if (m_actorInstanceDatas.empty())
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
// just search in the first actor instances data
|
|
const ActorInstanceData& actorInstanceData = *m_actorInstanceDatas[0];
|
|
const AnimGraphInstanceData* animGraphData = actorInstanceData.mAnimGraphData;
|
|
if (animGraphData == nullptr)
|
|
{
|
|
return MCORE_INVALIDINDEX32;
|
|
}
|
|
|
|
const uint32 numFrames = animGraphData->mFrames.GetLength();
|
|
if (numFrames == 0)
|
|
{
|
|
return MCORE_INVALIDINDEX32;
|
|
}
|
|
else
|
|
if (numFrames == 1)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (timeValue <= 0.0f)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
if (timeValue > animGraphData->mFrames.GetLast().mTimeValue)
|
|
{
|
|
return animGraphData->mFrames.GetLength() - 1;
|
|
}
|
|
|
|
for (uint32 i = 0; i < numFrames - 1; ++i)
|
|
{
|
|
const AnimGraphAnimFrame& curFrame = animGraphData->mFrames[i];
|
|
const AnimGraphAnimFrame& nextFrame = animGraphData->mFrames[i + 1];
|
|
if (curFrame.mTimeValue <= timeValue && nextFrame.mTimeValue > timeValue)
|
|
{
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return MCORE_INVALIDINDEX32;
|
|
}
|
|
|
|
void Recorder::RemoveActorInstanceFromRecording(ActorInstance* actorInstance)
|
|
{
|
|
Lock();
|
|
|
|
// Remove the actor instance from the record settings.
|
|
AZStd::vector<ActorInstance*>& recordedActorInstances = mRecordSettings.m_actorInstances;
|
|
recordedActorInstances.erase(AZStd::remove_if(recordedActorInstances.begin(), recordedActorInstances.end(),
|
|
[&actorInstance](ActorInstance* recordedActorInstance){ return recordedActorInstance == actorInstance;}),
|
|
recordedActorInstances.end());
|
|
|
|
// Remove the actual recorded data.
|
|
for (uint32 i = 0; i < m_actorInstanceDatas.size();)
|
|
{
|
|
if (m_actorInstanceDatas[i]->mActorInstance == actorInstance)
|
|
{
|
|
delete m_actorInstanceDatas[i];
|
|
m_actorInstanceDatas.erase(AZStd::next(m_actorInstanceDatas.begin(), i));
|
|
}
|
|
else
|
|
{
|
|
i++;
|
|
}
|
|
}
|
|
|
|
Unlock();
|
|
}
|
|
|
|
void Recorder::RemoveAnimGraphFromRecording(AnimGraph* animGraph)
|
|
{
|
|
Lock();
|
|
|
|
for (ActorInstanceData* actorInstanceData : m_actorInstanceDatas)
|
|
{
|
|
if (actorInstanceData->mAnimGraphData && actorInstanceData->mAnimGraphData->mAnimGraphInstance)
|
|
{
|
|
AnimGraph* curAnimGraph = actorInstanceData->mAnimGraphData->mAnimGraphInstance->GetAnimGraph();
|
|
if (animGraph != curAnimGraph)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
delete actorInstanceData->mAnimGraphData;
|
|
actorInstanceData->mAnimGraphData = nullptr;
|
|
}
|
|
}
|
|
|
|
Unlock();
|
|
}
|
|
|
|
void Recorder::OnActorInstanceDestroyed(EMotionFX::ActorInstance* actorInstance)
|
|
{
|
|
// Actor instances created by actor components do not use the command system and don't call a ClearRecorder command.
|
|
// Thus, these actor instances will have to be removed from the recorder to avoid dangling data.
|
|
RemoveActorInstanceFromRecording(actorInstance);
|
|
}
|
|
|
|
void Recorder::Lock()
|
|
{
|
|
mLock.Lock();
|
|
}
|
|
|
|
void Recorder::Unlock()
|
|
{
|
|
mLock.Unlock();
|
|
}
|
|
|
|
|
|
// extract sorted active items
|
|
void Recorder::ExtractNodeHistoryItems(const ActorInstanceData& actorInstanceData, float timeValue, bool sort, EValueType valueType, MCore::Array<ExtractedNodeHistoryItem>* outItems, MCore::Array<uint32>* outMap)
|
|
{
|
|
// clear the map array
|
|
const uint32 maxIndex = CalcMaxNodeHistoryTrackIndex(actorInstanceData);
|
|
outItems->Resize(maxIndex + 1);
|
|
for (uint32 i = 0; i <= maxIndex; ++i)
|
|
{
|
|
ExtractedNodeHistoryItem item;
|
|
item.mTrackIndex = i;
|
|
item.mValue = 0.0f;
|
|
item.mKeyTrackSampleTime = 0.0f;
|
|
item.mNodeHistoryItem = nullptr;
|
|
outItems->SetElem(i, item);
|
|
}
|
|
|
|
// find all node history items
|
|
const MCore::Array<NodeHistoryItem*>& historyItems = actorInstanceData.mNodeHistoryItems;
|
|
const uint32 numItems = historyItems.GetLength();
|
|
for (uint32 i = 0; i < numItems; ++i)
|
|
{
|
|
NodeHistoryItem* curItem = historyItems[i];
|
|
if (curItem->mStartTime <= timeValue && curItem->mEndTime > timeValue)
|
|
{
|
|
ExtractedNodeHistoryItem item;
|
|
item.mTrackIndex = curItem->mTrackIndex;
|
|
item.mKeyTrackSampleTime = timeValue - curItem->mStartTime;
|
|
item.mNodeHistoryItem = curItem;
|
|
|
|
switch (valueType)
|
|
{
|
|
case VALUETYPE_GLOBALWEIGHT:
|
|
item.mValue = curItem->mGlobalWeights.GetValueAtTime(item.mKeyTrackSampleTime, nullptr, nullptr, mRecordSettings.mInterpolate);
|
|
break;
|
|
|
|
case VALUETYPE_LOCALWEIGHT:
|
|
item.mValue = curItem->mLocalWeights.GetValueAtTime(item.mKeyTrackSampleTime, nullptr, nullptr, mRecordSettings.mInterpolate);
|
|
break;
|
|
|
|
case VALUETYPE_PLAYTIME:
|
|
item.mValue = curItem->mPlayTimes.GetValueAtTime(item.mKeyTrackSampleTime, nullptr, nullptr, mRecordSettings.mInterpolate);
|
|
break;
|
|
|
|
default:
|
|
MCORE_ASSERT(false); // unsupported mode
|
|
item.mValue = curItem->mGlobalWeights.GetValueAtTime(item.mKeyTrackSampleTime, nullptr, nullptr, mRecordSettings.mInterpolate);
|
|
}
|
|
|
|
outItems->SetElem(curItem->mTrackIndex, item);
|
|
}
|
|
}
|
|
|
|
// build the map
|
|
outMap->Resize(maxIndex + 1);
|
|
for (uint32 i = 0; i <= maxIndex; ++i)
|
|
{
|
|
outMap->SetElem(i, i);
|
|
}
|
|
|
|
// sort if desired
|
|
if (sort)
|
|
{
|
|
outItems->Sort();
|
|
|
|
for (uint32 i = 0; i <= maxIndex; ++i)
|
|
{
|
|
outMap->SetElem(outItems->GetItem(i).mTrackIndex, i);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
AZ::u32 Recorder::CalcMaxNumActiveMotions(const ActorInstanceData& actorInstanceData) const
|
|
{
|
|
AZ::u32 result = 0;
|
|
|
|
// Array of flags that each iteration will use to determine if a given track has already been counted in or not.
|
|
AZStd::vector<bool> trackFlags;
|
|
const size_t maxNumTracks = static_cast<size_t>(CalcMaxNodeHistoryTrackIndex()) + 1;
|
|
trackFlags.resize(maxNumTracks);
|
|
|
|
const uint32 numNodeHistoryItems = actorInstanceData.mNodeHistoryItems.GetLength();
|
|
for (uint32 i = 0; i < numNodeHistoryItems; ++i)
|
|
{
|
|
EMotionFX::Recorder::NodeHistoryItem* item = actorInstanceData.mNodeHistoryItems[i];
|
|
|
|
// Only process motion history items.
|
|
if (item->mMotionID == MCORE_INVALIDINDEX32)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Reset the track flags
|
|
for (size_t t = 0; t < maxNumTracks; ++t)
|
|
{
|
|
trackFlags[t] = false;
|
|
}
|
|
|
|
// We at least have a single active motion.
|
|
AZ::u32 intermediateResult = 1;
|
|
trackFlags[item->mTrackIndex] = true;
|
|
|
|
for (uint32 j = 0; j < numNodeHistoryItems; ++j)
|
|
{
|
|
EMotionFX::Recorder::NodeHistoryItem* innerItem = actorInstanceData.mNodeHistoryItems[j];
|
|
|
|
// Did we count this track in already? If yes, skip.
|
|
if (trackFlags[innerItem->mTrackIndex])
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Skip self comparison and only process motion history items.
|
|
if (i == j || innerItem->mMotionID == MCORE_INVALIDINDEX32)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Are the item and innerItem events overlapping?
|
|
if ((item->mStartTime >= innerItem->mStartTime && item->mStartTime <= innerItem->mEndTime) ||
|
|
(item->mEndTime >= innerItem->mStartTime && item->mEndTime <= innerItem->mEndTime) ||
|
|
(innerItem->mStartTime >= item->mStartTime && innerItem->mStartTime <= item->mEndTime) ||
|
|
(innerItem->mEndTime >= item->mStartTime && innerItem->mEndTime <= item->mEndTime))
|
|
{
|
|
intermediateResult++;
|
|
trackFlags[innerItem->mTrackIndex] = true;
|
|
}
|
|
}
|
|
|
|
result = MCore::Max<AZ::u32>(result, intermediateResult);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
AZ::u32 Recorder::CalcMaxNumActiveMotions() const
|
|
{
|
|
AZ::u32 result = 0;
|
|
|
|
for (const ActorInstanceData* actorInstanceData : m_actorInstanceDatas)
|
|
{
|
|
result = MCore::Max<AZ::u32>(result, CalcMaxNumActiveMotions(*actorInstanceData));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
} // namespace EMotionFX
|