You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Gems/Maestro/Code/Source/Cinematics/AnimComponentNode.cpp

1062 lines
50 KiB
C++

/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "AnimComponentNode.h"
#include <AzCore/Component/TransformBus.h>
#include <AzCore/Math/Color.h>
#include <AzFramework/Components/TransformComponent.h>
#include <Maestro/Bus/EditorSequenceComponentBus.h>
#include <Maestro/Bus/SequenceComponentBus.h>
#include <Maestro/Types/AnimNodeType.h>
#include <Maestro/Types/AnimValueType.h>
#include <Maestro/Types/AnimParamType.h>
#include <Maestro/Types/AssetBlends.h>
#include "CharacterTrack.h"
CAnimComponentNode::CAnimComponentNode(int id)
: CAnimNode(id, AnimNodeType::Component)
, m_refCount(0)
, m_componentTypeId(AZ::Uuid::CreateNull())
, m_componentId(AZ::InvalidComponentId)
, m_skipComponentAnimationUpdates(false)
{
}
CAnimComponentNode::CAnimComponentNode()
: CAnimComponentNode(0)
{
}
CAnimComponentNode::~CAnimComponentNode()
{
if (m_characterTrackAnimator)
{
delete m_characterTrackAnimator;
m_characterTrackAnimator = nullptr;
}
}
void CAnimComponentNode::OnStart()
{
}
void CAnimComponentNode::OnResume()
{
}
//////////////////////////////////////////////////////////////////////////
void CAnimComponentNode::OnReset()
{
// OnReset is called when sequences are loaded
if (m_characterTrackAnimator)
{
m_characterTrackAnimator->OnReset(this);
}
UpdateDynamicParams();
}
//////////////////////////////////////////////////////////////////////////
void CAnimComponentNode::OnResetHard()
{
OnReset();
if (m_pOwner)
{
m_pOwner->OnNodeReset(this);
}
}
//////////////////////////////////////////////////////////////////////////
CAnimParamType CAnimComponentNode::GetParamType(unsigned int nIndex) const
{
(void)nIndex;
return AnimParamType::Invalid;
};
//////////////////////////////////////////////////////////////////////////
void CAnimComponentNode::SetComponent(AZ::ComponentId componentId, const AZ::Uuid& componentTypeId)
{
m_componentId = componentId;
m_componentTypeId = componentTypeId;
// call OnReset() to update dynamic params
// (i.e. virtual properties from the exposed EBuses from the BehaviorContext)
OnReset();
}
//////////////////////////////////////////////////////////////////////////
bool CAnimComponentNode::GetParamInfoFromType(const CAnimParamType& paramId, SParamInfo& info) const
{
auto findIter = m_paramTypeToBehaviorPropertyInfoMap.find(paramId);
if (findIter != m_paramTypeToBehaviorPropertyInfoMap.end())
{
info = findIter->second.m_animNodeParamInfo;
return true;
}
return false;
}
//////////////////////////////////////////////////////////////////////////
bool CAnimComponentNode::SetTrackMultiplier(IAnimTrack* track) const
{
bool trackMultiplierWasSet = false;
CAnimParamType paramType(track->GetParameterType());
if (paramType.GetType() == AnimParamType::ByString)
{
// check to see if we need to use a track multiplier
// Get Property TypeId
Maestro::SequenceComponentRequests::AnimatablePropertyAddress propertyAddress(m_componentId, paramType.GetName());
AZ::Uuid propertyTypeId = AZ::Uuid::CreateNull();
Maestro::SequenceComponentRequestBus::EventResult(propertyTypeId, m_pSequence->GetSequenceEntityId(), &Maestro::SequenceComponentRequestBus::Events::GetAnimatedAddressTypeId,
GetParentAzEntityId(), propertyAddress);
if (propertyTypeId == AZ::Color::TYPEINFO_Uuid())
{
track->SetMultiplier(255.0f);
trackMultiplierWasSet = true;
}
}
return trackMultiplierWasSet;
}
int CAnimComponentNode::SetKeysForChangedBoolTrackValue(IAnimTrack* track, int keyIdx, float time)
{
int retNumKeysSet = 0;
bool currTrackValue;
track->GetValue(time, currTrackValue);
Maestro::SequenceComponentRequests::AnimatedBoolValue currValue(currTrackValue);
Maestro::SequenceComponentRequests::AnimatablePropertyAddress animatableAddress(m_componentId, track->GetParameterType().GetName());
Maestro::SequenceComponentRequestBus::Event(m_pSequence->GetSequenceEntityId(), &Maestro::SequenceComponentRequestBus::Events::GetAnimatedPropertyValue, currValue, GetParentAzEntityId(), animatableAddress);
if (currTrackValue != currValue.GetBoolValue())
{
keyIdx = track->FindKey(time);
if (keyIdx == -1)
{
keyIdx = track->CreateKey(time);
}
// no need to set a value of a Bool key - it's existence implies a Boolean toggle.
retNumKeysSet++;
}
return retNumKeysSet;
}
int CAnimComponentNode::SetKeysForChangedFloatTrackValue(IAnimTrack* track, int keyIdx, float time)
{
int retNumKeysSet = 0;
float currTrackValue;
track->GetValue(time, currTrackValue);
Maestro::SequenceComponentRequests::AnimatedFloatValue currValue(currTrackValue);
Maestro::SequenceComponentRequests::AnimatablePropertyAddress animatableAddress(m_componentId, track->GetParameterType().GetName());
Maestro::SequenceComponentRequestBus::Event(m_pSequence->GetSequenceEntityId(), &Maestro::SequenceComponentRequestBus::Events::GetAnimatedPropertyValue, currValue, GetParentAzEntityId(), animatableAddress);
if (currTrackValue != currValue.GetFloatValue())
{
keyIdx = track->FindKey(time);
if (keyIdx == -1)
{
keyIdx = track->CreateKey(time);
}
if (track->GetValueType() == AnimValueType::DiscreteFloat)
{
IDiscreteFloatKey key;
track->GetKey(keyIdx, &key);
key.SetValue(currValue.GetFloatValue());
}
else
{
I2DBezierKey key;
track->GetKey(keyIdx, &key);
key.value.y = currValue.GetFloatValue();
track->SetKey(keyIdx, &key);
}
retNumKeysSet++;
}
return retNumKeysSet;
}
int CAnimComponentNode::SetKeysForChangedVector3TrackValue(IAnimTrack* track, [[maybe_unused]] int keyIdx, float time, bool applyTrackMultiplier, float isChangedTolerance)
{
int retNumKeysSet = 0;
AZ::Vector3 currTrackValue;
track->GetValue(time, currTrackValue, applyTrackMultiplier);
Maestro::SequenceComponentRequests::AnimatedVector3Value currValue(currTrackValue);
Maestro::SequenceComponentRequests::AnimatablePropertyAddress animatableAddress(m_componentId, track->GetParameterType().GetName());
Maestro::SequenceComponentRequestBus::Event(m_pSequence->GetSequenceEntityId(), &Maestro::SequenceComponentRequestBus::Events::GetAnimatedPropertyValue, currValue, GetParentAzEntityId(), animatableAddress);
AZ::Vector3 currVector3Value;
currValue.GetValue(currVector3Value);
if (!currTrackValue.IsClose(currVector3Value, isChangedTolerance))
{
// track will be a CCompoundSplineTrack. For these we can simply call SetValue at the and keys will be added if needed.
track->SetValue(time, currVector3Value, false, applyTrackMultiplier);
retNumKeysSet++; // we treat the compound vector as a single key for simplicity and speed - if needed, we can go through each component and count them up if this is important.
}
return retNumKeysSet;
}
int CAnimComponentNode::SetKeysForChangedQuaternionTrackValue(IAnimTrack* track, [[maybe_unused]] int keyIdx, float time)
{
int retNumKeysSet = 0;
AZ::Quaternion currTrackValue;
track->GetValue(time, currTrackValue);
Maestro::SequenceComponentRequests::AnimatedQuaternionValue currValue(currTrackValue);
Maestro::SequenceComponentRequests::AnimatablePropertyAddress animatableAddress(m_componentId, track->GetParameterType().GetName());
Maestro::SequenceComponentRequestBus::Event(m_pSequence->GetSequenceEntityId(), &Maestro::SequenceComponentRequestBus::Events::GetAnimatedPropertyValue, currValue, GetParentAzEntityId(), animatableAddress);
AZ::Quaternion currQuaternionValue;
currValue.GetValue(currQuaternionValue);
if (!currTrackValue.IsClose(currQuaternionValue))
{
// track will be a CCompoundSplineTrack. For these we can simply call SetValue at the and keys will be added if needed.
track->SetValue(time, currQuaternionValue, false);
retNumKeysSet++; // we treat the compound vector as a single key for simplicity and speed - if needed, we can go through each component and count them up if this is important.
}
return retNumKeysSet;
}
//////////////////////////////////////////////////////////////////////////
int CAnimComponentNode::SetKeysForChangedTrackValues(float time)
{
int retNumKeysSet = 0;
for (int i = GetTrackCount(); --i >= 0;)
{
IAnimTrack* track = GetTrackByIndex(i);
int keyIdx = -1;
switch (track->GetValueType())
{
case AnimValueType::Bool:
retNumKeysSet += SetKeysForChangedBoolTrackValue(track, keyIdx, time);
break;
case AnimValueType::Float:
case AnimValueType::DiscreteFloat:
retNumKeysSet += SetKeysForChangedFloatTrackValue(track, keyIdx, time);
break;
case AnimValueType::RGB:
retNumKeysSet += SetKeysForChangedVector3TrackValue(track, keyIdx, time, true, (1.0f) / 255.0f);
break;
case AnimValueType::Vector:
retNumKeysSet += SetKeysForChangedVector3TrackValue(track, keyIdx, time, true);
break;
case AnimValueType::Quat:
retNumKeysSet += SetKeysForChangedQuaternionTrackValue(track, keyIdx, time);
break;
case AnimValueType::Vector4:
AZ_Warning("TrackView", false, "Vector4's are not supported for recording.");
break;
}
}
return retNumKeysSet;
}
//////////////////////////////////////////////////////////////////////////
void CAnimComponentNode::OnStartPlayInEditor()
{
// reset key states for entering AI/Physics SIM mode
ForceAnimKeyChangeInCharacterTrackAnimator();
}
void CAnimComponentNode::OnStopPlayInEditor()
{
// reset key states for returning to Editor mode
ForceAnimKeyChangeInCharacterTrackAnimator();
}
void CAnimComponentNode::SetNodeOwner(IAnimNodeOwner* pOwner)
{
CAnimNode::SetNodeOwner(pOwner);
if (pOwner && gEnv->IsEditor())
{
// SetNodeOwner is called when a node is added on undo/redo - we have to update dynamic params in such a case
UpdateDynamicParams();
}
}
//////////////////////////////////////////////////////////////////////////
void CAnimComponentNode::GetParentWorldTransform(AZ::Transform& retTransform) const
{
AZ::EntityId parentId;
AZ::TransformBus::EventResult(parentId, GetParentAzEntityId(), &AZ::TransformBus::Events::GetParentId);
if (parentId.IsValid())
{
AZ::TransformBus::EventResult(retTransform, parentId, &AZ::TransformBus::Events::GetWorldTM);
}
}
//////////////////////////////////////////////////////////////////////////
void CAnimComponentNode::ConvertBetweenWorldAndLocalPosition(Vec3& position, ETransformSpaceConversionDirection conversionDirection) const
{
AZ::Vector3 pos(position.x, position.y, position.z);
AZ::Transform parentTransform = AZ::Transform::Identity();
GetParentWorldTransform(parentTransform);
if (conversionDirection == eTransformConverstionDirection_toLocalSpace)
{
parentTransform.Invert();
}
pos = parentTransform.TransformPoint(pos);
position.Set(pos.GetX(), pos.GetY(), pos.GetZ());
}
//////////////////////////////////////////////////////////////////////////
void CAnimComponentNode::ConvertBetweenWorldAndLocalRotation(Quat& rotation, ETransformSpaceConversionDirection conversionDirection) const
{
AZ::Quaternion rot(rotation.v.x, rotation.v.y, rotation.v.z, rotation.w);
AZ::Transform rotTransform = AZ::Transform::CreateFromQuaternion(rot);
rotTransform.ExtractUniformScale();
AZ::Transform parentTransform = AZ::Transform::Identity();
GetParentWorldTransform(parentTransform);
parentTransform.ExtractUniformScale();
if (conversionDirection == eTransformConverstionDirection_toLocalSpace)
{
parentTransform.Invert();
}
rotTransform = parentTransform * rotTransform;
rot = rotTransform.GetRotation();
rotation = Quat(rot);
}
//////////////////////////////////////////////////////////////////////////
void CAnimComponentNode::ConvertBetweenWorldAndLocalScale(Vec3& scale, ETransformSpaceConversionDirection conversionDirection) const
{
AZ::Transform parentTransform = AZ::Transform::Identity();
AZ::Transform scaleTransform = AZ::Transform::CreateUniformScale(AZ::Vector3(scale.x, scale.y, scale.z).GetMaxElement());
GetParentWorldTransform(parentTransform);
if (conversionDirection == eTransformConverstionDirection_toLocalSpace)
{
parentTransform.Invert();
}
scaleTransform = parentTransform * scaleTransform;
const float uniformScale = scaleTransform.GetUniformScale();
scale.Set(uniformScale, uniformScale, uniformScale);
}
//////////////////////////////////////////////////////////////////////////
void CAnimComponentNode::SetPos(float time, const Vec3& pos)
{
if (m_componentTypeId == AZ::Uuid(AZ::EditorTransformComponentTypeId) || m_componentTypeId == AzFramework::TransformComponent::TYPEINFO_Uuid())
{
bool bDefault = !(gEnv->pMovieSystem->IsRecording() && (GetParent()->GetFlags() & eAnimNodeFlags_EntitySelected)); // Only selected nodes can be recorded
IAnimTrack* posTrack = GetTrackForParameter(AnimParamType::Position);
if (posTrack)
{
// pos is in world position, even if the entity is parented - because Component Entity AZ::Transforms do not correctly set
// CBaseObject parenting. This should probably be fixed, but for now, we explicitly change from World to Local space here
Vec3 localPos(pos);
ConvertBetweenWorldAndLocalPosition(localPos, eTransformConverstionDirection_toLocalSpace);
posTrack->SetValue(time, localPos, bDefault);
}
if (!bDefault)
{
GetCMovieSystem()->Callback(IMovieCallback::CBR_CHANGETRACK, this);
}
}
}
Vec3 CAnimComponentNode::GetPos()
{
Maestro::SequenceComponentRequests::AnimatablePropertyAddress animatableAddress(m_componentId, "Position");
Maestro::SequenceComponentRequests::AnimatedVector3Value posValue(AZ::Vector3::CreateZero());
Maestro::SequenceComponentRequestBus::Event(m_pSequence->GetSequenceEntityId(), &Maestro::SequenceComponentRequestBus::Events::GetAnimatedPropertyValue, posValue, GetParentAzEntityId(), animatableAddress);
// Always return world position because Component Entity AZ::Transforms do not correctly set
// CBaseObject parenting. This should probably be fixed, but for now, we explicitly change from Local to World space here.
Vec3 worldPos(posValue.GetVector3Value());
ConvertBetweenWorldAndLocalPosition(worldPos, eTransformConverstionDirection_toWorldSpace);
return worldPos;
}
//////////////////////////////////////////////////////////////////////////
void CAnimComponentNode::SetRotate(float time, const Quat& rotation)
{
if (m_componentTypeId == AZ::Uuid(AZ::EditorTransformComponentTypeId) || m_componentTypeId == AzFramework::TransformComponent::TYPEINFO_Uuid())
{
bool bDefault = !(gEnv->pMovieSystem->IsRecording() && (GetParent()->GetFlags() & eAnimNodeFlags_EntitySelected)); // Only selected nodes can be recorded
IAnimTrack* rotTrack = GetTrackForParameter(AnimParamType::Rotation);
if (rotTrack)
{
// Rotation is in world space, even if the entity is parented - because Component Entity AZ::Transforms do not correctly set
// CBaseObject parenting, so we convert it to Local space here. This should probably be fixed, but for now, we explicitly change from World to Local space here.
Quat localRot(rotation);
ConvertBetweenWorldAndLocalRotation(localRot, eTransformConverstionDirection_toLocalSpace);
rotTrack->SetValue(time, localRot, bDefault);
}
if (!bDefault)
{
GetCMovieSystem()->Callback(IMovieCallback::CBR_CHANGETRACK, this);
}
}
}
Quat CAnimComponentNode::GetRotate(float time)
{
Quat worldRot;
// If there is rotation track data, get the rotation from there.
// Otherwise just use the current entity rotation value.
IAnimTrack* rotTrack = GetTrackForParameter(AnimParamType::Rotation);
if (rotTrack != nullptr && rotTrack->GetNumKeys() > 0)
{
rotTrack->GetValue(time, worldRot);
// Track values are always stored as relative to the parent (local), so convert to world.
ConvertBetweenWorldAndLocalRotation(worldRot, eTransformConverstionDirection_toWorldSpace);
}
else
{
worldRot = GetRotate();
}
return worldRot;
}
Quat CAnimComponentNode::GetRotate()
{
Maestro::SequenceComponentRequests::AnimatablePropertyAddress animatableAddress(m_componentId, "Rotation");
Maestro::SequenceComponentRequests::AnimatedQuaternionValue rotValue(AZ::Quaternion::CreateIdentity());
Maestro::SequenceComponentRequestBus::Event(m_pSequence->GetSequenceEntityId(), &Maestro::SequenceComponentRequestBus::Events::GetAnimatedPropertyValue, rotValue, GetParentAzEntityId(), animatableAddress);
// Always return world rotation because Component Entity AZ::Transforms do not correctly set
// CBaseObject parenting. This should probably be fixed, but for now, we explicitly change from Local to World space here.
Quat worldRot(rotValue.GetQuaternionValue());
ConvertBetweenWorldAndLocalRotation(worldRot, eTransformConverstionDirection_toWorldSpace);
return worldRot;
}
//////////////////////////////////////////////////////////////////////////
void CAnimComponentNode::SetScale(float time, const Vec3& scale)
{
if (m_componentTypeId == AZ::Uuid(AZ::EditorTransformComponentTypeId) || m_componentTypeId == AzFramework::TransformComponent::TYPEINFO_Uuid())
{
bool bDefault = !(gEnv->pMovieSystem->IsRecording() && (GetParent()->GetFlags() & eAnimNodeFlags_EntitySelected)); // Only selected nodes can be recorded
IAnimTrack* scaleTrack = GetTrackForParameter(AnimParamType::Scale);
if (scaleTrack)
{
// Scale is in World space, even if the entity is parented - because Component Entity AZ::Transforms do not correctly set
// CBaseObject parenting, so we convert it to Local space here. This should probably be fixed, but for now, we explicitly change from World to Local space here.
Vec3 localScale(scale);
ConvertBetweenWorldAndLocalScale(localScale, eTransformConverstionDirection_toLocalSpace);
scaleTrack->SetValue(time, localScale, bDefault);
}
if (!bDefault)
{
GetCMovieSystem()->Callback(IMovieCallback::CBR_CHANGETRACK, this);
}
}
}
Vec3 CAnimComponentNode::GetScale()
{
Maestro::SequenceComponentRequests::AnimatablePropertyAddress animatableAddress(m_componentId, "Scale");
Maestro::SequenceComponentRequests::AnimatedVector3Value scaleValue(AZ::Vector3::CreateZero());
Maestro::SequenceComponentRequestBus::Event(m_pSequence->GetSequenceEntityId(), &Maestro::SequenceComponentRequestBus::Events::GetAnimatedPropertyValue, scaleValue, GetParentAzEntityId(), animatableAddress);
// Always return World scale because Component Entity AZ::Transforms do not correctly set
// CBaseObject parenting. This should probably be fixed, but for now, we explicitly change from Local to World space here.
Vec3 worldScale(scaleValue.GetVector3Value());
ConvertBetweenWorldAndLocalScale(worldScale, eTransformConverstionDirection_toWorldSpace);
return worldScale;
}
void CAnimComponentNode::Activate(bool bActivate)
{
// Connect to EditorSequenceAgentComponentNotificationBus. The Sequence Agent Component
// is always added to the Entity that is being animated aka the entity at GetParentAzEntityId().
if (bActivate)
{
Maestro::EditorSequenceAgentComponentNotificationBus::Handler::BusConnect(GetParentAzEntityId());
}
else
{
Maestro::EditorSequenceAgentComponentNotificationBus::Handler::BusDisconnect();
}
}
///////////////////////////////////////////////////////////////////////////
void CAnimComponentNode::OnSequenceAgentConnected()
{
// Whenever the Sequence Agent is connected to the Sequence, refresh the params.
// This is redundant in most cases, but sometimes depending on the order of entity activation
// a slice may be activated while a animated entity with the Sequence Agent is not active
// (and thus not connected to the Sequence). This happens during save slice overrides.
UpdateDynamicParamsInternal();
}
///////////////////////////////////////////////////////////////////////////
void CAnimComponentNode::ForceAnimKeyChangeInCharacterTrackAnimator()
{
if (m_characterTrackAnimator)
{
IAnimTrack* animTrack = GetTrackForParameter(AnimParamType::Animation);
if (animTrack && animTrack->HasKeys())
{
// resets anim key change states so animation will update correctly on the next Animate()
m_characterTrackAnimator->ForceAnimKeyChange();
}
}
}
//////////////////////////////////////////////////////////////////////////
IAnimTrack* CAnimComponentNode::CreateTrack(const CAnimParamType& paramType)
{
IAnimTrack* retTrack = CAnimNode::CreateTrack(paramType);
if (retTrack)
{
SetTrackMultiplier(retTrack);
if (paramType.GetType() == AnimParamType::Animation && !m_characterTrackAnimator)
{
m_characterTrackAnimator = new CCharacterTrackAnimator();
}
}
return retTrack;
}
//////////////////////////////////////////////////////////////////////////
bool CAnimComponentNode::RemoveTrack(IAnimTrack* pTrack)
{
if (pTrack && pTrack->GetParameterType().GetType() == AnimParamType::Animation && m_characterTrackAnimator)
{
delete m_characterTrackAnimator;
m_characterTrackAnimator = nullptr;
}
return CAnimNode::RemoveTrack(pTrack);
}
//////////////////////////////////////////////////////////////////////////
/// @deprecated Serialization for Sequence data in Component Entity Sequences now occurs through AZ::SerializeContext and the Sequence Component
void CAnimComponentNode::Serialize(XmlNodeRef& xmlNode, bool bLoading, bool bLoadEmptyTracks)
{
CAnimNode::Serialize(xmlNode, bLoading, bLoadEmptyTracks);
if (bLoading)
{
XmlString uuidString;
xmlNode->getAttr("ComponentId", m_componentId);
if (xmlNode->getAttr("ComponentTypeId", uuidString))
{
m_componentTypeId = uuidString.c_str();
}
else
{
m_componentTypeId = AZ::Uuid::CreateNull();
}
}
else
{
// saving
char uuidStringBuf[AZ::Uuid::MaxStringBuffer] = { 0 };
xmlNode->setAttr("ComponentId", m_componentId);
m_componentTypeId.ToString(uuidStringBuf, AZ::Uuid::MaxStringBuffer);
xmlNode->setAttr("ComponentTypeId", uuidStringBuf);
}
}
//////////////////////////////////////////////////////////////////////////
// Property Value types are detected in this function
void CAnimComponentNode::AddPropertyToParamInfoMap(const CAnimParamType& paramType)
{
BehaviorPropertyInfo propertyInfo; // the default value type is AnimValueType::Float
{
// property is handled by Component animation (Behavior Context getter/setters). Regardless of the param Type, it must have a non-empty name
// (the VirtualProperty name)
AZ_Assert(paramType.GetName() && strlen(paramType.GetName()), "All AnimParamTypes animated on Components must have a name for its VirtualProperty");
// Initialize property name string, which sets to AnimParamType::ByString by default
propertyInfo = paramType.GetName();
if (paramType.GetType() != AnimParamType::ByString)
{
// This sets the eAnimParamType enumeration but leaves the string name untouched
propertyInfo.m_animNodeParamInfo.paramType = paramType.GetType();
}
//////////////////////////////////////////////////////////////////////////////////////////////////
//! Detect the value type from reflection in the Behavior Context
//
// Query the property type Id from the Sequence Component and set it if a supported type is found
AZ::Uuid propertyTypeId = AZ::Uuid::CreateNull();
Maestro::SequenceComponentRequests::AnimatablePropertyAddress propertyAddress(m_componentId, propertyInfo.m_displayName.c_str());
Maestro::SequenceComponentRequestBus::EventResult(propertyTypeId, m_pSequence->GetSequenceEntityId(), &Maestro::SequenceComponentRequestBus::Events::GetAnimatedAddressTypeId,
GetParentAzEntityId(), propertyAddress);
if (propertyTypeId == AZ::Vector3::TYPEINFO_Uuid())
{
propertyInfo.m_animNodeParamInfo.valueType = AnimValueType::Vector;
}
else if (propertyTypeId == AZ::Color::TYPEINFO_Uuid())
{
propertyInfo.m_animNodeParamInfo.valueType = AnimValueType::RGB;
}
else if (propertyTypeId == AZ::Quaternion::TYPEINFO_Uuid())
{
propertyInfo.m_animNodeParamInfo.valueType = AnimValueType::Quat;
}
else if (propertyTypeId == AZ::AzTypeInfo<bool>::Uuid())
{
propertyInfo.m_animNodeParamInfo.valueType = AnimValueType::Bool;
}
// Special case, if an AssetId property named "Motion" is found, create an AssetBlend.
// The Simple Motion Component exposes a virtual property named "motion" of type AssetId.
// We it is detected here create an AssetBlend type in Track View. The Asset Blend has special
// UI and will be used to drive mulitple properties on this component, not just the motion AssetId.
else if (propertyTypeId == AZ::Data::AssetId::TYPEINFO_Uuid() && 0 == azstricmp(paramType.GetName(), "motion"))
{
propertyInfo.m_animNodeParamInfo.valueType = AnimValueType::AssetBlend;
}
// the fall-through default type is propertyInfo.m_animNodeParamInfo.valueType = AnimValueType::Float
}
m_paramTypeToBehaviorPropertyInfoMap[paramType] = propertyInfo;
}
//////////////////////////////////////////////////////////////////////////
void CAnimComponentNode::Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<CAnimComponentNode, CAnimNode>()
->Version(1)
->Field("ComponentID", &CAnimComponentNode::m_componentId)
->Field("ComponentTypeID", &CAnimComponentNode::m_componentTypeId);
}
}
//////////////////////////////////////////////////////////////////////////
void CAnimComponentNode::UpdateDynamicParams_Editor()
{
IAnimNode::AnimParamInfos animatableParams;
// add all parameters supported by the component
Maestro::EditorSequenceComponentRequestBus::Event(m_pSequence->GetSequenceEntityId(), &Maestro::EditorSequenceComponentRequestBus::Events::GetAllAnimatablePropertiesForComponent,
animatableParams, GetParentAzEntityId(), m_componentId);
for (int i = 0; i < animatableParams.size(); i++)
{
AddPropertyToParamInfoMap(animatableParams[i].paramType);
}
}
//////////////////////////////////////////////////////////////////////////
void CAnimComponentNode::UpdateDynamicParams_Game()
{
// Fill m_paramTypeToBehaviorPropertyInfoMap based on our animated tracks
for (uint32 i = 0; i < m_tracks.size(); ++i)
{
AddPropertyToParamInfoMap(m_tracks[i]->GetParameterType());
}
}
//////////////////////////////////////////////////////////////////////////
void CAnimComponentNode::UpdateDynamicParamsInternal()
{
m_paramTypeToBehaviorPropertyInfoMap.clear();
// editor stores *all* properties of *every* entity used in an AnimEntityNode.
// In pure game mode we just need to store the properties that we know are going to be used in a track, so we can save a lot of memory.
if (gEnv->IsEditor() && !gEnv->IsEditorSimulationMode() && !gEnv->IsEditorGameMode())
{
UpdateDynamicParams_Editor();
}
else
{
UpdateDynamicParams_Game();
}
// Go through all tracks and set Multipliers if required
for (uint32 i = 0; i < m_tracks.size(); ++i)
{
AZStd::intrusive_ptr<IAnimTrack> track = m_tracks[i];
SetTrackMultiplier(track.get());
}
}
//////////////////////////////////////////////////////////////////////////
void CAnimComponentNode::InitializeTrackDefaultValue(IAnimTrack* pTrack, const CAnimParamType& paramType)
{
// Initialize new track to property value
if (paramType.GetType() == AnimParamType::ByString && pTrack)
{
auto findIter = m_paramTypeToBehaviorPropertyInfoMap.find(paramType);
if (findIter != m_paramTypeToBehaviorPropertyInfoMap.end())
{
BehaviorPropertyInfo& propertyInfo = findIter->second;
Maestro::SequenceComponentRequests::AnimatablePropertyAddress address(m_componentId, propertyInfo.m_animNodeParamInfo.name);
switch (pTrack->GetValueType())
{
case AnimValueType::Float:
{
Maestro::SequenceComponentRequests::AnimatedFloatValue defaultValue(.0f);
Maestro::SequenceComponentRequestBus::Event(m_pSequence->GetSequenceEntityId(), &Maestro::SequenceComponentRequestBus::Events::GetAnimatedPropertyValue, defaultValue, GetParentAzEntityId(), address);
pTrack->SetValue(0, defaultValue.GetFloatValue(), true);
break;
}
case AnimValueType::Vector:
{
Maestro::SequenceComponentRequests::AnimatedVector3Value defaultValue(AZ::Vector3::CreateZero());
AZ::Vector3 vector3Value = AZ::Vector3::CreateZero();
Maestro::SequenceComponentRequestBus::Event(m_pSequence->GetSequenceEntityId(), &Maestro::SequenceComponentRequestBus::Events::GetAnimatedPropertyValue, defaultValue, GetParentAzEntityId(), address);
defaultValue.GetValue(vector3Value);
pTrack->SetValue(0, Vec3(vector3Value.GetX(), vector3Value.GetY(), vector3Value.GetZ()), true);
break;
}
case AnimValueType::Quat:
{
Maestro::SequenceComponentRequests::AnimatedQuaternionValue defaultValue(AZ::Quaternion::CreateIdentity());
Maestro::SequenceComponentRequestBus::Event(m_pSequence->GetSequenceEntityId(), &Maestro::SequenceComponentRequestBus::Events::GetAnimatedPropertyValue, defaultValue, GetParentAzEntityId(), address);
pTrack->SetValue(0, Quat(defaultValue.GetQuaternionValue()), true);
break;
}
case AnimValueType::RGB:
{
Maestro::SequenceComponentRequests::AnimatedVector3Value defaultValue(AZ::Vector3::CreateOne());
AZ::Vector3 vector3Value = AZ::Vector3::CreateOne();
Maestro::SequenceComponentRequestBus::Event(m_pSequence->GetSequenceEntityId(), &Maestro::SequenceComponentRequestBus::Events::GetAnimatedPropertyValue, defaultValue, GetParentAzEntityId(), address);
defaultValue.GetValue(vector3Value);
pTrack->SetValue(0, Vec3(clamp_tpl((float)vector3Value.GetX(), .0f, 1.0f), clamp_tpl((float)vector3Value.GetY(), .0f, 1.0f), clamp_tpl((float)vector3Value.GetZ(), .0f, 1.0f)), /*setDefault=*/ true, /*applyMultiplier=*/ true);
break;
}
case AnimValueType::Bool:
{
Maestro::SequenceComponentRequests::AnimatedBoolValue defaultValue(true);
Maestro::SequenceComponentRequestBus::Event(m_pSequence->GetSequenceEntityId(), &Maestro::SequenceComponentRequestBus::Events::GetAnimatedPropertyValue, defaultValue, GetParentAzEntityId(), address);
pTrack->SetValue(0, defaultValue.GetBoolValue(), true);
break;
}
case AnimValueType::AssetBlend:
{
// Just init to an empty value.
Maestro::AssetBlends<AZ::Data::AssetData> assetData;
pTrack->SetValue(0, assetData, true);
break;
}
default:
{
AZ_Warning("TrackView", false, "Unsupported value type requested for Component Node Track %s, skipping...", paramType.GetName());
break;
}
}
}
}
}
//////////////////////////////////////////////////////////////////////////
void CAnimComponentNode::Animate(SAnimContext& ac)
{
if (m_skipComponentAnimationUpdates)
{
return;
}
// Evaluate all tracks
// indices used for character animation (SimpleAnimationComponent)
int characterAnimationLayer = 0;
int characterAnimationTrackIdx = 0;
int trackCount = NumTracks();
for (int paramIndex = 0; paramIndex < trackCount; paramIndex++)
{
CAnimParamType paramType = m_tracks[paramIndex]->GetParameterType();
IAnimTrack* pTrack = m_tracks[paramIndex].get();
if ((pTrack->HasKeys() == false) || (pTrack->GetFlags() & IAnimTrack::eAnimTrackFlags_Disabled) || pTrack->IsMasked(ac.trackMask))
{
continue;
}
if (!ac.resetting)
{
if (paramType.GetType() == AnimParamType::Animation)
{
// special handling for Character Animation. We short-circuit the SimpleAnimation behavior using m_characterTrackAnimator
if (!m_characterTrackAnimator)
{
m_characterTrackAnimator = new CCharacterTrackAnimator;
}
if (characterAnimationLayer < MAX_CHARACTER_TRACKS + ADDITIVE_LAYERS_OFFSET)
{
int index = characterAnimationLayer;
CCharacterTrack* pCharTrack = (CCharacterTrack*)pTrack;
if (pCharTrack->GetAnimationLayerIndex() >= 0) // If the track has an animation layer specified,
{
assert(pCharTrack->GetAnimationLayerIndex() < 16);
index = pCharTrack->GetAnimationLayerIndex(); // use it instead.
}
m_characterTrackAnimator->AnimateTrack(pCharTrack, ac, index, characterAnimationTrackIdx);
if (characterAnimationLayer == 0)
{
characterAnimationLayer += ADDITIVE_LAYERS_OFFSET;
}
++characterAnimationLayer;
++characterAnimationTrackIdx;
}
}
else
{
// handle all other non-specialized Components
auto findIter = m_paramTypeToBehaviorPropertyInfoMap.find(paramType);
if (findIter != m_paramTypeToBehaviorPropertyInfoMap.end())
{
BehaviorPropertyInfo& propertyInfo = findIter->second;
Maestro::SequenceComponentRequests::AnimatablePropertyAddress animatableAddress(m_componentId, propertyInfo.m_animNodeParamInfo.name);
switch (pTrack->GetValueType())
{
case AnimValueType::Float:
{
if (pTrack->HasKeys())
{
float floatValue = .0f;
pTrack->GetValue(ac.time, floatValue, /*applyMultiplier= */ true);
Maestro::SequenceComponentRequests::AnimatedFloatValue value(floatValue);
Maestro::SequenceComponentRequests::AnimatedFloatValue prevValue(floatValue);
Maestro::SequenceComponentRequestBus::Event(m_pSequence->GetSequenceEntityId(), &Maestro::SequenceComponentRequestBus::Events::GetAnimatedPropertyValue, prevValue, GetParentAzEntityId(), animatableAddress);
if (!value.IsClose(prevValue))
{
// only set the value if it's changed
Maestro::SequenceComponentRequestBus::Event(m_pSequence->GetSequenceEntityId(), &Maestro::SequenceComponentRequestBus::Events::SetAnimatedPropertyValue, GetParentAzEntityId(), animatableAddress, value);
}
}
break;
}
case AnimValueType::Vector: // fall-through
case AnimValueType::RGB:
{
float tolerance = AZ::Constants::FloatEpsilon;
Vec3 vec3Value(.0f, .0f, .0f);
pTrack->GetValue(ac.time, vec3Value, /*applyMultiplier= */ true);
AZ::Vector3 vector3Value(vec3Value.x, vec3Value.y, vec3Value.z);
if (pTrack->GetValueType() == AnimValueType::RGB)
{
vec3Value.x = clamp_tpl(vec3Value.x, 0.0f, 1.0f);
vec3Value.y = clamp_tpl(vec3Value.y, 0.0f, 1.0f);
vec3Value.z = clamp_tpl(vec3Value.z, 0.0f, 1.0f);
// set tolerance to just under 1 unit in normalized RGB space
tolerance = (1.0f - AZ::Constants::FloatEpsilon) / 255.0f;
}
Maestro::SequenceComponentRequests::AnimatedVector3Value value(AZ::Vector3(vec3Value.x, vec3Value.y, vec3Value.z));
Maestro::SequenceComponentRequests::AnimatedVector3Value prevValue(AZ::Vector3(vec3Value.x, vec3Value.y, vec3Value.z));
Maestro::SequenceComponentRequestBus::Event(m_pSequence->GetSequenceEntityId(), &Maestro::SequenceComponentRequestBus::Events::GetAnimatedPropertyValue, prevValue, GetParentAzEntityId(), animatableAddress);
AZ::Vector3 vector3PrevValue;
prevValue.GetValue(vector3PrevValue);
// Check sub-tracks for keys. If there are none, use the prevValue for that track (essentially making a non-keyed track a no-op)
vector3Value.Set(pTrack->GetSubTrack(0)->HasKeys() ? vector3Value.GetX() : vector3PrevValue.GetX(),
pTrack->GetSubTrack(1)->HasKeys() ? vector3Value.GetY() : vector3PrevValue.GetY(),
pTrack->GetSubTrack(2)->HasKeys() ? vector3Value.GetZ() : vector3PrevValue.GetZ());
value.SetValue(vector3Value);
if (!value.IsClose(prevValue, tolerance))
{
// only set the value if it's changed
Maestro::SequenceComponentRequestBus::Event(m_pSequence->GetSequenceEntityId(), &Maestro::SequenceComponentRequestBus::Events::SetAnimatedPropertyValue, GetParentAzEntityId(), animatableAddress, value);
}
break;
}
case AnimValueType::Quat:
{
if (pTrack->HasKeys())
{
float tolerance = AZ::Constants::FloatEpsilon;
AZ::Quaternion quaternionValue(AZ::Quaternion::CreateIdentity());
pTrack->GetValue(ac.time, quaternionValue);
Maestro::SequenceComponentRequests::AnimatedQuaternionValue value(quaternionValue);
Maestro::SequenceComponentRequests::AnimatedQuaternionValue prevValue(quaternionValue);
Maestro::SequenceComponentRequestBus::Event(m_pSequence->GetSequenceEntityId(), &Maestro::SequenceComponentRequestBus::Events::GetAnimatedPropertyValue, prevValue, GetParentAzEntityId(), animatableAddress);
AZ::Quaternion prevQuaternionValue;
prevValue.GetValue(prevQuaternionValue);
if (!prevQuaternionValue.IsClose(quaternionValue, tolerance))
{
// only set the value if it's changed
Maestro::SequenceComponentRequestBus::Event(m_pSequence->GetSequenceEntityId(), &Maestro::SequenceComponentRequestBus::Events::SetAnimatedPropertyValue, GetParentAzEntityId(), animatableAddress, value);
}
}
break;
}
case AnimValueType::Bool:
{
if (pTrack->HasKeys())
{
bool boolValue = true;
pTrack->GetValue(ac.time, boolValue);
Maestro::SequenceComponentRequests::AnimatedBoolValue value(boolValue);
Maestro::SequenceComponentRequests::AnimatedBoolValue prevValue(boolValue);
Maestro::SequenceComponentRequestBus::Event(m_pSequence->GetSequenceEntityId(), &Maestro::SequenceComponentRequestBus::Events::GetAnimatedPropertyValue, prevValue, GetParentAzEntityId(), animatableAddress);
if (!value.IsClose(prevValue))
{
// only set the value if it's changed
Maestro::SequenceComponentRequestBus::Event(m_pSequence->GetSequenceEntityId(), &Maestro::SequenceComponentRequestBus::Events::SetAnimatedPropertyValue, GetParentAzEntityId(), animatableAddress, value);
}
}
break;
}
case AnimValueType::AssetBlend:
{
if (pTrack->HasKeys())
{
// Get the AssetBlends from the Track and update Properties on Component
Maestro::AssetBlends<AZ::Data::AssetData> assetBlendValue;
pTrack->GetValue(ac.time, assetBlendValue);
AnimateAssetBlendSubProperties(assetBlendValue);
}
break;
}
default:
{
AZ_Warning("TrackView", false, "Unsupported value type %d requested for Component Node Track %s, skipping...", pTrack->GetValueType(), paramType.GetName());
break;
}
}
}
}
}
}
if (m_pOwner)
{
m_bIgnoreSetParam = true; // Prevents feedback change of track.
m_pOwner->OnNodeAnimated(this);
m_bIgnoreSetParam = false;
}
}
//////////////////////////////////////////////////////////////////////////
void CAnimComponentNode::AnimateAssetBlendSubProperties(const Maestro::AssetBlends<AZ::Data::AssetData>& assetBlendValue)
{
// These are the params to set for the Simple Motion Component
bool previewInEditor = true;
float playTime = 0.0f;
float playSpeed = 0.0f;
AZ::Data::AssetId assetId;
float blendInTime = 0.0f;
float blendOutTime = 0.0f;
// Populate params based on the last AssetBlend found.
// So new keys will be picked up and played on top of currently
// playing animations (resulting in a blend).
if (assetBlendValue.m_assetBlends.size() > 0)
{
Maestro::AssetBlend assetData = assetBlendValue.m_assetBlends.back();
playTime = assetData.m_time;
assetId = assetData.m_assetId;
blendInTime = assetData.m_blendInTime;
blendOutTime = assetData.m_blendOutTime;
}
// Set Preview in Editor
Maestro::SequenceComponentRequests::AnimatablePropertyAddress previewInEditorAnimatableAddress(m_componentId, "PreviewInEditor");
Maestro::SequenceComponentRequests::AnimatedFloatValue prevPreviewInEditorValue(previewInEditor);
Maestro::SequenceComponentRequestBus::Event(m_pSequence->GetSequenceEntityId(), &Maestro::SequenceComponentRequestBus::Events::GetAnimatedPropertyValue, prevPreviewInEditorValue, GetParentAzEntityId(), previewInEditorAnimatableAddress);
Maestro::SequenceComponentRequests::AnimatedFloatValue previewInEditorValue(previewInEditor);
if (!previewInEditorValue.IsClose(prevPreviewInEditorValue))
{
Maestro::SequenceComponentRequestBus::Event(m_pSequence->GetSequenceEntityId(), &Maestro::SequenceComponentRequestBus::Events::SetAnimatedPropertyValue, GetParentAzEntityId(), previewInEditorAnimatableAddress, previewInEditorValue);
}
// Set Blend In Time before Motion so the Blend In will be used on the Motion that is about to Play.
Maestro::SequenceComponentRequests::AnimatablePropertyAddress blendInTimeAnimatableAddress(m_componentId, "BlendInTime");
Maestro::SequenceComponentRequests::AnimatedFloatValue prevBlendInTimeValue(blendInTime);
Maestro::SequenceComponentRequestBus::Event(m_pSequence->GetSequenceEntityId(), &Maestro::SequenceComponentRequestBus::Events::GetAnimatedPropertyValue, prevBlendInTimeValue, GetParentAzEntityId(), blendInTimeAnimatableAddress);
Maestro::SequenceComponentRequests::AnimatedFloatValue blendInTimeValue(blendInTime);
if (!blendInTimeValue.IsClose(prevBlendInTimeValue))
{
Maestro::SequenceComponentRequestBus::Event(m_pSequence->GetSequenceEntityId(), &Maestro::SequenceComponentRequestBus::Events::SetAnimatedPropertyValue, GetParentAzEntityId(), blendInTimeAnimatableAddress, blendInTimeValue);
}
// Set Motion
Maestro::SequenceComponentRequests::AnimatablePropertyAddress motionAnimatableAddress(m_componentId, "Motion");
Maestro::SequenceComponentRequests::AnimatedAssetIdValue prevMotionValue(assetId);
Maestro::SequenceComponentRequestBus::Event(m_pSequence->GetSequenceEntityId(), &Maestro::SequenceComponentRequestBus::Events::GetAnimatedPropertyValue, prevMotionValue, GetParentAzEntityId(), motionAnimatableAddress);
Maestro::SequenceComponentRequests::AnimatedAssetIdValue motionValue(assetId);
if (!motionValue.IsClose(prevMotionValue))
{
Maestro::SequenceComponentRequestBus::Event(m_pSequence->GetSequenceEntityId(), &Maestro::SequenceComponentRequestBus::Events::SetAnimatedPropertyValue, GetParentAzEntityId(), motionAnimatableAddress, motionValue);
}
// Set Blend Out Time after Motion so the Blend Out will not be used on Play, but instead used on that 'last' Motion 'Stop' as fade out.
Maestro::SequenceComponentRequests::AnimatablePropertyAddress blendOutTimeAnimatableAddress(m_componentId, "BlendOutTime");
Maestro::SequenceComponentRequests::AnimatedFloatValue prevBlendOutTimeValue(blendOutTime);
Maestro::SequenceComponentRequestBus::Event(m_pSequence->GetSequenceEntityId(), &Maestro::SequenceComponentRequestBus::Events::GetAnimatedPropertyValue, prevBlendOutTimeValue, GetParentAzEntityId(), blendOutTimeAnimatableAddress);
Maestro::SequenceComponentRequests::AnimatedFloatValue blendOutTimeValue(blendOutTime);
if (!blendOutTimeValue.IsClose(prevBlendOutTimeValue))
{
Maestro::SequenceComponentRequestBus::Event(m_pSequence->GetSequenceEntityId(), &Maestro::SequenceComponentRequestBus::Events::SetAnimatedPropertyValue, GetParentAzEntityId(), blendOutTimeAnimatableAddress, blendOutTimeValue);
}
// Set Play Time
Maestro::SequenceComponentRequests::AnimatablePropertyAddress playTimeAnimatableAddress(m_componentId, "PlayTime");
Maestro::SequenceComponentRequests::AnimatedFloatValue prevPlayTimeValue(playTime);
Maestro::SequenceComponentRequestBus::Event(m_pSequence->GetSequenceEntityId(), &Maestro::SequenceComponentRequestBus::Events::GetAnimatedPropertyValue, prevPlayTimeValue, GetParentAzEntityId(), playTimeAnimatableAddress);
Maestro::SequenceComponentRequests::AnimatedFloatValue playTimeValue(playTime);
if (!playTimeValue.IsClose(prevPlayTimeValue))
{
Maestro::SequenceComponentRequestBus::Event(m_pSequence->GetSequenceEntityId(), &Maestro::SequenceComponentRequestBus::Events::SetAnimatedPropertyValue, GetParentAzEntityId(), playTimeAnimatableAddress, playTimeValue);
}
// Set Play Speed
Maestro::SequenceComponentRequests::AnimatablePropertyAddress playSpeedAnimatableAddress(m_componentId, "PlaySpeed");
Maestro::SequenceComponentRequests::AnimatedFloatValue prevPlaySpeedValue(playSpeed);
Maestro::SequenceComponentRequestBus::Event(m_pSequence->GetSequenceEntityId(), &Maestro::SequenceComponentRequestBus::Events::GetAnimatedPropertyValue, prevPlaySpeedValue, GetParentAzEntityId(), playSpeedAnimatableAddress);
Maestro::SequenceComponentRequests::AnimatedFloatValue playSpeedValue(playSpeed);
if (!playSpeedValue.IsClose(prevPlaySpeedValue))
{
Maestro::SequenceComponentRequestBus::Event(m_pSequence->GetSequenceEntityId(), &Maestro::SequenceComponentRequestBus::Events::SetAnimatedPropertyValue, GetParentAzEntityId(), playSpeedAnimatableAddress, playSpeedValue);
}
}