Initial commit

This commit is contained in:
alexpete
2021-03-05 11:26:34 -08:00
commit a10351f38d
27091 changed files with 5521199 additions and 0 deletions
@@ -0,0 +1,105 @@
/*
* 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 <EMotionFX/Source/ActorManager.h>
#include <EMotionFX/Source/ActorInstance.h>
#include <EMotionFX/Source/EMotionFXManager.h>
#include <EMotionFX/Source/Importer/Importer.h>
#include <Integration/Assets/ActorAsset.h>
#include <Integration/Rendering/RenderBackendManager.h>
namespace EMotionFX
{
namespace Integration
{
AZ_CLASS_ALLOCATOR_IMPL(ActorAsset, EMotionFXAllocator, 0)
AZ_CLASS_ALLOCATOR_IMPL(ActorAssetHandler, EMotionFXAllocator, 0)
ActorAsset::ActorAsset(AZ::Data::AssetId id)
: EMotionFXAsset(id)
{}
ActorAsset::ActorInstancePtr ActorAsset::CreateInstance(AZ::Entity* entity)
{
AZ_Assert(m_emfxActor, "Actor asset is not loaded");
ActorInstancePtr actorInstance = ActorInstancePtr::MakeFromNew(EMotionFX::ActorInstance::Create(m_emfxActor.get(), entity));
if (actorInstance)
{
actorInstance->SetIsOwnedByRuntime(true);
}
return actorInstance;
}
void ActorAsset::SetData(AZStd::shared_ptr<Actor> actor)
{
m_emfxActor = AZStd::move(actor);
m_status = AZ::Data::AssetData::AssetStatus::Ready;
}
bool ActorAssetHandler::OnInitAsset(const AZ::Data::Asset<AZ::Data::AssetData>& asset)
{
ActorAsset* assetData = asset.GetAs<ActorAsset>();
Importer::ActorSettings actorSettings;
if (GetEMotionFX().GetEnableServerOptimization())
{
actorSettings.mOptimizeForServer = true;
}
assetData->m_emfxActor = EMotionFX::GetImporter().LoadActor(
assetData->m_emfxNativeData.data(),
assetData->m_emfxNativeData.size(),
&actorSettings,
"");
if (!assetData->m_emfxActor)
{
AZ_Error("EMotionFX", false, "Failed to initialize actor asset %s", asset.ToString<AZStd::string>().c_str());
return false;
}
assetData->m_emfxActor->SetIsOwnedByRuntime(true);
RenderBackend* renderBackend = AZ::Interface<RenderBackendManager>::Get()->GetRenderBackend();
assetData->m_renderActor.reset(renderBackend->CreateActor(assetData));
return static_cast<bool>(assetData->m_emfxActor);
}
AZ::Data::AssetType ActorAssetHandler::GetAssetType() const
{
return azrtti_typeid<ActorAsset>();
}
void ActorAssetHandler::GetAssetTypeExtensions(AZStd::vector<AZStd::string>& extensions)
{
extensions.push_back("actor");
}
AZ::Uuid ActorAssetHandler::GetComponentTypeId() const
{
// EditorActorComponent
return AZ::Uuid("{A863EE1B-8CFD-4EDD-BA0D-1CEC2879AD44}");
}
const char* ActorAssetHandler::GetAssetTypeDisplayName() const
{
return "EMotion FX Actor";
}
const char* ActorAssetHandler::GetBrowserIcon() const
{
return "Editor/Images/AssetBrowser/Actor_16.svg";
}
} //namespace Integration
} // namespace EMotionFX
@@ -0,0 +1,93 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzFramework/Asset/SimpleAsset.h>
#include <LmbrCentral/Rendering/MaterialAsset.h>
#include <Integration/Assets/AssetCommon.h>
#include <Integration/Rendering/RenderActor.h>
#include <EMotionFX/Source/AutoRegisteredActor.h>
namespace EMotionFX
{
class Actor;
class ActorInstance;
namespace Integration
{
/**
* Represents an EMotionFX actor asset.
* Each asset maintains storage of the original EMotionFX binary asset (via EMotionFXAsset base class).
* Initialization of the asset constructs Lumberyard rendering objects, such as the render mesh and material,
* directly from the instantiated EMotionFX actor.
* An easy future memory optimization is to wipe the EMotionFXAsset buffer after the actor, render meshes,
* and materials are created, since it's technically no longer necessary. At this stage it's worth keeping
* around for testing.
*/
class ActorAsset
: public EMotionFXAsset
{
public:
friend class ActorAssetHandler;
AZ_RTTI(ActorAsset, "{F67CC648-EA51-464C-9F5D-4A9CE41A7F86}", EMotionFXAsset)
AZ_CLASS_ALLOCATOR_DECL
ActorAsset(AZ::Data::AssetId id = AZ::Data::AssetId());
using MaterialList = AZStd::vector<AzFramework::SimpleAssetReference<LmbrCentral::MaterialAsset> >;
using ActorInstancePtr = EMotionFXPtr<EMotionFX::ActorInstance>;
ActorInstancePtr CreateInstance(AZ::Entity* entity);
Actor* GetActor() const { return m_emfxActor.get(); }
RenderActor* GetRenderActor() const { return m_renderActor.get(); }
void SetData(AZStd::shared_ptr<Actor> actor);
private:
AutoRegisteredActor m_emfxActor; ///< Pointer to shared EMotionFX actor
AZStd::unique_ptr<RenderActor> m_renderActor;
};
/**
* Asset handler for loading and initializing actor assets.
* The OnInitAsset stage constructs Lumberyard render meshes and materials by extracting
* said data from the EMotionFX actor.
*/
class ActorAssetHandler
: public EMotionFXAssetHandler<ActorAsset>
{
public:
AZ_CLASS_ALLOCATOR_DECL
bool OnInitAsset(const AZ::Data::Asset<AZ::Data::AssetData>& asset) override;
AZ::Data::AssetType GetAssetType() const override;
void GetAssetTypeExtensions(AZStd::vector<AZStd::string>& extensions) override;
AZ::Uuid GetComponentTypeId() const override;
const char* GetAssetTypeDisplayName() const override;
const char* GetBrowserIcon() const override;
};
} // namespace Integration
} // namespace EMotionFX
namespace AZ
{
AZ_TYPE_INFO_SPECIALIZE(EMotionFX::Integration::EMotionFXPtr<EMotionFX::Integration::ActorAsset>, "{3F60D391-F1C8-4A40-9946-A2637D088C48}");
AZ_TYPE_INFO_SPECIALIZE(EMotionFX::Integration::EMotionFXPtr<EMotionFX::ActorInstance>, "{169ACF47-3DEF-482A-AB7D-4CC11934D932}");
AZ_TYPE_INFO_SPECIALIZE(EMotionFX::ActorInstance, "{280A0170-EB6A-4E90-B2F1-E18D8EAEFB36}");
}
@@ -0,0 +1,169 @@
/*
* 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 "EMotionFX_precompiled.h"
#include <Integration/Assets/AnimGraphAsset.h>
#include <EMotionFX/Source/Allocators.h>
#include <EMotionFX/Source/AnimGraphManager.h>
namespace EMotionFX
{
namespace Integration
{
AZ_CLASS_ALLOCATOR_IMPL(AnimGraphAsset, EMotionFXAllocator, 0)
AZ_CLASS_ALLOCATOR_IMPL(AnimGraphAssetHandler, EMotionFXAllocator, 0)
AnimGraphAsset::AnimGraphAsset(AZ::Data::AssetId id)
: EMotionFXAsset(id)
{}
AnimGraphAsset::AnimGraphInstancePtr AnimGraphAsset::CreateInstance(
EMotionFX::ActorInstance* actorInstance,
EMotionFX::MotionSet* motionSet)
{
AZ_Assert(m_emfxAnimGraph, "Anim graph asset is not loaded");
auto animGraphInstance = EMotionFXPtr<EMotionFX::AnimGraphInstance>::MakeFromNew(
EMotionFX::AnimGraphInstance::Create(m_emfxAnimGraph.get(), actorInstance, motionSet));
if (animGraphInstance)
{
animGraphInstance->SetIsOwnedByRuntime(true);
}
return animGraphInstance;
}
void AnimGraphAsset::SetData(EMotionFX::AnimGraph* animGraph)
{
m_emfxAnimGraph.reset(animGraph);
m_status = AZ::Data::AssetData::AssetStatus::Ready;
}
//////////////////////////////////////////////////////////////////////////
bool AnimGraphAssetHandler::OnInitAsset(const AZ::Data::Asset<AZ::Data::AssetData>& asset)
{
AnimGraphAsset* assetData = asset.GetAs<AnimGraphAsset>();
assetData->m_emfxAnimGraph.reset(EMotionFX::GetImporter().LoadAnimGraph(
assetData->m_emfxNativeData.data(),
assetData->m_emfxNativeData.size(),
nullptr));
if (assetData->m_emfxAnimGraph)
{
assetData->m_emfxAnimGraph->SetIsOwnedByAsset(true);
assetData->m_emfxAnimGraph->SetIsOwnedByRuntime(true);
assetData->m_emfxAnimGraph->FindAndRemoveCycles();
// The following code is required to be set so the FileManager detects changes to the files loaded
// through this method. Once EMotionFX is integrated to the asset system this can go away.
AZStd::string assetFilename;
EBUS_EVENT_RESULT(assetFilename, AZ::Data::AssetCatalogRequestBus, GetAssetPathById, asset.GetId());
const char* devAssetsPath = AZ::IO::FileIOBase::GetInstance()->GetAlias("@devassets@");
if (devAssetsPath)
{
AZStd::string assetSourcePath = devAssetsPath;
AzFramework::StringFunc::AssetDatabasePath::Normalize(assetSourcePath);
AZStd::string filename;
AzFramework::StringFunc::AssetDatabasePath::Join(assetSourcePath.c_str(), assetFilename.c_str(), filename);
assetData->m_emfxAnimGraph->SetFileName(filename.c_str());
}
else
{
if (GetEMotionFX().GetIsInEditorMode())
{
AZ_Warning("EMotionFX", false, "Failed to retrieve asset source path with alias '@devassets@'. Cannot set absolute filename for '%s'", assetFilename.c_str());
}
assetData->m_emfxAnimGraph->SetFileName(assetFilename.c_str());
}
}
AZ_Error("EMotionFX", assetData->m_emfxAnimGraph, "Failed to initialize anim graph asset %s", asset.GetHint().c_str());
return static_cast<bool>(assetData->m_emfxAnimGraph);
}
void AnimGraphAssetHandler::DestroyAsset(AZ::Data::AssetPtr ptr)
{
AnimGraphAsset* animGraphAsset = static_cast<AnimGraphAsset*>(ptr);
EMotionFX::AnimGraph* animGraph = animGraphAsset->GetAnimGraph();
if (animGraph)
{
// Get rid of all anim graph instances that refer to the anim graph we're about to destroy.
EMotionFX::GetAnimGraphManager().RemoveAnimGraphInstances(animGraph);
}
delete ptr;
}
//////////////////////////////////////////////////////////////////////////
AZ::Data::AssetType AnimGraphAssetHandler::GetAssetType() const
{
return azrtti_typeid<AnimGraphAsset>();
}
//////////////////////////////////////////////////////////////////////////
void AnimGraphAssetHandler::GetAssetTypeExtensions(AZStd::vector<AZStd::string>& extensions)
{
extensions.push_back("animgraph");
}
//////////////////////////////////////////////////////////////////////////
AZ::Uuid AnimGraphAssetHandler::GetComponentTypeId() const
{
// The system is not in place to allow for components to drive creation of required components
// Future work will enable this functionality which will allow dropping in viewport or Inspector panel
// EditorAnimGraphComponent
// return AZ::Uuid("{770F0A71-59EA-413B-8DAB-235FB0FF1384}");
// Returning null keeps the animgraph from being drag/dropped
return AZ::Uuid::CreateNull();
}
//////////////////////////////////////////////////////////////////////////
const char* AnimGraphAssetHandler::GetAssetTypeDisplayName() const
{
return "EMotion FX Anim Graph";
}
const char* AnimGraphAssetHandler::GetBrowserIcon() const
{
return "Editor/Images/AssetBrowser/AnimGraph_16.svg";
}
//////////////////////////////////////////////////////////////////////////
void AnimGraphAssetBuilderHandler::InitAsset(const AZ::Data::Asset<AZ::Data::AssetData>& asset, bool loadStageSucceeded, bool isReload)
{
// Don't need to load the referenced animpgraph asset since we only care about the product ID or relative path of the product dependency
AZ_UNUSED(asset);
AZ_UNUSED(loadStageSucceeded);
AZ_UNUSED(isReload);
}
AZ::Data::AssetHandler::LoadResult AnimGraphAssetBuilderHandler::LoadAssetData(
const AZ::Data::Asset<AZ::Data::AssetData>& asset,
AZStd::shared_ptr<AZ::Data::AssetDataStream> stream,
const AZ::Data::AssetFilterCB& assetLoadFilterCB)
{
AZ_UNUSED(asset);
AZ_UNUSED(stream);
AZ_UNUSED(assetLoadFilterCB);
return AZ::Data::AssetHandler::LoadResult::LoadComplete;
}
} // namespace Integration
} // namespace EMotionFX
@@ -0,0 +1,87 @@
/*
* 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.
*
*/
#pragma once
#include <Integration/Assets/AssetCommon.h>
namespace EMotionFX
{
class AnimGraph;
class AnimGraphInstance;
class ActorInstance;
class MotionSet;
namespace Integration
{
class ActorAsset;
class AnimGraphAsset
: public EMotionFXAsset
{
public:
friend class AnimGraphAssetHandler;
AZ_CLASS_ALLOCATOR_DECL
AZ_RTTI(AnimGraphAsset, "{28003359-4A29-41AE-8198-0AEFE9FF5263}", EMotionFXAsset);
AnimGraphAsset(AZ::Data::AssetId id = AZ::Data::AssetId());
typedef EMotionFXPtr<EMotionFX::AnimGraphInstance> AnimGraphInstancePtr;
AnimGraphInstancePtr CreateInstance(
EMotionFX::ActorInstance* actorInstance,
EMotionFX::MotionSet* motionSet);
EMotionFX::AnimGraph* GetAnimGraph() { return m_emfxAnimGraph ? m_emfxAnimGraph.get() : nullptr; }
void SetData(EMotionFX::AnimGraph* animGraph);
void SetStatus(AssetStatus newStatus) { m_status = newStatus; }
private:
AZStd::unique_ptr<EMotionFX::AnimGraph> m_emfxAnimGraph;
};
class AnimGraphAssetHandler : public EMotionFXAssetHandler<AnimGraphAsset>
{
public:
AZ_CLASS_ALLOCATOR_DECL
bool OnInitAsset(const AZ::Data::Asset<AZ::Data::AssetData>& asset) override final;
void DestroyAsset(AZ::Data::AssetPtr ptr) override final;
AZ::Data::AssetType GetAssetType() const override final;
void GetAssetTypeExtensions(AZStd::vector<AZStd::string>& extensions) override final;
AZ::Uuid GetComponentTypeId() const override final;
const char* GetAssetTypeDisplayName() const override final;
const char* GetBrowserIcon() const override;
};
class AnimGraphAssetBuilderHandler : public AnimGraphAssetHandler
{
public:
void InitAsset(const AZ::Data::Asset<AZ::Data::AssetData>& asset, bool loadStageSucceeded, bool isReload) override;
AZ::Data::AssetHandler::LoadResult LoadAssetData(
const AZ::Data::Asset<AZ::Data::AssetData>& asset,
AZStd::shared_ptr<AZ::Data::AssetDataStream> stream,
const AZ::Data::AssetFilterCB& assetLoadFilterCB) override;
};
} // namespace Integration
} // namespace EMotionFX
namespace AZ
{
AZ_TYPE_INFO_SPECIALIZE(EMotionFX::Integration::EMotionFXPtr<EMotionFX::Integration::AnimGraphAsset>, "{BF1ACFB9-8295-4B55-8B55-DC64BFF36BD3}");
AZ_TYPE_INFO_SPECIALIZE(EMotionFX::Integration::EMotionFXPtr<EMotionFX::AnimGraphInstance>, "{769ED685-EC18-449D-9453-7D47D9BC1B8A}");
}
@@ -0,0 +1,158 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/Asset/AssetManager.h>
#include <AzCore/Asset/AssetTypeInfoBus.h>
#include <AzCore/IO/FileIO.h>
#include <AzFramework/Asset/AssetSystemBus.h>
#include <EMotionFX/Source/Allocators.h>
#include <Integration/System/SystemCommon.h>
namespace EMotionFX
{
namespace Integration
{
/**
*
*/
class EMotionFXAsset : public AZ::Data::AssetData
{
public:
AZ_RTTI(EMotionFXAsset, "{043F606A-A483-4910-8110-D8BC4B78922C}", AZ::Data::AssetData)
AZ_CLASS_ALLOCATOR(EMotionFXAsset, EMotionFXAllocator, 0)
EMotionFXAsset(AZ::Data::AssetId id = AZ::Data::AssetId())
: AZ::Data::AssetData(id)
{}
AZStd::vector<AZ::u8> m_emfxNativeData;
};
/**
*
*/
template<typename DataType>
class EMotionFXAssetHandler
: public AZ::Data::AssetHandler
, private AZ::AssetTypeInfoBus::Handler
{
public:
AZ_CLASS_ALLOCATOR(EMotionFXAssetHandler<DataType>, EMotionFXAllocator, 0)
EMotionFXAssetHandler()
{
Register();
}
~EMotionFXAssetHandler() override
{
Unregister();
}
AZ::Data::AssetPtr CreateAsset(const AZ::Data::AssetId& id, const AZ::Data::AssetType& type) override
{
(void)type;
return aznew DataType(id);
}
AZ::Data::AssetId AssetMissingInCatalog(const AZ::Data::Asset<AZ::Data::AssetData>& asset) override
{
// missing assets should at least get escalated to the top of the list. Sub-handlers could override this and do
// additional things like substitute some default asset Id.
AzFramework::AssetSystemRequestBus::Broadcast(&AzFramework::AssetSystem::AssetSystemRequests::EscalateAssetByUuid, asset.GetId().m_guid);
return AZ::Data::AssetId();
}
AZ::Data::AssetHandler::LoadResult LoadAssetData(
const AZ::Data::Asset<AZ::Data::AssetData>& asset,
AZStd::shared_ptr<AZ::Data::AssetDataStream> stream,
const AZ::Data::AssetFilterCB& assetLoadFilterCB) override
{
(void)assetLoadFilterCB;
DataType* assetData = asset.GetAs<DataType>();
if (stream->GetLength() > 0)
{
assetData->m_emfxNativeData.resize(stream->GetLength());
stream->Read(stream->GetLength(), assetData->m_emfxNativeData.data());
return AZ::Data::AssetHandler::LoadResult::LoadComplete;
}
return AZ::Data::AssetHandler::LoadResult::Error;
}
bool SaveAssetData(const AZ::Data::Asset<AZ::Data::AssetData>& asset, AZ::IO::GenericStream* stream) override
{
(void)asset;
(void)stream;
AZ_Error("EMotionFX", false, "Asset handler does not support asset saving.");
return false;
}
void DestroyAsset(AZ::Data::AssetPtr ptr) override
{
delete ptr;
}
void GetHandledAssetTypes(AZStd::vector<AZ::Data::AssetType>& assetTypes) override
{
assetTypes.push_back(azrtti_typeid<DataType>());
}
void Register()
{
AZ_Assert(AZ::Data::AssetManager::IsReady(), "Asset database isn't ready!");
AZ::Data::AssetManager::Instance().RegisterHandler(this, azrtti_typeid<DataType>());
AZ::AssetTypeInfoBus::Handler::BusConnect(azrtti_typeid<DataType>());
}
void Unregister()
{
AZ::AssetTypeInfoBus::Handler::BusDisconnect(azrtti_typeid<DataType>());
if (AZ::Data::AssetManager::IsReady())
{
AZ::Data::AssetManager::Instance().UnregisterHandler(this);
}
}
void InitAsset(const AZ::Data::Asset<AZ::Data::AssetData>& asset, bool loadStageSucceeded, bool isReload) override
{
if (!loadStageSucceeded || !OnInitAsset(asset))
{
AssetHandler::InitAsset(asset, false, isReload);
return;
}
AssetHandler::InitAsset(asset, true, isReload);
}
virtual bool OnInitAsset(const AZ::Data::Asset<AZ::Data::AssetData>& asset)
{
(void)asset;
return true;
}
const char* GetGroup() const override
{
return "Animation";
}
};
} // namespace Integration
} // namespace EMotionFX
@@ -0,0 +1,79 @@
/*
* 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 "EMotionFX_precompiled.h"
#include <Integration/Assets/MotionAsset.h>
namespace EMotionFX
{
namespace Integration
{
AZ_CLASS_ALLOCATOR_IMPL(MotionAsset, EMotionFXAllocator, 0);
AZ_CLASS_ALLOCATOR_IMPL(MotionAssetHandler, EMotionFXAllocator, 0);
MotionAsset::MotionAsset(AZ::Data::AssetId id)
: EMotionFXAsset(id)
{}
//////////////////////////////////////////////////////////////////////////
void MotionAsset::SetData(EMotionFX::Motion* motion)
{
m_emfxMotion.reset(motion);
m_status = AZ::Data::AssetData::AssetStatus::Ready;
}
//////////////////////////////////////////////////////////////////////////
bool MotionAssetHandler::OnInitAsset(const AZ::Data::Asset<AZ::Data::AssetData>& asset)
{
MotionAsset* assetData = asset.GetAs<MotionAsset>();
assetData->m_emfxMotion = EMotionFXPtr<EMotionFX::Motion>::MakeFromNew(EMotionFX::GetImporter().LoadMotion(
assetData->m_emfxNativeData.data(),
assetData->m_emfxNativeData.size(),
nullptr));
if (assetData->m_emfxMotion)
{
assetData->m_emfxMotion->SetIsOwnedByRuntime(true);
}
AZ_Error("EMotionFX", assetData->m_emfxMotion, "Failed to initialize motion asset %s", asset.GetHint().c_str());
return (assetData->m_emfxMotion);
}
//////////////////////////////////////////////////////////////////////////
AZ::Data::AssetType MotionAssetHandler::GetAssetType() const
{
return azrtti_typeid<MotionAsset>();
}
//////////////////////////////////////////////////////////////////////////
void MotionAssetHandler::GetAssetTypeExtensions(AZStd::vector<AZStd::string>& extensions)
{
extensions.push_back("motion");
}
//////////////////////////////////////////////////////////////////////////
const char* MotionAssetHandler::GetAssetTypeDisplayName() const
{
return "EMotion FX Motion";
}
//////////////////////////////////////////////////////////////////////////
const char* MotionAssetHandler::GetBrowserIcon() const
{
return "Editor/Images/AssetBrowser/Motion_16.svg";
}
} // namespace Integration
} // namespace EMotionFX
@@ -0,0 +1,57 @@
/*
* 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.
*
*/
#pragma once
#include <Integration/Assets/AssetCommon.h>
namespace EMotionFX
{
class Motion;
class MotionInstance;
namespace Integration
{
class MotionAsset : public EMotionFXAsset
{
public:
AZ_RTTI(MotionAsset, "{00494B8E-7578-4BA2-8B28-272E90680787}", EMotionFXAsset)
AZ_CLASS_ALLOCATOR_DECL
MotionAsset(AZ::Data::AssetId id = AZ::Data::AssetId());
void SetData(EMotionFX::Motion* motion); // Only Used for testing
EMotionFXPtr<EMotionFX::Motion> m_emfxMotion;
};
class MotionAssetHandler : public EMotionFXAssetHandler<MotionAsset>
{
public:
AZ_CLASS_ALLOCATOR_DECL
bool OnInitAsset(const AZ::Data::Asset<AZ::Data::AssetData>& asset) override;
AZ::Data::AssetType GetAssetType() const override;
void GetAssetTypeExtensions(AZStd::vector<AZStd::string>& extensions) override;
const char* GetAssetTypeDisplayName() const override;
const char* GetBrowserIcon() const override;
};
} // namespace Integration
} // namespace EMotionFX
namespace AZ
{
AZ_TYPE_INFO_SPECIALIZE(EMotionFX::Integration::EMotionFXPtr<EMotionFX::Integration::MotionAsset>, "{B51E66B5-B576-432A-9D01-9C8DA4757CE9}");
AZ_TYPE_INFO_SPECIALIZE(EMotionFX::Integration::EMotionFXPtr<EMotionFX::MotionInstance>, "{491DEAEE-A540-4187-A25F-743BEB74E01C}");
}
@@ -0,0 +1,283 @@
/*
* 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 "EMotionFX_precompiled.h"
#include <AzCore/Asset/AssetManager.h>
#include <AzCore/Component/TickBus.h>
#include <Integration/Assets/MotionSetAsset.h>
namespace EMotionFX
{
namespace Integration
{
AZ_CLASS_ALLOCATOR_IMPL(MotionSetAsset, EMotionFXAllocator, 0)
AZ_CLASS_ALLOCATOR_IMPL(MotionSetAssetHandler, EMotionFXAllocator, 0)
/**
* Custom callback registered with EMotion FX for the purpose of intercepting
* motion load requests. We want to pipe all requested loads through our
* asset system.
*/
class CustomMotionSetCallback
: public EMotionFX::MotionSetCallback
{
public:
AZ_CLASS_ALLOCATOR(CustomMotionSetCallback, EMotionFXAllocator, 0);
CustomMotionSetCallback(const AZ::Data::Asset<MotionSetAsset>& asset)
: MotionSetCallback(asset.Get()->m_emfxMotionSet.get())
, m_assetData(asset.Get())
{
}
EMotionFX::Motion* LoadMotion(EMotionFX::MotionSet::MotionEntry* entry) override
{
// When EMotionFX requests a motion to be loaded, retrieve it from the asset database.
// It should already be loaded through a motion set.
const char* motionFile = entry->GetFilename();
AZ::Data::AssetId motionAssetId;
EBUS_EVENT_RESULT(motionAssetId, AZ::Data::AssetCatalogRequestBus, GetAssetIdByPath, motionFile, azrtti_typeid<MotionAsset>(), false);
// if it failed to find it, it might be still compiling - try forcing an immediate compile:
if (!motionAssetId.IsValid())
{
AZ_TracePrintf("EMotionFX", "Motion \"%s\" is missing, requesting the asset system to compile it now.\n", motionFile);
AzFramework::AssetSystemRequestBus::Broadcast(&AzFramework::AssetSystem::AssetSystemRequests::CompileAssetSync, motionFile);
// and then try again:
AZ::Data::AssetCatalogRequestBus::BroadcastResult(motionAssetId, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetIdByPath, motionFile, azrtti_typeid<MotionAsset>(), false);
if (motionAssetId.IsValid())
{
AZ_TracePrintf("EMotionFX", "Motion \"%s\" successfully compiled.\n", motionFile);
}
}
if (motionAssetId.IsValid())
{
for (const auto& motionAsset : m_assetData->m_motionAssets)
{
if (motionAsset.GetId() == motionAssetId)
{
AZ_Assert(motionAsset, "Motion \"%s\" was found in the asset database, but is not initialized.", entry->GetFilename());
AZ_Error("EMotionFX", motionAsset.Get()->m_emfxMotion.get(), "Motion \"%s\" was found in the asset database, but is not valid.", entry->GetFilename());
return motionAsset.Get()->m_emfxMotion.get();
}
}
}
AZ_Error("EMotionFX", false, "Failed to locate motion \"%s\" in the asset database.", entry->GetFilename());
return nullptr;
}
MotionSetAsset* m_assetData;
};
MotionSetAsset::MotionSetAsset(AZ::Data::AssetId id)
: EMotionFXAsset(id)
{}
MotionSetAsset::~MotionSetAsset()
{
AZ::Data::AssetBus::MultiHandler::BusDisconnect();
}
void MotionSetAsset::SetData(EMotionFX::MotionSet* motionSet)
{
m_emfxMotionSet.reset(motionSet);
m_status = AZ::Data::AssetData::AssetStatus::Ready;
}
//////////////////////////////////////////////////////////////////////////
void MotionSetAsset::OnAssetReloaded(AZ::Data::Asset<AZ::Data::AssetData> asset)
{
for (AZ::Data::Asset<MotionAsset>& motionAsset : m_motionAssets)
{
if (motionAsset.GetId() == asset.GetId())
{
motionAsset = asset;
NotifyMotionSetModified(AZ::Data::Asset<MotionSetAsset>(this, AZ::Data::AssetLoadBehavior::Default));
break;
}
}
}
//////////////////////////////////////////////////////////////////////////
void MotionSetAsset::NotifyMotionSetModified(const AZ::Data::Asset<MotionSetAsset>& asset)
{
// When a dependent motion reloads, consider the motion set reloaded as well.
// This allows characters using this motion set to refresh state and reference the new motions.
if (!asset.Get()->m_isReloadPending)
{
AZStd::function<void()> notifyReload = [asset]()
{
using namespace AZ::Data;
AssetBus::Event(asset.GetId(), &AssetBus::Events::OnAssetReloaded, asset);
asset.Get()->m_isReloadPending = false;
};
AZ::TickBus::QueueFunction(notifyReload);
}
}
//////////////////////////////////////////////////////////////////////////
bool MotionSetAssetHandler::OnInitAsset(const AZ::Data::Asset<AZ::Data::AssetData>& asset)
{
MotionSetAsset* assetData = asset.GetAs<MotionSetAsset>();
EMotionFX::Importer::MotionSetSettings motionSettings;
motionSettings.m_isOwnedByRuntime = true;
assetData->m_emfxMotionSet.reset(EMotionFX::GetImporter().LoadMotionSet(
assetData->m_emfxNativeData.data(),
assetData->m_emfxNativeData.size(),
&motionSettings));
if (!assetData->m_emfxMotionSet)
{
AZ_Error("EMotionFX", false, "Failed to initialize motion set asset %s", asset.GetHint().c_str());
return false;
}
// The following code is required to be set so the FileManager detects changes to the files loaded
// through this method. Once EMotionFX is integrated to the asset system this can go away.
AZStd::string assetFilename;
EBUS_EVENT_RESULT(assetFilename, AZ::Data::AssetCatalogRequestBus, GetAssetPathById, asset.GetId());
const char* devAssetsPath = AZ::IO::FileIOBase::GetInstance()->GetAlias("@devassets@");
if (devAssetsPath)
{
AZStd::string assetSourcePath = devAssetsPath;
AzFramework::StringFunc::AssetDatabasePath::Normalize(assetSourcePath);
AZStd::string filename;
AzFramework::StringFunc::AssetDatabasePath::Join(assetSourcePath.c_str(), assetFilename.c_str(), filename);
assetData->m_emfxMotionSet->SetFilename(filename.c_str());
}
else
{
if (GetEMotionFX().GetIsInEditorMode())
{
AZ_Warning("EMotionFX", false, "Failed to retrieve asset source path with alias '@devassets@'. Cannot set absolute filename for '%s'", assetFilename.c_str());
}
assetData->m_emfxMotionSet->SetFilename(assetFilename.c_str());
}
// now load them in:
const EMotionFX::MotionSet::MotionEntries& motionEntries = assetData->m_emfxMotionSet->GetMotionEntries();
// Get the motions in the motion set. Escalate them to the top of the build queue first so that they can be done in parallel.
// This call is fire-and-forget and is very lightweight.
for (const auto& item : motionEntries)
{
const EMotionFX::MotionSet::MotionEntry* motionEntry = item.second;
const char* motionFilename = motionEntry->GetFilename();
AzFramework::AssetSystemRequestBus::Broadcast(&AzFramework::AssetSystem::AssetSystemRequests::EscalateAssetBySearchTerm, motionFilename);
}
// now that they're all escalated, the asset processor will be processing them across all threads, and we can request them one by one:
for (const auto& item : motionEntries)
{
const EMotionFX::MotionSet::MotionEntry* motionEntry = item.second;
const char* motionFilename = motionEntry->GetFilename();
// Find motion file in catalog and grab the asset.
// Jump on the AssetBus for the asset, and queue load.
AZ::Data::AssetId motionAssetId;
EBUS_EVENT_RESULT(motionAssetId, AZ::Data::AssetCatalogRequestBus, GetAssetIdByPath, motionFilename, AZ::Data::s_invalidAssetType, false);
// if it failed to find it, it might be still compiling - try forcing an immediate compile. CompileAssetSync
// will block until the compilation completes AND the catalog is up to date.
if (!motionAssetId.IsValid())
{
AZ_TracePrintf("EMotionFX", "Motion \"%s\" is missing, requesting the asset system to compile it now.\n", motionFilename);
AzFramework::AssetSystemRequestBus::Broadcast(&AzFramework::AssetSystem::AssetSystemRequests::CompileAssetSync, motionFilename);
// and then try again:
AZ::Data::AssetCatalogRequestBus::BroadcastResult(motionAssetId, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetIdByPath, motionFilename, azrtti_typeid<MotionAsset>(), false);
if (motionAssetId.IsValid())
{
AZ_TracePrintf("EMotionFX", "Motion \"%s\" successfully compiled.\n", motionFilename);
}
}
if (motionAssetId.IsValid())
{
AZ::Data::Asset<MotionAsset> motionAsset = AZ::Data::AssetManager::Instance().GetAsset<MotionAsset>(motionAssetId, AZ::Data::AssetLoadBehavior::Default);
if (motionAsset)
{
motionAsset.BlockUntilLoadComplete();
assetData->BusConnect(motionAssetId);
assetData->m_motionAssets.push_back(motionAsset);
}
else
{
AZ_Warning("EMotionFX", false, "Motion \"%s\" in motion set \"%s\" could not be loaded.", motionFilename, assetFilename.c_str());
}
}
else
{
AZ_Warning("EMotionFX", false, "Motion \"%s\" in motion set \"%s\" could not be found in the asset catalog.", motionFilename, assetFilename.c_str());
}
}
// Set motion set's motion load callback, so if EMotion FX queries back for a motion,
// we can pull the one managed through an AZ::Asset.
assetData->m_emfxMotionSet->SetCallback(aznew CustomMotionSetCallback(asset));
return true;
}
//////////////////////////////////////////////////////////////////////////
AZ::Data::AssetType MotionSetAssetHandler::GetAssetType() const
{
return azrtti_typeid<MotionSetAsset>();
}
//////////////////////////////////////////////////////////////////////////
void MotionSetAssetHandler::GetAssetTypeExtensions(AZStd::vector<AZStd::string>& extensions)
{
extensions.push_back("motionset");
}
//////////////////////////////////////////////////////////////////////////
const char* MotionSetAssetHandler::GetAssetTypeDisplayName() const
{
return "EMotion FX Motion Set";
}
//////////////////////////////////////////////////////////////////////////
const char* MotionSetAssetHandler::GetBrowserIcon() const
{
return "Editor/Images/AssetBrowser/MotionSet_16.svg";
}
//////////////////////////////////////////////////////////////////////////
void MotionSetAssetBuilderHandler::InitAsset(const AZ::Data::Asset<AZ::Data::AssetData>& asset, bool loadStageSucceeded, bool isReload)
{
// Don't need to load the referenced motionset and motion assets since we only care about the product ID ot relative path of the product dependency
AZ_UNUSED(asset);
AZ_UNUSED(loadStageSucceeded);
AZ_UNUSED(isReload);
}
AZ::Data::AssetHandler::LoadResult MotionSetAssetBuilderHandler::LoadAssetData(
const AZ::Data::Asset<AZ::Data::AssetData>& asset,
AZStd::shared_ptr<AZ::Data::AssetDataStream> stream,
const AZ::Data::AssetFilterCB& assetLoadFilterCB)
{
AZ_UNUSED(asset);
AZ_UNUSED(stream);
AZ_UNUSED(assetLoadFilterCB);
return AZ::Data::AssetHandler::LoadResult::LoadComplete;
}
} // namespace Integration
} // namespace EMotionFX
@@ -0,0 +1,94 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/Asset/AssetCommon.h>
#include <Integration/System/SystemCommon.h>
#include <Integration/Assets/AssetCommon.h>
#include <Integration/Assets/MotionAsset.h>
namespace AZ
{
namespace IO
{
class GenericStream;
}
}
namespace EMotionFX
{
class MotionSet;
namespace Integration
{
class CustomMotionSetCallback;
/**
* Represents a shared motion set asset in-memory, and is registered with the AZ::Data::AssetDatabase.
*/
class MotionSetAsset
: public EMotionFXAsset
, public AZ::Data::AssetBus::MultiHandler
{
public:
AZ_RTTI(MotionSetAsset, "{1DA936A0-F766-4B2F-B89C-9F4C8E1310F9}", EMotionFXAsset)
AZ_CLASS_ALLOCATOR_DECL
MotionSetAsset(AZ::Data::AssetId id = AZ::Data::AssetId());
~MotionSetAsset() override;
// AZ::Data::AssetBus::MultiHandler
void OnAssetReloaded(AZ::Data::Asset<AZ::Data::AssetData> asset) override;
static void NotifyMotionSetModified(const AZ::Data::Asset<MotionSetAsset>& asset);
void SetData(EMotionFX::MotionSet* motionSet);
AZStd::unique_ptr<EMotionFX::MotionSet> m_emfxMotionSet; ///< EMotionFX motion set
AZStd::vector<AZ::Data::Asset<MotionAsset>> m_motionAssets; ///< Handles to all contained motions
bool m_isReloadPending = false; ///< True if a dependent motion was reloaded and we're pending our own reload notification.
};
/**
* Handler responsible for creating, loading, and initializing shared motion set assets.
*/
class MotionSetAssetHandler : public EMotionFXAssetHandler<MotionSetAsset>
{
public:
AZ_CLASS_ALLOCATOR_DECL
bool OnInitAsset(const AZ::Data::Asset<AZ::Data::AssetData>& asset) override;
AZ::Data::AssetType GetAssetType() const override;
void GetAssetTypeExtensions(AZStd::vector<AZStd::string>& extensions) override;
const char* GetAssetTypeDisplayName() const override;
const char* GetBrowserIcon() const override;
};
class MotionSetAssetBuilderHandler : public MotionSetAssetHandler
{
public:
void InitAsset(const AZ::Data::Asset<AZ::Data::AssetData>& asset, bool loadStageSucceeded, bool isReload) override;
AZ::Data::AssetHandler::LoadResult LoadAssetData(
const AZ::Data::Asset<AZ::Data::AssetData>& asset,
AZStd::shared_ptr<AZ::Data::AssetDataStream> stream,
const AZ::Data::AssetFilterCB& assetLoadFilterCB) override;
};
} // namespace Integration
} // namespace EMotionFX
namespace AZ
{
AZ_TYPE_INFO_SPECIALIZE(EMotionFX::Integration::EMotionFXPtr<EMotionFX::Integration::MotionSetAsset>, "{5A306008-884B-486C-BEBB-186E28E3B63D}");
}
@@ -0,0 +1,833 @@
/*
* 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 "EMotionFX_precompiled.h"
#include <AzCore/Component/Entity.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/Math/Transform.h>
#include <AzFramework/Physics/Ragdoll.h>
#include <AzFramework/Physics/RagdollPhysicsBus.h>
#include <AzFramework/Physics/World.h>
#include <AzFramework/Visibility/BoundsBus.h>
#include <LmbrCentral/Animation/AttachmentComponentBus.h>
#include <LmbrCentral/Rendering/MeshComponentBus.h>
#include <LmbrCentral/Rendering/Utils/MaterialOwnerRequestBusHandlerImpl.h>
#include <Integration/Components/ActorComponent.h>
#include <Integration/Rendering/RenderBackendManager.h>
#include <EMotionFX/Source/Transform.h>
#include <EMotionFX/Source/RagdollInstance.h>
#include <EMotionFX/Source/DebugDraw.h>
namespace EMotionFX
{
namespace Integration
{
//////////////////////////////////////////////////////////////////////////
class ActorComponentNotificationBehaviorHandler
: public ActorComponentNotificationBus::Handler, public AZ::BehaviorEBusHandler
{
public:
AZ_EBUS_BEHAVIOR_BINDER(ActorComponentNotificationBehaviorHandler, "{4631E2E1-62CB-451D-A6E3-CC40501879AE}", AZ::SystemAllocator,
OnActorInstanceCreated, OnActorInstanceDestroyed);
void OnActorInstanceCreated(EMotionFX::ActorInstance* actorInstance) override
{
Call(FN_OnActorInstanceCreated, actorInstance);
}
void OnActorInstanceDestroyed(EMotionFX::ActorInstance* actorInstance) override
{
Call(FN_OnActorInstanceDestroyed, actorInstance);
}
};
//////////////////////////////////////////////////////////////////////////
void ActorComponent::Configuration::Reflect(AZ::ReflectContext* context)
{
auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<Configuration>()
->Version(3)
->Field("ActorAsset", &Configuration::m_actorAsset)
->Field("MaterialPerLOD", &Configuration::m_materialPerLOD)
->Field("RenderSkeleton", &Configuration::m_renderSkeleton)
->Field("RenderCharacter", &Configuration::m_renderCharacter)
->Field("RenderBounds", &Configuration::m_renderBounds)
->Field("AttachmentType", &Configuration::m_attachmentType)
->Field("AttachmentTarget", &Configuration::m_attachmentTarget)
->Field("SkinningMethod", &Configuration::m_skinningMethod)
->Field("LODLevel", &Configuration::m_lodLevel)
->Field("ForceJointsUpdateOOV", &Configuration::m_forceUpdateJointsOOV)
;
}
}
//////////////////////////////////////////////////////////////////////////
void ActorComponent::Reflect(AZ::ReflectContext* context)
{
auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
if (serializeContext)
{
// Register the AZStd::vector<AzFramework::SimpleAssetReference<MaterialAsset>> class using
// the old AZ_TYPE_INFO_SPECIALIZE TypeID specialization for the SimpleAssetReference<MaterialAsset>
// Performs a sha1 calculation of the following typeids AzFramework::SimpleAssetReference<MaterialAsset> + AZStd::allocator + AZStd::vector
AZ::TypeId deprecatedTypeId = AZ::TypeId("{B7B8ECC7-FF89-4A76-A50E-4C6CA2B6E6B4}") + AZ::AzTypeInfo<AZStd::allocator>::Uuid()
+ AZ::TypeId("{A60E3E61-1FF6-4982-B6B8-9E4350C4C679}");
serializeContext->ClassDeprecate("AZStd::vector<SimpleAssetReference_MaterialAsset>", deprecatedTypeId,
[](AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& rootElement)
{
AZStd::vector<AZ::SerializeContext::DataElementNode> childNodeElements;
for (int index = 0; index < rootElement.GetNumSubElements(); ++index)
{
childNodeElements.push_back(rootElement.GetSubElement(index));
}
rootElement.Convert<AZStd::vector<AzFramework::SimpleAssetReference<LmbrCentral::MaterialAsset>>>(context);
for (AZ::SerializeContext::DataElementNode& childNodeElement : childNodeElements)
{
rootElement.AddElement(AZStd::move(childNodeElement));
}
return true;
});
Configuration::Reflect(context);
serializeContext->Class<ActorComponent, AZ::Component>()
->Version(1)
->Field("Configuration", &ActorComponent::m_configuration)
;
AZ::EditContext* editContext = serializeContext->GetEditContext();
if (editContext)
{
editContext->Enum<EMotionFX::Integration::Space>("Space", "The transformation space.")
->Value("Local Space", Space::LocalSpace)
->Value("Model Space", Space::ModelSpace)
->Value("World Space", Space::WorldSpace);
}
}
auto* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context);
if (behaviorContext)
{
behaviorContext->EBus<ActorComponentRequestBus>("ActorComponentRequestBus")
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::Preview)
->Event("GetJointIndexByName", &ActorComponentRequestBus::Events::GetJointIndexByName)
->Event("GetJointTransform", &ActorComponentRequestBus::Events::GetJointTransform)
->Event("AttachToEntity", &ActorComponentRequestBus::Events::AttachToEntity)
->Event("DetachFromEntity", &ActorComponentRequestBus::Events::DetachFromEntity)
->Event("DebugDrawRoot", &ActorComponentRequestBus::Events::DebugDrawRoot)
->Event("GetRenderCharacter", &ActorComponentRequestBus::Events::GetRenderCharacter)
->Event("SetRenderCharacter", &ActorComponentRequestBus::Events::SetRenderCharacter)
->VirtualProperty("RenderCharacter", "GetRenderCharacter", "SetRenderCharacter")
;
behaviorContext->Class<ActorComponent>()->RequestBus("ActorComponentRequestBus");
behaviorContext->EBus<ActorComponentNotificationBus>("ActorComponentNotificationBus")
->Handler<ActorComponentNotificationBehaviorHandler>()
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::List)
;
}
}
//////////////////////////////////////////////////////////////////////////
ActorComponent::ActorComponent(const Configuration* configuration)
: m_debugDrawRoot(false)
{
if (configuration)
{
m_configuration = *configuration;
}
//m_materialBusHandler = aznew LmbrCentral::MaterialOwnerRequestBusHandlerImpl;
}
//////////////////////////////////////////////////////////////////////////
ActorComponent::~ActorComponent()
{
//delete m_materialBusHandler;
}
//////////////////////////////////////////////////////////////////////////
void ActorComponent::Activate()
{
m_actorInstance.reset();
AZ::Data::AssetBus::Handler::BusDisconnect();
auto& cfg = m_configuration;
if (cfg.m_actorAsset.GetId().IsValid())
{
AZ::Data::AssetBus::Handler::BusConnect(cfg.m_actorAsset.GetId());
cfg.m_actorAsset.QueueLoad();
}
AZ::TickBus::Handler::BusConnect();
const AZ::EntityId entityId = GetEntityId();
LmbrCentral::AttachmentComponentNotificationBus::Handler::BusConnect(entityId);
AzFramework::CharacterPhysicsDataRequestBus::Handler::BusConnect(entityId);
AzFramework::RagdollPhysicsNotificationBus::Handler::BusConnect(entityId);
if (cfg.m_attachmentTarget.IsValid())
{
AttachToEntity(cfg.m_attachmentTarget, cfg.m_attachmentType);
}
}
//////////////////////////////////////////////////////////////////////////
void ActorComponent::Deactivate()
{
AzFramework::RagdollPhysicsNotificationBus::Handler::BusDisconnect();
AzFramework::CharacterPhysicsDataRequestBus::Handler::BusDisconnect();
Physics::WorldNotificationBus::Handler::BusDisconnect();
ActorComponentRequestBus::Handler::BusDisconnect();
AZ::TickBus::Handler::BusDisconnect();
ActorComponentNotificationBus::Handler::BusDisconnect();
LmbrCentral::AttachmentComponentNotificationBus::Handler::BusDisconnect();
AZ::TransformNotificationBus::MultiHandler::BusDisconnect();
AZ::Data::AssetBus::Handler::BusDisconnect();
DestroyActor();
m_configuration.m_actorAsset.Release();
}
//////////////////////////////////////////////////////////////////////////
void ActorComponent::AttachToEntity(AZ::EntityId targetEntityId, [[maybe_unused]] AttachmentType attachmentType)
{
if (targetEntityId.IsValid() && targetEntityId != GetEntityId())
{
ActorComponentNotificationBus::Handler::BusDisconnect();
ActorComponentNotificationBus::Handler::BusConnect(targetEntityId);
AZ::TransformNotificationBus::MultiHandler::BusConnect(targetEntityId);
m_attachmentTargetEntityId = targetEntityId;
// There's no guarantee that we will receive a on transform change call for the target entity because of the entity activate order.
// Enforce a transform query on target to get the correct initial transform.
AZ::Transform transform;
AZ::TransformBus::EventResult(transform, GetEntityId(), &AZ::TransformBus::Events::GetWorldTM); // default to using our own TM
AZ::TransformBus::EventResult(transform, targetEntityId, &AZ::TransformBus::Events::GetWorldTM); // attempt to get target's TM
AZ::TransformBus::Event(GetEntityId(), &AZ::TransformBus::Events::SetWorldTM, transform); // set our TM
}
else
{
DetachFromEntity();
}
}
//////////////////////////////////////////////////////////////////////////
void ActorComponent::DetachFromEntity()
{
if (m_attachmentTargetActor)
{
m_attachmentTargetActor->RemoveAttachment(m_actorInstance.get());
AZ::TransformBus::Event(GetEntityId(), &AZ::TransformBus::Events::SetParent, AZ::EntityId());
AZ::TransformBus::Event(GetEntityId(), &AZ::TransformBus::Events::SetLocalTM, AZ::Transform::CreateIdentity());
AZ::TransformNotificationBus::MultiHandler::BusDisconnect(m_attachmentTargetEntityId);
m_attachmentTargetEntityId.SetInvalid();
}
}
//////////////////////////////////////////////////////////////////////////
void ActorComponent::DebugDrawRoot(bool enable)
{
m_debugDrawRoot = enable;
}
//////////////////////////////////////////////////////////////////////////
bool ActorComponent::GetRenderCharacter() const
{
return m_configuration.m_renderCharacter;
}
//////////////////////////////////////////////////////////////////////////
void ActorComponent::SetRenderCharacter(bool enable)
{
if (m_configuration.m_renderCharacter != enable)
{
m_configuration.m_renderCharacter = enable;
if (m_renderActorInstance)
{
m_renderActorInstance->SetIsVisible(m_configuration.m_renderCharacter);
}
}
}
//////////////////////////////////////////////////////////////////////////
SkinningMethod ActorComponent::GetSkinningMethod() const
{
return m_configuration.m_skinningMethod;
}
//////////////////////////////////////////////////////////////////////////
void ActorComponent::OnAssetReloaded(AZ::Data::Asset<AZ::Data::AssetData> asset)
{
OnAssetReady(asset);
}
void ActorComponent::OnAssetReady(AZ::Data::Asset<AZ::Data::AssetData> asset)
{
m_configuration.m_actorAsset = asset;
CheckActorCreation();
}
bool ActorComponent::IsWorldNotificationBusConnected(AZ::Crc32 worldId) const
{
return Physics::WorldNotificationBus::Handler::BusIsConnectedId(worldId);
}
void ActorComponent::CheckActorCreation()
{
if (m_configuration.m_actorAsset.IsReady())
{
// Create actor instance.
auto* actorAsset = m_configuration.m_actorAsset.GetAs<ActorAsset>();
AZ_Error("EMotionFX", actorAsset, "Actor asset is not valid.");
if (!actorAsset)
{
return;
}
DestroyActor();
m_actorInstance = actorAsset->CreateInstance(GetEntity());
if (!m_actorInstance)
{
AZ_Error("EMotionFX", actorAsset, "Failed to create actor instance.");
return;
}
ActorComponentRequestBus::Handler::BusConnect(GetEntityId());
ActorComponentNotificationBus::Event(
GetEntityId(),
&ActorComponentNotificationBus::Events::OnActorInstanceCreated,
m_actorInstance.get());
m_actorInstance->SetLODLevel(m_configuration.m_lodLevel);
// Setup initial transform and listen for transform changes.
AZ::Transform transform = AZ::Transform::CreateIdentity();
AZ::TransformBus::EventResult(transform, GetEntityId(), &AZ::TransformBus::Events::GetWorldTM);
OnTransformChanged(transform, transform);
AZ::TransformNotificationBus::MultiHandler::BusConnect(GetEntityId());
m_actorInstance->UpdateWorldTransform();
m_actorInstance->UpdateBounds(0, ActorInstance::EBoundsType::BOUNDS_STATIC_BASED);
RenderBackend* renderBackend = AZ::Interface<RenderBackendManager>::Get()->GetRenderBackend();
// If there is already a RenderActorInstance, destroy it before creating the new one so there are not two instances potentially handling events for the same entityId
m_renderActorInstance.reset(nullptr);
// Create the new RenderActorInstance
m_renderActorInstance.reset(renderBackend->CreateActorInstance(GetEntityId(),
m_actorInstance,
m_configuration.m_actorAsset,
m_configuration.m_materialPerLOD,
m_configuration.m_skinningMethod,
transform));
if (m_renderActorInstance)
{
m_renderActorInstance->SetIsVisible(m_configuration.m_renderCharacter);
}
/*
const bool registerBus = true;
m_materialBusHandler->Activate(m_renderNode.get(), m_entity->GetId(), registerBus);
*/
// Reattach all attachments
for (AZ::EntityId& attachment : m_attachments)
{
LmbrCentral::AttachmentComponentRequestBus::Event(attachment, &LmbrCentral::AttachmentComponentRequestBus::Events::Reattach, true);
}
const AZ::EntityId entityId = GetEntityId();
LmbrCentral::AttachmentComponentRequestBus::Event(entityId, &LmbrCentral::AttachmentComponentRequestBus::Events::Reattach, true);
CheckAttachToEntity();
// Send general mesh creation notification to interested parties.
LmbrCentral::MeshComponentNotificationBus::Event(entityId, &LmbrCentral::MeshComponentNotifications::OnMeshCreated, m_configuration.m_actorAsset);
AzFramework::CharacterPhysicsDataNotificationBus::Event(entityId, &AzFramework::CharacterPhysicsDataNotifications::OnRagdollConfigurationReady);
}
}
//////////////////////////////////////////////////////////////////////////
void ActorComponent::CheckAttachToEntity()
{
// Attach to the target actor if we're both ready.
// Note that m_attachmentTargetActor will always be null if we're not configured to attach to anything.
if (m_actorInstance && m_attachmentTargetActor)
{
DetachFromEntity();
// Make sure we don't generate some circular loop by attaching to each other.
if (!m_attachmentTargetActor.get()->CheckIfCanHandleAttachment(m_actorInstance.get()))
{
AZ_Error("EMotionFX", false, "You cannot attach to yourself or create circular dependencies!\n");
return;
}
// Create the attachment.
AZ_Assert(m_configuration.m_attachmentType == AttachmentType::SkinAttachment, "Expected a skin attachment.");
Attachment* attachment = AttachmentSkin::Create(m_attachmentTargetActor.get(), m_actorInstance.get());
m_actorInstance->SetLocalSpaceTransform(Transform::CreateIdentity());
m_attachmentTargetActor->AddAttachment(attachment);
AZ::TransformBus::Event(GetEntityId(), &AZ::TransformBus::Events::SetParent, m_attachmentTargetActor->GetEntityId());
AZ::TransformBus::Event(GetEntityId(), &AZ::TransformBus::Events::SetLocalTM, AZ::Transform::CreateIdentity());
}
}
//////////////////////////////////////////////////////////////////////////
void ActorComponent::DestroyActor()
{
m_renderActorInstance.reset();
if (m_actorInstance)
{
//m_materialBusHandler->Deactivate();
DetachFromEntity();
m_attachmentTargetActor = nullptr;
// Send general mesh destruction notification to interested parties.
LmbrCentral::MeshComponentNotificationBus::Event(
GetEntityId(),
&LmbrCentral::MeshComponentNotifications::OnMeshDestroyed);
ActorComponentNotificationBus::Event(
GetEntityId(),
&ActorComponentNotificationBus::Events::OnActorInstanceDestroyed,
m_actorInstance.get());
m_actorInstance.reset();
}
}
//////////////////////////////////////////////////////////////////////////
void ActorComponent::OnTransformChanged(const AZ::Transform& local, const AZ::Transform& world)
{
AZ_UNUSED(local);
const AZ::EntityId* busIdPtr = AZ::TransformNotificationBus::GetCurrentBusId();
if (!busIdPtr || *busIdPtr == GetEntityId()) // Our own entity has moved.
{
// If we're not attached to another actor, keep the EMFX root in sync with any external changes to the entity's transform.
if (m_actorInstance)
{
const Transform localTransform = m_actorInstance->GetParentWorldSpaceTransform().Inversed() * Transform(world);
m_actorInstance->SetLocalSpacePosition(localTransform.mPosition);
m_actorInstance->SetLocalSpaceRotation(localTransform.mRotation);
// Disable updating the scale to prevent feedback from adding up.
// We need to find a better way to handle this or to prevent this feedback loop.
EMFX_SCALECODE
(
m_actorInstance->SetLocalSpaceScale(localTransform.mScale);
)
}
}
}
//////////////////////////////////////////////////////////////////////////
void ActorComponent::OnTick(float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::Animation);
if (!m_actorInstance || !m_actorInstance->GetIsEnabled())
{
return;
}
if (m_renderActorInstance)
{
m_renderActorInstance->OnTick(deltaTime);
m_renderActorInstance->UpdateBounds();
AzFramework::EntityBoundsUnionRequestBus::Broadcast(
&AzFramework::EntityBoundsUnionRequestBus::Events::RefreshEntityLocalBoundsUnion, GetEntityId());
// Optimization: Set the actor instance invisible when character is out of camera view. This will stop the joint transforms update, except the root joint.
// Calling it after the bounds on the render actor updated.
if (!m_configuration.m_forceUpdateJointsOOV)
{
const bool isInCameraFrustum = m_renderActorInstance->IsInCameraFrustum();
m_actorInstance->SetIsVisible(isInCameraFrustum);
}
RenderActorInstance::DebugOptions debugOptions;
debugOptions.m_drawAABB = m_configuration.m_renderBounds;
debugOptions.m_drawSkeleton = m_configuration.m_renderSkeleton;
debugOptions.m_drawRootTransform = m_debugDrawRoot;
debugOptions.m_rootWorldTransform = GetEntity()->GetTransform()->GetWorldTM();
debugOptions.m_emfxDebugDraw = true;
m_renderActorInstance->DebugDraw(debugOptions);
}
}
int ActorComponent::GetTickOrder()
{
return AZ::TICK_PRE_RENDER;
}
void ActorComponent::OnPostPhysicsSubtick(float fixedDeltaTime)
{
if (m_actorInstance)
{
m_actorInstance->PostPhysicsUpdate(fixedDeltaTime);
}
}
int ActorComponent::GetPhysicsTickOrder()
{
return WorldNotifications::Animation;
}
//////////////////////////////////////////////////////////////////////////
void ActorComponent::OnActorInstanceCreated(ActorInstance* actorInstance)
{
auto it = AZStd::find(m_attachments.begin(), m_attachments.end(), actorInstance->GetEntityId());
if (it != m_attachments.end())
{
if (m_actorInstance)
{
LmbrCentral::AttachmentComponentRequestBus::Event(actorInstance->GetEntityId(), &LmbrCentral::AttachmentComponentRequestBus::Events::Reattach, true);
}
}
else
{
m_attachmentTargetActor.reset(actorInstance);
CheckAttachToEntity();
}
}
void ActorComponent::OnActorInstanceDestroyed([[maybe_unused]] ActorInstance* actorInstance)
{
DetachFromEntity();
m_attachmentTargetActor = nullptr;
}
//////////////////////////////////////////////////////////////////////////
bool ActorComponent::GetRagdollConfiguration(Physics::RagdollConfiguration& ragdollConfiguration) const
{
if (!m_actorInstance)
{
return false;
}
const AZStd::shared_ptr<PhysicsSetup>& physicsSetup = m_actorInstance->GetActor()->GetPhysicsSetup();
ragdollConfiguration = physicsSetup->GetRagdollConfig();
return true;
}
AZStd::string ActorComponent::GetParentNodeName(const AZStd::string& childName) const
{
if (!m_actorInstance)
{
return AZStd::string();
}
const Skeleton* skeleton = m_actorInstance->GetActor()->GetSkeleton();
Node* childNode = skeleton->FindNodeByName(childName);
if (childNode)
{
const Node* parentNode = childNode->GetParentNode();
if (parentNode)
{
return parentNode->GetNameString();
}
}
return AZStd::string();
}
//////////////////////////////////////////////////////////////////////////
Physics::RagdollState ActorComponent::GetBindPose(const Physics::RagdollConfiguration& config) const
{
Physics::RagdollState physicsPose;
if (!m_actorInstance)
{
return physicsPose;
}
const Actor* actor = m_actorInstance->GetActor();
const Skeleton* skeleton = actor->GetSkeleton();
const Pose* emfxPose = actor->GetBindPose();
size_t numNodes = config.m_nodes.size();
physicsPose.resize(numNodes);
for (size_t nodeIndex = 0; nodeIndex < numNodes; nodeIndex++)
{
const char* nodeName = config.m_nodes[nodeIndex].m_debugName.data();
Node* emfxNode = skeleton->FindNodeByName(nodeName);
AZ_Error("EMotionFX", emfxNode, "Could not find bind pose for node %s", nodeName);
if (emfxNode)
{
const Transform& nodeTransform = emfxPose->GetModelSpaceTransform(emfxNode->GetNodeIndex());
physicsPose[nodeIndex].m_position = nodeTransform.mPosition;
physicsPose[nodeIndex].m_orientation = nodeTransform.mRotation;
}
}
return physicsPose;
}
void ActorComponent::OnRagdollActivated()
{
Physics::Ragdoll* ragdoll;
AzFramework::RagdollPhysicsRequestBus::EventResult(ragdoll, m_entity->GetId(), &AzFramework::RagdollPhysicsRequestBus::Events::GetRagdoll);
if (ragdoll && m_actorInstance)
{
m_actorInstance->SetRagdoll(ragdoll);
RagdollInstance* ragdollInstance = m_actorInstance->GetRagdollInstance();
AZ_Assert(ragdollInstance, "As the ragdoll passed in ActorInstance::SetRagdoll() is valid, a valid ragdoll instance is expected to exist.");
Physics::WorldNotificationBus::Handler::BusConnect(ragdollInstance->GetRagdollWorldId());
}
}
void ActorComponent::OnRagdollDeactivated()
{
if (m_actorInstance)
{
Physics::WorldNotificationBus::Handler::BusDisconnect();
m_actorInstance->SetRagdoll(nullptr);
}
}
size_t ActorComponent::GetNumJoints() const
{
AZ_Assert(m_actorInstance, "The actor instance needs to be valid.");
return m_actorInstance->GetActor()->GetNumNodes();
}
size_t ActorComponent::GetJointIndexByName(const char* name) const
{
AZ_Assert(m_actorInstance, "The actor instance needs to be valid.");
Node* node = m_actorInstance->GetActor()->GetSkeleton()->FindNodeByNameNoCase(name);
if (node)
{
return static_cast<size_t>(node->GetNodeIndex());
}
return ActorComponentRequests::s_invalidJointIndex;
}
AZ::Transform ActorComponent::GetJointTransform(size_t jointIndex, Space space) const
{
AZ_Assert(m_actorInstance, "The actor instance needs to be valid.");
const AZ::u32 index = static_cast<AZ::u32>(jointIndex);
const AZ::u32 numNodes = m_actorInstance->GetActor()->GetNumNodes();
AZ_Error("EMotionFX", index < numNodes, "GetJointTransform: The joint index %d is out of bounds [0;%d]. Entity: %s",
index, numNodes, GetEntity()->GetName().c_str());
if (index >= numNodes)
{
return AZ::Transform::CreateIdentity();
}
Pose* currentPose = m_actorInstance->GetTransformData()->GetCurrentPose();
switch (space)
{
case Space::LocalSpace:
{
return MCore::EmfxTransformToAzTransform(currentPose->GetLocalSpaceTransform(index));
}
case Space::ModelSpace:
{
return MCore::EmfxTransformToAzTransform(currentPose->GetModelSpaceTransform(index));
}
case Space::WorldSpace:
{
return MCore::EmfxTransformToAzTransform(currentPose->GetWorldSpaceTransform(index));
}
default:
AZ_Assert(false, "Unsupported space in GetJointTransform!");
}
return AZ::Transform::CreateIdentity();
}
void ActorComponent::GetJointTransformComponents(size_t jointIndex, Space space, AZ::Vector3& outPosition, AZ::Quaternion& outRotation, AZ::Vector3& outScale) const
{
AZ_Assert(m_actorInstance, "The actor instance needs to be valid.");
const AZ::u32 index = static_cast<AZ::u32>(jointIndex);
const AZ::u32 numNodes = m_actorInstance->GetActor()->GetNumNodes();
AZ_Error("EMotionFX", index < numNodes, "GetJointTransformComponents: The joint index %d is out of bounds [0;%d]. Entity: %s",
index, numNodes, GetEntity()->GetName().c_str());
if (index >= numNodes)
{
return;
}
Pose* currentPose = m_actorInstance->GetTransformData()->GetCurrentPose();
switch (space)
{
case Space::LocalSpace:
{
const Transform& localTransform = currentPose->GetLocalSpaceTransform(index);
outPosition = localTransform.mPosition;
outRotation = localTransform.mRotation;
EMFX_SCALECODE
(
outScale = localTransform.mScale;
)
return;
}
case Space::ModelSpace:
{
const Transform& modelTransform = currentPose->GetModelSpaceTransform(index);
outPosition = modelTransform.mPosition;
outRotation = modelTransform.mRotation;
EMFX_SCALECODE
(
outScale = modelTransform.mScale;
)
return;
}
case Space::WorldSpace:
{
const Transform worldTransform = currentPose->GetWorldSpaceTransform(index);
outPosition = worldTransform.mPosition;
outRotation = worldTransform.mRotation;
EMFX_SCALECODE
(
outScale = worldTransform.mScale;
)
return;
}
default:
{
AZ_Assert(false, "Unsupported space in GetJointTransform!");
outPosition = AZ::Vector3::CreateZero();
outRotation = AZ::Quaternion::CreateIdentity();
outScale = AZ::Vector3::CreateOne();
}
}
}
Physics::AnimationConfiguration* ActorComponent::GetPhysicsConfig() const
{
if (m_actorInstance)
{
Actor* actor = m_actorInstance->GetActor();
const AZStd::shared_ptr<PhysicsSetup>& physicsSetup = actor->GetPhysicsSetup();
if (physicsSetup)
{
return &physicsSetup->GetConfig();
}
}
return nullptr;
}
// The entity has attached to the target.
void ActorComponent::OnAttached(AZ::EntityId attachedEntityId)
{
const AZ::EntityId* busIdPtr = LmbrCentral::AttachmentComponentNotificationBus::GetCurrentBusId();
if (busIdPtr)
{
const auto result = AZStd::find(m_attachments.begin(), m_attachments.end(), attachedEntityId);
if (result == m_attachments.end())
{
m_attachments.emplace_back(attachedEntityId);
}
else
{
return;
}
}
if (!m_actorInstance)
{
return;
}
ActorInstance* targetActorInstance = nullptr;
ActorComponentRequestBus::EventResult(targetActorInstance, attachedEntityId, &ActorComponentRequestBus::Events::GetActorInstance);
const char* jointName = nullptr;
LmbrCentral::AttachmentComponentRequestBus::EventResult(jointName, attachedEntityId, &LmbrCentral::AttachmentComponentRequestBus::Events::GetJointName);
if (targetActorInstance)
{
Node* node = jointName ? m_actorInstance->GetActor()->GetSkeleton()->FindNodeByName(jointName) : m_actorInstance->GetActor()->GetSkeleton()->GetNode(0);
if (node)
{
const AZ::u32 jointIndex = node->GetNodeIndex();
Attachment* attachment = AttachmentNode::Create(m_actorInstance.get(), jointIndex, targetActorInstance, true /* Managed externally, by this component. */);
m_actorInstance->AddAttachment(attachment);
}
}
}
// The entity is detaching from the target.
void ActorComponent::OnDetached(AZ::EntityId targetId)
{
// Remove the targetId from the attachment list
const AZ::EntityId* busIdPtr = LmbrCentral::AttachmentComponentNotificationBus::GetCurrentBusId();
if (busIdPtr)
{
m_attachments.erase(AZStd::remove(m_attachments.begin(), m_attachments.end(), targetId), m_attachments.end());
}
if (!m_actorInstance)
{
return;
}
ActorInstance* targetActorInstance = nullptr;
ActorComponentRequestBus::EventResult(targetActorInstance, targetId, &ActorComponentRequestBus::Events::GetActorInstance);
if (targetActorInstance)
{
m_actorInstance->RemoveAttachment(targetActorInstance);
}
}
} // namespace Integration
} // namespace EMotionFX
@@ -0,0 +1,197 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/Component/Component.h>
#include <AzCore/Component/TransformBus.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/Interface/Interface.h>
#include <AzFramework/Physics/RagdollPhysicsBus.h>
#include <AzFramework/Physics/CharacterPhysicsDataBus.h>
#include <AzFramework/Physics/World.h>
#include <Integration/Assets/ActorAsset.h>
#include <Integration/ActorComponentBus.h>
#include <Integration/Rendering/RenderActorInstance.h>
#include <LmbrCentral/Animation/AttachmentComponentBus.h>
#include <AzFramework/API/AtomActiveInterface.h>
namespace LmbrCentral
{
class MaterialOwnerRequestBusHandlerImpl;
}
namespace EMotionFX
{
namespace Integration
{
class ActorComponent
: public AZ::Component
, private AZ::Data::AssetBus::Handler
, private AZ::TransformNotificationBus::MultiHandler
, private AZ::TickBus::Handler
, private ActorComponentRequestBus::Handler
, private ActorComponentNotificationBus::Handler
, private LmbrCentral::AttachmentComponentNotificationBus::Handler
, private AzFramework::CharacterPhysicsDataRequestBus::Handler
, private AzFramework::RagdollPhysicsNotificationBus::Handler
, protected Physics::WorldNotificationBus::Handler
{
public:
AZ_COMPONENT(ActorComponent, "{BDC97E7F-A054-448B-A26F-EA2B5D78E377}");
friend class EditorActorComponent;
/**
* Configuration struct for procedural configuration of Actor Components.
*/
struct Configuration
{
AZ_TYPE_INFO(Configuration, "{053BFBC0-ABAA-4F4E-911F-5320F941E1A8}")
AZ::Data::Asset<ActorAsset> m_actorAsset{AZ::Data::AssetLoadBehavior::NoLoad}; ///< Selected actor asset.
ActorAsset::MaterialList m_materialPerLOD{}; ///< Material assignment per LOD.
AZ::EntityId m_attachmentTarget{}; ///< Target entity this actor should attach to.
AZ::u32 m_attachmentJointIndex = MCORE_INVALIDINDEX32; ///< Index of joint on target skeleton for actor attachments.
AttachmentType m_attachmentType = AttachmentType::None; ///< Type of attachment.
bool m_renderSkeleton = false; ///< Toggles debug rendering of the skeleton.
bool m_renderCharacter = true; ///< Toggles rendering of the character.
bool m_renderBounds = false; ///< Toggles rendering of the character bounds used for visibility testing.
SkinningMethod m_skinningMethod = SkinningMethod::DualQuat; ///< The skinning method for this actor
AZ::u32 m_lodLevel = 0;
// Force updating the joints when it is out of camera view. By
// default, joints level update (beside the root joint) on
// actor are disabled when the actor is out of view.
bool m_forceUpdateJointsOOV = false;
static void Reflect(AZ::ReflectContext* context);
};
ActorComponent(const Configuration* configuration = nullptr);
~ActorComponent() override;
//////////////////////////////////////////////////////////////////////////
// AZ::Component interface implementation
void Activate() override;
void Deactivate() override;
//////////////////////////////////////////////////////////////////////////
// ActorComponentRequestBus::Handler
size_t GetNumJoints() const override;
size_t GetJointIndexByName(const char* name) const override;
AZ::Transform GetJointTransform(size_t jointIndex, Space space) const override;
void GetJointTransformComponents(size_t jointIndex, Space space, AZ::Vector3& outPosition, AZ::Quaternion& outRotation, AZ::Vector3& outScale) const override;
Physics::AnimationConfiguration* GetPhysicsConfig() const override;
ActorInstance* GetActorInstance() override { return m_actorInstance.get(); }
void AttachToEntity(AZ::EntityId targetEntityId, AttachmentType attachmentType) override;
void DetachFromEntity() override;
void DebugDrawRoot(bool enable) override;
bool GetRenderCharacter() const override;
void SetRenderCharacter(bool enable) override;
SkinningMethod GetSkinningMethod() const override;
//////////////////////////////////////////////////////////////////////////
// ActorComponentNotificationBus::Handler
void OnActorInstanceCreated(ActorInstance* actorInstance) override;
void OnActorInstanceDestroyed(ActorInstance* actorInstance) override;
//////////////////////////////////////////////////////////////////////////
// The entity has attached to the target.
void OnAttached(AZ::EntityId targetId) override;
// The entity is detaching from the target.
void OnDetached(AZ::EntityId targetId) override;
//////////////////////////////////////////////////////////////////////////
// AzFramework::CharacterPhysicsDataBus::Handler
bool GetRagdollConfiguration(Physics::RagdollConfiguration& config) const override;
Physics::RagdollState GetBindPose(const Physics::RagdollConfiguration& config) const override;
AZStd::string GetParentNodeName(const AZStd::string& childName) const override;
//////////////////////////////////////////////////////////////////////////
// AzFramework::RagdollPhysicsNotificationBus::Handler
void OnRagdollActivated() override;
void OnRagdollDeactivated() override;
//////////////////////////////////////////////////////////////////////////
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC("EMotionFXActorService", 0xd6e8f48d));
provided.push_back(AZ_CRC("MeshService", 0x71d8a455));
provided.push_back(AZ_CRC("CharacterPhysicsDataService", 0x34757927));
if (AZ::Interface<AzFramework::AtomActiveInterface>::Get())
{
provided.push_back(AZ_CRC("MaterialReceiverService", 0x0d1a6a74));
}
}
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
{
incompatible.push_back(AZ_CRC("EMotionFXActorService", 0xd6e8f48d));
incompatible.push_back(AZ_CRC("MeshService", 0x71d8a455));
}
static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent)
{
dependent.push_back(AZ_CRC("PhysicsService", 0xa7350d22));
}
static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
{
required.push_back(AZ_CRC("TransformService", 0x8ee22c50));
}
static void Reflect(AZ::ReflectContext* context);
// AZ::Data::AssetBus::Handler
void OnAssetReady(AZ::Data::Asset<AZ::Data::AssetData> asset) override;
void OnAssetReloaded(AZ::Data::Asset<AZ::Data::AssetData> asset) override;
// Physics::WorldNotificationBus::Handler
bool IsWorldNotificationBusConnected(AZ::Crc32 worldId) const;
private:
// AZ::TransformNotificationBus::MultiHandler
void OnTransformChanged(const AZ::Transform& local, const AZ::Transform& world) override;
// AZ::TickBus::Handler
void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
int GetTickOrder() override;
// Physics::WorldNotifications::Handler
void OnPostPhysicsSubtick(float fixedDeltaTime) override;
int GetPhysicsTickOrder() override;
void CheckActorCreation();
void DestroyActor();
void CheckAttachToEntity();
Configuration m_configuration; ///< Component configuration.
/// Live state
ActorAsset::ActorInstancePtr m_attachmentTargetActor; ///< Target actor instance to attach to.
AZ::EntityId m_attachmentTargetEntityId; ///< Target actor entity ID
ActorAsset::ActorInstancePtr m_actorInstance; ///< Live actor instance.
AZStd::vector<AZ::EntityId> m_attachments;
AZStd::unique_ptr<RenderActorInstance> m_renderActorInstance;
bool m_debugDrawRoot; ///< Enables drawing of actor root and facing.
};
} //namespace Integration
} // namespace EMotionFX
@@ -0,0 +1,639 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates, or
* a third party where indicated.
*
* 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/PlatformDef.h>
#include "EMotionFX_precompiled.h"
#include <AzCore/Component/TransformBus.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <Integration/Components/AnimAudioComponent.h>
#include <LmbrCentral/Audio/AudioProxyComponentBus.h>
#include <LmbrCentral/Rendering/MeshComponentBus.h> // for SkeletalHierarchyRequestBus
#include <MathConversion.h>
using namespace LmbrCentral;
namespace EMotionFX
{
namespace Integration
{
void AudioTriggerEvent::Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<AudioTriggerEvent>()
->Version(0)
->Field("event", &AudioTriggerEvent::m_eventName)
->Field("trigger", &AudioTriggerEvent::m_triggerName)
->Field("joint", &AudioTriggerEvent::m_jointName);
}
}
void AnimAudioComponent::AddTriggerEvent(const AZStd::string& eventName, const AZStd::string& triggerName, const AZStd::string& jointName)
{
AZ::Entity* entity = GetEntity();
AZ_Assert(entity, "Component must be added to entity prior to adding an audio trigger event.");
if (entity->GetState() == AZ::Entity::State::Active)
{
AddTriggerEventInternal(eventName, triggerName, jointName);
}
else
{
m_eventsToAdd.emplace_back(eventName, triggerName, jointName);
}
}
void AnimAudioComponent::ClearTriggerEvents()
{
m_eventsToAdd.clear();
m_eventsToRemove.clear();
m_eventTriggerMap.clear();
}
void AnimAudioComponent::RemoveTriggerEvent(const AZStd::string& eventName)
{
const AZ::Crc32 eventCrc(eventName.c_str());
const AZ::Entity* entity = GetEntity();
AZ_Assert(entity, "Component must be added to entity prior to removing an audio trigger event.");
if (entity->GetState() == AZ::Entity::State::Active)
{
RemoveTriggerEventInternal(eventCrc);
}
else
{
m_eventsToRemove.push_back(eventCrc);
}
}
bool AnimAudioComponent::ExecuteSourceTrigger(
const Audio::TAudioControlID triggerID,
const Audio::SAudioCallBackInfos& callbackInfo,
const Audio::TAudioControlID& sourceID,
const AZStd::string& jointName)
{
if (triggerID == INVALID_AUDIO_CONTROL_ID)
{
return false;
}
bool success = false;
AZ::s32 jointId = -1;
SkeletalHierarchyRequestBus::EventResult(jointId, GetEntityId(),
&SkeletalHierarchyRequestBus::Events::GetJointIndexByName, jointName.c_str());
if (jointId < 0)
{
if (jointName.empty())
{
AZ_Warning("Editor", false, "'ExecuteSourceTrigger' called on default entity proxy. If this was the intent, a more explicit practice would be requesting this via the AudioProxyComponentBus.");
AudioProxyComponentRequestBus::EventResult(success, GetEntityId(), &AudioProxyComponentRequests::ExecuteSourceTrigger, triggerID, callbackInfo, sourceID);
}
else
{
AZ_Warning("Editor", false, "Joint not found. 'ExecuteSourceTrigger' call not performed on joint '%s'", jointName.c_str());
}
return success;
}
for (auto const& iter : m_jointProxies)
{
if (iter.first == jointId)
{
if (Audio::IAudioProxy* proxy = iter.second)
{
proxy->ExecuteSourceTrigger(triggerID, sourceID, callbackInfo);
success = true;
}
}
}
return success;
}
bool AnimAudioComponent::ExecuteTrigger(
const Audio::TAudioControlID triggerID,
const Audio::SAudioCallBackInfos& callbackInfo,
const AZStd::string& jointName)
{
if (triggerID == INVALID_AUDIO_CONTROL_ID)
{
return false;
}
bool success = false;
AZ::s32 jointId = -1;
SkeletalHierarchyRequestBus::EventResult(jointId, GetEntityId(),
&SkeletalHierarchyRequestBus::Events::GetJointIndexByName, jointName.c_str());
if (jointId < 0)
{
if (jointName.empty())
{
AZ_Warning("Editor", false, "'ExecuteTrigger' called on default entity proxy. If this was the intent, a more explicit practice would be requesting this via the AudioProxyComponentBus.");
AudioProxyComponentRequestBus::EventResult(success, GetEntityId(), &AudioProxyComponentRequests::ExecuteTrigger, triggerID, callbackInfo);
}
else
{
AZ_Warning("Editor", false, "Joint not found. 'ExecuteTrigger' call not performed on joint '%s'", jointName.c_str());
}
return success;
}
for (auto const& iter : m_jointProxies)
{
if (iter.first == jointId)
{
if (Audio::IAudioProxy* proxy = iter.second)
{
proxy->ExecuteTrigger(triggerID, callbackInfo);
success = true;
}
}
}
return success;
}
void AnimAudioComponent::KillTrigger(const Audio::TAudioControlID triggerId, const AZStd::string* jointName)
{
AZ::s32 jointId = -1;
if (jointName)
{
SkeletalHierarchyRequestBus::EventResult(jointId, GetEntityId(),
&SkeletalHierarchyRequestBus::Events::GetJointIndexByName, jointName->c_str());
if (jointId < 0)
{
if (jointName->empty())
{
AZ_Warning("Editor", false, "'KillTrigger' called on default entity proxy. If this was the intent, a more explicit practice would be requesting this via the AudioProxyComponentBus.");
AudioProxyComponentRequestBus::Event(GetEntityId(), &AudioProxyComponentRequests::KillTrigger, triggerId);
}
else
{
AZ_Warning("Editor", false, "Joint not found. 'KillTrigger' call not performed on joint '%s'", jointName->c_str());
}
return;
}
}
for (auto const& iter : m_jointProxies)
{
if (!jointName || iter.first == jointId)
{
if (Audio::IAudioProxy* proxy = iter.second)
{
proxy->StopTrigger(triggerId);
}
}
}
}
void AnimAudioComponent::KillAllTriggers(const AZStd::string* jointName)
{
AZ::s32 jointId = -1;
if (jointName)
{
SkeletalHierarchyRequestBus::EventResult(jointId, GetEntityId(),
&SkeletalHierarchyRequestBus::Events::GetJointIndexByName, jointName->c_str());
if (jointId < 0)
{
if (jointName->empty())
{
AZ_Warning("Editor", false, "'KillAllTrigger' called on default entity proxy. If this was the intent, a more explicit practice would be requesting this via the AudioProxyComponentBus.");
AudioProxyComponentRequestBus::Event(GetEntityId(), &AudioProxyComponentRequests::KillAllTriggers);
}
else
{
AZ_Warning("Editor", false, "Joint not found. 'KillAllTrigger' call not performed on joint '%s'", jointName->c_str());
}
return;
}
}
for (auto const& iter : m_jointProxies)
{
if (!jointName || iter.first == jointId)
{
if (Audio::IAudioProxy* proxy = iter.second)
{
proxy->StopAllTriggers();
}
}
}
}
void AnimAudioComponent::SetRtpcValue(const Audio::TAudioControlID rtpcID, float value, const AZStd::string* jointName)
{
AZ::s32 jointId = -1;
if (jointName)
{
SkeletalHierarchyRequestBus::EventResult(jointId, GetEntityId(),
&SkeletalHierarchyRequestBus::Events::GetJointIndexByName, jointName->c_str());
if (jointId < 0)
{
if (jointName->empty())
{
AZ_Warning("Editor", false, "'SetRtpcValue' called on default entity proxy. If this was the intent, a more explicit practice would be requesting this via the AudioProxyComponentBus.");
AudioProxyComponentRequestBus::Event(GetEntityId(), &AudioProxyComponentRequests::SetRtpcValue, rtpcID, value);
}
else
{
AZ_Warning("Editor", false, "Joint not found. 'SetRtpcValue' call not performed on joint '%s'", jointName->c_str());
}
return;
}
}
for (auto const& iter : m_jointProxies)
{
if (!jointName || iter.first == jointId)
{
if (Audio::IAudioProxy* proxy = iter.second)
{
proxy->SetRtpcValue(rtpcID, value);
}
}
}
}
void AnimAudioComponent::SetSwitchState(const Audio::TAudioControlID switchID, const Audio::TAudioSwitchStateID stateID, const AZStd::string* jointName)
{
AZ::s32 jointId = -1;
if (jointName)
{
SkeletalHierarchyRequestBus::EventResult(jointId, GetEntityId(),
&SkeletalHierarchyRequestBus::Events::GetJointIndexByName, jointName->c_str());
if (jointId < 0)
{
if (jointName->empty())
{
AZ_Warning("Editor", false, "'SetSwitchState' called on default entity proxy. If this was the intent, a more explicit practice would be requesting this via the AudioProxyComponentBus.");
AudioProxyComponentRequestBus::Event(GetEntityId(), &AudioProxyComponentRequests::SetSwitchState, switchID, stateID);
}
else
{
AZ_Warning("Editor", false, "Joint not found. 'SetSwitchState' call not performed on joint '%s'", jointName->c_str());
}
return;
}
}
for (auto const& iter : m_jointProxies)
{
if (!jointName || iter.first == jointId)
{
if (Audio::IAudioProxy* proxy = iter.second)
{
proxy->SetSwitchState(switchID, stateID);
}
}
}
}
void AnimAudioComponent::SetEnvironmentAmount(const Audio::TAudioEnvironmentID environmentID, float amount, const AZStd::string* jointName)
{
AZ::s32 jointId = -1;
if (jointName)
{
SkeletalHierarchyRequestBus::EventResult(jointId, GetEntityId(),
&SkeletalHierarchyRequestBus::Events::GetJointIndexByName, jointName->c_str());
if (jointId < 0)
{
if (jointName->empty())
{
AZ_Warning("Editor", false, "'SetEnvironmentAmount' called on default entity proxy. If this was the intent, a more explicit practice would be requesting this via the AudioProxyComponentBus.");
AudioProxyComponentRequestBus::Event(GetEntityId(), &AudioProxyComponentRequests::SetEnvironmentAmount, environmentID, amount);
}
else
{
AZ_Warning("Editor", false, "Joint not found. 'SetEnvironmentAmount' call not performed on joint '%s'", jointName->c_str());
}
return;
}
}
for (auto const& iter : m_jointProxies)
{
if (!jointName || iter.first == jointId)
{
if (Audio::IAudioProxy* proxy = iter.second)
{
proxy->SetEnvironmentAmount(environmentID, amount);
}
}
}
}
void AnimAudioComponent::OnTriggerStarted(const Audio::TAudioControlID /* triggerID */)
{
if (!m_activeVoices)
{
AZ::TickBus::Handler::BusConnect();
AZ::TransformNotificationBus::Handler::BusConnect(GetEntityId());
}
++m_activeVoices;
}
void AnimAudioComponent::OnTriggerFinished(const Audio::TAudioControlID /* triggerID */)
{
--m_activeVoices;
if (!m_activeVoices)
{
AZ::TickBus::Handler::BusDisconnect();
AZ::TransformNotificationBus::Handler::BusDisconnect(GetEntityId());
}
}
void AnimAudioComponent::Init()
{
}
void AnimAudioComponent::Activate()
{
AZStd::for_each(m_eventsToAdd.begin(), m_eventsToAdd.end(), [this](const auto& triggerEvent)
{
AddTriggerEventInternal(triggerEvent.m_eventName, triggerEvent.m_triggerName, triggerEvent.m_jointName);
});
m_eventsToAdd.clear();
AZStd::for_each(m_eventsToRemove.begin(), m_eventsToRemove.end(), [this](const auto& eventCrc)
{
RemoveTriggerEventInternal(eventCrc);
});
m_eventsToRemove.clear();
ActivateJointProxies();
Audio::AudioSystemRequestBus::Broadcast(&Audio::AudioSystemRequestBus::Events::AddRequestListener,
&AnimAudioComponent::OnAudioEvent,
this,
Audio::eART_AUDIO_CALLBACK_MANAGER_REQUEST,
Audio::eACMRT_REPORT_FINISHED_TRIGGER_INSTANCE);
m_callbackInfo.reset(new Audio::SAudioCallBackInfos(
this,
static_cast<AZ::u64>(GetEntityId()),
nullptr,
(Audio::eARF_PRIORITY_NORMAL | Audio::eARF_SYNC_FINISHED_CALLBACK)
));
ActorNotificationBus::Handler::BusConnect(GetEntityId());
AnimAudioComponentNotificationBus::Handler::BusConnect(GetEntityId());
}
void AnimAudioComponent::Deactivate()
{
AZ::TickBus::Handler::BusDisconnect();
AZ::TransformNotificationBus::Handler::BusDisconnect(GetEntityId());
m_activeVoices = 0;
DeactivateJointProxies();
Audio::AudioSystemRequestBus::Broadcast(&Audio::AudioSystemRequestBus::Events::RemoveRequestListener,
&AnimAudioComponent::OnAudioEvent, this);
ActorNotificationBus::Handler::BusDisconnect(GetEntityId());
AnimAudioComponentNotificationBus::Handler::BusDisconnect(GetEntityId());
}
void AnimAudioComponent::OnTick(float deltaTime, AZ::ScriptTimePoint time)
{
AZ_UNUSED(deltaTime);
AZ_UNUSED(time);
for (auto& iter : m_jointProxies)
{
if (Audio::IAudioProxy* proxy = iter.second)
{
AZ::Transform jointTransform = AZ::Transform::CreateIdentity();
auto getJointTransform = &SkeletalHierarchyRequestBus::Events::GetJointTransformCharacterRelative;
SkeletalHierarchyRequestBus::EventResult(jointTransform, GetEntityId(), getJointTransform, iter.first);
Audio::SATLWorldPosition atlTransform(m_transform * jointTransform);
proxy->SetPosition(m_transform * jointTransform);
}
}
}
void AnimAudioComponent::OnTransformChanged(const AZ::Transform& local, const AZ::Transform& world)
{
AZ_UNUSED(local);
m_transform = world;
}
void AnimAudioComponent::OnMotionEvent(EMotionFX::Integration::MotionEvent motionEvent)
{
// 1. Check if event is registered
auto eventIter = m_eventTriggerMap.find(AZ::Crc32(motionEvent.m_eventTypeName));
if (!motionEvent.m_isEventStart || eventIter == m_eventTriggerMap.end())
{
return;
}
// 2. If registered but jointId is unset, play on ProxyComponent's proxy
const AZ::s32 jointId = eventIter->second.GetJointId();
if (jointId < 0)
{
AudioProxyComponentRequestBus::Event(GetEntityId(), &AudioProxyComponentRequests::ExecuteTrigger,
eventIter->second.GetTriggerId(), Audio::SAudioCallBackInfos::GetEmptyObject());
return;
}
// 3. If no joint is registered with the component, then don't play anything
// (If joints can be removed, then this would occur when event mapping and
// event call still exist)
auto jointIter = m_jointProxies.find(jointId);
if (jointIter == m_jointProxies.end())
{
return;
}
// 4. If we have a joint proxy, update its position and play request.
if (Audio::IAudioProxy* proxy = jointIter->second)
{
const Audio::TAudioControlID triggerId = eventIter->second.GetTriggerId();
AZ::Transform jointTransform = AZ::Transform::CreateIdentity();
const auto getJointTransform = &SkeletalHierarchyRequestBus::Events::GetJointTransformCharacterRelative;
SkeletalHierarchyRequestBus::EventResult(jointTransform, GetEntityId(), getJointTransform, jointId);
const Audio::SATLWorldPosition atlTransform(m_transform * jointTransform);
proxy->SetPosition(atlTransform);
proxy->ExecuteTrigger(triggerId, *m_callbackInfo);
AnimAudioComponentNotificationBus::Event(GetEntityId(), &AnimAudioComponentNotificationBus::Events::OnTriggerStarted, triggerId);
}
}
void AnimAudioComponent::Reflect(AZ::ReflectContext* context)
{
AudioTriggerEvent::Reflect(context);
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<AnimAudioComponent, AZ::Component>()
->Version(0)
->Field("AudioTriggerEvents", &AnimAudioComponent::m_eventsToAdd);
}
if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->EBus<EMotionFX::Integration::AnimAudioComponentRequestBus>("AnimAudioComponentRequestBus")
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::Preview)
->Attribute(AZ::Script::Attributes::Category, "Animation")
->Event("AddTriggerEvent", &AnimAudioComponentRequestBus::Events::AddTriggerEvent)
->Event("ClearTriggerEvents", &AnimAudioComponentRequestBus::Events::ClearTriggerEvents)
->Event("RemoveTriggerEvent", &AnimAudioComponentRequestBus::Events::RemoveTriggerEvent);
}
}
void AnimAudioComponent::AddTriggerEventInternal(const AZStd::string& eventName, const AZStd::string& triggerName, const AZStd::string& jointName)
{
Audio::TAudioControlID triggerId = INVALID_AUDIO_CONTROL_ID;
Audio::AudioSystemRequestBus::BroadcastResult(triggerId,
&Audio::AudioSystemRequestBus::Events::GetAudioTriggerID, triggerName.c_str());
if (triggerId == INVALID_AUDIO_CONTROL_ID)
{
AZ_Warning("Editor", false, "Audio trigger '%s' not found. Trigger not registered for motion event '%s'",
triggerName.c_str(), eventName.c_str());
}
else
{
AZ::s32 jointId = -1;
if (!jointName.empty())
{
SkeletalHierarchyRequestBus::EventResult(jointId, GetEntityId(),
&SkeletalHierarchyRequestBus::Events::GetJointIndexByName, jointName.c_str());
if (jointId < 0)
{
AZ_Warning("Editor", false, "Joint name '%s' not found: anim event '%s' audio trigger '%s' will be played on default proxy",
jointName.c_str(),
eventName.c_str(),
triggerName.c_str());
}
}
const AZ::Crc32 eventCrc(eventName.c_str());
RemoveTriggerEventInternal(eventCrc);
auto entity = GetEntity();
AZ_Assert(entity, "AnimAudioComponent must be attached to entity prior to adding a trigger event");
m_eventTriggerMap.emplace(eventCrc, TriggerEventData(*entity, triggerId, jointId));
}
}
void AnimAudioComponent::RemoveTriggerEventInternal(const AZ::Crc32& eventCrc)
{
const auto iter = m_eventTriggerMap.find(eventCrc);
if (iter != m_eventTriggerMap.end())
{
m_eventTriggerMap.erase(iter);
}
}
void AnimAudioComponent::ActivateJointProxies()
{
const AZ::Entity* entity = GetEntity();
AZ_Assert(entity, "Parent entity not found");
const AZStd::string& name = entity->GetName();
for (auto& eventIter : m_eventTriggerMap)
{
const AZ::s32 jointId = eventIter.second.GetJointId();
if (jointId >= 0)
{
auto jointIter = m_jointProxies.find(eventIter.second.GetJointId());
if (jointIter == m_jointProxies.end())
{
Audio::IAudioProxy* proxy = nullptr;
Audio::AudioSystemRequestBus::BroadcastResult(proxy, &Audio::AudioSystemRequestBus::Events::GetFreeAudioProxy);
AZ_Assert(proxy, "Failed to get free audio proxy");
AZStd::string proxyName = AZStd::string::format("%s:%d", name.c_str(), jointId);
proxy->Initialize(proxyName.c_str());
proxy->SetObstructionCalcType(Audio::eAOOCT_IGNORE);
m_jointProxies.emplace(jointId, proxy);
}
}
}
}
void AnimAudioComponent::DeactivateJointProxies()
{
for (auto& iter : m_jointProxies)
{
if (Audio::IAudioProxy* proxy = iter.second)
{
proxy->StopAllTriggers();
proxy->Release();
}
}
m_jointProxies.clear();
}
void AnimAudioComponent::OnAudioEvent(const Audio::SAudioRequestInfo* const requestInfo)
{
if (requestInfo->eAudioRequestType == Audio::eART_AUDIO_CALLBACK_MANAGER_REQUEST)
{
const auto notificationType = static_cast<Audio::EAudioCallbackManagerRequestType>(requestInfo->nSpecificAudioRequest);
if (notificationType == Audio::eACMRT_REPORT_FINISHED_TRIGGER_INSTANCE)
{
if (requestInfo->eResult == Audio::eARR_SUCCESS)
{
AZ::EntityId entityId(reinterpret_cast<AZ::u64>(requestInfo->pUserData));
AnimAudioComponentNotificationBus::Event(entityId, &AnimAudioComponentNotificationBus::Events::OnTriggerFinished, requestInfo->nAudioControlID);
}
}
}
}
AnimAudioComponent::TriggerEventData::TriggerEventData(const AZ::Entity& entity, Audio::TAudioControlID triggerId, AZ::s32 jointId)
: m_jointId(jointId)
, m_triggerId(triggerId)
{
AZ_UNUSED(entity);
}
AZ::s32 AnimAudioComponent::TriggerEventData::GetJointId() const
{
return m_jointId;
}
Audio::TAudioControlID AnimAudioComponent::TriggerEventData::GetTriggerId() const
{
return m_triggerId;
}
} // namespace Integration
} // namespace EMotionFX
@@ -0,0 +1,147 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates, or
* a third party where indicated.
*
* 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.
*
*/
#pragma once
#include <AzCore/Component/Entity.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/Component/TransformBus.h>
#include <Integration/AnimationBus.h>
#include <Integration/AnimAudioComponentBus.h>
#include <IAudioSystem.h>
namespace EMotionFX
{
namespace Integration
{
struct AudioTriggerEvent
{
AZ_RTTI(AudioTriggerEvent, "{1AA35052-477B-4F8D-9DE3-6411E96B871D}");
AZ_CLASS_ALLOCATOR(AudioTriggerEvent, EMotionFXAllocator, 0);
AudioTriggerEvent() = default;
virtual ~AudioTriggerEvent() = default;
AudioTriggerEvent(const AZStd::string& eventName, const AZStd::string& triggerName, const AZStd::string& jointName)
: m_eventName(eventName)
, m_triggerName(triggerName)
, m_jointName(jointName)
{
}
static void Reflect(AZ::ReflectContext* context);
AZStd::string m_eventName;
AZStd::string m_triggerName;
AZStd::string m_jointName;
};
class AnimAudioComponent
: public AZ::Component
, protected AZ::TickBus::Handler
, protected AZ::TransformNotificationBus::Handler
, protected ActorNotificationBus::Handler
, protected AnimAudioComponentRequestBus::Handler
, protected AnimAudioComponentNotificationBus::Handler
{
public:
AZ_COMPONENT(AnimAudioComponent, "{E39F772F-FE4C-405E-9008-A5B8F27CB57D}");
void Init() override;
void Activate() override;
void Deactivate() override;
// AZ::TickBus implementation
void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
// AZ::TransformNotificationBus interface implementation
void OnTransformChanged(const AZ::Transform& local, const AZ::Transform& world) override;
// ActorNotificationBus interface implementation
void OnMotionEvent(EMotionFX::Integration::MotionEvent motionEvent) override;
// AnimAudioComponentRequestBus interface implementation
void AddTriggerEvent(const AZStd::string& eventName, const AZStd::string& triggerName, const AZStd::string& jointName) override;
void ClearTriggerEvents() override;
void RemoveTriggerEvent(const AZStd::string& eventName) override;
bool ExecuteSourceTrigger(
const Audio::TAudioControlID triggerID,
const Audio::SAudioCallBackInfos& callbackInfo,
const Audio::TAudioControlID& sourceId,
const AZStd::string& jointName) override;
bool ExecuteTrigger(
const Audio::TAudioControlID triggerID,
const Audio::SAudioCallBackInfos& callbackInfo,
const AZStd::string& jointName) override;
void KillTrigger(const Audio::TAudioControlID triggerID, const AZStd::string* jointName) override;
void KillAllTriggers(const AZStd::string* jointName) override;
void SetRtpcValue(const Audio::TAudioControlID rtpcID, float value, const AZStd::string* jointName) override;
void SetSwitchState(const Audio::TAudioControlID switchID, const Audio::TAudioSwitchStateID stateID, const AZStd::string* jointName) override;
void SetEnvironmentAmount(const Audio::TAudioEnvironmentID environmentID, float amount, const AZStd::string* jointName) override;
// AnimAudioComponentNotificationBus interface implementation
void OnTriggerStarted(const Audio::TAudioControlID triggerID) override;
void OnTriggerFinished(const Audio::TAudioControlID triggerID) override;
static void Reflect(AZ::ReflectContext* context);
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC("AnimationAudioService", 0xaed4f3ea));
}
static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
{
required.push_back(AZ_CRC("EMotionFXActorService", 0xd6e8f48d));
required.push_back(AZ_CRC("AudioProxyService", 0x7da4c79c));
}
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
{
incompatible.push_back(AZ_CRC("AnimationAudioService", 0xaed4f3ea));
}
private:
class TriggerEventData
{
public:
TriggerEventData(const AZ::Entity& entity, Audio::TAudioControlID triggerId, AZ::s32 jointId);
AZ::s32 GetJointId() const;
Audio::TAudioControlID GetTriggerId() const;
private:
AZ::s32 m_jointId = -1;
Audio::TAudioControlID m_triggerId = INVALID_AUDIO_CONTROL_ID;
};
void AddTriggerEventInternal(const AZStd::string& eventName, const AZStd::string& triggerName, const AZStd::string& jointName);
void RemoveTriggerEventInternal(const AZ::Crc32& eventName);
void ActivateJointProxies();
void DeactivateJointProxies();
static void OnAudioEvent(const Audio::SAudioRequestInfo* const requestInfo);
AZ::u32 m_activeVoices = 0;
AZStd::vector<AudioTriggerEvent> m_eventsToAdd;
AZStd::vector<AZ::Crc32> m_eventsToRemove;
AZStd::unordered_map<AZ::Crc32, TriggerEventData> m_eventTriggerMap;
AZStd::unordered_map<AZ::s32, Audio::IAudioProxy*> m_jointProxies;
AZStd::unique_ptr<Audio::SAudioCallBackInfos> m_callbackInfo;
AZ::Transform m_transform;
};
} // namespace Integration
} // namespace EMotionFX
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,220 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/Component/Component.h>
#include <AzCore/Component/TransformBus.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/Script/ScriptProperty.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <Integration/Assets/AnimGraphAsset.h>
#include <Integration/Assets/MotionSetAsset.h>
#include <Integration/ActorComponentBus.h>
#include <Integration/AnimGraphComponentBus.h>
#include <Integration/AnimGraphNetworkingBus.h>
namespace EMotionFX
{
namespace Integration
{
class AnimGraphComponent
: public AZ::Component
, private AZ::Data::AssetBus::MultiHandler
, private ActorComponentNotificationBus::Handler
, private AnimGraphComponentRequestBus::Handler
, private AnimGraphComponentNotificationBus::Handler
, private AnimGraphComponentNetworkRequestBus::Handler
{
public:
friend class EditorAnimGraphComponent;
AZ_COMPONENT(AnimGraphComponent, "{77624349-D5C4-4902-9F08-665814520999}");
/**
* Structure containing data-driven properties extracted from the anim graph,
* to allow override control per-entity via the component inspector UI.
*/
struct ParameterDefaults
{
AZ_TYPE_INFO(ParameterDefaults, "{E6826EB9-C79B-43F3-A03F-3298DD3C724E}")
~ParameterDefaults();
ParameterDefaults& operator=(const ParameterDefaults& rhs)
{
if (this == &rhs)
{
return *this;
}
Reset();
m_parameters.reserve(rhs.m_parameters.size());
for (AZ::ScriptProperty* p : rhs.m_parameters)
{
m_parameters.push_back(p->Clone());
}
return *this;
}
using ParameterList = AZStd::vector<AZ::ScriptProperty*>;
ParameterList m_parameters;
void Reset();
static void Reflect(AZ::ReflectContext* context);
};
/**
* Configuration struct for procedural configuration of Actor Components.
*/
struct Configuration
{
AZ_TYPE_INFO(Configuration, "{F5A93340-60CD-4A16-BEF3-1014D762B217}")
AZ::Data::Asset<AnimGraphAsset> m_animGraphAsset; ///< Selected anim graph.
AZ::Data::Asset<MotionSetAsset> m_motionSetAsset; ///< Selected motion set asset.
AZStd::string m_activeMotionSetName; ///< Selected motion set.
bool m_visualize = false; ///< Debug visualization.
ParameterDefaults m_parameterDefaults; ///< Defaults for parameter values.
static void Reflect(AZ::ReflectContext* context);
};
AnimGraphComponent(const Configuration* config = nullptr);
~AnimGraphComponent() override;
//////////////////////////////////////////////////////////////////////////
// AZ::Component interface implementation
void Init() override;
void Activate() override;
void Deactivate() override;
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// AnimGraphComponentRequestBus::Handler
EMotionFX::AnimGraphInstance* GetAnimGraphInstance() override;
AZ::u32 FindParameterIndex(const char* parameterName) override;
const char* FindParameterName(AZ::u32 parameterIndex) override;
void SetParameterFloat(AZ::u32 parameterIndex, float value) override;
void SetParameterBool(AZ::u32 parameterIndex, bool value) override;
void SetParameterString(AZ::u32 parameterIndex, const char* value) override;
void SetParameterVector2(AZ::u32 parameterIndex, const AZ::Vector2& value) override;
void SetParameterVector3(AZ::u32 parameterIndex, const AZ::Vector3& value) override;
void SetParameterRotationEuler(AZ::u32 parameterIndex, const AZ::Vector3& value) override;
void SetParameterRotation(AZ::u32 parameterIndex, const AZ::Quaternion& value) override;
void SetNamedParameterFloat(const char* parameterName, float value) override;
void SetNamedParameterBool(const char* parameterName, bool value) override;
void SetNamedParameterString(const char* parameterName, const char* value) override;
void SetNamedParameterVector2(const char* parameterName, const AZ::Vector2& value) override;
void SetNamedParameterVector3(const char* parameterName, const AZ::Vector3& value) override;
void SetNamedParameterRotationEuler(const char* parameterName, const AZ::Vector3& value) override;
void SetNamedParameterRotation(const char* parameterName, const AZ::Quaternion& value) override;
void SetVisualizeEnabled(bool enabled) override;
float GetParameterFloat(AZ::u32 parameterIndex) override;
bool GetParameterBool(AZ::u32 parameterIndex) override;
AZStd::string GetParameterString(AZ::u32 parameterIndex) override;
AZ::Vector2 GetParameterVector2(AZ::u32 parameterIndex) override;
AZ::Vector3 GetParameterVector3(AZ::u32 parameterIndex) override;
AZ::Vector3 GetParameterRotationEuler(AZ::u32 parameterIndex) override;
AZ::Quaternion GetParameterRotation(AZ::u32 parameterIndex) override;
float GetNamedParameterFloat(const char* parameterName) override;
bool GetNamedParameterBool(const char* parameterName) override;
AZStd::string GetNamedParameterString(const char* parameterName) override;
AZ::Vector2 GetNamedParameterVector2(const char* parameterName) override;
AZ::Vector3 GetNamedParameterVector3(const char* parameterName) override;
AZ::Vector3 GetNamedParameterRotationEuler(const char* parameterName) override;
AZ::Quaternion GetNamedParameterRotation(const char* parameterName) override;
bool GetVisualizeEnabled() override;
void SyncAnimGraph(AZ::EntityId leaderEntityId) override;
void DesyncAnimGraph(AZ::EntityId leaderEntityId) override;
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// ActorComponentNotificationBus::Handler
void OnActorInstanceCreated(EMotionFX::ActorInstance* /*actorInstance*/) override;
void OnActorInstanceDestroyed(EMotionFX::ActorInstance* /*actorInstance*/) override;
//////////////////////////////////////////////////////////////////////////
// AnimGraphComponentNetworkRequestBus
bool IsAssetReady() const override;
bool HasSnapshot() const override;
void CreateSnapshot(bool isAuthoritative) override;
void SetActiveStates(const NodeIndexContainer& activeStates) override;
const NodeIndexContainer& GetActiveStates() const override;
void SetMotionPlaytimes(const MotionNodePlaytimeContainer& motionNodePlaytimes) override;
const MotionNodePlaytimeContainer& GetMotionPlaytimes() const override;
void UpdateActorExternal(float deltatime) override;
void SetNetworkRandomSeed(AZ::u64 seed) override;
AZ::u64 GetNetworkRandomSeed() const override;
//////////////////////////////////////////////////////////////////////////
// AnimGraphComponentNotificationBus::Handler
void OnAnimGraphSynced(EMotionFX::AnimGraphInstance* /*animGraphInstance(Follower)*/) override;
void OnAnimGraphDesynced(EMotionFX::AnimGraphInstance* /*animGraphInstance(Follower)*/) override;
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC("EMotionFXAnimGraphService", 0x9ec3c819));
}
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
{
incompatible.push_back(AZ_CRC("EMotionFXAnimGraphService", 0x9ec3c819));
incompatible.push_back(AZ_CRC("EMotionFXSimpleMotionService", 0xea7a05d8));
}
static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent)
{
dependent.push_back(AZ_CRC("PhysicsService", 0xa7350d22));
dependent.push_back(AZ_CRC("MeshService", 0x71d8a455));
}
static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
{
required.push_back(AZ_CRC("TransformService", 0x8ee22c50));
required.push_back(AZ_CRC("EMotionFXActorService", 0xd6e8f48d));
}
static void Reflect(AZ::ReflectContext* context);
//////////////////////////////////////////////////////////////////////////
// AZ::Data::AssetBus::Handler
void OnAssetReady(AZ::Data::Asset<AZ::Data::AssetData> asset) override;
void OnAssetReloaded(AZ::Data::Asset<AZ::Data::AssetData> asset) override;
void SetAnimGraphAssetId(const AZ::Data::AssetId& assetId);
void SetMotionSetAssetId(const AZ::Data::AssetId& assetId);
private:
void CheckCreateAnimGraphInstance();
void DestroyAnimGraphInstance();
// Helper functions to wrap special logic required for EMFX anim graph ref-counting.
void AnimGraphInstancePostCreate();
void AnimGraphInstancePreDestroy();
Configuration m_configuration; ///< Component configuration.
EMotionFXPtr<EMotionFX::ActorInstance> m_actorInstance; ///< Associated actor instance (retrieved from Actor Component).
EMotionFXPtr<EMotionFX::AnimGraphInstance> m_animGraphInstance; ///< Live anim graph instance.
};
} // namespace Integration
} // namespace EMotionFX
@@ -0,0 +1,448 @@
/*
* 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 "EMotionFX_precompiled.h"
#include <Integration/Components/AnimGraphNetSyncComponent.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <GridMate/Replica/ReplicaFunctions.h>
#include <GridMate/Serialize/MathMarshal.h>
#include <AzFramework/Network/NetBindingHandlerBus.h>
namespace EMotionFX
{
namespace Integration
{
namespace Network
{
/**
* \brief This is a GridMate chunk that replicates Anim Graph parameters.
* It's challenge is to replicate any of the supported parameter types where
* the types are only known at runtime. To solve that, many datasets are created
* with helper macros to avoid code duplication (@PARAM_DATASET and @PARAM_DATASET_NAME).
*
* For maximum compression, one should build a custom component that specifies the anim graph parameters by hand, for example:
*
* DataSet<float> m_param0;
*
* or if using delta compression feature of GridMate:
*
* DeltaCompressedDataSet<float, 1> m_param1;
*
* Active nodes (@m_activeNodes) change infrequently.
*
* Warning: @m_motionNodes motion nodes often do change frequently as their motion play time ticks down.
* Care must be applied when aiming for the network budget of a project.
*/
class AnimGraphNetSyncComponent::Chunk : public GridMate::ReplicaChunkBase
{
public:
GM_CLASS_ALLOCATOR(Chunk);
Chunk() : m_activeNodes("Active Nodes", NodeIndexContainer{}), m_motionNodes("Motion Nodes", MotionNodePlaytimeContainer{}) {}
static const char* GetChunkName() { return "AnimGraphNetSyncComponent::Chunk"; }
bool IsReplicaMigratable() override { return true; }
using AnimDataSetType = GridMate::DataSet<AnimParameter, AnimParameterMarshaler, AnimParameterThrottler>;
template <void (AnimGraphNetSyncComponent::* CallbackMethod)(const AnimParameter&, const GridMate::TimeContext&)>
using AnimDataSet = AnimDataSetType::BindInterface<AnimGraphNetSyncComponent, CallbackMethod>;
// A helper macro that creates a variable like this one:
// AnimDataSet<&AnimGraphNetSyncComponent::OnAnimParameterChanged<0>> m_parameter0 = { "Param 0" };
#define PARAM_DATASET( N ) AnimDataSet<&AnimGraphNetSyncComponent::OnAnimParameterChanged< N >> m_parameter##N = { "Param " #N }
PARAM_DATASET(0);
PARAM_DATASET(1);
PARAM_DATASET(2);
PARAM_DATASET(3);
PARAM_DATASET(4);
PARAM_DATASET(5);
PARAM_DATASET(6);
PARAM_DATASET(7);
PARAM_DATASET(8);
PARAM_DATASET(9);
/*
* Note: GridMate by default supports up to 32 DataSets per ReplicaChunk: @GM_MAX_DATASETS_IN_CHUNK.
* That means that a component can sync up to 32 separate network fields. One can vary the number of supported number
* of parameters by simply creating new entries of @PARAM_DATASET above and @PARAM_DATASET_NAME below.
*/
// A collection of datasets that are used to synchronize anim graph parameters.
AZStd::array<AnimDataSetType*, 10> m_parameters = { { // clang pre-6.0 requires double "{{" here but doesn't perform compile length verification :(
&m_parameter0,
&m_parameter1,
&m_parameter2,
&m_parameter3,
&m_parameter4,
&m_parameter5,
&m_parameter6,
&m_parameter7,
&m_parameter8,
&m_parameter9,
} };
GridMate::DataSet<NodeIndexContainer, NodeIndexContainerMarshaler>::
BindInterface<AnimGraphNetSyncComponent, &AnimGraphNetSyncComponent::OnActiveNodesChanged> m_activeNodes;
GridMate::DataSet<MotionNodePlaytimeContainer, MotionNodePlaytimeContainerMarshaler>::
BindInterface<AnimGraphNetSyncComponent, &AnimGraphNetSyncComponent::OnMotionNodesChanged> m_motionNodes;
};
void AnimGraphNetSyncComponent::Reflect(AZ::ReflectContext* context)
{
GridMate::ReplicaChunkDescriptorTable& descTable = GridMate::ReplicaChunkDescriptorTable::Get();
if (!descTable.FindReplicaChunkDescriptor(GridMate::ReplicaChunkClassId(Chunk::GetChunkName())))
{
descTable.RegisterChunkType<Chunk>();
}
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<AnimGraphNetSyncComponent, AZ::Component>()
->Version(1)
->Field( "Sync parameters", &AnimGraphNetSyncComponent::m_syncParameters )
->Field( "Sync active nodes", &AnimGraphNetSyncComponent::m_syncActiveNodes )
->Field( "Sync motion nodes", &AnimGraphNetSyncComponent::m_syncMotionNodes )
;
AZ::EditContext* editContent = serializeContext->GetEditContext();
if (editContent)
{
editContent->Class<AnimGraphNetSyncComponent>("Anim Graph Net Sync",
"Replicates anim graph parameters over the network using GridMate")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c))
->Attribute(AZ::Edit::Attributes::Category, "Networking")
->Attribute(AZ::Edit::Attributes::Icon, "Editor/Icons/Components/AnimGraphNetSync.svg")
->DataElement( AZ::Edit::UIHandlers::Default, &AnimGraphNetSyncComponent::m_syncParameters, "Sync parameters",
"Synchronize parameters of the anim graph on the entity" )
->DataElement( AZ::Edit::UIHandlers::Default, &AnimGraphNetSyncComponent::m_syncActiveNodes, "Sync active nodes",
"Synchronize active nodes in the anim graph on the entity" )
->DataElement( AZ::Edit::UIHandlers::Default, &AnimGraphNetSyncComponent::m_syncMotionNodes, "Sync motion nodes",
"Synchronize motion nodes in the anim graph on the entity. Warning: this may take a significant amount of network bandwidth" )
;
}
}
}
void AnimGraphNetSyncComponent::Activate()
{
AnimGraphComponentNotificationBus::Handler::BusConnect(GetEntityId());
if (m_syncMotionNodes || m_syncActiveNodes) // if there is anything synchronize over the network
{
const bool isAuthoritative = AzFramework::NetQuery::IsEntityAuthoritative(GetEntityId());
if (isAuthoritative)
{
// Only the server (or authoritative entity) needs to watch the nodes values.
AZ::TickBus::Handler::BusConnect();
}
// We need to get anim graph instance. It will be either available to us now or later via a notification bus. See @OnAnimGraphInstanceCreated
AnimGraphComponentRequestBus::EventResult(m_instance, GetEntityId(), &AnimGraphComponentRequestBus::Events::GetAnimGraphInstance);
if (m_instance)
{
if (!m_instance->GetSnapshot())
{
m_instance->CreateSnapshot(isAuthoritative);
}
}
}
}
void AnimGraphNetSyncComponent::Deactivate()
{
AnimGraphComponentNotificationBus::Handler::BusDisconnect();
AZ::TickBus::Handler::BusDisconnect();
}
void AnimGraphNetSyncComponent::SetParameterOnClient(const AnimParameter& value, AZ::u8 index)
{
switch (value.m_type)
{
case AnimParameter::Type::Unsupported:
break;
case AnimParameter::Type::Float:
AnimGraphComponentRequestBus::Event(GetEntityId(), &AnimGraphComponentRequestBus::Events::SetParameterFloat, index, value.m_value.f);
break;
case AnimParameter::Type::Bool:
AnimGraphComponentRequestBus::Event(GetEntityId(), &AnimGraphComponentRequestBus::Events::SetParameterBool, index, value.m_value.b);
break;
case AnimParameter::Type::Vector2:
AnimGraphComponentRequestBus::Event(GetEntityId(), &AnimGraphComponentRequestBus::Events::SetParameterVector2, index, value.m_value.v2);
break;
case AnimParameter::Type::Vector3:
AnimGraphComponentRequestBus::Event(GetEntityId(), &AnimGraphComponentRequestBus::Events::SetParameterVector3, index, value.m_value.v3);
break;
case AnimParameter::Type::Quaternion:
AnimGraphComponentRequestBus::Event(GetEntityId(), &AnimGraphComponentRequestBus::Events::SetParameterRotation, index, value.m_value.q);
break;
default:
AZ_Assert(false, "Unsupported type");
break;
}
}
template <AZ::u8 Index>
void AnimGraphNetSyncComponent::OnAnimParameterChanged(const AnimParameter& value, const GridMate::TimeContext&)
{
SetParameterOnClient(value, Index);
}
template <AnimParameter::Type AnimParameterType, typename FieldType>
void AnimGraphNetSyncComponent::SetParameterOnServer(AZ::u8 parameterIndex, const FieldType& newValue)
{
if (m_syncParameters)
{
if (Chunk* chunk = GetChunk())
{
if (parameterIndex < chunk->m_parameters.size())
{
AnimParameter param;
param.m_type = AnimParameterType;
static_assert(sizeof(FieldType) <= sizeof(param.m_value), "The largest value param.m_value can store is a Quaternion");
// This is to simplify writing a value into a union.
// Ideally, one would use std::variant (C++17) instead of a union.
memcpy(&param.m_value, &newValue, sizeof(FieldType));
chunk->m_parameters[parameterIndex]->Set(param);
}
else
{
AZ_Warning("EMotionFX", false, "AnimGraphNetSyncComponent does not support synchronizing more than %u parameters", chunk->m_parameters.size());
}
}
}
}
void AnimGraphNetSyncComponent::OnAnimGraphFloatParameterChanged(EMotionFX::AnimGraphInstance*,
AZ::u32 parameterIndex,
float beforeValue,
float afterValue)
{
AZ_UNUSED(beforeValue);
SetParameterOnServer<AnimParameter::Type::Float>(static_cast<AZ::u8>(parameterIndex), afterValue);
}
void AnimGraphNetSyncComponent::OnAnimGraphBoolParameterChanged(EMotionFX::AnimGraphInstance*,
AZ::u32 parameterIndex,
bool beforeValue,
bool afterValue)
{
AZ_UNUSED(beforeValue);
SetParameterOnServer<AnimParameter::Type::Bool>(static_cast<AZ::u8>(parameterIndex), afterValue);
}
void AnimGraphNetSyncComponent::OnAnimGraphStringParameterChanged(EMotionFX::AnimGraphInstance*,
AZ::u32 parameterIndex,
const char* beforeValue,
const char* afterValue)
{
AZ_UNUSED(parameterIndex);
AZ_UNUSED(beforeValue);
AZ_UNUSED(afterValue);
AZ_Warning("EMotionFX", false, "AnimGraphNetSync component does not supported synchronizing string parameters, please consider refactoring your anim graph to replace strings with integers or enum values.");
}
void AnimGraphNetSyncComponent::OnAnimGraphVector2ParameterChanged(EMotionFX::AnimGraphInstance*,
AZ::u32 parameterIndex,
const AZ::Vector2& beforeValue,
const AZ::Vector2& afterValue)
{
AZ_UNUSED(beforeValue);
SetParameterOnServer<AnimParameter::Type::Vector2>(static_cast<AZ::u8>(parameterIndex), afterValue);
}
void AnimGraphNetSyncComponent::OnAnimGraphVector3ParameterChanged(EMotionFX::AnimGraphInstance*,
AZ::u32 parameterIndex,
const AZ::Vector3& beforeValue,
const AZ::Vector3& afterValue)
{
AZ_UNUSED(beforeValue);
SetParameterOnServer<AnimParameter::Type::Vector3>(static_cast<AZ::u8>(parameterIndex), afterValue);
}
void AnimGraphNetSyncComponent::OnAnimGraphRotationParameterChanged(EMotionFX::AnimGraphInstance*,
AZ::u32 parameterIndex,
const AZ::Quaternion& beforeValue,
const AZ::Quaternion& afterValue)
{
AZ_UNUSED(beforeValue);
SetParameterOnServer<AnimParameter::Type::Quaternion>(static_cast<AZ::u8>(parameterIndex), afterValue);
}
void AnimGraphNetSyncComponent::OnActiveNodesChanged(const NodeIndexContainer& activeNodes, const GridMate::TimeContext& tc)
{
AZ_UNUSED(tc);
// Client receiving values
if (m_instance)
{
if (const AZStd::shared_ptr<AnimGraphSnapshot> snapshot = m_instance->GetSnapshot())
{
snapshot->SetActiveNodes(activeNodes);
}
}
}
void AnimGraphNetSyncComponent::OnMotionNodesChanged(const MotionNodePlaytimeContainer& motionNodes, const GridMate::TimeContext& tc)
{
AZ_UNUSED(tc);
// Client receiving values
if (m_instance)
{
if (const AZStd::shared_ptr<AnimGraphSnapshot> snapshot = m_instance->GetSnapshot())
{
snapshot->SetMotionNodePlaytimes(motionNodes);
}
}
}
bool AnimGraphNetSyncComponent::IsDifferent(const MotionNodePlaytimeContainer& oldList, const MotionNodePlaytimeContainer& newList) const
{
if (oldList.size() != newList.size())
{
return true;
}
AZStd::size_t i = 0;
for (auto& value : oldList)
{
if (value.first != newList[i].first || value.second != newList[i].second)
{
return true;
}
++i;
}
return false;
}
bool AnimGraphNetSyncComponent::IsDifferent(const NodeIndexContainer& oldList, const NodeIndexContainer& newList) const
{
if (oldList.size() != newList.size())
{
return true;
}
AZStd::size_t i = 0;
for (AZ::u32 value : oldList)
{
if (value != newList[i])
{
return true;
}
++i;
}
return false;
}
void AnimGraphNetSyncComponent::OnTick(float deltaTime, AZ::ScriptTimePoint time)
{
AZ_UNUSED(deltaTime);
AZ_UNUSED(time);
if (!GetChunk())
{
return; // network is not ready yet
}
if (m_instance)
{
if (const AZStd::shared_ptr<AnimGraphSnapshot> snapshot = m_instance->GetSnapshot())
{
if (m_syncActiveNodes)
{
const NodeIndexContainer& activeNodes = snapshot->GetActiveNodes();
const NodeIndexContainer& currentValue = GetChunk()->m_activeNodes.Get();
if (IsDifferent(currentValue, activeNodes))
{
GetChunk()->m_activeNodes.Set(activeNodes); // Server sending the values
}
}
if (m_syncMotionNodes)
{
const MotionNodePlaytimeContainer& playTimes = snapshot->GetMotionNodePlaytimes();
const MotionNodePlaytimeContainer& currentTimes = GetChunk()->m_motionNodes.Get();
if (IsDifferent(currentTimes, playTimes))
{
GetChunk()->m_motionNodes.Set(playTimes); // Server sending the values
}
}
}
}
}
void AnimGraphNetSyncComponent::OnAnimGraphInstanceCreated(EMotionFX::AnimGraphInstance* instance)
{
m_instance = instance;
if (m_instance)
{
const bool isAuthoritative = AzFramework::NetQuery::IsEntityAuthoritative(GetEntityId());
if (!m_instance->GetSnapshot())
{
m_instance->CreateSnapshot(isAuthoritative);
}
}
}
void AnimGraphNetSyncComponent::OnAnimGraphInstanceDestroyed(EMotionFX::AnimGraphInstance*)
{
m_instance = nullptr;
}
AnimGraphNetSyncComponent::Chunk* AnimGraphNetSyncComponent::GetChunk() const
{
return static_cast<Chunk*>(m_chunk.get());
}
GridMate::ReplicaChunkPtr AnimGraphNetSyncComponent::GetNetworkBinding()
{
m_chunk = GridMate::CreateReplicaChunk<Chunk>();
AZ_Assert(m_chunk, "Failed to create a chunk");
if (m_instance)
{
if (!m_instance->GetSnapshot())
{
m_instance->CreateSnapshot(true /* authoritative */);
}
}
return m_chunk;
}
void AnimGraphNetSyncComponent::SetNetworkBinding(GridMate::ReplicaChunkPtr chunk)
{
m_chunk = chunk;
m_chunk->SetHandler(this);
}
void AnimGraphNetSyncComponent::UnbindFromNetwork()
{
AZ_Assert(m_chunk, "There wasn't any chunk present");
if (m_chunk)
{
m_chunk->SetHandler(nullptr);
m_chunk = nullptr;
}
}
}
} // namespace Integration
} // namespace EMotionFXAnimation
@@ -0,0 +1,153 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/Component/Component.h>
#include <AzCore/Component/TickBus.h>
#include <AzFramework/Network/NetBindable.h>
#include <Integration/AnimGraphComponentBus.h>
#include <Integration/Components/AnimGraphNetSyncTypes.h>
namespace EMotionFX
{
namespace Integration
{
namespace Network
{
/**
* \brief Generic solution for synchronizing parameters of Anim Graph component.
* Synchronization is done over GridMate.
*
* Note that this is not the most optimal synchronization but it does
* work for just about all Anim Graphs.
*
* Disclaimer: string parameters are not supported! Because one should not synchronize
* strings over the network. They ought to be converted to enum/int values beforehand.
*/
class AnimGraphNetSyncComponent
: public AZ::Component
, public AzFramework::NetBindable
, public AnimGraphComponentNotificationBus::Handler
, public AZ::TickBus::Handler
{
public:
AZ_COMPONENT(AnimGraphNetSyncComponent, "{2F9428C1-0F07-4667-B052-40D9BC473AD3}", NetBindable);
static void Reflect(AZ::ReflectContext* context);
// AZ::Component interface implementation
void Activate() override;
void Deactivate() override;
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC("EMotionFXAnimGraphNetSyncService", 0x42e6f127));
}
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
{
incompatible.push_back(AZ_CRC("EMotionFXAnimGraphNetSyncService", 0x42e6f127));
}
static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
{
required.push_back(AZ_CRC("EMotionFXAnimGraphService", 0x9ec3c819));
required.push_back(AZ_CRC("ReplicaChunkService", 0xf86b88a8));
}
protected:
// NetBindable interface implementation
GridMate::ReplicaChunkPtr GetNetworkBinding() override;
void SetNetworkBinding(GridMate::ReplicaChunkPtr chunk) override;
void UnbindFromNetwork() override;
// AnimGraphComponentNotificationBus interface implementation
void OnAnimGraphFloatParameterChanged(EMotionFX::AnimGraphInstance*,
AZ::u32 parameterIndex,
float beforeValue,
float afterValue) override;
void OnAnimGraphBoolParameterChanged(EMotionFX::AnimGraphInstance*,
AZ::u32 parameterIndex,
bool beforeValue,
bool afterValue) override;
void OnAnimGraphStringParameterChanged(EMotionFX::AnimGraphInstance*,
AZ::u32 parameterIndex,
const char* beforeValue,
const char* afterValue) override;
void OnAnimGraphVector2ParameterChanged(EMotionFX::AnimGraphInstance*,
AZ::u32 parameterIndex,
const AZ::Vector2& beforeValue,
const AZ::Vector2& afterValue) override;
void OnAnimGraphVector3ParameterChanged(EMotionFX::AnimGraphInstance*,
AZ::u32 parameterIndex,
const AZ::Vector3& beforeValue,
const AZ::Vector3& afterValue) override;
void OnAnimGraphRotationParameterChanged(EMotionFX::AnimGraphInstance*,
AZ::u32 parameterIndex,
const AZ::Quaternion& beforeValue,
const AZ::Quaternion& afterValue) override;
// TickBus
void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
// AnimGraphComponentNotificationBus
void OnAnimGraphInstanceCreated(EMotionFX::AnimGraphInstance* instance) override;
void OnAnimGraphInstanceDestroyed(EMotionFX::AnimGraphInstance* instance) override;
private:
class Chunk;
GridMate::ReplicaChunkPtr m_chunk;
Chunk* GetChunk() const;
// DataSet callback, it's a template to avoid duplicating similar callbacks
template <AZ::u8 Index>
void OnAnimParameterChanged(const AnimParameter& value, const GridMate::TimeContext& tc);
// Helper on a client side
void SetParameterOnClient(const AnimParameter& value, AZ::u8 index);
// Helper on the server side to avoid duplicating very similar callbacks
template <AnimParameter::Type AnimParameterType, typename FieldType>
void SetParameterOnServer(AZ::u8 parameterIndex, const FieldType& newValue);
/**
* \brief Optionally turn on or off replicating parameters of an anim graph on the same entity as this component.
*/
bool m_syncParameters = true;
/**
* \brief Optionally turn on or off replicating active nodes of an anim graph on the same entity as this component.
*/
bool m_syncActiveNodes = false;
/**
* \brief Optionally turn on or off replicating motion playtime nodes of an anim graph on the same entity as this component.
*
* It's off by default because these nodes are very frequently changing and would result in a high network bandwidth use.
*/
bool m_syncMotionNodes = false;
// GridMate DataSet callback on clients
void OnActiveNodesChanged(const NodeIndexContainer& activeNodes, const GridMate::TimeContext& tc);
// GridMate DataSet callback on clients
void OnMotionNodesChanged(const MotionNodePlaytimeContainer& motionNodes, const GridMate::TimeContext& tc);
// Helper comparison method to avoid sending the same data
bool IsDifferent(const NodeIndexContainer& oldList, const NodeIndexContainer& newList) const;
// Helper comparison method to avoid sending the same data
bool IsDifferent(const MotionNodePlaytimeContainer& oldList, const MotionNodePlaytimeContainer& newList) const;
EMotionFX::AnimGraphInstance* m_instance = nullptr;
};
}
} // namespace Integration
} // namespace EMotionFX
@@ -0,0 +1,285 @@
/*
* 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.
*
*/
#pragma once
#include <GridMate/Serialize/Buffer.h>
#include <GridMate/Serialize/MathMarshal.h>
#include <GridMate/Serialize/CompressionMarshal.h>
namespace EMotionFX
{
namespace Integration
{
namespace Network
{
/**
* \brief A general storage for an anim graph parameter.
*/
class AnimParameter
{
public:
/**
* \brief String type is not supported because one should not be syncing strings over the network.
*/
enum class Type : AZ::u8
{
Unsupported,
Float,
Bool,
Vector2,
Vector3,
Quaternion,
};
/**
* \brief A storage for all possible supported types in @AnimGraphNetSyncComponent
*/
union Value
{
Value()
{
q = AZ::Quaternion::CreateZero();
}
float f;
bool b = false;
AZ::Vector2 v2;
AZ::Vector3 v3;
AZ::Quaternion q;
};
AnimParameter() : m_type(Type::Unsupported) {}
Type m_type;
Value m_value;
AnimParameter(const AnimParameter& other)
{
m_type = other.m_type;
CopyValue(other);
}
AnimParameter& operator=(const AnimParameter& other)
{
m_type = other.m_type;
CopyValue(other);
return *this;
}
friend bool operator==(const AnimParameter& lhs, const AnimParameter& rhs)
{
if (lhs.m_type != rhs.m_type)
{
return false;
}
switch (lhs.m_type)
{
case Type::Float:
return lhs.m_value.f == rhs.m_value.f;
case Type::Bool:
return lhs.m_value.b == rhs.m_value.b;
case Type::Vector2:
return lhs.m_value.v2 == rhs.m_value.v2;
case Type::Vector3:
return lhs.m_value.v3 == rhs.m_value.v3;
case Type::Quaternion:
return lhs.m_value.q == rhs.m_value.q;
default:
return true;
}
}
private:
void CopyValue(const AnimParameter& other)
{
switch (m_type)
{
case Type::Float:
m_value.f = other.m_value.f;
break;
case Type::Bool:
m_value.b = other.m_value.b;
break;
case Type::Vector2:
m_value.v2 = other.m_value.v2;
break;
case Type::Vector3:
m_value.v3 = other.m_value.v3;
break;
case Type::Quaternion:
m_value.q = other.m_value.q;
break;
default:
break;
}
}
};
/**
* \brief Custom GridMate throttler. See GridMate:: @BasicThrottle
*/
class AnimParameterThrottler
{
public:
bool WithinThreshold(const AnimParameter& newValue) const
{
return m_baseline == newValue;
}
void UpdateBaseline(const AnimParameter& baseline)
{
m_baseline = baseline;
}
private:
AnimParameter m_baseline;
};
/**
* \brief A custom GridMate marshaler.
* 1 byte is spend on the type. And a variable number of bytes afterwards for the value.
*/
class AnimParameterMarshaler
{
public:
void Marshal(GridMate::WriteBuffer& wb, const AnimParameter& parameter)
{
wb.Write(AZ::u8(parameter.m_type));
switch (parameter.m_type)
{
case AnimParameter::Type::Float:
wb.Write(parameter.m_value.f);
break;
case AnimParameter::Type::Bool:
wb.Write(parameter.m_value.b);
break;
case AnimParameter::Type::Vector2:
wb.Write(parameter.m_value.v2);
break;
case AnimParameter::Type::Vector3:
wb.Write(parameter.m_value.v3);
break;
case AnimParameter::Type::Quaternion:
wb.Write(parameter.m_value.q);
break;
default:
// other types are not supported
break;
}
}
void Unmarshal(AnimParameter& parameter, GridMate::ReadBuffer& rb)
{
AZ::u8 type;
rb.Read(type);
parameter.m_type = static_cast<AnimParameter::Type>(type);
switch (parameter.m_type)
{
case AnimParameter::Type::Float:
rb.Read(parameter.m_value.f);
break;
case AnimParameter::Type::Bool:
rb.Read(parameter.m_value.b);
break;
case AnimParameter::Type::Vector2:
rb.Read(parameter.m_value.v2);
break;
case AnimParameter::Type::Vector3:
rb.Read(parameter.m_value.v3);
break;
case AnimParameter::Type::Quaternion:
rb.Read(parameter.m_value.q);
break;
default:
// other types are not supported
break;
}
}
};
/**
* \brief Custom marshaler for Animation node index that is used by Activate Nodes list
*/
struct NodeIndexContainerMarshaler
{
void Marshal(GridMate::WriteBuffer& wb, const NodeIndexContainer& source) const
{
GridMate::VlqU64Marshaler m64;
GridMate::VlqU32Marshaler m32;
m64.Marshal(wb, source.size()); // 1 byte most of the time (if the size is less than 127)
for (AZ::u32 item : source)
{
m32.Marshal(wb, item); // 1 byte most of the time (if the value is less than 127)
}
}
void Unmarshal(NodeIndexContainer& target, GridMate::ReadBuffer& rb) const
{
target.clear();
GridMate::VlqU64Marshaler m64;
GridMate::VlqU32Marshaler m32;
AZ::u64 arraySize;
m64.Unmarshal(arraySize, rb);
target.resize(arraySize);
for (AZ::u64 i = 0; i < arraySize; ++i)
{
m32.Unmarshal(target[i], rb);
}
}
};
/**
* \brief Custom marshaler for Animation motion node information that is used by motion node playtime list
*/
struct MotionNodePlaytimeContainerMarshaler
{
void Marshal(GridMate::WriteBuffer& wb, const MotionNodePlaytimeContainer& source) const
{
GridMate::VlqU64Marshaler m64;
GridMate::VlqU32Marshaler m32;
m64.Marshal(wb, source.size());
for (const AZStd::pair<AZ::u32, float>& item : source)
{
m32.Marshal(wb, item.first); // average of 1 byte
wb.Write(item.second); // 4 bytes
}
}
void Unmarshal(MotionNodePlaytimeContainer& target, GridMate::ReadBuffer& rb) const
{
target.clear();
GridMate::VlqU64Marshaler m64;
GridMate::VlqU32Marshaler m32;
AZ::u64 arraySize;
m64.Unmarshal(arraySize, rb);
target.resize(arraySize);
for (AZ::u64 i = 0; i < arraySize; ++i)
{
m32.Unmarshal(target[i].first, rb);
rb.Read(target[i].second);
}
}
};
}
} // namespace Integration
} // namespace EMotionFXAnimation
@@ -0,0 +1,218 @@
/*
* 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/PlatformDef.h>
#include "EMotionFX_precompiled.h"
#include <AzCore/Component/Entity.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <Integration/Components/SimpleLODComponent.h>
#include <MCore/Source/AttributeString.h>
#include <MathConversion.h>
#include <IRenderAuxGeom.h>
namespace EMotionFX
{
namespace Integration
{
void SimpleLODComponent::Configuration::Reflect(AZ::ReflectContext *context)
{
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<Configuration>()
->Version(2)
->Field("LODDistances", &Configuration::m_lodDistances)
->Field("EnableLODSampling", &Configuration::m_enableLodSampling)
->Field("LODSampleRates", &Configuration::m_lodSampleRates)
;
AZ::EditContext* editContext = serializeContext->GetEditContext();
if (editContext)
{
editContext->Class<SimpleLODComponent::Configuration>("Configuration", "The LOD Configuration.")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AutoExpand, "")
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
->DataElement(0, &SimpleLODComponent::Configuration::m_lodDistances,
"LOD distance (Max)", "The maximum camera distance of this LOD.")
->Attribute(AZ::Edit::Attributes::ContainerCanBeModified, false)
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->ElementAttribute(AZ::Edit::Attributes::Step, 0.01f)
->ElementAttribute(AZ::Edit::Attributes::Suffix, " m")
->DataElement(0, &SimpleLODComponent::Configuration::m_enableLodSampling,
"Enable LOD anim graph sampling", "AnimGraph sample rate will adjust based on LOD level.")
->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::EntireTree)
->DataElement(0, &SimpleLODComponent::Configuration::m_lodSampleRates,
"Anim graph sample rates", "The sample rate of anim graph based on LOD. Setting it to O means the maximum sample rate.")
->Attribute(AZ::Edit::Attributes::Visibility, &SimpleLODComponent::Configuration::GetEnableLodSampling)
->Attribute(AZ::Edit::Attributes::ContainerCanBeModified, false)
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->ElementAttribute(AZ::Edit::Attributes::Step, 1.0f);
}
}
}
void SimpleLODComponent::Configuration::Reset()
{
m_lodDistances.clear();
}
void SimpleLODComponent::Configuration::GenerateDefaultValue(AZ::u32 numLODs)
{
if (numLODs != m_lodDistances.size())
{
// Generate the default LOD (max) distance to 10, 20, 30....
m_lodDistances.resize(numLODs);
for (AZ::u32 i = 0; i < numLODs; ++i)
{
m_lodDistances[i] = i * 10.0f + 10.0f;
}
}
if (numLODs != m_lodSampleRates.size())
{
// Generate the default LOD Sample Rate to 140, 60, 45, 25, 15, 10
const float defaultSampleRate[] = {140.0f, 60.0f, 45.0f, 25.0f, 15.0f, 10.0f};
m_lodSampleRates.resize(numLODs);
for (AZ::u32 i = 0; i < numLODs; ++i)
{
m_lodSampleRates[i] = defaultSampleRate[i];
}
}
}
bool SimpleLODComponent::Configuration::GetEnableLodSampling()
{
return m_enableLodSampling;
}
void SimpleLODComponent::Reflect(AZ::ReflectContext* context)
{
Configuration::Reflect(context);
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<SimpleLODComponent, AZ::Component>()
->Version(1)
->Field("Configuration", &SimpleLODComponent::m_configuration)
;
AZ::EditContext* editContext = serializeContext->GetEditContext();
if (editContext)
{
editContext->Class<SimpleLODComponent>(
"Simple LOD distance", "The Simple LOD distance component alters the actor LOD level based on distance to camera")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
;
}
}
}
SimpleLODComponent::SimpleLODComponent(const Configuration* config)
: m_actorInstance(nullptr)
{
if (config)
{
m_configuration = *config;
}
}
SimpleLODComponent::~SimpleLODComponent()
{
}
void SimpleLODComponent::Init()
{
}
void SimpleLODComponent::Activate()
{
ActorComponentNotificationBus::Handler::BusConnect(GetEntityId());
AZ::TickBus::Handler::BusConnect();
}
void SimpleLODComponent::Deactivate()
{
AZ::TickBus::Handler::BusDisconnect();
ActorComponentNotificationBus::Handler::BusDisconnect();
}
void SimpleLODComponent::OnActorInstanceCreated(EMotionFX::ActorInstance* actorInstance)
{
m_actorInstance = actorInstance;
}
void SimpleLODComponent::OnActorInstanceDestroyed(EMotionFX::ActorInstance* actorInstance)
{
AZ_UNUSED(actorInstance);
m_actorInstance = nullptr;
}
void SimpleLODComponent::OnTick(float deltaTime, AZ::ScriptTimePoint time)
{
AZ_UNUSED(deltaTime);
AZ_UNUSED(time);
UpdateLodLevelByDistance(m_actorInstance, m_configuration, GetEntityId());
}
AZ::u32 SimpleLODComponent::GetLodByDistance(const AZStd::vector<float>& distances, float distance)
{
const size_t max = distances.size();
for (size_t i = 0; i < max; ++i)
{
const float rDistance = distances[i];
if (distance < rDistance)
{
return static_cast<AZ::u32>(i);
}
}
return static_cast<AZ::u32>(max - 1);
}
void SimpleLODComponent::UpdateLodLevelByDistance(EMotionFX::ActorInstance * actorInstance, const Configuration& configuration, AZ::EntityId entityId)
{
if (actorInstance)
{
AZ::Transform worldTransform;
AZ::TransformBus::EventResult(worldTransform, entityId, &AZ::TransformBus::Events::GetWorldTM);
const AZ::Vector3& worldPos = worldTransform.GetTranslation();
// Compute the distance between the camera and the entity
if (gEnv->pSystem)
{
const CCamera& camera = gEnv->pSystem->GetViewCamera();
const AZ::Vector3& cameraPos = LYVec3ToAZVec3(camera.GetPosition());
const float distance = cameraPos.GetDistance(worldPos);
const AZ::u32 lodByDistance = GetLodByDistance(configuration.m_lodDistances, distance);
actorInstance->SetLODLevel(lodByDistance);
if (configuration.m_enableLodSampling)
{
const float animGraphSampleRate = configuration.m_lodSampleRates[lodByDistance];
const float updateRateInSeconds = animGraphSampleRate > 0.0f ? 1.0f / animGraphSampleRate : 0.0f;
actorInstance->SetMotionSamplingRate(updateRateInSeconds);
}
}
}
}
} // namespace integration
} // namespace EMotionFX
@@ -0,0 +1,104 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/Component/Component.h>
#include <AzCore/Component/TransformBus.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/Script/ScriptProperty.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <Integration/Assets/MotionAsset.h>
#include <Integration/ActorComponentBus.h>
namespace EMotionFX
{
namespace Integration
{
class SimpleLODComponent
: public AZ::Component
, private AZ::TickBus::Handler
, private ActorComponentNotificationBus::Handler
{
public:
friend class EditorSimpleLODComponent;
AZ_COMPONENT(SimpleLODComponent, "{9380B039-EB03-4920-9F06-D90481E739E6}");
/**
* Configuration struct for procedural configuration of SimpleLODComponents.
*/
struct Configuration
{
AZ_TYPE_INFO(Configuration, "{262470E5-57D8-4C45-8BB4-88EDFBC54D7E}");
Configuration() = default;
void Reset();
// Generate the default value based on LOD level.
void GenerateDefaultValue(AZ::u32 numLODs);
bool GetEnableLodSampling();
static void Reflect(AZ::ReflectContext* context);
AZStd::vector<float> m_lodDistances; // LOD distances that decide which lod the actor should choose.
AZStd::vector<float> m_lodSampleRates; // Per LOD sample rate.
bool m_enableLodSampling = false; // Enable per LOD sampling rate. This will allow animation to sample at a lower rate for performance improvement.
};
SimpleLODComponent(const Configuration* config = nullptr);
~SimpleLODComponent();
// AZ::Component interface implementation
void Init() override;
void Activate() override;
void Deactivate() override;
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC("EMotionFXSimpleLODService", 0xa9b5f358));
}
static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent)
{
AZ_UNUSED(dependent);
}
static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
{
required.push_back(AZ_CRC("EMotionFXActorService", 0xd6e8f48d));
}
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
{
incompatible.push_back(AZ_CRC("EMotionFXSimpleLODService", 0xa9b5f358));
}
static void Reflect(AZ::ReflectContext* context);
private:
// ActorComponentNotificationBus::Handler
void OnActorInstanceCreated(EMotionFX::ActorInstance* actorInstance) override;
void OnActorInstanceDestroyed(EMotionFX::ActorInstance* actorInstance) override;
// AZ::TickBus::Handler
void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
static AZ::u32 GetLodByDistance(const AZStd::vector<float>& distances, float distance);
static void UpdateLodLevelByDistance(EMotionFX::ActorInstance* actorInstance, const Configuration& configuration, AZ::EntityId entityId);
Configuration m_configuration; // Component configuration.
EMotionFX::ActorInstance* m_actorInstance; // Associated actor instance (retrieved from Actor Component).
};
} // namespace Integration
} // namespace EMotionFX
@@ -0,0 +1,466 @@
/*
* 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 "EMotionFX_precompiled.h"
#include <AzCore/Component/Entity.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/Script/ScriptContext.h>
#include <Integration/Components/SimpleMotionComponent.h>
#include <MCore/Source/AttributeString.h>
namespace EMotionFX
{
namespace Integration
{
void SimpleMotionComponent::Configuration::Reflect(AZ::ReflectContext *context)
{
auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<Configuration>()
->Version(2)
->Field("MotionAsset", &Configuration::m_motionAsset)
->Field("Loop", &Configuration::m_loop)
->Field("Retarget", &Configuration::m_retarget)
->Field("Reverse", &Configuration::m_reverse)
->Field("Mirror", &Configuration::m_mirror)
->Field("PlaySpeed", &Configuration::m_playspeed)
->Field("BlendIn", &Configuration::m_blendInTime)
->Field("BlendOut", &Configuration::m_blendOutTime)
->Field("PlayOnActivation", &Configuration::m_playOnActivation)
;
AZ::EditContext* editContext = serializeContext->GetEditContext();
if (editContext)
{
editContext->Class<Configuration>( "Configuration", "Settings for this Simple Motion")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->DataElement(AZ::Edit::UIHandlers::Default, &Configuration::m_motionAsset, "Motion", "EMotion FX motion to be loaded for this actor")
->DataElement(AZ::Edit::UIHandlers::Default, &Configuration::m_loop, "Loop motion", "Toggles looping of the animation")
->DataElement(AZ::Edit::UIHandlers::Default, &Configuration::m_retarget, "Retarget motion", "Toggles retargeting of the animation")
->DataElement(AZ::Edit::UIHandlers::Default, &Configuration::m_reverse, "Reverse motion", "Toggles reversing of the animation")
->DataElement(AZ::Edit::UIHandlers::Default, &Configuration::m_mirror, "Mirror motion", "Toggles mirroring of the animation")
->DataElement(AZ::Edit::UIHandlers::Default, &Configuration::m_playspeed, "Play speed", "Determines the rate at which the motion is played")
->Attribute(AZ::Edit::Attributes::Min, 0.0f)
->DataElement(AZ::Edit::UIHandlers::Default, &Configuration::m_blendInTime, "Blend In Time", "Determines the blend in time in seconds")
->Attribute(AZ::Edit::Attributes::Min, 0.0f)
->DataElement(AZ::Edit::UIHandlers::Default, &Configuration::m_blendOutTime, "Blend Out Time", "Determines the blend out time in seconds")
->Attribute(AZ::Edit::Attributes::Min, 0.0f)
->DataElement(AZ::Edit::UIHandlers::Default, &Configuration::m_playOnActivation, "Play on active", "Playing animation immediately after activition.")
;
}
}
}
void SimpleMotionComponent::Reflect(AZ::ReflectContext* context)
{
Configuration::Reflect(context);
auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<SimpleMotionComponent, AZ::Component>()
->Version(1)
->Field("Configuration", &SimpleMotionComponent::m_configuration)
;
}
auto* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context);
if (behaviorContext)
{
behaviorContext->EBus<SimpleMotionComponentRequestBus>("SimpleMotionComponentRequestBus")
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::Preview)
->Event("LoopMotion", &SimpleMotionComponentRequestBus::Events::LoopMotion)
->Event("GetLoopMotion", &SimpleMotionComponentRequestBus::Events::GetLoopMotion)
->Attribute("Hidden", AZ::Edit::Attributes::PropertyHidden)
->VirtualProperty("LoopMotion", "GetLoopMotion", "LoopMotion")
->Event("RetargetMotion", &SimpleMotionComponentRequestBus::Events::RetargetMotion)
->Event("ReverseMotion", &SimpleMotionComponentRequestBus::Events::ReverseMotion)
->Event("MirrorMotion", &SimpleMotionComponentRequestBus::Events::MirrorMotion)
->Event("SetPlaySpeed", &SimpleMotionComponentRequestBus::Events::SetPlaySpeed)
->Event("GetPlaySpeed", &SimpleMotionComponentRequestBus::Events::GetPlaySpeed)
->Attribute("Hidden", AZ::Edit::Attributes::PropertyHidden)
->VirtualProperty("PlaySpeed", "GetPlaySpeed", "SetPlaySpeed")
->Event("PlayTime", &SimpleMotionComponentRequestBus::Events::PlayTime)
->Event("GetPlayTime", &SimpleMotionComponentRequestBus::Events::GetPlayTime)
->Attribute("Hidden", AZ::Edit::Attributes::PropertyHidden)
->VirtualProperty("PlayTime", "GetPlayTime", "PlayTime")
->Event("Motion", &SimpleMotionComponentRequestBus::Events::Motion)
->Attribute(AZ::Script::Attributes::Ignore, true)
->Event("GetMotion", &SimpleMotionComponentRequestBus::Events::GetMotion)
->Attribute(AZ::Script::Attributes::Ignore, true)
->VirtualProperty("Motion", "GetMotion", "Motion")
->Event("BlendInTime", &SimpleMotionComponentRequestBus::Events::BlendInTime)
->Event("GetBlendInTime", &SimpleMotionComponentRequestBus::Events::GetBlendInTime)
->Attribute("Hidden", AZ::Edit::Attributes::PropertyHidden)
->VirtualProperty("BlendInTime", "GetBlendInTime", "BlendInTime")
->Event("BlendOutTime", &SimpleMotionComponentRequestBus::Events::BlendOutTime)
->Event("GetBlendOutTime", &SimpleMotionComponentRequestBus::Events::GetBlendOutTime)
->Attribute("Hidden", AZ::Edit::Attributes::PropertyHidden)
->VirtualProperty("BlendOutTime", "GetBlendOutTime", "BlendOutTime")
->Event("PlayMotion", &SimpleMotionComponentRequestBus::Events::PlayMotion)
;
behaviorContext->Class<SimpleMotionComponent>()->RequestBus("SimpleMotionComponentRequestBus");
}
}
SimpleMotionComponent::Configuration::Configuration()
: m_loop(false)
, m_retarget(false)
, m_reverse(false)
, m_mirror(false)
, m_playspeed(1.f)
, m_blendInTime(0.0f)
, m_blendOutTime(0.0f)
, m_playOnActivation(true)
{
}
SimpleMotionComponent::SimpleMotionComponent(const Configuration* config)
: m_actorInstance(nullptr)
, m_motionInstance(nullptr)
, m_lastMotionInstance(nullptr)
{
if (config)
{
m_configuration = *config;
}
}
SimpleMotionComponent::~SimpleMotionComponent()
{
}
void SimpleMotionComponent::Init()
{
}
void SimpleMotionComponent::Activate()
{
m_motionInstance = nullptr;
AZ::Data::AssetBus::MultiHandler::BusDisconnect();
SimpleMotionComponentRequestBus::Handler::BusConnect(GetEntityId());
auto& cfg = m_configuration;
if (cfg.m_motionAsset.GetId().IsValid())
{
AZ::Data::AssetBus::MultiHandler::BusConnect(cfg.m_motionAsset.GetId());
cfg.m_motionAsset.QueueLoad();
}
ActorComponentNotificationBus::Handler::BusConnect(GetEntityId());
}
void SimpleMotionComponent::Deactivate()
{
SimpleMotionComponentRequestBus::Handler::BusDisconnect();
ActorComponentNotificationBus::Handler::BusDisconnect();
AZ::Data::AssetBus::MultiHandler::BusDisconnect();
RemoveMotionInstanceFromActor(m_motionInstance);
m_motionInstance = nullptr;
RemoveMotionInstanceFromActor(m_lastMotionInstance);
m_lastMotionInstance = nullptr;
m_configuration.m_motionAsset.Release();
m_lastMotionAsset.Release();
m_actorInstance.reset();
}
const MotionInstance* SimpleMotionComponent::GetMotionInstance()
{
return m_motionInstance;
}
void SimpleMotionComponent::SetMotionAssetId(const AZ::Data::AssetId& assetId)
{
m_configuration.m_motionAsset = AZ::Data::Asset<MotionAsset>(assetId, azrtti_typeid<MotionAsset>());
}
void SimpleMotionComponent::OnAssetReady(AZ::Data::Asset<AZ::Data::AssetData> asset)
{
auto& cfg = m_configuration;
if (asset.GetId() == cfg.m_motionAsset.GetId())
{
cfg.m_motionAsset = asset;
if (m_configuration.m_playOnActivation)
{
PlayMotion();
}
}
}
void SimpleMotionComponent::OnAssetReloaded(AZ::Data::Asset<AZ::Data::AssetData> asset)
{
OnAssetReady(asset);
}
void SimpleMotionComponent::OnActorInstanceCreated(EMotionFX::ActorInstance* actorInstance)
{
m_actorInstance = actorInstance;
if (m_configuration.m_playOnActivation)
{
PlayMotion();
}
}
void SimpleMotionComponent::OnActorInstanceDestroyed(EMotionFX::ActorInstance* actorInstance)
{
AZ_UNUSED(actorInstance);
RemoveMotionInstanceFromActor(m_motionInstance);
m_motionInstance = nullptr;
RemoveMotionInstanceFromActor(m_lastMotionInstance);
m_lastMotionInstance = nullptr;
m_actorInstance.reset();
}
void SimpleMotionComponent::PlayMotion()
{
m_motionInstance = PlayMotionInternal(m_actorInstance.get(), m_configuration, /*deleteOnZeroWeight*/true, /*inPlace*/false);
}
void SimpleMotionComponent::RemoveMotionInstanceFromActor(EMotionFX::MotionInstance* motionInstance)
{
if (motionInstance)
{
if (m_actorInstance && m_actorInstance->GetMotionSystem())
{
m_actorInstance->GetMotionSystem()->RemoveMotionInstance(motionInstance);
}
}
}
void SimpleMotionComponent::LoopMotion(bool enable)
{
m_configuration.m_loop = enable;
if (m_motionInstance)
{
m_motionInstance->SetMaxLoops(enable ? EMFX_LOOPFOREVER : 1);
}
}
bool SimpleMotionComponent::GetLoopMotion() const
{
return m_configuration.m_loop;
}
void SimpleMotionComponent::RetargetMotion(bool enable)
{
m_configuration.m_retarget = enable;
if (m_motionInstance)
{
m_motionInstance->SetRetargetingEnabled(enable);
}
}
void SimpleMotionComponent::ReverseMotion(bool enable)
{
m_configuration.m_reverse = enable;
if (m_motionInstance)
{
m_motionInstance->SetPlayMode(enable ? EMotionFX::EPlayMode::PLAYMODE_BACKWARD : EMotionFX::EPlayMode::PLAYMODE_FORWARD);
}
}
void SimpleMotionComponent::MirrorMotion(bool enable)
{
m_configuration.m_mirror = enable;
if (m_motionInstance)
{
m_motionInstance->SetMirrorMotion(enable);
}
}
void SimpleMotionComponent::SetPlaySpeed(float speed)
{
m_configuration.m_playspeed = speed;
if (m_motionInstance)
{
m_motionInstance->SetPlaySpeed(speed);
}
}
float SimpleMotionComponent::GetPlaySpeed() const
{
return m_configuration.m_playspeed;
}
void SimpleMotionComponent::PlayTime(float time)
{
if (m_motionInstance)
{
float delta = time - m_motionInstance->GetLastCurrentTime();
m_motionInstance->SetCurrentTime(time, false);
// Apply the same time step to the last animation
// so blend out will be good. Otherwise we are just blending
// from the last frame played of the last animation.
if (m_lastMotionInstance && m_lastMotionInstance->GetIsBlending())
{
m_lastMotionInstance->SetCurrentTime(m_lastMotionInstance->GetLastCurrentTime() + delta, false);
}
}
}
float SimpleMotionComponent::GetPlayTime() const
{
float result = 0.0f;
if (m_motionInstance)
{
result = m_motionInstance->GetCurrentTimeNormalized();
}
return result;
}
void SimpleMotionComponent::Motion(AZ::Data::AssetId assetId)
{
if (m_configuration.m_motionAsset.GetId() != assetId)
{
// Disconnect the old asset bus
if (AZ::Data::AssetBus::MultiHandler::BusIsConnectedId(m_configuration.m_motionAsset.GetId()))
{
AZ::Data::AssetBus::MultiHandler::BusDisconnect(m_configuration.m_motionAsset.GetId());
}
// Save the motion asset that we are about to be remove in case it can be reused.
AZ::Data::Asset<MotionAsset> oldLastMotionAsset = m_lastMotionAsset;
if (m_lastMotionInstance)
{
RemoveMotionInstanceFromActor(m_lastMotionInstance);
}
// Store the current motion asset as the last one for possible blending.
// If we don't keep a reference to the motion asset, the motion instance will be
// automatically released.
if (m_configuration.m_motionAsset.GetId().IsValid())
{
m_lastMotionAsset = m_configuration.m_motionAsset;
}
// Set the current motion instance as the last motion instance. The new current motion
// instance will then be set when the load is complete.
m_lastMotionInstance = m_motionInstance;
m_motionInstance = nullptr;
// Start the fade out if there is a blend out time. Otherwise just leave the
// m_lastMotionInstance where it is at so the next anim can blend from that frame.
if (m_lastMotionInstance && m_configuration.m_blendOutTime > 0.0f)
{
m_lastMotionInstance->Stop(m_configuration.m_blendOutTime);
}
// Reuse the old, last motion asset if possible. Otherwise, request a load.
if (assetId.IsValid() && oldLastMotionAsset.GetData() && assetId == oldLastMotionAsset.GetId())
{
// Even though we are not calling GetAsset here, OnAssetReady
// will be fired when the bus is connected because this asset is already loaded.
m_configuration.m_motionAsset = oldLastMotionAsset;
}
else
{
// Won't be able to reuse oldLastMotionAsset, release it.
oldLastMotionAsset.Release();
// Clear the old asset.
m_configuration.m_motionAsset.Release();
// Create a new asset
if (assetId.IsValid())
{
m_configuration.m_motionAsset = AZ::Data::AssetManager::Instance().GetAsset<MotionAsset>(assetId, m_configuration.m_motionAsset.GetAutoLoadBehavior());
}
}
// Connect the bus if the asset is is valid.
if (m_configuration.m_motionAsset.GetId().IsValid())
{
AZ::Data::AssetBus::MultiHandler::BusConnect(m_configuration.m_motionAsset.GetId());
}
}
}
AZ::Data::AssetId SimpleMotionComponent::GetMotion() const
{
return m_configuration.m_motionAsset.GetId();
}
void SimpleMotionComponent::BlendInTime(float time)
{
m_configuration.m_blendInTime = time;
}
float SimpleMotionComponent::GetBlendInTime() const
{
return m_configuration.m_blendInTime;
}
void SimpleMotionComponent::BlendOutTime(float time)
{
m_configuration.m_blendOutTime = time;
}
float SimpleMotionComponent::GetBlendOutTime() const
{
return m_configuration.m_blendOutTime;
}
EMotionFX::MotionInstance* SimpleMotionComponent::PlayMotionInternal(const EMotionFX::ActorInstance* actorInstance, const SimpleMotionComponent::Configuration& cfg, bool deleteOnZeroWeight, bool inPlace)
{
if (!actorInstance || !cfg.m_motionAsset.IsReady())
{
return nullptr;
}
if (!actorInstance->GetMotionSystem())
{
return nullptr;
}
auto* motionAsset = cfg.m_motionAsset.GetAs<MotionAsset>();
if (!motionAsset)
{
AZ_Error("EMotionFX", motionAsset, "Motion asset is not valid.");
return nullptr;
}
//init the PlaybackInfo based on our config
EMotionFX::PlayBackInfo info;
info.mNumLoops = cfg.m_loop ? EMFX_LOOPFOREVER : 1;
info.mRetarget = cfg.m_retarget;
info.mPlayMode = cfg.m_reverse ? EMotionFX::EPlayMode::PLAYMODE_BACKWARD : EMotionFX::EPlayMode::PLAYMODE_FORWARD;
info.mFreezeAtLastFrame = info.mNumLoops == 1;
info.mMirrorMotion = cfg.m_mirror;
info.mPlaySpeed = cfg.m_playspeed;
info.mPlayNow = true;
info.mDeleteOnZeroWeight = deleteOnZeroWeight;
info.mCanOverwrite = false;
info.mBlendInTime = cfg.m_blendInTime;
info.mBlendOutTime = cfg.m_blendOutTime;
info.mInPlace = inPlace;
return actorInstance->GetMotionSystem()->PlayMotion(motionAsset->m_emfxMotion.get(), &info);
}
} // namespace integration
} // namespace EMotionFX
@@ -0,0 +1,133 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/Component/Component.h>
#include <AzCore/Component/TransformBus.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/Script/ScriptProperty.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <Integration/Assets/MotionAsset.h>
#include <Integration/ActorComponentBus.h>
#include <Integration/SimpleMotionComponentBus.h>
namespace EMotionFX
{
namespace Integration
{
class SimpleMotionComponent
: public AZ::Component
, private AZ::Data::AssetBus::MultiHandler
, private ActorComponentNotificationBus::Handler
, private SimpleMotionComponentRequestBus::Handler
{
public:
friend class EditorSimpleMotionComponent;
AZ_COMPONENT(SimpleMotionComponent, "{DBE3C105-6FC1-418F-A8B1-D0F29FE8D5BD}");
/**
* Configuration struct for procedural configuration of SimpleMotionComponents.
*/
struct Configuration
{
AZ_TYPE_INFO(Configuration, "{DA661C5F-E79E-41C3-B055-5F5A4E353F84}")
Configuration();
AZ::Data::Asset<MotionAsset> m_motionAsset; ///< Assigned motion asset
bool m_loop; ///< Toggles looping of the motion
bool m_retarget; ///< Toggles retargeting of the motion
bool m_reverse; ///< Toggles reversing of the motion
bool m_mirror; ///< Toggles mirroring of the motion
float m_playspeed; ///< Determines the rate at which the motion is played
float m_blendInTime; ///< Determines the blend in time in seconds.
float m_blendOutTime; ///< Determines the blend out time in seconds.
bool m_playOnActivation; ///< Determines if the motion should be played immediately
static void Reflect(AZ::ReflectContext* context);
};
SimpleMotionComponent(const Configuration* config = nullptr);
~SimpleMotionComponent();
// AZ::Component interface implementation
void Init() override;
void Activate() override;
void Deactivate() override;
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC("EMotionFXSimpleMotionService", 0xea7a05d8));
}
static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent)
{
dependent.push_back(AZ_CRC("PhysicsService", 0xa7350d22));
dependent.push_back(AZ_CRC("MeshService", 0x71d8a455));
}
static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
{
required.push_back(AZ_CRC("EMotionFXActorService", 0xd6e8f48d));
}
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
{
incompatible.push_back(AZ_CRC("EMotionFXAnimGraphService", 0x9ec3c819));
incompatible.push_back(AZ_CRC("EMotionFXSimpleMotionService", 0xea7a05d8));
}
static void Reflect(AZ::ReflectContext* /*context*/);
// SimpleMotionComponentRequestBus::Handler
void LoopMotion(bool enable) override;
bool GetLoopMotion() const override;
void RetargetMotion(bool enable) override;
void ReverseMotion(bool enable) override;
void MirrorMotion(bool enable) override;
void SetPlaySpeed(float speed) override;
float GetPlaySpeed() const override;
void PlayTime(float time) override;
float GetPlayTime() const override;
void Motion(AZ::Data::AssetId assetId) override;
AZ::Data::AssetId GetMotion() const override;
void BlendInTime(float time) override;
float GetBlendInTime() const override;
void BlendOutTime(float time) override;
float GetBlendOutTime() const override;
void PlayMotion() override;
const EMotionFX::MotionInstance* GetMotionInstance();
// AZ::Data::AssetBus::Handler
void SetMotionAssetId(const AZ::Data::AssetId& assetId);
void OnAssetReady(AZ::Data::Asset<AZ::Data::AssetData> asset) override;
void OnAssetReloaded(AZ::Data::Asset<AZ::Data::AssetData> asset) override;
private:
// ActorComponentNotificationBus::Handler
void OnActorInstanceCreated(EMotionFX::ActorInstance* actorInstance) override;
void OnActorInstanceDestroyed(EMotionFX::ActorInstance* actorInstance) override;
void RemoveMotionInstanceFromActor(EMotionFX::MotionInstance* motionInstance);
static EMotionFX::MotionInstance* PlayMotionInternal(const EMotionFX::ActorInstance* actorInstance, const SimpleMotionComponent::Configuration& cfg, bool deleteOnZeroWeight, bool inPlace);
Configuration m_configuration; ///< Component configuration.
EMotionFXPtr<EMotionFX::ActorInstance> m_actorInstance; ///< Associated actor instance (retrieved from Actor Component).
EMotionFX::MotionInstance* m_motionInstance; ///< Motion to play on the actor
AZ::Data::Asset<MotionAsset> m_lastMotionAsset; ///< Last active motion asset, kept alive for blending.
EMotionFX::MotionInstance* m_lastMotionInstance; ///< Last active motion instance, kept alive for blending.
};
} // namespace Integration
} // namespace EMotionFX
@@ -0,0 +1,910 @@
/*
* 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 "EMotionFX_precompiled.h"
#include <AzCore/Component/Entity.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/Math/Transform.h>
#include <AzCore/Asset/AssetManager.h>
#include <AzFramework/Entity/EntityDebugDisplayBus.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
#include <AzToolsFramework/Entity/EditorEntityInfoBus.h>
#include <AzToolsFramework/ViewportSelection/EditorSelectionUtil.h>
#include <LmbrCentral/Rendering/MeshComponentBus.h>
#include <Integration/Editor/Components/EditorActorComponent.h>
#include <Integration/AnimGraphComponentBus.h>
#include <Integration/Rendering/RenderBackendManager.h>
#include <EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/EMStudioManager.h>
#include <EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/MainWindow.h>
#include <EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/NodeSelectionWindow.h>
#include <EMotionFX/CommandSystem/Source/SelectionList.h>
#include <MCore/Source/AzCoreConversions.h>
namespace EMotionFX
{
namespace Integration
{
void EditorActorComponent::Reflect(AZ::ReflectContext* context)
{
auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<EditorActorComponent, AzToolsFramework::Components::EditorComponentBase>()
->Version(4)
->Field("ActorAsset", &EditorActorComponent::m_actorAsset)
->Field("MaterialPerLOD", &EditorActorComponent::m_materialPerLOD)
->Field("MaterialPerActor", &EditorActorComponent::m_materialPerActor)
->Field("AttachmentType", &EditorActorComponent::m_attachmentType)
->Field("AttachmentTarget", &EditorActorComponent::m_attachmentTarget)
->Field("RenderSkeleton", &EditorActorComponent::m_renderSkeleton)
->Field("RenderCharacter", &EditorActorComponent::m_renderCharacter)
->Field("RenderBounds", &EditorActorComponent::m_renderBounds)
->Field("SkinningMethod", &EditorActorComponent::m_skinningMethod)
->Field("UpdateJointTransformsWhenOutOfView", &EditorActorComponent::m_forceUpdateJointsOOV)
->Field("LodLevel", &EditorActorComponent::m_lodLevel)
;
AZ::EditContext* editContext = serializeContext->GetEditContext();
if (editContext)
{
editContext->Class<EditorActorComponent>("Actor", "The Actor component manages an instance of an Actor")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Category, "Animation")
->Attribute(AZ::Edit::Attributes::Icon, ":/EMotionFX/ActorComponent.svg")
->Attribute(AZ::Edit::Attributes::PrimaryAssetType, azrtti_typeid<ActorAsset>())
->Attribute(AZ::Edit::Attributes::ViewportIcon, ":/EMotionFX/ActorComponent.svg")
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c))
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://docs.aws.amazon.com/lumberyard/latest/userguide/component-actor.html")
->DataElement(0, &EditorActorComponent::m_actorAsset,
"Actor asset", "Assigned actor asset")
->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorActorComponent::OnAssetSelected)
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->Attribute("EditButton", "")
->Attribute("EditDescription", "Open in Animation Editor")
->Attribute("EditCallback", &EditorActorComponent::LaunchAnimationEditor)
->DataElement(0, &EditorActorComponent::m_materialPerActor,
"Material", "Material assignment for this actor")
->Attribute(AZ::Edit::Attributes::Visibility, &EditorActorComponent::IsAtomDisabled)
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorActorComponent::OnMaterialPerActorChanged)
->ClassElement(AZ::Edit::ClassElements::Group, "Render options")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->DataElement(0, &EditorActorComponent::m_renderCharacter,
"Draw character", "Toggles rendering of character mesh.")
->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorActorComponent::OnDebugDrawFlagChanged)
->DataElement(0, &EditorActorComponent::m_renderSkeleton,
"Draw skeleton", "Toggles rendering of skeleton.")
->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorActorComponent::OnDebugDrawFlagChanged)
->DataElement(0, &EditorActorComponent::m_renderBounds, "Draw bounds", "Toggles rendering of world space bounding boxes.")
->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorActorComponent::OnDebugDrawFlagChanged)
->DataElement(AZ::Edit::UIHandlers::ComboBox, &EditorActorComponent::m_skinningMethod,
"Skinning method", "Choose the skinning method this actor is using")
->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorActorComponent::OnSkinningMethodChanged)
->EnumAttribute(SkinningMethod::DualQuat, "Dual quat skinning")
->EnumAttribute(SkinningMethod::Linear, "Linear skinning")
->ClassElement(AZ::Edit::ClassElements::Group, "Attach To")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->DataElement(AZ::Edit::UIHandlers::ComboBox, &EditorActorComponent::m_attachmentType,
"Attachment type", "Type of attachment to use when attaching to the target entity.")
->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::EntireTree)
->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorActorComponent::OnAttachmentTypeChanged)
->EnumAttribute(AttachmentType::None, "None")
->EnumAttribute(AttachmentType::SkinAttachment, "Skin attachment")
->DataElement(0, &EditorActorComponent::m_attachmentTarget,
"Target entity", "Entity Id whose actor instance we should attach to.")
->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::EntireTree)
->Attribute(AZ::Edit::Attributes::RequiredService, AZ_CRC("EMotionFXActorService", 0xd6e8f48d))
->Attribute(AZ::Edit::Attributes::Visibility, &EditorActorComponent::AttachmentTargetVisibility)
->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorActorComponent::OnAttachmentTargetChanged)
->ClassElement(AZ::Edit::ClassElements::Group, "Out of view")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->DataElement(0, &EditorActorComponent::m_forceUpdateJointsOOV,
"Force update joints", "Force update the joint transforms of actor, even when the character is out of the camera view.")
->ClassElement(AZ::Edit::ClassElements::Group, "Preview")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->DataElement(0, &EditorActorComponent::m_lodLevel,
"LOD Level", "Preview the LOD Level of the current actor.")
->Attribute(AZ::Edit::Attributes::Visibility, &EditorActorComponent::IsAtomDisabled)
->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorActorComponent::OnLODLevelChanged)
;
}
}
AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context);
if (behaviorContext)
{
behaviorContext->Class<EditorActorComponent>()->RequestBus("ActorComponentRequestBus");
}
}
//////////////////////////////////////////////////////////////////////////
EditorActorComponent::EditorActorComponent()
: m_renderCharacter(true)
, m_renderSkeleton(false)
, m_renderBounds(false)
, m_entityVisible(true)
, m_skinningMethod(SkinningMethod::DualQuat)
, m_attachmentType(AttachmentType::None)
, m_attachmentJointIndex(0)
, m_lodLevel(0)
, m_actorAsset(AZ::Data::AssetLoadBehavior::NoLoad)
{
}
//////////////////////////////////////////////////////////////////////////
EditorActorComponent::~EditorActorComponent()
{
}
//////////////////////////////////////////////////////////////////////////
void EditorActorComponent::Init()
{
}
//////////////////////////////////////////////////////////////////////////
void EditorActorComponent::Activate()
{
CreateActorInstance();
const AZ::EntityId entityId = GetEntityId();
AzToolsFramework::EditorEntityInfoRequestBus::EventResult(
m_entityVisible, entityId, &AzToolsFramework::EditorEntityInfoRequestBus::Events::IsVisible);
ActorComponentRequestBus::Handler::BusConnect(entityId);
EditorActorComponentRequestBus::Handler::BusConnect(entityId);
LmbrCentral::AttachmentComponentNotificationBus::Handler::BusConnect(entityId);
AzToolsFramework::EditorComponentSelectionRequestsBus::Handler::BusConnect(entityId);
AzToolsFramework::EditorVisibilityNotificationBus::Handler::BusConnect(entityId);
AzFramework::BoundsRequestBus::Handler::BusConnect(entityId);
}
//////////////////////////////////////////////////////////////////////////
void EditorActorComponent::Deactivate()
{
AzFramework::BoundsRequestBus::Handler::BusDisconnect();
AzToolsFramework::EditorVisibilityNotificationBus::Handler::BusDisconnect();
AzToolsFramework::EditorComponentSelectionRequestsBus::Handler::BusDisconnect();
LmbrCentral::AttachmentComponentNotificationBus::Handler::BusDisconnect();
EditorActorComponentRequestBus::Handler::BusDisconnect();
ActorComponentRequestBus::Handler::BusDisconnect();
AZ::TransformNotificationBus::Handler::BusDisconnect();
AZ::TickBus::Handler::BusDisconnect();
AZ::Data::AssetBus::Handler::BusDisconnect();
DestroyActorInstance();
m_actorAsset.Release();
}
//////////////////////////////////////////////////////////////////////////
bool EditorActorComponent::GetRenderCharacter() const
{
return m_renderCharacter;
}
//////////////////////////////////////////////////////////////////////////
void EditorActorComponent::SetRenderCharacter(bool enable)
{
if (m_renderCharacter != enable)
{
m_renderCharacter = enable;
OnEntityVisibilityChanged(m_renderCharacter);
}
}
//////////////////////////////////////////////////////////////////////////
size_t EditorActorComponent::GetNumJoints() const
{
const Actor* actor = m_actorAsset->GetActor();
if (actor)
{
return actor->GetNumNodes();
}
return 0;
}
//////////////////////////////////////////////////////////////////////////
SkinningMethod EditorActorComponent::GetSkinningMethod() const
{
return m_skinningMethod;
}
//////////////////////////////////////////////////////////////////////////
void EditorActorComponent::CreateActorInstance()
{
AZ::Data::AssetBus::Handler::BusDisconnect();
// Queue actor asset load. Instantiation occurs in OnAssetReady.
if (m_actorAsset.GetId().IsValid())
{
AZ::Data::AssetBus::Handler::BusConnect(m_actorAsset.GetId());
m_actorAsset.QueueLoad();
}
else
{
DestroyActorInstance();
}
}
//////////////////////////////////////////////////////////////////////////
void EditorActorComponent::DestroyActorInstance()
{
if (m_actorInstance)
{
// Send general mesh destruction notification to interested parties.
LmbrCentral::MeshComponentNotificationBus::Event(
GetEntityId(),
&LmbrCentral::MeshComponentNotifications::OnMeshDestroyed);
ActorComponentNotificationBus::Event(
GetEntityId(),
&ActorComponentNotificationBus::Events::OnActorInstanceDestroyed,
m_actorInstance.get());
}
m_actorInstance = nullptr;
m_renderActorInstance.reset();
}
//////////////////////////////////////////////////////////////////////////
// EditorActorComponentRequestBus::Handler
//////////////////////////////////////////////////////////////////////////
const AZ::Data::AssetId& EditorActorComponent::GetActorAssetId()
{
return m_actorAsset.GetId();
}
//////////////////////////////////////////////////////////////////////////
void EditorActorComponent::OnEntityVisibilityChanged(bool visibility)
{
m_entityVisible = visibility;
if (m_renderActorInstance)
{
m_renderActorInstance->SetIsVisible(m_entityVisible && m_renderCharacter);
}
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
AZ::Crc32 EditorActorComponent::OnAssetSelected()
{
CreateActorInstance();
if (!m_actorAsset.GetId().IsValid())
{
m_materialPerLOD.clear();
// Only need to refresh the values here.
return AZ::Edit::PropertyRefreshLevels::ValuesOnly;
}
return AZ::Edit::PropertyRefreshLevels::None;
}
//////////////////////////////////////////////////////////////////////////
void EditorActorComponent::OnMaterialChanged()
{
if (m_renderActorInstance)
{
m_renderActorInstance->SetMaterials(m_materialPerLOD);
}
}
void EditorActorComponent::OnMaterialPerActorChanged()
{
if (m_actorInstance)
{
m_materialPerLOD.resize(m_actorInstance->GetActor()->GetNumLODLevels());
for (auto& materialPath : m_materialPerLOD)
{
materialPath.SetAssetPath(m_materialPerActor.GetAssetPath().c_str());
}
}
OnMaterialChanged();
}
//////////////////////////////////////////////////////////////////////////
void EditorActorComponent::OnLODLevelChanged()
{
if (m_actorInstance)
{
m_actorInstance->SetLODLevel(m_lodLevel);
}
}
//////////////////////////////////////////////////////////////////////////
void EditorActorComponent::OnDebugDrawFlagChanged()
{
if (m_renderSkeleton || m_renderBounds || m_renderCharacter)
{
AZ::TickBus::Handler::BusConnect();
}
else
{
AZ::TickBus::Handler::BusDisconnect();
}
if (m_renderActorInstance)
{
m_renderActorInstance->SetIsVisible(m_entityVisible && m_renderCharacter);
}
}
//////////////////////////////////////////////////////////////////////////
void EditorActorComponent::OnSkinningMethodChanged()
{
if (m_renderActorInstance)
{
m_renderActorInstance->SetSkinningMethod(m_skinningMethod);
}
}
//////////////////////////////////////////////////////////////////////////
bool EditorActorComponent::AttachmentTargetVisibility()
{
return (m_attachmentType != AttachmentType::None);
}
//////////////////////////////////////////////////////////////////////////
bool EditorActorComponent::AttachmentTargetJointVisibility()
{
return (m_attachmentType == AttachmentType::ActorAttachment);
}
//////////////////////////////////////////////////////////////////////////
AZStd::string EditorActorComponent::AttachmentJointButtonText()
{
return m_attachmentJointName.empty() ?
AZStd::string("(No joint selected)") : m_attachmentJointName;
}
//////////////////////////////////////////////////////////////////////////
AZ::Crc32 EditorActorComponent::OnAttachmentTypeChanged()
{
if (m_attachmentType == AttachmentType::None)
{
m_attachmentTarget.SetInvalid();
m_attachmentJointName.clear();
}
return AZ::Edit::PropertyRefreshLevels::AttributesAndValues;
}
//////////////////////////////////////////////////////////////////////////
AZ::Crc32 EditorActorComponent::OnAttachmentTargetChanged()
{
if (!IsValidAttachment(GetEntityId(), m_attachmentTarget))
{
m_attachmentTarget.SetInvalid();
AZ_Error("EMotionFX", false, "You cannot attach to yourself or create circular dependencies! Attachment cannot be performed.");
}
return AZ::Edit::PropertyRefreshLevels::AttributesAndValues;
}
//////////////////////////////////////////////////////////////////////////
AZ::Crc32 EditorActorComponent::OnAttachmentTargetJointSelect()
{
// Grab actor instance and invoke UI for joint selection.
EMotionFXPtr<ActorInstance> actorInstance;
ActorComponentRequestBus::EventResult(
actorInstance,
m_attachmentTarget,
&ActorComponentRequestBus::Events::GetActorInstance);
AZ::Crc32 refreshLevel = AZ::Edit::PropertyRefreshLevels::None;
if (actorInstance)
{
EMStudio::NodeSelectionWindow* nodeSelectWindow = new EMStudio::NodeSelectionWindow(nullptr, true);
nodeSelectWindow->setWindowTitle(nodeSelectWindow->tr("Select Target Joint"));
CommandSystem::SelectionList selection;
// If a joint was previously selected, ensure it's pre-selected in the UI.
if (!m_attachmentJointName.empty())
{
Node* node = actorInstance->GetActor()->GetSkeleton()->FindNodeByName(m_attachmentJointName.c_str());
if (node)
{
selection.AddNode(node);
}
}
QObject::connect(nodeSelectWindow, &EMStudio::NodeSelectionWindow::accepted,
[this, nodeSelectWindow, &refreshLevel, &actorInstance]()
{
auto& selectedItems = nodeSelectWindow->GetNodeHierarchyWidget()->GetSelectedItems();
if (!selectedItems.empty())
{
const char* jointName = selectedItems[0].GetNodeName();
Node* node = actorInstance->GetActor()->GetSkeleton()->FindNodeByName(jointName);
if (node)
{
m_attachmentJointName = jointName;
m_attachmentJointIndex = node->GetNodeIndex();
refreshLevel = AZ::Edit::PropertyRefreshLevels::AttributesAndValues;
}
}
});
nodeSelectWindow->Update(actorInstance->GetID(), &selection);
nodeSelectWindow->exec();
delete nodeSelectWindow;
}
return refreshLevel;
}
void EditorActorComponent::LaunchAnimationEditor(const AZ::Data::AssetId& assetId, const AZ::Data::AssetType&)
{
if (assetId.IsValid())
{
AZ::Data::AssetId animgraphAssetId;
animgraphAssetId.SetInvalid();
EditorAnimGraphComponentRequestBus::EventResult(animgraphAssetId, GetEntityId(), &EditorAnimGraphComponentRequestBus::Events::GetAnimGraphAssetId);
AZ::Data::AssetId motionSetAssetId;
motionSetAssetId.SetInvalid();
EditorAnimGraphComponentRequestBus::EventResult(motionSetAssetId, GetEntityId(), &EditorAnimGraphComponentRequestBus::Events::GetMotionSetAssetId);
// call to open must be done before LoadCharacter
const char* panelName = EMStudio::MainWindow::GetEMotionFXPaneName();
EBUS_EVENT(AzToolsFramework::EditorRequests::Bus, OpenViewPane, panelName);
EMStudio::MainWindow* mainWindow = EMStudio::GetMainWindow();
if (mainWindow)
{
mainWindow->LoadCharacter(assetId, animgraphAssetId, motionSetAssetId);
}
}
}
//////////////////////////////////////////////////////////////////////////
void EditorActorComponent::OnAssetReloaded(AZ::Data::Asset<AZ::Data::AssetData> asset)
{
OnAssetReady(asset);
}
//////////////////////////////////////////////////////////////////////////
void EditorActorComponent::OnAssetReady(AZ::Data::Asset<AZ::Data::AssetData> asset)
{
m_actorAsset = asset;
// Enable/disable debug drawing.
OnDebugDrawFlagChanged();
// Create actor instance.
auto* actorAsset = m_actorAsset.GetAs<ActorAsset>();
AZ_Error("EMotionFX", actorAsset, "Actor asset is not valid.");
if (!actorAsset)
{
return;
}
if (m_actorInstance)
{
// Send general mesh destruction notification to interested parties.
LmbrCentral::MeshComponentNotificationBus::Event(
GetEntityId(),
&LmbrCentral::MeshComponentNotifications::OnMeshDestroyed);
ActorComponentNotificationBus::Event(
GetEntityId(),
&ActorComponentNotificationBus::Events::OnActorInstanceDestroyed,
m_actorInstance.get());
}
m_actorInstance = actorAsset->CreateInstance(GetEntity());
if (!m_actorInstance)
{
AZ_Error("EMotionFX", actorAsset, "Failed to create actor instance.");
return;
}
// If we are loading the actor for the first time, automatically add the material
// per lod information. If the amount of lods between different actors that are assigned
// to this component differ, then reinit the materials.
if (m_materialPerActor.GetAssetPath().empty())
{
InitializeMaterial(*actorAsset);
}
OnMaterialPerActorChanged();
// Assign entity Id to user data field, so we can extract owning entity from an EMFX actor pointer.
m_actorInstance->SetCustomData(reinterpret_cast<void*>(static_cast<AZ::u64>(GetEntityId())));
// Notify listeners that an actor instance has been created.
ActorComponentNotificationBus::Event(
GetEntityId(),
&ActorComponentNotificationBus::Events::OnActorInstanceCreated,
m_actorInstance.get());
// Setup initial transform and listen for transform changes.
AZ::Transform transform;
AZ::TransformBus::EventResult(transform, GetEntityId(), &AZ::TransformBus::Events::GetWorldTM);
OnTransformChanged(transform, transform);
AZ::TransformNotificationBus::Handler::BusConnect(GetEntityId());
// Force an update of node transforms so we can get an accurate bounding box.
m_actorInstance->UpdateTransformations(0.0f, true, false);
RenderBackend* renderBackend = AZ::Interface<RenderBackendManager>::Get()->GetRenderBackend();
// If there is already a RenderActorInstance, destroy it before creating the new one so there are not two instances potentially handling events for the same entityId
m_renderActorInstance.reset(nullptr);
// Create the new RenderActorInstance
m_renderActorInstance.reset(renderBackend->CreateActorInstance(GetEntityId(),
m_actorInstance,
m_actorAsset,
m_materialPerLOD,
m_skinningMethod,
transform));
if (m_renderActorInstance)
{
m_renderActorInstance->SetIsVisible(m_entityVisible && m_renderCharacter);
m_renderActorInstance->SetOnMaterialChangedCallback([this](const AZStd::string& materialName)
{
m_materialPerLOD.clear();
if (!materialName.empty())
{
m_materialPerActor.SetAssetPath(materialName.c_str());
}
else
{
m_materialPerActor.SetAssetPath("");
InitializeMaterial(*m_actorAsset.GetAs<ActorAsset>());
}
// Update the rendernode and the property grid
OnMaterialPerActorChanged();
AzToolsFramework::ToolsApplicationEvents::Bus::Broadcast(
&AzToolsFramework::ToolsApplicationEvents::InvalidatePropertyDisplay,
AzToolsFramework::Refresh_AttributesAndValues);
});
}
// Reattach all attachments
for (AZ::EntityId& attachment : m_attachments)
{
LmbrCentral::AttachmentComponentRequestBus::Event(attachment, &LmbrCentral::AttachmentComponentRequestBus::Events::Reattach, true);
}
// Send general mesh creation notification to interested parties.
LmbrCentral::MeshComponentNotificationBus::Event(GetEntityId(), &LmbrCentral::MeshComponentNotifications::OnMeshCreated, m_actorAsset);
}
void EditorActorComponent::InitializeMaterial(ActorAsset& actorAsset)
{
if (!m_materialPerLOD.empty())
{
// If the materialPerLOD exist, it means that we previously stored the path to the material. Use it.
m_materialPerActor.SetAssetPath(m_materialPerLOD[0].GetAssetPath().c_str());
}
else
{
// If a material exists next to the actor, pre - initialize LOD material slot with that material.
// This is merely an accelerator for the user, and is isolated to tools-only code (the editor actor component).
AZStd::string materialAssetPath;
EBUS_EVENT_RESULT(materialAssetPath, AZ::Data::AssetCatalogRequestBus, GetAssetPathById, actorAsset.GetId());
if (!materialAssetPath.empty())
{
// Query the catalog for a material of the same name as the actor.
AzFramework::StringFunc::Path::ReplaceExtension(materialAssetPath, "mtl");
AZ::Data::AssetId materialAssetId;
EBUS_EVENT_RESULT(materialAssetId, AZ::Data::AssetCatalogRequestBus, GetAssetIdByPath, materialAssetPath.c_str(), AZ::Data::s_invalidAssetType, false);
// If found, initialize all empty material slots with the material.
if (materialAssetId.IsValid())
{
m_materialPerActor.SetAssetPath(materialAssetPath.c_str());
}
}
}
using namespace AzToolsFramework;
ToolsApplicationEvents::Bus::Broadcast(&ToolsApplicationEvents::InvalidatePropertyDisplay, Refresh_EntireTree);
}
//////////////////////////////////////////////////////////////////////////
void EditorActorComponent::OnTransformChanged(const AZ::Transform& local, const AZ::Transform& world)
{
AZ_UNUSED(local);
if (!m_actorInstance)
{
return;
}
m_actorInstance->SetLocalSpaceTransform(MCore::AzTransformToEmfxTransform(world));
}
//////////////////////////////////////////////////////////////////////////
void EditorActorComponent::SetPrimaryAsset(const AZ::Data::AssetId& assetId)
{
AZ::Data::Asset<ActorAsset> asset = AZ::Data::AssetManager::Instance().FindOrCreateAsset<ActorAsset>(assetId, m_actorAsset.GetAutoLoadBehavior());
if (asset)
{
m_actorAsset = asset;
OnAssetSelected();
}
}
//////////////////////////////////////////////////////////////////////////
void EditorActorComponent::OnTick(float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time)
{
if (!m_actorInstance)
{
return;
}
if (m_renderActorInstance)
{
m_renderActorInstance->OnTick(deltaTime);
m_renderActorInstance->UpdateBounds();
RenderActorInstance::DebugOptions debugOptions;
debugOptions.m_drawAABB = m_renderBounds;
debugOptions.m_drawSkeleton = m_renderSkeleton;
m_renderActorInstance->DebugDraw(debugOptions);
}
}
void EditorActorComponent::BuildGameEntity(AZ::Entity* gameEntity)
{
ActorComponent::Configuration cfg;
cfg.m_actorAsset = m_actorAsset;
cfg.m_materialPerLOD = m_materialPerLOD;
cfg.m_renderSkeleton = m_renderSkeleton;
cfg.m_renderCharacter = m_renderCharacter;
cfg.m_renderBounds = m_renderBounds;
cfg.m_attachmentType = m_attachmentType;
cfg.m_attachmentTarget = m_attachmentTarget;
cfg.m_attachmentJointIndex = m_attachmentJointIndex;
cfg.m_lodLevel = m_lodLevel;
cfg.m_skinningMethod = m_skinningMethod;
cfg.m_forceUpdateJointsOOV = m_forceUpdateJointsOOV;
gameEntity->AddComponent(aznew ActorComponent(&cfg));
}
AZ::EntityId EditorActorComponent::GetAttachedToEntityId() const
{
return m_attachmentTarget;
}
AZ::Aabb EditorActorComponent::GetEditorSelectionBoundsViewport(
const AzFramework::ViewportInfo& /*viewportInfo*/)
{
return GetWorldBounds();
}
AZ::Aabb EditorActorComponent::GetWorldBounds()
{
if (m_renderActorInstance)
{
return m_renderActorInstance->GetWorldAABB();
}
return AZ::Aabb::CreateNull();
}
AZ::Aabb EditorActorComponent::GetLocalBounds()
{
if (m_renderActorInstance)
{
return m_renderActorInstance->GetLocalAABB();
}
return AZ::Aabb::CreateNull();
}
bool EditorActorComponent::EditorSelectionIntersectRayViewport(
const AzFramework::ViewportInfo& viewportInfo,
const AZ::Vector3& src, const AZ::Vector3& dir, float& distance)
{
if (!m_actorAsset.Get() || !m_actorAsset.Get()->GetActor() || !m_actorInstance || !m_actorInstance->GetTransformData() || !m_renderCharacter)
{
return false;
}
distance = std::numeric_limits<float>::max();
bool isHit = false;
// Get the MCore::Ray used by Mesh::Intersects
// Convert the input source position and direction to a line segment by using the frustum depth as line length.
const AzFramework::CameraState cameraState = AzToolsFramework::GetCameraState(viewportInfo.m_viewportId);
const float frustumDepth = cameraState.m_farClip - cameraState.m_nearClip;
const AZ::Vector3 dest = src + dir * frustumDepth;
const MCore::Ray ray(src, dest);
// Update the mesh deformers so the intersection test will hit the actor if it is being
// animated by a motion component that is previewing the animation in the editor
m_actorInstance->UpdateMeshDeformers(0.0f, true);
const TransformData* transformData = m_actorInstance->GetTransformData();
const Pose* currentPose = transformData->GetCurrentPose();
// Iterate through the meshes in the actor, looking for the closest hit
Actor* actor = m_actorAsset.Get()->GetActor();
const uint32 numNodes = actor->GetNumNodes();
const uint32 numLods = actor->GetNumLODLevels();
for (uint32 lod = 0; lod < numLods; ++lod)
{
for (uint32 nodeIndex = 0; nodeIndex < numNodes; ++nodeIndex)
{
Mesh* mesh = actor->GetMesh(lod, nodeIndex);
if (!mesh || mesh->GetIsCollisionMesh())
{
continue;
}
// Use the actor instance transform for skinned meshes (as the vertices are pre-transformed and in model space) and the node world transform otherwise.
const Transform meshTransform = currentPose->GetMeshNodeWorldSpaceTransform(lod, nodeIndex);
AZ::Vector3 hitPoint;
if (mesh->Intersects(meshTransform, ray, &hitPoint))
{
isHit = true;
float hitDistance = (src - hitPoint).GetLength();
if (hitDistance < distance)
{
distance = hitDistance;
}
}
}
}
return isHit;
}
// Check if the given attachment is valid.
bool EditorActorComponent::IsValidAttachment(const AZ::EntityId& attachment, const AZ::EntityId& attachTo) const
{
// Cannot attach to yourself.
if (attachment == attachTo)
{
return false;
}
// Detect if attachTo is already in another circular chain.
auto AttachmentStep = [](AZ::EntityId attach, int stride) -> AZ::EntityId
{
AZ_Assert(stride > 0, "Stride value has to be greater than 0.");
if (attach.IsValid())
{
for (int i = 0; i < stride; ++i)
{
AZ::EntityId next;
EditorActorComponentRequestBus::EventResult(next, attach, &EditorActorComponentRequestBus::Events::GetAttachedToEntityId);
if (!next.IsValid())
{
return next;
}
attach = next;
}
return attach;
}
else
{
return attach;
}
};
AZ::EntityId slowWalker = attachTo;
AZ::EntityId fastWalker = attachTo;
while (fastWalker.IsValid())
{
slowWalker = AttachmentStep(slowWalker, 1);
fastWalker = AttachmentStep(fastWalker, 2);
if (fastWalker.IsValid() && fastWalker == slowWalker)
{
return false; // Cycle detected if slowWalker meets fastWalker.
}
}
// Walk our way up to the root.
AZ::EntityId resultId;
EditorActorComponentRequestBus::EventResult(resultId, attachTo, &EditorActorComponentRequestBus::Events::GetAttachedToEntityId);
while (resultId.IsValid())
{
AZ::EntityId localResult;
EditorActorComponentRequestBus::EventResult(localResult, resultId, &EditorActorComponentRequestBus::Events::GetAttachedToEntityId);
// We detected a loop.
if (localResult == attachment)
{
return false;
}
resultId = localResult;
}
return true;
}
// The entity has attached to the target.
void EditorActorComponent::OnAttached(AZ::EntityId targetId)
{
const AZ::EntityId* busIdPtr = LmbrCentral::AttachmentComponentNotificationBus::GetCurrentBusId();
if (busIdPtr)
{
const auto result = AZStd::find(m_attachments.begin(), m_attachments.end(), *busIdPtr);
if (result == m_attachments.end())
{
m_attachments.emplace_back(*busIdPtr);
}
}
if (!m_actorInstance)
{
return;
}
ActorInstance* targetActorInstance = nullptr;
ActorComponentRequestBus::EventResult(targetActorInstance, targetId, &ActorComponentRequestBus::Events::GetActorInstance);
const char* jointName = nullptr;
LmbrCentral::AttachmentComponentRequestBus::EventResult(jointName, GetEntityId(), &LmbrCentral::AttachmentComponentRequestBus::Events::GetJointName);
if (targetActorInstance)
{
Node* node = jointName ? targetActorInstance->GetActor()->GetSkeleton()->FindNodeByName(jointName) : targetActorInstance->GetActor()->GetSkeleton()->GetNode(0);
if (node)
{
const AZ::u32 jointIndex = node->GetNodeIndex();
Attachment* attachment = AttachmentNode::Create(targetActorInstance, jointIndex, m_actorInstance.get(), true /* Managed externally, by this component. */);
targetActorInstance->AddAttachment(attachment);
}
}
}
// The entity is detaching from the target.
void EditorActorComponent::OnDetached(AZ::EntityId targetId)
{
// Remove the targetId from the attachment list
const AZ::EntityId* busIdPtr = LmbrCentral::AttachmentComponentNotificationBus::GetCurrentBusId();
if (busIdPtr)
{
m_attachments.erase(AZStd::remove(m_attachments.begin(), m_attachments.end(), *busIdPtr), m_attachments.end());
}
if (!m_actorInstance)
{
return;
}
ActorInstance* targetActorInstance = nullptr;
ActorComponentRequestBus::EventResult(targetActorInstance, targetId, &ActorComponentRequestBus::Events::GetActorInstance);
if (targetActorInstance)
{
targetActorInstance->RemoveAttachment(m_actorInstance.get());
}
}
bool EditorActorComponent::IsAtomDisabled() const
{
return !AZ::Interface<AzFramework::AtomActiveInterface>::Get();
}
} //namespace Integration
} // namespace EMotionFX
@@ -0,0 +1,174 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/Component/Component.h>
#include <AzCore/Component/TransformBus.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzFramework/Visibility/BoundsBus.h>
#include <AzToolsFramework/API/ComponentEntitySelectionBus.h>
#include <AzToolsFramework/ToolsComponents/EditorComponentBase.h>
#include <AzToolsFramework/ToolsComponents/EditorVisibilityBus.h>
#include <Integration/Components/ActorComponent.h>
#include <Integration/Rendering/RenderActorInstance.h>
#include <LmbrCentral/Rendering/MaterialAsset.h>
namespace EMotionFX
{
namespace Integration
{
class EditorActorComponent
: public AzToolsFramework::Components::EditorComponentBase
, private AZ::Data::AssetBus::Handler
, private AZ::TransformNotificationBus::Handler
, private AZ::TickBus::Handler
, private ActorComponentRequestBus::Handler
, private EditorActorComponentRequestBus::Handler
, private LmbrCentral::AttachmentComponentNotificationBus::Handler
, private AzToolsFramework::EditorComponentSelectionRequestsBus::Handler
, private AzToolsFramework::EditorVisibilityNotificationBus::Handler
, public AzFramework::BoundsRequestBus::Handler
{
public:
AZ_EDITOR_COMPONENT(EditorActorComponent, "{A863EE1B-8CFD-4EDD-BA0D-1CEC2879AD44}");
EditorActorComponent();
~EditorActorComponent() override;
// AZ::Component overrides ...
void Init() override;
void Activate() override;
void Deactivate() override;
// ActorComponentRequestBus overrides ...
ActorInstance* GetActorInstance() override { return m_actorInstance.get(); }
bool GetRenderCharacter() const override;
void SetRenderCharacter(bool enable) override;
size_t GetNumJoints() const override;
SkinningMethod GetSkinningMethod() const override;
// EditorActorComponentRequestBus overrides ...
const AZ::Data::AssetId& GetActorAssetId() override;
AZ::EntityId GetAttachedToEntityId() const override;
// EditorVisibilityNotificationBus overrides ...
void OnEntityVisibilityChanged(bool visibility) override;
// EditorComponentSelectionRequestsBus overrides ...
AZ::Aabb GetEditorSelectionBoundsViewport(const AzFramework::ViewportInfo& viewportInfo) override;
bool EditorSelectionIntersectRayViewport(
const AzFramework::ViewportInfo& viewportInfo,
const AZ::Vector3& src, const AZ::Vector3& dir, float& distance) override;
bool SupportsEditorRayIntersect() override { return true; }
// AZ::Data::AssetBus::Handler overrides ...
void OnAssetReady(AZ::Data::Asset<AZ::Data::AssetData> asset) override;
void OnAssetReloaded(AZ::Data::Asset<AZ::Data::AssetData> asset) override;
// BoundsRequestBus overrides ...
AZ::Aabb GetWorldBounds() override;
AZ::Aabb GetLocalBounds() override;
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
ActorComponent::GetProvidedServices(provided);
}
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
{
ActorComponent::GetIncompatibleServices(incompatible);
}
static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent)
{
ActorComponent::GetDependentServices(dependent);
}
static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
{
ActorComponent::GetRequiredServices(required);
}
static void Reflect(AZ::ReflectContext* context);
private:
// Property callbacks.
AZ::Crc32 OnAssetSelected();
void OnMaterialChanged();
void OnMaterialPerActorChanged();
void OnLODLevelChanged();
void OnDebugDrawFlagChanged();
void OnSkinningMethodChanged();
AZ::Crc32 OnAttachmentTypeChanged();
AZ::Crc32 OnAttachmentTargetChanged();
AZ::Crc32 OnAttachmentTargetJointSelect();
bool AttachmentTargetVisibility();
bool AttachmentTargetJointVisibility();
AZStd::string AttachmentJointButtonText();
void InitializeMaterial(ActorAsset& actorAsset);
void LaunchAnimationEditor(const AZ::Data::AssetId& assetId, const AZ::Data::AssetType&);
// AZ::TransformNotificationBus overrides ...
void OnTransformChanged(const AZ::Transform& local, const AZ::Transform& world) override;
// Called at edit-time when creating the component directly from an asset.
void SetPrimaryAsset(const AZ::Data::AssetId& assetId) override;
// AZ::TickBus::Handler
void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
// LmbrCentral::AttachmentComponentNotificationBus::Handler
void OnAttached(AZ::EntityId targetId) override;
void OnDetached(AZ::EntityId targetId) override;
void BuildGameEntity(AZ::Entity* gameEntity) override;
void CreateActorInstance();
void DestroyActorInstance();
bool IsValidAttachment(const AZ::EntityId& attachment, const AZ::EntityId& attachTo) const;
bool IsAtomDisabled() const;
AZ::Data::Asset<ActorAsset> m_actorAsset; ///< Assigned actor asset.
AZStd::vector<AZ::EntityId> m_attachments; ///< A list of entities that are attached to this entity.
bool m_renderSkeleton; ///< Toggles rendering of character skeleton.
bool m_renderCharacter; ///< Toggles rendering of character model.
bool m_renderBounds; ///< Toggles rendering of the world bounding box.
bool m_entityVisible; ///< Entity visible from the EditorVisibilityNotificationBus
SkinningMethod m_skinningMethod; ///< The skinning method for this actor
AttachmentType m_attachmentType; ///< Attachment type.
AZ::EntityId m_attachmentTarget; ///< Target entity to attach to, if any.
AZStd::string m_attachmentJointName; ///< Joint name on target to which to attach (if ActorAttachment).
AZ::u32 m_attachmentJointIndex;
AZ::u32 m_lodLevel;
bool m_forceUpdateJointsOOV = false;
// \todo attachmentTarget node nr
// Note: LOD work in progress. For now we use one material instead of a list of material, because we don't have the support for LOD with multiple FBXs.
// We purposely kept a materialList in actorComponent and actorRenderNode for the flexibility in future.
// At the moment, the materialList stores duplicates of the same material.
AzFramework::SimpleAssetReference<LmbrCentral::MaterialAsset> m_materialPerActor;
ActorAsset::MaterialList m_materialPerLOD; ///< Material assignment for each LOD level.
ActorAsset::ActorInstancePtr m_actorInstance; ///< Live actor instance.
AZStd::unique_ptr<RenderActorInstance> m_renderActorInstance;
};
} // namespace Integration
} // namespace EMotionFX
@@ -0,0 +1,62 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates, or
* a third party where indicated.
*
* 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 "EMotionFX_precompiled.h"
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <LmbrCentral/Audio/AudioProxyComponentBus.h>
#include <Integration/Editor/Components/EditorAnimAudioComponent.h>
#include <Integration/Components/AnimAudioComponent.h>
using namespace LmbrCentral;
namespace EMotionFX
{
namespace Integration
{
void EditorAnimAudioComponent::BuildGameEntity(AZ::Entity* gameEntity)
{
auto animAudioComponent = aznew AnimAudioComponent;
gameEntity->AddComponent(animAudioComponent);
for (const auto& triggerEvent : m_editorTriggerEvents)
{
animAudioComponent->AddTriggerEvent(triggerEvent.m_event, triggerEvent.m_trigger.m_controlName, triggerEvent.m_joint);
}
}
void EditorAnimAudioComponent::Reflect(AZ::ReflectContext* context)
{
EditorAudioTriggerEvent::Reflect(context);
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<EditorAnimAudioComponent, AZ::Component>()
->Version(0)
->Field("Trigger Map", &EditorAnimAudioComponent::m_editorTriggerEvents);
if (auto editContext = serializeContext->GetEditContext())
{
editContext->Class<EditorAnimAudioComponent>("Audio Animation", "Adds ability to execute audio triggers when animation events occur.")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Category, "Audio")
->Attribute(AZ::Edit::Attributes::Icon, "Editor/Icons/Components/AudioAnimation.svg")
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c))
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->DataElement(AZ::Edit::UIHandlers::Default, &EditorAnimAudioComponent::m_editorTriggerEvents, "Trigger Map", "Maps the animation events to executable audio triggers.");
}
}
}
} // namespace Integration
} // namespace EMotionFX
@@ -0,0 +1,100 @@
/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates, or
* a third party where indicated.
*
* 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.
*
*/
#pragma once
#include <AzCore/Component/Component.h>
#include <AzCore/RTTI/RTTI.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzToolsFramework/ToolsComponents/EditorComponentBase.h>
#include <AzToolsFramework/UI/PropertyEditor/PropertyAudioCtrlTypes.h>
#include <Integration/Components/AnimAudioComponent.h>
#include <IAudioSystem.h>
namespace EMotionFX
{
namespace Integration
{
struct EditorAudioTriggerEvent
{
AZStd::string m_event;
AzToolsFramework::CReflectedVarAudioControl m_trigger;
AZStd::string m_joint;
AZ_RTTI(EditorAudioTriggerEvent, "{AA4D9F3A-F6C1-4E92-961F-E1D9DE11AD06}");
AZ_CLASS_ALLOCATOR(EditorAudioTriggerEvent, EMotionFXAllocator, 0);
EditorAudioTriggerEvent()
{
m_trigger.m_propertyType = AzToolsFramework::AudioPropertyType::Trigger;
}
virtual ~EditorAudioTriggerEvent() = default;
static void Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<EditorAudioTriggerEvent>()
->Version(0)
->Field("event", &EditorAudioTriggerEvent::m_event)
->Field("trigger", &EditorAudioTriggerEvent::m_trigger)
->Field("joint", &EditorAudioTriggerEvent::m_joint);
if (auto editContext = serializeContext->GetEditContext())
{
editContext->Class<EditorAudioTriggerEvent>("Audio Trigger Event", "Audio trigger executed when animation event occurs")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::HideIcon, true)
->DataElement(AZ::Edit::UIHandlers::Default, &EditorAudioTriggerEvent::m_event, "Event", "EMotionFX event.")
->DataElement("AudioControl", &EditorAudioTriggerEvent::m_trigger, "Trigger", "Audio trigger to execute.")
->DataElement(AZ::Edit::UIHandlers::Default, &EditorAudioTriggerEvent::m_joint, "Joint", "Mesh joint (optional).");
}
}
}
};
class EditorAnimAudioComponent
: public AzToolsFramework::Components::EditorComponentBase
{
public:
AZ_EDITOR_COMPONENT(EditorAnimAudioComponent, "{DF2320B2-97E8-40C4-86C5-C3327D0DA3E6}");
virtual ~EditorAnimAudioComponent() = default;
protected:
void BuildGameEntity(AZ::Entity* gameEntity) override;
static void Reflect(AZ::ReflectContext* context);
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
AnimAudioComponent::GetProvidedServices(provided);
}
static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
{
AnimAudioComponent::GetRequiredServices(required);
}
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
{
AnimAudioComponent::GetIncompatibleServices(incompatible);
}
AZStd::vector<EditorAudioTriggerEvent> m_editorTriggerEvents;
};
} // namespace Integration
} // namespace EMotionFX
@@ -0,0 +1,395 @@
/*
* 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 "EMotionFX_precompiled.h"
#include <AzCore/Asset/AssetManager.h>
#include <AzCore/Script/ScriptProperty.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <EMotionFX/Source/Parameter/BoolParameter.h>
#include <EMotionFX/Source/Parameter/FloatParameter.h>
#include <EMotionFX/Source/Parameter/StringParameter.h>
#include <EMotionFX/Source/Parameter/IntParameter.h>
#include <EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/EMStudioManager.h>
#include <EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/MainWindow.h>
#include <Integration/ActorComponentBus.h>
#include <Integration/Editor/Components/EditorAnimGraphComponent.h>
#include <QApplication>
namespace EMotionFX
{
namespace Integration
{
//////////////////////////////////////////////////////////////////////////
void EditorAnimGraphComponent::Reflect(AZ::ReflectContext* context)
{
auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<EditorAnimGraphComponent, AzToolsFramework::Components::EditorComponentBase>()
->Version(2)
->Field("AnimGraphAsset", &EditorAnimGraphComponent::m_animGraphAsset)
->Field("MotionSetAsset", &EditorAnimGraphComponent::m_motionSetAsset)
->Field("ActiveMotionSetName", &EditorAnimGraphComponent::m_activeMotionSetName)
->Field("DebugVisualization", &EditorAnimGraphComponent::m_visualize)
->Field("ParameterDefaults", &EditorAnimGraphComponent::m_parameterDefaults)
;
AZ::EditContext* editContext = serializeContext->GetEditContext();
if (editContext)
{
editContext->Class<AnimGraphComponent::ParameterDefaults>(
"Parameter Defaults", "Default values for anim graph parameters.")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->DataElement(AZ::Edit::UIHandlers::Button, &AnimGraphComponent::ParameterDefaults::m_parameters, "", "")
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
->Attribute(AZ::Edit::Attributes::ContainerCanBeModified, false)
;
editContext->Class<EditorAnimGraphComponent>(
"Anim Graph", "The Anim Graph component manages a set of assets that are built in the Animation Editor, including the animation graph, default parameter settings, and assigned motion set for the associated Actor")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Category, "Animation")
->Attribute(AZ::Edit::Attributes::Icon, ":/EMotionFX/AnimGraphComponent.svg")
->Attribute(AZ::Edit::Attributes::PrimaryAssetType, azrtti_typeid<AnimGraphAsset>())
->Attribute(AZ::Edit::Attributes::ViewportIcon, ":/EMotionFX/AnimGraphComponent.svg")
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c))
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://docs.aws.amazon.com/lumberyard/latest/userguide/component-animgraph.html")
->DataElement(AZ::Edit::UIHandlers::Default, &EditorAnimGraphComponent::m_motionSetAsset,
"Motion set asset", "EMotion FX motion set asset to be loaded for this actor.")
->Attribute("EditButton", "")
->Attribute("EditDescription", "Open in Animation Editor")
->Attribute("EditCallback", &EditorAnimGraphComponent::LaunchAnimationEditor)
->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorAnimGraphComponent::OnMotionSetAssetSelected)
->DataElement(AZ_CRC("MotionSetName", 0xcf534ea6), &EditorAnimGraphComponent::m_activeMotionSetName, "Active motion set", "Motion set to use for this anim graph instance")
->Attribute(AZ_CRC("MotionSetAsset", 0xd4e88984), &EditorAnimGraphComponent::GetMotionAsset)
->DataElement(AZ::Edit::UIHandlers::Default, &EditorAnimGraphComponent::m_visualize, "Debug visualization", "Enable this to allow the anim graph to render debug visualization. Enable debug rendering on anim graph nodes first.")
->DataElement(AZ::Edit::UIHandlers::Default, &EditorAnimGraphComponent::m_animGraphAsset,
"Anim graph", "EMotion FX anim graph to be assigned to this actor.")
->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorAnimGraphComponent::OnAnimGraphAssetSelected)
->Attribute("EditButton", "")
->Attribute("EditDescription", "Open in Animation Editor")
->Attribute("EditCallback", &EditorAnimGraphComponent::LaunchAnimationEditor)
->DataElement(AZ::Edit::UIHandlers::Default, &EditorAnimGraphComponent::m_parameterDefaults,
"Parameters", "Anim graph default parameter values.")
;
}
}
}
//////////////////////////////////////////////////////////////////////////
EditorAnimGraphComponent::EditorAnimGraphComponent()
{
}
//////////////////////////////////////////////////////////////////////////
EditorAnimGraphComponent::~EditorAnimGraphComponent()
{
}
//////////////////////////////////////////////////////////////////////////
void EditorAnimGraphComponent::Activate()
{
// Refresh parameters in case anim graph asset changed since last session.
OnAnimGraphAssetSelected();
OnMotionSetAssetSelected();
EditorAnimGraphComponentRequestBus::Handler::BusConnect(GetEntityId());
}
//////////////////////////////////////////////////////////////////////////
void EditorAnimGraphComponent::Deactivate()
{
EditorAnimGraphComponentRequestBus::Handler::BusDisconnect();
AZ::Data::AssetBus::MultiHandler::BusDisconnect();
m_animGraphAsset.Release();
m_motionSetAsset.Release();
}
void EditorAnimGraphComponent::LaunchAnimationEditor(const AZ::Data::AssetId& assetId, [[maybe_unused]] const AZ::Data::AssetType& assetType)
{
if (assetId.IsValid())
{
AZ::Data::AssetId actorAssetId;
actorAssetId.SetInvalid();
EditorActorComponentRequestBus::EventResult(actorAssetId, GetEntityId(), &EditorActorComponentRequestBus::Events::GetActorAssetId);
// call to open must be done before LoadCharacter
const char* panelName = EMStudio::MainWindow::GetEMotionFXPaneName();
EBUS_EVENT(AzToolsFramework::EditorRequests::Bus, OpenViewPane, panelName);
EMStudio::MainWindow* mainWindow = EMStudio::GetMainWindow();
if (mainWindow)
{
mainWindow->LoadCharacter(actorAssetId, m_animGraphAsset.GetId(), m_motionSetAsset.GetId());
mainWindow->show();
mainWindow->LoadLayoutAfterShow();
// Force the window to be fully loaded before loading
// things. Remember that QMainWindow::show() doesn't
// actually show anything syncronously. All it does is put
// a QShowEvent onto the event queue. This call makes the
// ShowEvent process, blocking until it is done.
QApplication::instance()->processEvents(QEventLoop::ExcludeUserInputEvents);
// After loading we want to activate based on what we have in this component (anim grah and motion set)
// Only activate if we have a valid anim graph and a valid motion set. An empty m_motionSetName will use
// the root motionset from the motion set asset
if (m_animGraphAsset.IsReady() && m_motionSetAsset.IsReady())
{
AnimGraphAsset* animGraphAsset = m_animGraphAsset.GetAs<AnimGraphAsset>();
AZ_Assert(animGraphAsset, "Expected anim graph asset");
EMotionFX::AnimGraph* animGraph = animGraphAsset->GetAnimGraph();
MotionSetAsset* motionSetAsset = m_motionSetAsset.GetAs<MotionSetAsset>();
AZ_Assert(motionSetAsset, "Expected motion set asset");
EMotionFX::MotionSet* rootMotionSet = motionSetAsset->m_emfxMotionSet.get();
EMotionFX::MotionSet* motionSet = rootMotionSet;
if (!m_activeMotionSetName.empty())
{
motionSet = rootMotionSet->RecursiveFindMotionSetByName(m_activeMotionSetName, true);
if (!motionSet)
{
AZ_Warning("EMotionFX", false, "Failed to find motion set \"%s\" in motion set file %s.",
m_activeMotionSetName.c_str(),
rootMotionSet->GetName());
motionSet = rootMotionSet;
}
}
mainWindow->Activate(actorAssetId, animGraph, motionSet);
}
}
}
}
//////////////////////////////////////////////////////////////////////////
AZ::u32 EditorAnimGraphComponent::OnAnimGraphAssetSelected()
{
AZ::Data::AssetBus::MultiHandler::BusDisconnect();
if (m_motionSetAsset.GetId().IsValid())
{
AZ::Data::AssetBus::MultiHandler::BusConnect(m_motionSetAsset.GetId());
}
if (m_animGraphAsset.GetId().IsValid())
{
AZ::Data::AssetBus::MultiHandler::BusConnect(m_animGraphAsset.GetId());
m_animGraphAsset.QueueLoad();
}
else
{
m_parameterDefaults.m_parameters.clear();
}
return AZ::Edit::PropertyRefreshLevels::EntireTree;
}
AZ::u32 EditorAnimGraphComponent::OnMotionSetAssetSelected()
{
AZ::Data::AssetBus::MultiHandler::BusDisconnect();
if (m_animGraphAsset.GetId().IsValid())
{
AZ::Data::AssetBus::MultiHandler::BusConnect(m_animGraphAsset.GetId());
}
if (m_motionSetAsset.GetId().IsValid())
{
AZ::Data::AssetBus::MultiHandler::BusConnect(m_motionSetAsset.GetId());
m_motionSetAsset.QueueLoad();
}
return AZ::Edit::PropertyRefreshLevels::EntireTree;
}
//////////////////////////////////////////////////////////////////////////
void EditorAnimGraphComponent::OnAssetReloaded(AZ::Data::Asset<AZ::Data::AssetData> asset)
{
// Re-process anim graph asset.
OnAssetReady(asset);
}
//////////////////////////////////////////////////////////////////////////
void EditorAnimGraphComponent::SetPrimaryAsset(const AZ::Data::AssetId& assetId)
{
AZ::Data::Asset<AnimGraphAsset> asset = AZ::Data::AssetManager::Instance().FindOrCreateAsset<AnimGraphAsset>(assetId, m_animGraphAsset.GetAutoLoadBehavior());
if (asset)
{
m_animGraphAsset = asset;
}
}
bool EditorAnimGraphComponent::IsSupportedScriptPropertyType(const ValueParameter* param) const
{
return (azrtti_istypeof<FloatParameter>(param) ||
azrtti_istypeof<IntParameter>(param) ||
azrtti_istypeof<BoolParameter>(param) ||
azrtti_istypeof<StringParameter>(param));
}
//////////////////////////////////////////////////////////////////////////
void EditorAnimGraphComponent::OnAssetReady(AZ::Data::Asset<AZ::Data::AssetData> asset)
{
AZ_Assert(asset == m_animGraphAsset || asset == m_motionSetAsset, "Unexpected asset");
if (asset == m_animGraphAsset)
{
m_animGraphAsset = asset;
AnimGraphAsset* data = m_animGraphAsset.GetAs<AnimGraphAsset>();
if (!data)
{
return;
}
EMotionFX::AnimGraph* animGraph = data->GetAnimGraph();
// Remove any parameters we have values for that are no longer in the anim graph.
for (auto iter = m_parameterDefaults.m_parameters.begin(); iter != m_parameterDefaults.m_parameters.end(); )
{
const ValueParameter* valueParameter = animGraph->FindValueParameterByName((*iter)->m_name);
if (!valueParameter || !IsSupportedScriptPropertyType(valueParameter))
{
delete *iter;
iter = m_parameterDefaults.m_parameters.erase(iter);
}
else
{
++iter;
}
}
// Populate property array based on parameters found in the anim graph.
const EMotionFX::ValueParameterVector& valueParameters = animGraph->RecursivelyGetValueParameters();
for (const EMotionFX::ValueParameter* param : valueParameters)
{
const AZStd::string& paramName = param->GetName();
// If we already have a value for this property, skip it.
bool found = false;
for (AZ::ScriptProperty* prop : m_parameterDefaults.m_parameters)
{
if (paramName == prop->m_name)
{
found = true;
break;
}
}
if (found)
{
continue;
}
// Based on the anim graph param type, create an appropriate script property for serialization and editing.
if (azrtti_istypeof<EMotionFX::FloatParameter>(param))
{
const EMotionFX::FloatParameter* floatParam = static_cast<const EMotionFX::FloatParameter*>(param);
m_parameterDefaults.m_parameters.emplace_back(aznew AZ::ScriptPropertyNumber(paramName.c_str(), floatParam->GetDefaultValue()));
}
else if (azrtti_istypeof<EMotionFX::IntParameter>(param))
{
const EMotionFX::IntParameter* intParam = static_cast<const EMotionFX::IntParameter*>(param);
m_parameterDefaults.m_parameters.emplace_back(aznew AZ::ScriptPropertyNumber(paramName.c_str(), intParam->GetDefaultValue()));
}
else if (azrtti_istypeof<EMotionFX::BoolParameter>(param))
{
const EMotionFX::BoolParameter* boolParam = static_cast<const EMotionFX::BoolParameter*>(param);
m_parameterDefaults.m_parameters.emplace_back(aznew AZ::ScriptPropertyBoolean(paramName.c_str(), boolParam->GetDefaultValue()));
}
else if (azrtti_istypeof<EMotionFX::StringParameter>(param))
{
const EMotionFX::StringParameter* stringParam = static_cast<const EMotionFX::StringParameter*>(param);
m_parameterDefaults.m_parameters.emplace_back(aznew AZ::ScriptPropertyString(paramName.c_str(), stringParam->GetDefaultValue().c_str()));
}
else
{
AZ_Assert(!IsSupportedScriptPropertyType(param), "This value parameter of this type ('%s') should not be supported. Please update the IsSupportedScriptPropertyType() method.", param->GetTypeDisplayName());
}
}
}
else if (asset == m_motionSetAsset)
{
m_motionSetAsset = asset;
const MotionSetAsset* data = m_motionSetAsset.GetAs<MotionSetAsset>();
if (data)
{
const EMotionFX::MotionSet* rootMotionSet = data->m_emfxMotionSet.get();
if (rootMotionSet)
{
if (m_activeMotionSetName.empty())
{
// if motion set name is empty, grab the root
m_activeMotionSetName = rootMotionSet->GetName();
}
else
{
const EMotionFX::MotionSet* motionSet = rootMotionSet->RecursiveFindMotionSetByName(m_activeMotionSetName, /*isOwnedByRuntime = */true);
if (!motionSet)
{
m_activeMotionSetName = rootMotionSet->GetName();
}
}
}
}
}
// Force-refresh the property grid.
using namespace AzToolsFramework;
EBUS_EVENT(ToolsApplicationEvents::Bus, InvalidatePropertyDisplay, Refresh_EntireTree);
}
const AZ::Data::AssetId& EditorAnimGraphComponent::GetAnimGraphAssetId()
{
return m_animGraphAsset.GetId();
}
const AZ::Data::AssetId& EditorAnimGraphComponent::GetMotionSetAssetId()
{
return m_motionSetAsset.GetId();
}
void EditorAnimGraphComponent::SetAnimGraphAssetId(const AZ::Data::AssetId& assetId)
{
m_animGraphAsset = AZ::Data::Asset<AnimGraphAsset>(assetId, azrtti_typeid<AnimGraphAsset>());
}
void EditorAnimGraphComponent::SetMotionSetAssetId(const AZ::Data::AssetId& assetId)
{
m_motionSetAsset = AZ::Data::Asset<MotionSetAsset>(assetId, azrtti_typeid<MotionSetAsset>());
}
//////////////////////////////////////////////////////////////////////////
void EditorAnimGraphComponent::BuildGameEntity(AZ::Entity* gameEntity)
{
AnimGraphComponent::Configuration cfg;
cfg.m_animGraphAsset = m_animGraphAsset;
cfg.m_motionSetAsset = m_motionSetAsset;
cfg.m_activeMotionSetName = m_activeMotionSetName;
cfg.m_parameterDefaults = m_parameterDefaults;
cfg.m_visualize = m_visualize;
gameEntity->AddComponent(aznew AnimGraphComponent(&cfg));
}
} //namespace Integration
} // namespace EMotionFX
@@ -0,0 +1,111 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/Script/ScriptProperty.h>
#include <AzToolsFramework/ToolsComponents/EditorComponentBase.h>
#include <Integration/Components/AnimGraphComponent.h>
namespace EMotionFX
{
class ValueParameter;
namespace Integration
{
class EditorAnimGraphComponent
: public AzToolsFramework::Components::EditorComponentBase
, private AZ::Data::AssetBus::MultiHandler
, private EditorAnimGraphComponentRequestBus::Handler
{
public:
AZ_EDITOR_COMPONENT(EditorAnimGraphComponent, "{770F0A71-59EA-413B-8DAB-235FB0FF1384}");
EditorAnimGraphComponent();
~EditorAnimGraphComponent() override;
//////////////////////////////////////////////////////////////////////////
// AZ::Component interface implementation
void Activate() override;
void Deactivate() override;
//////////////////////////////////////////////////////////////////////////
void LaunchAnimationEditor(const AZ::Data::AssetId& assetId, const AZ::Data::AssetType&);
//////////////////////////////////////////////////////////////////////////
// AZ::Data::AssetBus::Handler
void OnAssetReady(AZ::Data::Asset<AZ::Data::AssetData> asset) override;
void OnAssetReloaded(AZ::Data::Asset<AZ::Data::AssetData> asset) override;
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// EditorAnimGraphComponentRequestBus::Handler
const AZ::Data::AssetId& GetAnimGraphAssetId() override;
const AZ::Data::AssetId& GetMotionSetAssetId() override;
void SetAnimGraphAssetId(const AZ::Data::AssetId& assetId);
void SetMotionSetAssetId(const AZ::Data::AssetId& assetId);
//////////////////////////////////////////////////////////////////////////
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
AnimGraphComponent::GetProvidedServices(provided);
}
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
{
AnimGraphComponent::GetIncompatibleServices(incompatible);
}
static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent)
{
AnimGraphComponent::GetDependentServices(dependent);
}
static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
{
AnimGraphComponent::GetRequiredServices(required);
}
static void Reflect(AZ::ReflectContext* context);
//////////////////////////////////////////////////////////////////////////
private:
AZ::Data::Asset<MotionSetAsset>* GetMotionAsset() { return &m_motionSetAsset; }
// Property callbacks.
AZ::u32 OnAnimGraphAssetSelected();
AZ::u32 OnMotionSetAssetSelected();
bool IsSupportedScriptPropertyType(const ValueParameter* param) const;
// Called at edit-time when creating the component directly from an asset.
void SetPrimaryAsset(const AZ::Data::AssetId& assetId) override;
// Called at export-time to produce runtime entities/components.
void BuildGameEntity(AZ::Entity* gameEntity) override;
AZ::Data::Asset<AnimGraphAsset> m_animGraphAsset; ///< Selected anim graph.
AZ::Data::Asset<MotionSetAsset> m_motionSetAsset; ///< Selected motion set asset.
AZStd::string m_activeMotionSetName; ///< Selected motion set.
bool m_visualize = false; ///< Enable debug visualisation?
AnimGraphComponent::ParameterDefaults m_parameterDefaults; ///< AnimGraph parameter defaults.
};
} // namespace Integration
} // namespace EMotionFX
@@ -0,0 +1,140 @@
/*
* 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/PlatformDef.h>
#include "EMotionFX_precompiled.h"
#include <MathConversion.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Math/Transform.h>
#include <AzCore/Asset/AssetManager.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <Integration/Assets/ActorAsset.h>
#include <Integration/Editor/Components/EditorSimpleLODComponent.h>
namespace EMotionFX
{
namespace Integration
{
void EditorSimpleLODComponent::Reflect(AZ::ReflectContext* context)
{
auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<EditorSimpleLODComponent, AzToolsFramework::Components::EditorComponentBase>()
->Version(2, VersionConverter)
->Field("LOD Configuration", &EditorSimpleLODComponent::m_configuration)
;
AZ::EditContext* editContext = serializeContext->GetEditContext();
if (editContext)
{
editContext->Class<EditorSimpleLODComponent>(
"Simple LOD Distance (DEPRECATED By Atom)", "This component does not work with Atom renderer. The Simple LOD distance component alters the actor LOD level based on")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Category, "Animation (Legacy)")
->Attribute(AZ::Edit::Attributes::Icon, "Editor/Icons/Components/SimpleLODDistance.svg")
->Attribute(AZ::Edit::Attributes::ViewportIcon, "Editor/Icons/Components/Viewport/Mannequin.png")
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c))
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->DataElement(0, &EditorSimpleLODComponent::m_configuration, "LOD Configuration", "");
}
}
}
bool EditorSimpleLODComponent::VersionConverter(AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& classElement)
{
const unsigned int version = classElement.GetVersion();
bool result = true;
if (version < 2)
{
AZ::SerializeContext::DataElementNode LODDistanceNode = classElement.GetSubElement(1);
classElement.RemoveElement(1);
result = result && classElement.AddElement<SimpleLODComponent::Configuration>(context, "LOD Configuration");
AZ::SerializeContext::DataElementNode& LODConfigurationNode = classElement.GetSubElement(1);
result = result && LODConfigurationNode.AddElement(LODDistanceNode);
}
return true;
}
EditorSimpleLODComponent::EditorSimpleLODComponent()
: m_actorInstance(nullptr)
{
}
EditorSimpleLODComponent::~EditorSimpleLODComponent()
{
}
void EditorSimpleLODComponent::Activate()
{
EMotionFXPtr<EMotionFX::ActorInstance> actorInstance;
ActorComponentRequestBus::EventResult(
actorInstance,
GetEntityId(),
&ActorComponentRequestBus::Events::GetActorInstance);
if (actorInstance)
{
m_actorInstance = actorInstance.get();
const AZ::u32 numLODs = m_actorInstance->GetActor()->GetNumLODLevels();
m_configuration.GenerateDefaultValue(numLODs);
}
else
{
m_actorInstance = nullptr;
}
ActorComponentNotificationBus::Handler::BusConnect(GetEntityId());
AZ::TickBus::Handler::BusConnect();
}
void EditorSimpleLODComponent::Deactivate()
{
AZ::TickBus::Handler::BusDisconnect();
ActorComponentNotificationBus::Handler::BusDisconnect();
}
void EditorSimpleLODComponent::OnActorInstanceCreated(EMotionFX::ActorInstance* actorInstance)
{
if (m_actorInstance != actorInstance)
{
m_actorInstance = actorInstance;
const AZ::u32 numLODs = m_actorInstance->GetActor()->GetNumLODLevels();
m_configuration.GenerateDefaultValue(numLODs);
}
}
void EditorSimpleLODComponent::OnActorInstanceDestroyed([[maybe_unused]] EMotionFX::ActorInstance* actorInstance)
{
m_actorInstance = nullptr;
m_configuration.Reset();
}
void EditorSimpleLODComponent::OnTick([[maybe_unused]] float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time)
{
SimpleLODComponent::UpdateLodLevelByDistance(m_actorInstance, m_configuration, GetEntityId());
}
void EditorSimpleLODComponent::BuildGameEntity(AZ::Entity* gameEntity)
{
gameEntity->AddComponent(aznew SimpleLODComponent(&m_configuration));
}
}
}
@@ -0,0 +1,82 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/Component/Component.h>
#include <AzCore/Asset/AssetCommon.h>
#include <AzToolsFramework/ToolsComponents/EditorComponentBase.h>
#include <Integration/Components/SimpleLODComponent.h>
#include <Integration/Components/ActorComponent.h>
namespace EMotionFX
{
namespace Integration
{
class EditorSimpleLODComponent
: public AzToolsFramework::Components::EditorComponentBase
, private AZ::Data::AssetBus::Handler
, private AZ::TickBus::Handler
, private ActorComponentNotificationBus::Handler
{
public:
AZ_EDITOR_COMPONENT(EditorSimpleLODComponent, "{2A78936A-FA43-41C5-89C4-B588ED45DE2F}");
EditorSimpleLODComponent();
~EditorSimpleLODComponent() override;
// AZ::Component interface implementation
void Activate() override;
void Deactivate() override;
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
SimpleLODComponent::GetProvidedServices(provided);
}
static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent)
{
SimpleLODComponent::GetDependentServices(dependent);
}
static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
{
SimpleLODComponent::GetRequiredServices(required);
}
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
{
SimpleLODComponent::GetIncompatibleServices(incompatible);
}
static void Reflect(AZ::ReflectContext* context);
static bool VersionConverter(AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& classElement);
private:
EditorSimpleLODComponent(const EditorSimpleLODComponent&) = delete;
// ActorComponentNotificationBus::Handler
void OnActorInstanceCreated(EMotionFX::ActorInstance* actorInstance) override;
void OnActorInstanceDestroyed(EMotionFX::ActorInstance* actorInstance) override;
// AZ::TickBus::Handler
void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
void BuildGameEntity(AZ::Entity* gameEntity) override;
EMotionFX::ActorInstance* m_actorInstance; // Associated actor instance (retrieved from Actor Component).
SimpleLODComponent::Configuration m_configuration;
};
}
}
@@ -0,0 +1,411 @@
/*
* 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 "EMotionFX_precompiled.h"
#include <AzCore/Component/Entity.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Math/Transform.h>
#include <AzCore/Asset/AssetManager.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <Integration/Assets/ActorAsset.h>
#include <Integration/Editor/Components/EditorSimpleMotionComponent.h>
#include <Integration/SimpleMotionComponentBus.h>
namespace EMotionFX
{
namespace Integration
{
void EditorSimpleMotionComponent::Reflect(AZ::ReflectContext* context)
{
auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<EditorSimpleMotionComponent, AzToolsFramework::Components::EditorComponentBase>()
->Version(3)
->Field("PreviewInEditor", &EditorSimpleMotionComponent::m_previewInEditor)
->Field("Configuration", &EditorSimpleMotionComponent::m_configuration)
;
AZ::EditContext* editContext = serializeContext->GetEditContext();
if (editContext)
{
editContext->Class<EditorSimpleMotionComponent>(
"Simple Motion", "The Simple Motion component assigns a single motion to the associated Actor in lieu of an Anim Graph component")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Category, "Animation")
->Attribute(AZ::Edit::Attributes::Icon, "Editor/Icons/Components/SimpleMotion.svg")
->Attribute(AZ::Edit::Attributes::PrimaryAssetType, azrtti_typeid<MotionAsset>())
->Attribute(AZ::Edit::Attributes::ViewportIcon, "Editor/Icons/Components/Viewport/Mannequin.png")
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c))
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->DataElement(0, &EditorSimpleMotionComponent::m_previewInEditor, "Preview In Editor", "Plays motion in Editor")
->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorSimpleMotionComponent::OnEditorPropertyChanged)
->DataElement(0, &EditorSimpleMotionComponent::m_configuration, "Configuration", "Settings for this Simple Motion")
->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorSimpleMotionComponent::OnEditorPropertyChanged)
->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://docs.aws.amazon.com/lumberyard/latest/userguide/component-simple-motion.html")
;
}
}
AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context);
if (behaviorContext)
{
behaviorContext->EBus<EditorSimpleMotionComponentRequestBus>("EditorSimpleMotionComponentRequestBus")
->Event("SetPreviewInEditor", &EditorSimpleMotionComponentRequestBus::Events::SetPreviewInEditor)
->Event("GetPreviewInEditor", &EditorSimpleMotionComponentRequestBus::Events::GetPreviewInEditor)
->Attribute("Hidden", AZ::Edit::Attributes::PropertyHidden)
->VirtualProperty("PreviewInEditor", "GetPreviewInEditor", "SetPreviewInEditor")
->Event("GetAssetDuration", &EditorSimpleMotionComponentRequestBus::Events::GetAssetDuration)
->Attribute(AZ::Script::Attributes::Ignore, true)
;
behaviorContext->Class<EditorSimpleMotionComponent>()
->RequestBus("SimpleMotionComponentRequestBus")
->RequestBus("EditorSimpleMotionComponentRequestBus")
;
}
}
EditorSimpleMotionComponent::EditorSimpleMotionComponent()
: m_previewInEditor(false)
, m_configuration()
, m_actorInstance(nullptr)
, m_motionInstance(nullptr)
, m_lastMotionInstance(nullptr)
{
}
EditorSimpleMotionComponent::~EditorSimpleMotionComponent()
{
}
void EditorSimpleMotionComponent::Activate()
{
AZ::Data::AssetBus::MultiHandler::BusDisconnect();
SimpleMotionComponentRequestBus::Handler::BusConnect(GetEntityId());
EditorSimpleMotionComponentRequestBus::Handler::BusConnect(GetEntityId());
//check if our motion has changed
VerifyMotionAssetState();
ActorComponentNotificationBus::Handler::BusConnect(GetEntityId());
}
void EditorSimpleMotionComponent::Deactivate()
{
ActorComponentNotificationBus::Handler::BusDisconnect();
EditorSimpleMotionComponentRequestBus::Handler::BusDisconnect();
SimpleMotionComponentRequestBus::Handler::BusDisconnect();
AZ::Data::AssetBus::MultiHandler::BusDisconnect();
RemoveMotionInstanceFromActor(m_lastMotionInstance);
m_lastMotionInstance = nullptr;
RemoveMotionInstanceFromActor(m_motionInstance);
m_motionInstance = nullptr;
m_configuration.m_motionAsset.Release();
m_lastMotionAsset.Release();
m_actorInstance = nullptr;
}
void EditorSimpleMotionComponent::VerifyMotionAssetState()
{
AZ::Data::AssetBus::MultiHandler::BusDisconnect();
if (m_configuration.m_motionAsset.GetId().IsValid())
{
AZ::Data::AssetBus::MultiHandler::BusConnect(m_configuration.m_motionAsset.GetId());
m_configuration.m_motionAsset.QueueLoad();
}
}
void EditorSimpleMotionComponent::OnAssetReady(AZ::Data::Asset<AZ::Data::AssetData> asset)
{
if (asset == m_configuration.m_motionAsset)
{
m_configuration.m_motionAsset = asset;
PlayMotion();
}
}
void EditorSimpleMotionComponent::OnAssetReloaded(AZ::Data::Asset<AZ::Data::AssetData> asset)
{
OnAssetReady(asset);
}
void EditorSimpleMotionComponent::OnActorInstanceCreated(EMotionFX::ActorInstance* actorInstance)
{
m_actorInstance = actorInstance;
PlayMotion();
}
void EditorSimpleMotionComponent::OnActorInstanceDestroyed([[maybe_unused]] EMotionFX::ActorInstance* actorInstance)
{
RemoveMotionInstanceFromActor(m_lastMotionInstance);
m_lastMotionInstance = nullptr;
RemoveMotionInstanceFromActor(m_motionInstance);
m_motionInstance = nullptr;
m_actorInstance = nullptr;
}
void EditorSimpleMotionComponent::PlayMotion()
{
if (m_previewInEditor)
{
// The Editor allows scrubbing back and forth on animation blending transitions, so don't delete
// motion instances if it's blend weight is zero.
// The Editor preview should preview the motion in place to prevent off center movement.
m_motionInstance = SimpleMotionComponent::PlayMotionInternal(m_actorInstance, m_configuration, /*deleteOnZeroWeight*/false, /*inPlace*/true);
}
}
void EditorSimpleMotionComponent::RemoveMotionInstanceFromActor(EMotionFX::MotionInstance* motionInstance)
{
if (motionInstance)
{
if (m_actorInstance && m_actorInstance->GetMotionSystem())
{
m_actorInstance->GetMotionSystem()->RemoveMotionInstance(motionInstance);
}
}
}
void EditorSimpleMotionComponent::BuildGameEntity(AZ::Entity* gameEntity)
{
gameEntity->AddComponent(aznew SimpleMotionComponent(&m_configuration));
}
void EditorSimpleMotionComponent::LoopMotion(bool enable)
{
m_configuration.m_loop = enable;
if (m_motionInstance)
{
m_motionInstance->SetMaxLoops(enable ? EMFX_LOOPFOREVER : 1);
}
}
bool EditorSimpleMotionComponent::GetLoopMotion() const
{
return m_configuration.m_loop;
}
void EditorSimpleMotionComponent::RetargetMotion(bool enable)
{
m_configuration.m_retarget = enable;
if (m_motionInstance)
{
m_motionInstance->SetRetargetingEnabled(enable);
}
}
void EditorSimpleMotionComponent::ReverseMotion(bool enable)
{
m_configuration.m_reverse = enable;
if (m_motionInstance)
{
m_motionInstance->SetPlayMode(enable ? EMotionFX::EPlayMode::PLAYMODE_BACKWARD : EMotionFX::EPlayMode::PLAYMODE_FORWARD);
}
}
void EditorSimpleMotionComponent::MirrorMotion(bool enable)
{
m_configuration.m_mirror = enable;
if (m_motionInstance)
{
m_motionInstance->SetMirrorMotion(enable);
}
}
void EditorSimpleMotionComponent::SetPlaySpeed(float speed)
{
m_configuration.m_playspeed = speed;
if (m_motionInstance)
{
m_motionInstance->SetPlaySpeed(speed);
}
}
float EditorSimpleMotionComponent::GetPlaySpeed() const
{
return m_configuration.m_playspeed;
}
float EditorSimpleMotionComponent::GetAssetDuration(const AZ::Data::AssetId& assetId)
{
float result = 1.0f;
// Do a blocking load of the asset.
AZ::Data::Asset<MotionAsset> motionAsset = AZ::Data::AssetManager::Instance().GetAsset<MotionAsset>(assetId, AZ::Data::AssetLoadBehavior::Default);
motionAsset.BlockUntilLoadComplete();
if (motionAsset && motionAsset.Get()->m_emfxMotion)
{
result = motionAsset.Get()->m_emfxMotion.get()->GetDuration();
}
motionAsset.Release();
return result;
}
void EditorSimpleMotionComponent::PlayTime(float time)
{
if (m_motionInstance)
{
float delta = time - m_motionInstance->GetLastCurrentTime();
m_motionInstance->SetCurrentTime(time, false);
// Apply the same time step to the last animation
// so blend out will be good. Otherwise we are just blending
// from the last frame played of the last animation.
if (m_lastMotionInstance && m_lastMotionInstance->GetIsBlending())
{
m_lastMotionInstance->SetCurrentTime(m_lastMotionInstance->GetLastCurrentTime() + delta, false);
}
}
}
float EditorSimpleMotionComponent::GetPlayTime() const
{
float result = 0.0f;
if (m_motionInstance)
{
result = m_motionInstance->GetCurrentTimeNormalized();
}
return result;
}
void EditorSimpleMotionComponent::Motion(AZ::Data::AssetId assetId)
{
if (m_configuration.m_motionAsset.GetId() != assetId)
{
// Disconnect the old asset bus
if (AZ::Data::AssetBus::MultiHandler::BusIsConnectedId(m_configuration.m_motionAsset.GetId()))
{
AZ::Data::AssetBus::MultiHandler::BusDisconnect(m_configuration.m_motionAsset.GetId());
}
// Save the motion asset that we are about to be remove in case it can be reused.
AZ::Data::Asset<MotionAsset> oldLastMotionAsset = m_lastMotionAsset;
if (m_lastMotionInstance)
{
RemoveMotionInstanceFromActor(m_lastMotionInstance);
}
// Store the current motion asset as the last one for possible blending.
// If we don't keep a reference to the motion asset, the motion instance will be
// automatically released.
if (m_configuration.m_motionAsset.GetId().IsValid())
{
m_lastMotionAsset = m_configuration.m_motionAsset;
}
// Set the current motion instance as the last motion instance. The new current motion
// instance will then be set when the load is complete.
m_lastMotionInstance = m_motionInstance;
m_motionInstance = nullptr;
// Start the fade out if there is a blend out time. Otherwise just leave the
// m_lastMotionInstance where it is at so the next anim can blend from that frame.
if (m_lastMotionInstance && m_configuration.m_blendOutTime > 0.0f)
{
m_lastMotionInstance->Stop(m_configuration.m_blendOutTime);
}
// Reuse the old, last motion asset if possible. Otherwise, request a load.
if (assetId.IsValid() && oldLastMotionAsset.GetData() && assetId == oldLastMotionAsset.GetId())
{
// Even though we are not calling GetAsset here, OnAssetReady
// will be fired when the bus is connected because this asset is already loaded.
m_configuration.m_motionAsset = oldLastMotionAsset;
}
else
{
// Won't be able to reuse oldLastMotionAsset, release it.
oldLastMotionAsset.Release();
// Clear the old asset.
m_configuration.m_motionAsset.Release();
// Create a new asset
if (assetId.IsValid())
{
m_configuration.m_motionAsset = AZ::Data::AssetManager::Instance().GetAsset<MotionAsset>(assetId, m_configuration.m_motionAsset.GetAutoLoadBehavior());
}
}
// Connect the bus if the asset is valid.
if (m_configuration.m_motionAsset.GetId().IsValid())
{
AZ::Data::AssetBus::MultiHandler::BusConnect(m_configuration.m_motionAsset.GetId());
}
}
}
AZ::Data::AssetId EditorSimpleMotionComponent::GetMotion() const
{
return m_configuration.m_motionAsset.GetId();
}
void EditorSimpleMotionComponent::SetPreviewInEditor(bool enable)
{
if (m_previewInEditor != enable)
{
m_previewInEditor = enable;
OnEditorPropertyChanged();
}
}
bool EditorSimpleMotionComponent::GetPreviewInEditor() const
{
return m_previewInEditor;
}
void EditorSimpleMotionComponent::BlendInTime(float time)
{
m_configuration.m_blendInTime = time;
}
float EditorSimpleMotionComponent::GetBlendInTime() const
{
return m_configuration.m_blendInTime;
}
void EditorSimpleMotionComponent::BlendOutTime(float time)
{
m_configuration.m_blendOutTime = time;
}
float EditorSimpleMotionComponent::GetBlendOutTime() const
{
return m_configuration.m_blendOutTime;
}
AZ::Crc32 EditorSimpleMotionComponent::OnEditorPropertyChanged()
{
RemoveMotionInstanceFromActor(m_lastMotionInstance);
m_lastMotionInstance = nullptr;
RemoveMotionInstanceFromActor(m_motionInstance);
m_motionInstance = nullptr;
m_configuration.m_motionAsset.Release();
VerifyMotionAssetState();
return AZ::Edit::PropertyRefreshLevels::None;
}
}
}
@@ -0,0 +1,115 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/Component/Component.h>
#include <AzCore/Asset/AssetCommon.h>
#include <AzToolsFramework/ToolsComponents/EditorComponentBase.h>
#include <Integration/Components/SimpleMotionComponent.h>
#include <Integration/Components/ActorComponent.h>
#include <Integration/SimpleMotionComponentBus.h>
#include <Integration/EditorSimpleMotionComponentBus.h>
namespace EMotionFX
{
namespace Integration
{
class EditorSimpleMotionComponent
: public AzToolsFramework::Components::EditorComponentBase
, private AZ::Data::AssetBus::MultiHandler
, private ActorComponentNotificationBus::Handler
, private SimpleMotionComponentRequestBus::Handler
, private EditorSimpleMotionComponentRequestBus::Handler
{
public:
AZ_EDITOR_COMPONENT(EditorSimpleMotionComponent, "{0CF1ADF7-DA51-4183-89EC-BDD7D2E17D36}");
EditorSimpleMotionComponent();
~EditorSimpleMotionComponent() override;
// AZ::Component interface implementation
void Activate() override;
void Deactivate() override;
// ActorComponentNotificationBus::Handler
void OnActorInstanceCreated(EMotionFX::ActorInstance* actorInstance) override;
void OnActorInstanceDestroyed(EMotionFX::ActorInstance* actorInstance) override;
// AZ::Data::AssetBus::Handler
void OnAssetReady(AZ::Data::Asset<AZ::Data::AssetData> asset) override;
void OnAssetReloaded(AZ::Data::Asset<AZ::Data::AssetData> asset) override;
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
SimpleMotionComponent::GetProvidedServices(provided);
}
static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent)
{
SimpleMotionComponent::GetDependentServices(dependent);
}
static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
{
SimpleMotionComponent::GetRequiredServices(required);
}
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
{
SimpleMotionComponent::GetIncompatibleServices(incompatible);
}
static void Reflect(AZ::ReflectContext* context);
// SimpleMotionComponentRequestBus::Handler
void LoopMotion(bool enable) override;
bool GetLoopMotion() const override;
void RetargetMotion(bool enable) override;
void ReverseMotion(bool enable) override;
void MirrorMotion(bool enable) override;
void SetPlaySpeed(float speed) override;
float GetPlaySpeed() const override;
void PlayTime(float time) override;
float GetPlayTime() const override;
void Motion(AZ::Data::AssetId assetId) override;
AZ::Data::AssetId GetMotion() const override;
void BlendInTime(float time) override;
float GetBlendInTime() const override;
void BlendOutTime(float time) override;
float GetBlendOutTime() const override;
void PlayMotion() override;
// EditorSimpleMotionComponentRequestBus::Handler
void SetPreviewInEditor(bool enable) override;
bool GetPreviewInEditor() const override;
float GetAssetDuration(const AZ::Data::AssetId& assetId) override;
private:
EditorSimpleMotionComponent(const EditorSimpleMotionComponent&) = delete;
void RemoveMotionInstanceFromActor(EMotionFX::MotionInstance* motionInstance);
void BuildGameEntity(AZ::Entity* gameEntity) override;
void VerifyMotionAssetState();
AZ::Crc32 OnEditorPropertyChanged();
bool m_previewInEditor; ///< Plays motion in Editor.
SimpleMotionComponent::Configuration m_configuration;
EMotionFX::ActorInstance* m_actorInstance; ///< Associated actor instance (retrieved from Actor Component).
EMotionFX::MotionInstance* m_motionInstance; ///< Motion to play on the actor
AZ::Data::Asset<MotionAsset> m_lastMotionAsset; ///< Last active motion asset, kept alive for blending.
EMotionFX::MotionInstance* m_lastMotionInstance; ///< Last active motion instance, kept alive for blending.
};
}
}
@@ -0,0 +1,476 @@
/*
* 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 <EMotionFX_precompiled.h>
#include <AzCore/base.h>
#include <AzCore/Casting/numeric_cast.h>
#include <AzCore/Component/EntityId.h>
#include <AzCore/Jobs/LegacyJobExecutor.h>
#include <AzCore/Component/TransformBus.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/Debug/Profiler.h>
#include <AzCore/Math/Transform.h>
#include <LmbrCentral/Rendering/MeshComponentBus.h>
#include <Integration/Rendering/Cry/CryRenderBackendCommon.h>
#include <Integration/Rendering/Cry/CryRenderActor.h>
#include <Integration/Assets/ActorAsset.h>
#include <Integration/System/SystemCommon.h>
#include <I3DEngine.h>
#include <IRenderMesh.h>
#include <MathConversion.h>
#include <QTangent.h>
namespace EMotionFX
{
namespace Integration
{
AZ_CLASS_ALLOCATOR_IMPL(CryRenderActor, EMotionFXAllocator, 0);
CryRenderActor::CryRenderActor(ActorAsset* actorAsset)
: RenderActor()
, m_actorAsset(actorAsset)
{
}
CryRenderActor::~CryRenderActor()
{
}
bool CryRenderActor::Init()
{
// Populate CMeshes on the job thread, so we can at least build data streams asynchronously.
if (!BuildLODMeshes())
{
return false;
}
// RenderMesh creation must be performed on the main thread, as required by the renderer. It will get lazily created
// in the Finalize() call, which will get called when each CryRenderActorInstance is created.
return true;
}
bool CryRenderActor::BuildLODMeshes()
{
AZ_Assert(m_actorAsset, "Invalid asset data");
EMotionFX::Actor* actor = m_actorAsset->GetActor();
EMotionFX::Skeleton* skeleton = actor->GetSkeleton();
const uint32 numNodes = actor->GetNumNodes();
const uint32 numLODs = actor->GetNumLODLevels();
const uint32 maxInfluences = AZ_ARRAY_SIZE(((SMeshBoneMapping_uint16*)nullptr)->boneIds);
m_meshLODs.clear();
m_meshLODs.reserve(numLODs);
//
// Process all LODs from the EMotionFX actor data.
//
for (uint32 lodIndex = 0; lodIndex < numLODs; ++lodIndex)
{
m_meshLODs.push_back(MeshLOD());
MeshLOD& lod = m_meshLODs.back();
// Get the amount of vertices and indices
// Get the meshes to process
bool hasUVs = false;
bool hasUVs2 = false;
bool hasTangents = false;
bool hasBitangents = false;
bool hasClothData = false;
// Find the number of submeshes in the full actor.
// This will be the number of primitives.
size_t numPrimitives = 0;
for (uint32 n = 0; n < numNodes; ++n)
{
EMotionFX::Mesh* mesh = actor->GetMesh(lodIndex, n);
if (!mesh || mesh->GetIsCollisionMesh())
{
continue;
}
numPrimitives += mesh->GetNumSubMeshes();
}
lod.m_primitives.resize(numPrimitives);
bool hasDynamicMeshes = false;
size_t primitiveIndex = 0;
for (uint32 n = 0; n < numNodes; ++n)
{
EMotionFX::Mesh* mesh = actor->GetMesh(lodIndex, n);
if (!mesh || mesh->GetIsCollisionMesh())
{
continue;
}
const EMotionFX::Node* node = skeleton->GetNode(n);
const EMotionFX::Mesh::EMeshType meshType = mesh->ClassifyMeshType(lodIndex, actor, node->GetNodeIndex(), false, 4, 255);
hasUVs = (mesh->FindOriginalVertexData(EMotionFX::Mesh::ATTRIB_UVCOORDS, 0) != nullptr);
hasUVs2 = (mesh->FindOriginalVertexData(EMotionFX::Mesh::ATTRIB_UVCOORDS, 1) != nullptr);
hasTangents = (mesh->FindOriginalVertexData(EMotionFX::Mesh::ATTRIB_TANGENTS) != nullptr);
hasClothData = (mesh->FindOriginalVertexData(EMotionFX::Mesh::ATTRIB_CLOTH_DATA) != nullptr);
const AZ::Vector3* sourcePositions = static_cast<AZ::Vector3*>(mesh->FindOriginalVertexData(EMotionFX::Mesh::ATTRIB_POSITIONS));
const AZ::Vector3* sourceNormals = static_cast<AZ::Vector3*>(mesh->FindOriginalVertexData(EMotionFX::Mesh::ATTRIB_NORMALS));
const AZ::u32* sourceOriginalVertex = static_cast<AZ::u32*>(mesh->FindOriginalVertexData(EMotionFX::Mesh::ATTRIB_ORGVTXNUMBERS));
const AZ::Vector4* sourceTangents = static_cast<AZ::Vector4*>(mesh->FindOriginalVertexData(EMotionFX::Mesh::ATTRIB_TANGENTS));
const AZ::Vector3* sourceBitangents = static_cast<AZ::Vector3*>(mesh->FindOriginalVertexData(EMotionFX::Mesh::ATTRIB_BITANGENTS));
const AZ::Vector2* sourceUVs = static_cast<AZ::Vector2*>(mesh->FindOriginalVertexData(EMotionFX::Mesh::ATTRIB_UVCOORDS, 0));
const AZ::Vector2* sourceUVs2 = static_cast<AZ::Vector2*>(mesh->FindOriginalVertexData(EMotionFX::Mesh::ATTRIB_UVCOORDS, 1));
const AZ::u32* sourceColors32 = static_cast<AZ::u32*>(mesh->FindOriginalVertexData(EMotionFX::Mesh::ATTRIB_COLORS32, 0));
const AZ::Vector4* sourceColors128 = static_cast<AZ::Vector4*>(mesh->FindOriginalVertexData(EMotionFX::Mesh::ATTRIB_COLORS128, 0));
EMotionFX::SkinningInfoVertexAttributeLayer* sourceSkinningInfo = static_cast<EMotionFX::SkinningInfoVertexAttributeLayer*>(mesh->FindSharedVertexAttributeLayer(EMotionFX::SkinningInfoVertexAttributeLayer::TYPE_ID));
// For each sub-mesh within each mesh, we want to create a separate sub-piece.
const uint32 numSubMeshes = mesh->GetNumSubMeshes();
for (uint32 subMeshIndex = 0; subMeshIndex < numSubMeshes; ++subMeshIndex)
{
EMotionFX::SubMesh* subMesh = mesh->GetSubMesh(subMeshIndex);
AZ_Assert(primitiveIndex < numPrimitives, "Unexpected primitive index");
Primitive& primitive = lod.m_primitives[primitiveIndex++];
primitive.m_mesh = new CMesh();
primitive.m_isDynamic = (meshType == EMotionFX::Mesh::MESHTYPE_CPU_DEFORMED);
primitive.m_useUniqueMesh = primitive.m_isDynamic || hasClothData;
primitive.m_subMesh = subMesh;
if (primitive.m_isDynamic)
{
hasDynamicMeshes = true;
}
// Destination initialization. We are going to put all meshes and submeshes for one lod
// into one destination mesh
primitive.m_vertexBoneMappings.resize(subMesh->GetNumVertices());
primitive.m_mesh->SetIndexCount(subMesh->GetNumIndices());
primitive.m_mesh->SetVertexCount(subMesh->GetNumVertices());
// Positions and normals are reallocated by the SetVertexCount
if (hasTangents)
{
primitive.m_mesh->ReallocStream(CMesh::TANGENTS, 0, subMesh->GetNumVertices());
}
if (hasUVs)
{
primitive.m_mesh->ReallocStream(CMesh::TEXCOORDS, 0, subMesh->GetNumVertices());
}
if (hasUVs2)
{
primitive.m_mesh->ReallocStream(CMesh::TEXCOORDS, 1, subMesh->GetNumVertices());
}
if (sourceColors128 || sourceColors32)
{
primitive.m_mesh->ReallocStream(CMesh::COLORS, 0, subMesh->GetNumVertices());
}
primitive.m_mesh->m_pBoneMapping = primitive.m_vertexBoneMappings.data();
// Pointers to the destination
vtx_idx* targetIndices = primitive.m_mesh->GetStreamPtr<vtx_idx>(CMesh::INDICES);
Vec3* destVertices = primitive.m_mesh->GetStreamPtr<Vec3>(CMesh::POSITIONS);
Vec3* destNormals = primitive.m_mesh->GetStreamPtr<Vec3>(CMesh::NORMALS);
SMeshTexCoord* destTexCoords = primitive.m_mesh->GetStreamPtr<SMeshTexCoord>(CMesh::TEXCOORDS);
SMeshTexCoord* destTexCoords2 = primitive.m_mesh->GetStreamPtr<SMeshTexCoord>(CMesh::TEXCOORDS, 1);
SMeshTangents* destTangents = primitive.m_mesh->GetStreamPtr<SMeshTangents>(CMesh::TANGENTS);
SMeshColor* destColors = primitive.m_mesh->GetStreamPtr<SMeshColor>(CMesh::COLORS);
SMeshBoneMapping_uint16* destBoneMapping = primitive.m_vertexBoneMappings.data();
primitive.m_mesh->m_subsets.push_back();
SMeshSubset& subset = primitive.m_mesh->m_subsets.back();
subset.nFirstIndexId = 0;
subset.nNumIndices = subMesh->GetNumIndices();
subset.nFirstVertId = 0;
subset.nNumVerts = subMesh->GetNumVertices();
subset.nMatID = subMesh->GetMaterial();
subset.fTexelDensity = 0.0f;
subset.nPhysicalizeType = -1;
const uint32* subMeshIndices = subMesh->GetIndices();
const uint32 numSubMeshIndices = subMesh->GetNumIndices();
const uint32 subMeshStartVertex = subMesh->GetStartVertex();
for (uint32 index = 0; index < numSubMeshIndices; ++index)
{
targetIndices[index] = subMeshIndices[index] - subMeshStartVertex;
}
// Process vertices
const uint32 numSubMeshVertices = subMesh->GetNumVertices();
const AZ::Vector3* subMeshPositions = &sourcePositions[subMesh->GetStartVertex()];
for (uint32 vertexIndex = 0; vertexIndex < numSubMeshVertices; ++vertexIndex)
{
const AZ::Vector3& sourcePosition = subMeshPositions[vertexIndex];
destVertices->x = sourcePosition.GetX();
destVertices->y = sourcePosition.GetY();
destVertices->z = sourcePosition.GetZ();
++destVertices;
}
// Process normals
const AZ::Vector3* subMeshNormals = &sourceNormals[subMesh->GetStartVertex()];
for (uint32 vertexIndex = 0; vertexIndex < numSubMeshVertices; ++vertexIndex)
{
const AZ::Vector3& sourceNormal = subMeshNormals[vertexIndex];
destNormals->x = sourceNormal.GetX();
destNormals->y = sourceNormal.GetY();
destNormals->z = sourceNormal.GetZ();
++destNormals;
}
// Process UVs (TextCoords)
// First UV set.
if (hasUVs)
{
if (sourceUVs)
{
const AZ::Vector2* subMeshUVs = &sourceUVs[subMeshStartVertex];
for (uint32 vertexIndex = 0; vertexIndex < numSubMeshVertices; ++vertexIndex)
{
const AZ::Vector2& uv = subMeshUVs[vertexIndex];
*destTexCoords = SMeshTexCoord(uv.GetX(), uv.GetY());
++destTexCoords;
}
}
else
{
for (uint32 vertexIndex = 0; vertexIndex < numSubMeshVertices; ++vertexIndex)
{
*destTexCoords = SMeshTexCoord(0.0f, 0.0f);
++destTexCoords;
}
}
}
// Second UV set.
if (hasUVs2)
{
if (sourceUVs2)
{
const AZ::Vector2* subMeshUVs = &sourceUVs2[subMeshStartVertex];
for (uint32 vertexIndex = 0; vertexIndex < numSubMeshVertices; ++vertexIndex)
{
const AZ::Vector2& uv2 = subMeshUVs[vertexIndex];
*destTexCoords2 = SMeshTexCoord(uv2.GetX(), uv2.GetY());
++destTexCoords2;
}
}
else
{
for (uint32 vertexIndex = 0; vertexIndex < numSubMeshVertices; ++vertexIndex)
{
*destTexCoords2 = SMeshTexCoord(0.0f, 0.0f);
++destTexCoords2;
}
}
}
// Process tangents
if (hasTangents)
{
if (sourceTangents)
{
const AZ::Vector4* subMeshTangents = &sourceTangents[subMeshStartVertex];
for (uint32 vertexIndex = 0; vertexIndex < numSubMeshVertices; ++vertexIndex)
{
const AZ::Vector4& sourceTangent = subMeshTangents[vertexIndex];
const AZ::Vector3 sourceNormal(subMeshNormals[vertexIndex]);
AZ::Vector3 bitangent;
if (sourceBitangents)
{
bitangent = sourceBitangents[vertexIndex + subMeshStartVertex];
}
else
{
bitangent = sourceNormal.Cross(sourceTangent.GetAsVector3()) * sourceTangent.GetW();
}
*destTangents = SMeshTangents(
Vec3(sourceTangent.GetX(), sourceTangent.GetY(), sourceTangent.GetZ()),
Vec3(bitangent.GetX(), bitangent.GetY(), bitangent.GetZ()),
Vec3(sourceNormal.GetX(), sourceNormal.GetY(), sourceNormal.GetZ()));
++destTangents;
}
}
else
{
for (uint32 vertexIndex = 0; vertexIndex < numSubMeshVertices; ++vertexIndex)
{
*destTangents = SMeshTangents();
++destTangents;
}
}
}
// Pass vertex colors to the renderer.
if (sourceColors128 && destColors) // 128 bit colors
{
const AZ::Vector4* subMeshColors = &sourceColors128[subMeshStartVertex];
for (uint32 vertexIndex = 0; vertexIndex < numSubMeshVertices; ++vertexIndex)
{
const AZ::Vector4& colorVector = subMeshColors[vertexIndex];
const AZ::Color color(
AZ::GetClamp(static_cast<float>(colorVector.GetX()), 0.0f, 1.0f),
AZ::GetClamp(static_cast<float>(colorVector.GetY()), 0.0f, 1.0f),
AZ::GetClamp(static_cast<float>(colorVector.GetZ()), 0.0f, 1.0f),
AZ::GetClamp(static_cast<float>(colorVector.GetW()), 0.0f, 1.0f) );
*destColors = SMeshColor(color.GetR8(), color.GetG8(), color.GetB8(), color.GetA8());
++destColors;
}
}
else if (sourceColors32 && destColors) // 32 bit colors
{
AZ::Color color;
const AZ::u32* subMeshColors = &sourceColors32[subMeshStartVertex];
for (uint32 vertexIndex = 0; vertexIndex < numSubMeshVertices; ++vertexIndex)
{
color.FromU32(subMeshColors[vertexIndex]);
*destColors = SMeshColor(color.GetR8(), color.GetG8(), color.GetB8(), color.GetA8());
++destColors;
}
}
// Process AABB
AABB localAabb(AABB::RESET);
for (uint32 vertexIndex = 0; vertexIndex < numSubMeshVertices; ++vertexIndex)
{
const AZ::Vector3& sourcePosition = subMeshPositions[vertexIndex];
localAabb.Add(Vec3(sourcePosition.GetX(), sourcePosition.GetY(), sourcePosition.GetZ()));
}
subset.fRadius = localAabb.GetRadius();
subset.vCenter = localAabb.GetCenter();
primitive.m_mesh->m_bbox.Add(localAabb.min);
primitive.m_mesh->m_bbox.Add(localAabb.max);
// Process Skinning info
if (sourceSkinningInfo)
{
for (uint32 vertexIndex = 0; vertexIndex < numSubMeshVertices; ++vertexIndex)
{
const AZ::u32 originalVertex = sourceOriginalVertex[vertexIndex + subMesh->GetStartVertex()];
const AZ::u32 influenceCount = AZ::GetMin<AZ::u32>(maxInfluences, sourceSkinningInfo->GetNumInfluences(originalVertex));
AZ::u32 influenceIndex = 0;
int weightError = 255;
for (; influenceIndex < influenceCount; ++influenceIndex)
{
EMotionFX::SkinInfluence* influence = sourceSkinningInfo->GetInfluence(originalVertex, influenceIndex);
destBoneMapping->boneIds[influenceIndex] = influence->GetNodeNr();
destBoneMapping->weights[influenceIndex] = static_cast<AZ::u8>(AZ::GetClamp<float>(influence->GetWeight() * 255.0f, 0.0f, 255.0f));
weightError -= destBoneMapping->weights[influenceIndex];
}
destBoneMapping->weights[0] += weightError;
for (; influenceIndex < maxInfluences; ++influenceIndex)
{
destBoneMapping->boneIds[influenceIndex] = 0;
destBoneMapping->weights[influenceIndex] = 0;
}
++destBoneMapping;
}
}
// Legacy index buffer fix.
primitive.m_mesh->m_subsets[0].FixRanges(primitive.m_mesh->m_pIndices);
// Convert tangent frame from matrix to quaternion based.
// Without this, materials do NOT render correctly on skinned characters.
if (primitive.m_mesh->m_pTangents && !primitive.m_mesh->m_pQTangents)
{
primitive.m_mesh->m_pQTangents = (SMeshQTangents*)primitive.m_mesh->m_pTangents;
MeshTangentsFrameToQTangents(
primitive.m_mesh->m_pTangents, sizeof(primitive.m_mesh->m_pTangents[0]), primitive.m_mesh->GetVertexCount(),
primitive.m_mesh->m_pQTangents, sizeof(primitive.m_mesh->m_pQTangents[0]));
}
} // for all submeshes
} // for all meshes
lod.m_hasDynamicMeshes = hasDynamicMeshes;
} // for all lods
return true;
}
void CryRenderActor::Finalize()
{
//
// The CMesh, which contains vertex streams, indices, uvs, bone influences, etc, is computed within
// the job thread.
// However, the render mesh and material need to be constructed on the main thread, as imposed by
// the renderer. Naturally this is undesirable, but a limitation of the engine at the moment.
//
// The material also cannot be constructed natively. Materials only seem to be fully valid
// if loaded from Xml data. Attempts to build procedurally, outside of the renderer code, have
// been unsuccessful due to some aspects of the data being inaccessible.
// Jumping through this hoop is acceptable for now since we'll soon be generating the material asset
// in the asset pipeline and loading it via the game, as opposed to extracting the data here.
//
if (!gEnv)
{
return;
}
// Every CryRenderActorInstance will attempt to finalize the data, so ensure we only perform this action once.
if (m_isFinalized)
{
return;
}
AZ_Assert(m_actorAsset, "Invalid asset data");
AZ_Assert(m_actorAsset->IsReady(), "Finalize has been called unexpectedly before the Actor asset has finished loading.");
AZStd::string assetPath;
EBUS_EVENT_RESULT(assetPath, AZ::Data::AssetCatalogRequestBus, GetAssetPathById, m_actorAsset->GetId());
EMotionFX::Actor* actor = m_actorAsset->GetActor();
const AZ::u32 numLODs = actor->GetNumLODLevels();
// Process all LODs from the EMotionFX actor data.
for (AZ::u32 lodIndex = 0; lodIndex < numLODs; ++lodIndex)
{
MeshLOD& lod = m_meshLODs[lodIndex];
for (Primitive& primitive : lod.m_primitives)
{
// Create and initialize render mesh.
primitive.m_renderMesh = gEnv->pRenderer->CreateRenderMesh("EMotion FX Actor", assetPath.c_str(), nullptr, eRMT_Dynamic);
const AZ::u32 renderMeshFlags = FSM_ENABLE_NORMALSTREAM | FSM_VERTEX_VELOCITY;
if (primitive.m_mesh)
{
primitive.m_renderMesh->SetMesh(*primitive.m_mesh, 0, renderMeshFlags, false);
}
// Free temporary load objects & buffers.
primitive.m_vertexBoneMappings.resize(0);
}
// It's now safe to use this LOD.
lod.m_isReady = true;
}
m_isFinalized = true;
}
} // namespace Integration
} // namespace EMotionFX
@@ -0,0 +1,57 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/EBus/EBus.h>
#include <AzCore/Memory/Memory.h>
#include <AzCore/RTTI/RTTI.h>
#include <Integration/Rendering/Cry/CryRenderBackendCommon.h>
#include <Integration/Rendering/RenderActor.h>
namespace EMotionFX
{
namespace Integration
{
class ActorAsset;
class CryRenderActor
: public RenderActor
{
public:
AZ_RTTI(EMotionFX::Integration::CryRenderActor, "{5DCC47DC-448A-4CF8-B370-1764B45FD1D5}", EMotionFX::Integration::RenderActor)
AZ_CLASS_ALLOCATOR_DECL
CryRenderActor(ActorAsset* actorAsset);
~CryRenderActor() override;
bool Init();
size_t GetNumLODs() const { return m_meshLODs.size(); }
MeshLOD* GetMeshLOD(size_t lodIndex) { return m_meshLODs[lodIndex].m_isReady ? &m_meshLODs[lodIndex] : nullptr; }
void Finalize();
bool ReadyForRendering()
{
return m_isFinalized && (GetNumLODs() > 0);
}
private:
bool BuildLODMeshes();
ActorAsset* m_actorAsset;
AZStd::vector<MeshLOD> m_meshLODs; ///< Mesh render data (for CryRenderer)
bool m_isFinalized = false;
};
} // namespace Integration
} // namespace EMotionFX
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,213 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/RTTI/RTTI.h>
#include <AzFramework/Visibility/BoundsBus.h>
#include <Integration/Rendering/Cry/CryRenderBackendCommon.h>
#include <Integration/Rendering/RenderActorInstance.h>
#include <LmbrCentral/Rendering/MaterialOwnerBus.h>
#include <LmbrCentral/Rendering/MeshComponentBus.h>
#include <LmbrCentral/Rendering/MeshModificationBus.h>
#include <LmbrCentral/Rendering/RenderBoundsBus.h>
#include <LmbrCentral/Rendering/RenderNodeBus.h>
#include <LmbrCentral/Rendering/Utils/MaterialOwnerRequestBusHandlerImpl.h>
namespace EMotionFX
{
class Actor;
class ActorInstance;
namespace Integration
{
class CryRenderActor;
class CryRenderActorInstanceRequests
: public AZ::EBusTraits
{
public:
using MutexType = AZStd::mutex;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
using BusIdType = AZ::EntityId;
virtual void BuildRenderMeshPerLOD() = 0;
};
using CryRenderActorInstanceRequestBus = AZ::EBus<CryRenderActorInstanceRequests>;
/**
* Render node for managing and rendering actor instances. Each Actor Component
* creates an ActorRenderNode. The render node is responsible for drawing meshes and
* passing skinning transforms to the skinning pipeline.
*/
class CryRenderActorInstance
: public IRenderNode
, private AZ::TransformNotificationBus::Handler
, private LmbrCentral::MeshComponentRequestBus::Handler
, private LmbrCentral::SkeletalHierarchyRequestBus::Handler
, private AzFramework::BoundsRequestBus::Handler
, private LmbrCentral::RenderNodeRequestBus::Handler
, private CryRenderActorInstanceRequestBus::Handler
, public RenderActorInstance
{
public:
AZ_CLASS_ALLOCATOR_DECL
AZ_RTTI(EMotionFX::Integration::CryRenderActorInstance, "{9C41129F-E448-4C2A-B428-0E4E624734CF}", EMotionFX::Integration::RenderActorInstance)
CryRenderActorInstance(AZ::EntityId entityId,
const EMotionFXPtr<EMotionFX::ActorInstance>& actorInstance,
const AZ::Data::Asset<ActorAsset>& asset,
const AZ::Transform& worldTransform);
~CryRenderActorInstance() override;
//////////////////////////////////////////////////////////////////////////
// IRenderNode interface implementation
void Render(const struct SRendParams& inRenderParams, const struct SRenderingPassInfo& passInfo) override;
bool GetLodDistances(const SFrameLodInfo& frameLodInfo, float* distances) const override;
EERType GetRenderNodeType() override;
const char* GetName() const override;
const char* GetEntityClassName() const override;
Vec3 GetPos(bool bWorldOnly = true) const override;
void GetLocalBounds(AABB& bbox) override;
const AABB GetBBox() const override;
void SetBBox(const AABB& WSBBox) override;
void OffsetPosition(const Vec3& delta) override;
void SetMaterial(_smart_ptr<IMaterial> pMat) override;
_smart_ptr<IMaterial> GetMaterial(Vec3* pHitPos = nullptr) override;
_smart_ptr<IMaterial> GetMaterialOverride() override;
IStatObj* GetEntityStatObj(unsigned int nPartId = 0, unsigned int nSubPartId = 0, Matrix34A* pMatrix = nullptr, bool bReturnOnlyVisible = false) override;
_smart_ptr<IMaterial> GetEntitySlotMaterial(unsigned int nPartId, bool bReturnOnlyVisible = false, bool* pbDrawNear = nullptr) override;
float GetMaxViewDist() override;
void GetMemoryUsage(class ICrySizer* pSizer) const override;
//////////////////////////////////////////////////////////////////////////
// AZ::TransformNotificationBus::Handler interface implementation
void OnTransformChanged(const AZ::Transform& local, const AZ::Transform& world) override;
//////////////////////////////////////////////////////////////////////////
// SkeletalHierarchyRequestBus::Handler
AZ::u32 GetJointCount() override;
const char* GetJointNameByIndex(AZ::u32 jointIndex) override;
AZ::s32 GetJointIndexByName(const char* jointName) override;
AZ::Transform GetJointTransformCharacterRelative(AZ::u32 jointIndex) override;
// RenderNodeRequestBus::Handler
IRenderNode* GetRenderNode() override;
float GetRenderNodeRequestBusOrder() const override;
static const float s_renderNodeRequestBusOrder;
// BoundsRequestBus and MeshComponentRequestBus overrides ...
AZ::Aabb GetWorldBounds() override { return GetWorldAABB(); }
AZ::Aabb GetLocalBounds() override { return GetLocalAABB(); }
//////////////////////////////////////////////////////////////////////////
// LmbrCentral::MeshComponentRequestBus::Handler
bool GetVisibility() override;
void SetVisibility(bool isVisible) override;
void SetMeshAsset(const AZ::Data::AssetId& id) override;
AZ::Data::Asset<AZ::Data::AssetData> GetMeshAsset() override { return m_actorAsset; }
//////////////////////////////////////////////////////////////////////////
// MaterialOwnerRequestBus interface implementation
// Helper class needed as we inherit a SetMaterial() function from the IRenderNode
// as well as the MaterialOwnerRequestBus
class MaterialOwner
: public LmbrCentral::MaterialOwnerRequestBusHandlerImpl
{
public:
AZ_CLASS_ALLOCATOR_DECL
MaterialOwner(CryRenderActorInstance* renderActorInstance, AZ::EntityId entityId);
~MaterialOwner();
void SetMaterial(_smart_ptr<IMaterial>) override;
_smart_ptr<IMaterial> GetMaterial() override;
private:
CryRenderActorInstance* m_renderActorInstance = nullptr;
};
friend class MaterialOwner;
AZStd::unique_ptr<MaterialOwner> m_materialOwner;
//////////////////////////////////////////////////////////////////////////
// RenderActorInstance
void OnTick(float timeDelta) override;
void UpdateBounds() override;
void DebugDraw(const DebugOptions& debugOptions) override;
void SetMaterials(const ActorAsset::MaterialList& materialPerLOD) override;
void SetIsVisible(bool isVisible) override;
// Helpers
void DrawAABB();
void DrawSkeleton();
void DrawRootTransform(const AZ::Transform& worldTransform);
void EmfxDebugDraw();
CryRenderActor* GetRenderActor() const;
void UpdateWorldBoundingBox();
void RegisterWithRenderer();
void DeregisterWithRenderer();
void UpdateWorldTransform(const AZ::Transform& entityTransform);
SSkinningData* GetSkinningData();
bool IsInCameraFrustum() const override;
// Determines if the morph target weights were updated since the last call.
// It is used to avoid calling UpdateDynamicSkin if the weights have not been
// updated.
bool MorphTargetWeightsWereUpdated(uint32 lodLevel);
// Updates the vertices, normals and tangents buffers in cry based on the emfx
// mesh. This is used to update morph targets in the ly viewport.
void UpdateDynamicSkin(size_t lodIndex, size_t primitiveIndex);
private:
void QueueBuildRenderMesh();
void BuildRenderMeshPerLOD() override;
Matrix34 m_renderTransform;
AABB m_worldBoundingBox;
AZStd::vector<_smart_ptr<IMaterial>> m_materialPerLOD;
bool m_isRegisteredWithRenderer = false;
AZStd::vector<float> m_lastMorphTargetWeights;
// history for skinning data, needed for motion blur
struct
{
SSkinningData* pSkinningData = nullptr;
int nFrameID;
} m_arrSkinningRendererData[3]; // triple buffered for motion blur
// Helper to store indices for meshes to be modified by other components.
LmbrCentral::MeshModificationRequestHelper m_modificationHelper;
// If our actor has dynamic skin, we need each actor instance to have its own render mesh so we can send separate
// meshes to cry to render. If they don't have dynamic skin, the render mesh will be the same as the one in the
// actor asset
AZStd::vector<AZStd::vector<_smart_ptr<IRenderMesh>>> m_renderMeshesPerLOD; // Index as: [lod][primitiveNr]
bool m_materialReadyEventSent = false; ///< Tracks whether OnMaterialOwnerReady has been sent yet. - TBD
bool m_shouldBuildRenderMesh = false; ///< Ensures that a render mesh only gets built once per instance / queue request
};
} // namespace Integration
} // namespace EMotionFX
@@ -0,0 +1,55 @@
/*
* 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 <EMotionFX/Source/Actor.h>
#include <Integration/Assets/ActorAsset.h>
#include <Integration/Rendering/Cry/CryRenderBackend.h>
#include <Integration/Rendering/Cry/CryRenderActor.h>
#include <Integration/Rendering/Cry/CryRenderActorInstance.h>
#include <Integration/System/SystemCommon.h>
namespace EMotionFX
{
namespace Integration
{
AZ_CLASS_ALLOCATOR_IMPL(CryRenderBackend, EMotionFXAllocator, 0);
RenderActor* CryRenderBackend::CreateActor(ActorAsset* asset)
{
CryRenderActor* renderActor = aznew CryRenderActor(asset);
if (!renderActor->Init())
{
AZ_Warning("EMotionFX", false, "Cannot initialize Cry render actor for asset with id %s.", asset->GetId().ToString<AZStd::string>().c_str());
delete renderActor;
return nullptr;
}
return renderActor;
}
RenderActorInstance* CryRenderBackend::CreateActorInstance(AZ::EntityId entityId,
const EMotionFXPtr<EMotionFX::ActorInstance>& actorInstance,
const AZ::Data::Asset<ActorAsset>& asset,
const ActorAsset::MaterialList& materialPerLOD,
SkinningMethod skinningMethod,
const AZ::Transform& worldTransform)
{
CryRenderActorInstance* renderActorInstance = aznew CryRenderActorInstance(entityId, actorInstance, asset, worldTransform);
renderActorInstance->SetMaterials(materialPerLOD);
renderActorInstance->RegisterWithRenderer();
renderActorInstance->SetSkinningMethod(skinningMethod);
return renderActorInstance;
}
} // namespace Integration
} // namespace EMotionFX
@@ -0,0 +1,41 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/Memory/Memory.h>
#include <AzCore/RTTI/RTTI.h>
#include <Integration/Rendering/RenderBackend.h>
namespace EMotionFX
{
namespace Integration
{
class CryRenderBackend
: public RenderBackend
{
public:
AZ_RTTI(EMotionFX::Integration::CryRenderBackend, "{CC4AF6B1-D5D2-4EAA-8198-DED4F875D1F4}", EMotionFX::Integration::RenderBackend);
AZ_CLASS_ALLOCATOR_DECL;
RenderActor * CreateActor(ActorAsset * asset) override;
RenderActorInstance* CreateActorInstance(AZ::EntityId entityId,
const EMotionFXPtr<EMotionFX::ActorInstance>& actorInstance,
const AZ::Data::Asset<ActorAsset>& asset,
const ActorAsset::MaterialList& materialPerLOD,
SkinningMethod skinningMethod,
const AZ::Transform& worldTransform) override;
};
} // namespace Integration
} // namespace EMotionFX
@@ -0,0 +1,73 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/Math/Aabb.h>
#include <AzCore/Component/TransformBus.h>
#include <AzCore/std/parallel/atomic.h>
#include <AzFramework/Asset/SimpleAsset.h>
#include <EMotionFX/Source/SubMesh.h>
#include <IEntityRenderState.h>
#include <IIndexedMesh.h>
struct IStatObj; // Cry mesh wrapper.
struct SSkinningData;
namespace EMotionFX
{
namespace Integration
{
struct Primitive
{
AZStd::vector<SMeshBoneMapping_uint16> m_vertexBoneMappings;
_smart_ptr<IRenderMesh> m_renderMesh;
CMesh* m_mesh = nullptr; // Non-null only until asset is finalized.
bool m_isDynamic = false; // Indicates if the mesh is dynamic (e.g. has morph targets)
bool m_useUniqueMesh = false;
EMotionFX::SubMesh* m_subMesh = nullptr;
Primitive() = default;
~Primitive()
{
delete m_mesh;
}
};
/// Holds render representation for a single LOD.
struct MeshLOD
{
AZStd::vector<Primitive> m_primitives;
AZStd::atomic_bool m_isReady{ false };
bool m_hasDynamicMeshes;
MeshLOD()
: m_hasDynamicMeshes(false)
{
m_isReady.store(false);
}
MeshLOD(MeshLOD&& rhs)
{
m_primitives = AZStd::move(rhs.m_primitives);
m_hasDynamicMeshes = rhs.m_hasDynamicMeshes;
m_isReady.store(rhs.m_isReady.load());
}
~MeshLOD() = default;
};
} // namespace Integration
} // namespace EMotionFX
@@ -0,0 +1,22 @@
/*
* 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 <Integration/Rendering/RenderActor.h>
#include <Integration/System/SystemCommon.h>
namespace EMotionFX
{
namespace Integration
{
AZ_CLASS_ALLOCATOR_IMPL(RenderActor, EMotionFXAllocator, 0);
} // namespace Integration
} // namespace EMotionFX
@@ -0,0 +1,33 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/Memory/Memory.h>
#include <AzCore/RTTI/RTTI.h>
namespace EMotionFX
{
namespace Integration
{
class RenderActor
{
public:
AZ_RTTI(EMotionFX::Integration::RenderActor, "{827A2CD5-C5FC-4D14-984D-B44A52EA92CC}")
AZ_CLASS_ALLOCATOR_DECL
RenderActor() = default;
virtual ~RenderActor() = default;
};
} // namespace Integration
} // namespace EMotionFX
@@ -0,0 +1,82 @@
/*
* 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 <EMotionFX/Source/Actor.h>
#include <Integration/Rendering/RenderActorInstance.h>
#include <Integration/System/SystemCommon.h>
namespace EMotionFX
{
namespace Integration
{
AZ_CLASS_ALLOCATOR_IMPL(RenderActorInstance, EMotionFXAllocator, 0);
RenderActorInstance::RenderActorInstance(const AZ::Data::Asset<ActorAsset>& actorAsset,
ActorInstance* actorInstance, AZ::EntityId entityId)
: m_actorAsset(actorAsset)
, m_actorInstance(actorInstance)
, m_entityId(entityId)
{
}
SkinningMethod RenderActorInstance::GetSkinningMethod() const
{
return m_skinningMethod;
}
void RenderActorInstance::SetSkinningMethod(SkinningMethod skinningMethod)
{
m_skinningMethod = skinningMethod;
}
void RenderActorInstance::SetOnMaterialChangedCallback(MaterialChangedFunction callback)
{
m_onMaterialChangedCallback = callback;
}
const AZ::Aabb& RenderActorInstance::GetWorldAABB() const
{
return m_worldAABB;
}
const AZ::Aabb& RenderActorInstance::GetLocalAABB() const
{
return m_localAABB;
}
bool RenderActorInstance::IsVisible() const
{
return m_isVisible;
}
void RenderActorInstance::SetIsVisible(bool isVisible)
{
m_isVisible = isVisible;
}
bool RenderActorInstance::IsInCameraFrustum() const
{
return true;
}
Actor* RenderActorInstance::GetActor() const
{
ActorAsset* actorAsset = m_actorAsset.Get();
if (actorAsset)
{
return actorAsset->GetActor();
}
return nullptr;
}
} // namespace Integration
} // namespace EMotionFX
@@ -0,0 +1,81 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/Math/Aabb.h>
#include <AzCore/Memory/Memory.h>
#include <AzCore/RTTI/RTTI.h>
#include <EMotionFX/Source/ActorInstance.h>
#include <Integration/ActorComponentBus.h>
#include <Integration/Assets/ActorAsset.h>
namespace EMotionFX
{
namespace Integration
{
class RenderActorInstance
{
public:
AZ_RTTI(EMotionFX::Integration::RenderActorInstance, "{7F5FA3A7-BE62-4384-9C99-72305404C0BF}")
AZ_CLASS_ALLOCATOR_DECL
RenderActorInstance(const AZ::Data::Asset<ActorAsset>& actorAsset,
ActorInstance* actorInstance,
AZ::EntityId entityId);
virtual ~RenderActorInstance() = default;
virtual void OnTick(float timeDelta) = 0;
struct DebugOptions
{
bool m_drawAABB = false;
bool m_drawSkeleton = false;
bool m_drawRootTransform = false;
AZ::Transform m_rootWorldTransform = AZ::Transform::CreateIdentity();
bool m_emfxDebugDraw = false;
};
virtual void DebugDraw(const DebugOptions& debugOptions) = 0;
SkinningMethod GetSkinningMethod() const;
virtual void SetSkinningMethod(SkinningMethod skinningMethod);
virtual void UpdateBounds() = 0;
const AZ::Aabb& GetWorldAABB() const;
const AZ::Aabb& GetLocalAABB() const;
bool IsVisible() const;
virtual void SetIsVisible(bool isVisible);
virtual bool IsInCameraFrustum() const;
virtual void SetMaterials(const ActorAsset::MaterialList& materialsPerLOD) = 0;
typedef AZStd::function<void(const AZStd::string& materialName)> MaterialChangedFunction;
void SetOnMaterialChangedCallback(MaterialChangedFunction callback);
Actor* GetActor() const;
protected:
AZ::Data::Asset<ActorAsset> m_actorAsset;
ActorInstance* m_actorInstance = nullptr;
const AZ::EntityId m_entityId;
AZ::Aabb m_localAABB = AZ::Aabb::CreateNull();
AZ::Aabb m_worldAABB = AZ::Aabb::CreateNull();
bool m_isVisible = true;
SkinningMethod m_skinningMethod = SkinningMethod::DualQuat;
MaterialChangedFunction m_onMaterialChangedCallback;
};
} // namespace Integration
} // namespace EMotionFX
@@ -0,0 +1,23 @@
/*
* 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 <EMotionFX/Source/Actor.h>
#include <Integration/Rendering/RenderBackend.h>
#include <Integration/System/SystemCommon.h>
namespace EMotionFX
{
namespace Integration
{
AZ_CLASS_ALLOCATOR_IMPL(RenderBackend, EMotionFXAllocator, 0);
} // namespace Integration
} // namespace EMotionFX
@@ -0,0 +1,46 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/Memory/Memory.h>
#include <AzCore/RTTI/RTTI.h>
#include <Integration/Assets/ActorAsset.h>
#include <Integration/Rendering/RenderActorInstance.h>
namespace EMotionFX
{
namespace Integration
{
class RenderActor;
class RenderBackend
{
public:
AZ_RTTI(EMotionFX::Integration::RenderBackend, "{999AC1A7-0FBA-4F36-81B8-939FC80F1042}")
AZ_CLASS_ALLOCATOR_DECL
RenderBackend() = default;
virtual ~RenderBackend() = default;
virtual RenderActor* CreateActor(ActorAsset* asset) = 0;
virtual RenderActorInstance* CreateActorInstance(AZ::EntityId entityId,
const EMotionFXPtr<EMotionFX::ActorInstance>& actorInstance,
const AZ::Data::Asset<ActorAsset>& asset,
const ActorAsset::MaterialList& materialPerLOD,
SkinningMethod skinningMethod,
const AZ::Transform& worldTransform) = 0;
};
} // namespace Integration
} // namespace EMotionFX
@@ -0,0 +1,43 @@
/*
* 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 <EMotionFX/Source/Actor.h>
#include <Integration/Rendering/RenderBackendManager.h>
#include <Integration/System/SystemCommon.h>
namespace EMotionFX
{
namespace Integration
{
AZ_CLASS_ALLOCATOR_IMPL(RenderBackendManager, EMotionFXAllocator, 0);
RenderBackendManager::RenderBackendManager()
{
AZ::Interface<RenderBackendManager>::Register(this);
}
RenderBackendManager::~RenderBackendManager()
{
AZ::Interface<RenderBackendManager>::Unregister(this);
}
void RenderBackendManager::SetRenderBackend(RenderBackend* backend)
{
m_renderBackend.reset(backend);
}
RenderBackend* RenderBackendManager::GetRenderBackend() const
{
return m_renderBackend.get();
}
} // namespace Integration
} // namespace EMotionFX
@@ -0,0 +1,43 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/Interface/Interface.h>
#include <AzCore/Memory/Memory.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/RTTI/RTTI.h>
#include <Integration/Rendering/RenderBackend.h>
namespace EMotionFX
{
namespace Integration
{
class RenderBackendManager
{
public:
AZ_RTTI(EMotionFX::Integration::RenderBackendManager, "{D4C67563-0BFC-49CA-A3FC-40363F5BFC79}")
AZ_CLASS_ALLOCATOR_DECL
RenderBackendManager();
virtual ~RenderBackendManager();
void SetRenderBackend(RenderBackend* backend);
RenderBackend* GetRenderBackend() const;
private:
AZStd::unique_ptr<RenderBackend> m_renderBackend;
};
} // namespace Integration
} // namespace EMotionFX
@@ -0,0 +1,153 @@
/*
* 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 "EMotionFX_precompiled.h"
#include <Integration/System/SystemComponent.h>
#include <Integration/Components/ActorComponent.h>
#include <Integration/Components/AnimAudioComponent.h>
#include <Integration/Components/AnimGraphComponent.h>
#include <Integration/Components/AnimGraphNetSyncComponent.h>
#include <Integration/Components/SimpleMotionComponent.h>
#include <Integration/Components/SimpleLODComponent.h>
#include <AzCore/Module/DynamicModuleHandle.h>
#include <IGem.h>
#if defined (EMOTIONFXANIMATION_EDITOR)
# include <Integration/System/PipelineComponent.h>
# include <Integration/Editor/Components/EditorActorComponent.h>
# include <Integration/Editor/Components/EditorAnimAudioComponent.h>
# include <Integration/Editor/Components/EditorAnimGraphComponent.h>
# include <Integration/Editor/Components/EditorSimpleMotionComponent.h>
# include <Integration/Editor/Components/EditorSimpleLODComponent.h>
# include <SceneAPIExt/Behaviors/ActorGroupBehavior.h>
# include <SceneAPIExt/Behaviors/MeshRuleBehavior.h>
# include <SceneAPIExt/Behaviors/MotionGroupBehavior.h>
# include <SceneAPIExt/Behaviors/MotionRangeRuleBehavior.h>
# include <SceneAPIExt/Behaviors/SkinRuleBehavior.h>
# include <SceneAPIExt/Behaviors/MorphTargetRuleBehavior.h>
# include <SceneAPIExt/Behaviors/LodRuleBehavior.h>
# include <SceneAPIExt/Behaviors/SkeletonOptimizationRuleBehavior.h>
# include <RCExt/Actor/ActorExporter.h>
# include <RCExt/Actor/ActorGroupExporter.h>
# include <RCExt/Actor/ActorBuilder.h>
# include <RCExt/Actor/MorphTargetExporter.h>
# include <RCExt/Motion/MotionExporter.h>
# include <RCExt/Motion/MotionGroupExporter.h>
# include <RCExt/Motion/MotionDataBuilder.h>
# include <EMotionFXBuilder/EMotionFXBuilderComponent.h>
#endif // EMOTIONFXANIMATION_EDITOR
namespace EMotionFX
{
namespace Integration
{
/**
* Animation module class for EMotion FX animation gem.
*/
class EMotionFXIntegrationModule
: public CryHooksModule
{
public:
AZ_RTTI(EMotionFXIntegrationModule, "{02533EDC-F2AA-4076-86E9-5E3702202E15}", CryHooksModule);
EMotionFXIntegrationModule()
: CryHooksModule()
{
// Push results of [MyComponent]::CreateDescriptor() into m_descriptors here.
m_descriptors.insert(m_descriptors.end(), {
// System components
SystemComponent::CreateDescriptor(),
// Runtime components
ActorComponent::CreateDescriptor(),
AnimAudioComponent::CreateDescriptor(),
AnimGraphComponent::CreateDescriptor(),
SimpleMotionComponent::CreateDescriptor(),
SimpleLODComponent::CreateDescriptor(),
Network::AnimGraphNetSyncComponent::CreateDescriptor(),
#if defined(EMOTIONFXANIMATION_EDITOR)
// Pipeline components
EMotionFX::Pipeline::PipelineComponent::CreateDescriptor(),
// Editor components
EditorActorComponent::CreateDescriptor(),
EditorAnimAudioComponent::CreateDescriptor(),
EditorAnimGraphComponent::CreateDescriptor(),
EditorSimpleMotionComponent::CreateDescriptor(),
EditorSimpleLODComponent::CreateDescriptor(),
// EmotionFX asset builder
EMotionFXBuilder::EMotionFXBuilderComponent::CreateDescriptor(),
// Actor
EMotionFX::Pipeline::Behavior::ActorGroupBehavior::CreateDescriptor(),
EMotionFX::Pipeline::Behavior::MeshRuleBehavior::CreateDescriptor(),
EMotionFX::Pipeline::Behavior::MorphTargetRuleBehavior::CreateDescriptor(),
EMotionFX::Pipeline::Behavior::LodRuleBehavior::CreateDescriptor(),
EMotionFX::Pipeline::Behavior::SkeletonOptimizationRuleBehavior::CreateDescriptor(),
EMotionFX::Pipeline::ActorExporter::CreateDescriptor(),
EMotionFX::Pipeline::ActorGroupExporter::CreateDescriptor(),
EMotionFX::Pipeline::ActorBuilder::CreateDescriptor(),
EMotionFX::Pipeline::MorphTargetExporter::CreateDescriptor(),
// Motion
EMotionFX::Pipeline::Behavior::MotionGroupBehavior::CreateDescriptor(),
EMotionFX::Pipeline::Behavior::SkinRuleBehavior::CreateDescriptor(),
EMotionFX::Pipeline::Behavior::MotionRangeRuleBehavior::CreateDescriptor(),
EMotionFX::Pipeline::MotionExporter::CreateDescriptor(),
EMotionFX::Pipeline::MotionGroupExporter::CreateDescriptor(),
EMotionFX::Pipeline::MotionDataBuilder::CreateDescriptor()
#endif // EMOTIONFXANIMATION_EDITOR
});
}
~EMotionFXIntegrationModule()
{
}
/**
* Add required SystemComponents to the SystemEntity.
*/
AZ::ComponentTypeList GetRequiredSystemComponents() const override
{
return AZ::ComponentTypeList{
azrtti_typeid<SystemComponent>(),
};
}
};
}
}
#if defined(EMOTIONFXANIMATION_EDITOR)
#include <QDir>
// Qt resources are defined in the EMotionFX.Editor static library, so we must
// initialize them manually
extern int qInitResources_Resources();
extern int qCleanupResources_Resources();
namespace {
struct initializer {
initializer() { qInitResources_Resources(); }
~initializer() { qCleanupResources_Resources(); }
} dummy;
} // namespace
#endif
// DO NOT MODIFY THIS LINE UNLESS YOU RENAME THE GEM
// The first parameter should be GemName_GemIdLower
// The second should be the fully qualified name of the class above
AZ_DECLARE_MODULE_CLASS(Gem_EMotionFX, EMotionFX::Integration::EMotionFXIntegrationModule)
@@ -0,0 +1,23 @@
/*
* 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.
*
*/
#pragma once
namespace EMotionFX::Integration
{
class CVars
{
public:
static inline int emfx_updateEnabled = 1;
static inline int emfx_actorRenderEnabled = 1;
};
};
@@ -0,0 +1,87 @@
/*
* 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.
*
*/
#if defined(EMOTIONFXANIMATION_EDITOR)
#include <AzCore/Serialization/SerializeContext.h>
#include <MCore/Source/MCoreSystem.h>
#include <Integration/System/PipelineComponent.h>
#include <EMotionFX/Source/EMotionFXManager.h>
#include <EMotionFX/CommandSystem/Source/CommandManager.h>
#include <Integration/System/SystemCommon.h>
namespace EMotionFX
{
namespace Pipeline
{
AZ::EnvironmentVariable<EMotionFXAllocatorInitializer> PipelineComponent::s_eMotionFXAllocatorInitializer = nullptr;
PipelineComponent::PipelineComponent()
: m_EMotionFXInited(false)
{
}
void PipelineComponent::Activate()
{
if (!m_EMotionFXInited)
{
// Start EMotionFX allocator or increase the reference counting
s_eMotionFXAllocatorInitializer = AZ::Environment::CreateVariable<EMotionFXAllocatorInitializer>(EMotionFXAllocatorInitializer::EMotionFXAllocatorInitializerTag);
MCore::Initializer::InitSettings coreSettings;
if (!MCore::Initializer::Init(&coreSettings))
{
AZ_Error("EMotionFX", false, "Failed to initialize EMotion FX SDK Core");
return;
}
// Initialize EMotion FX runtime.
EMotionFX::Initializer::InitSettings emfxSettings;
emfxSettings.mUnitType = MCore::Distance::UNITTYPE_METERS;
if (!EMotionFX::Initializer::Init(&emfxSettings))
{
AZ_Error("EMotionFX", false, "Failed to initialize EMotion FX SDK Runtime");
return;
}
// Initialize the EMotionFX command system.
m_commandManager = AZStd::make_unique<CommandSystem::CommandManager>();
m_EMotionFXInited = true;
}
}
void PipelineComponent::Deactivate()
{
if (m_EMotionFXInited)
{
m_EMotionFXInited = false;
m_commandManager.reset();
EMotionFX::Initializer::Shutdown();
MCore::Initializer::Shutdown();
// Remove our reference
s_eMotionFXAllocatorInitializer = nullptr;
}
}
void PipelineComponent::Reflect(AZ::ReflectContext* context)
{
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<PipelineComponent, AZ::SceneAPI::SceneCore::SceneSystemComponent>()->Version(1);
}
}
} // Pipeline
} // EMotionFX
#endif
@@ -0,0 +1,49 @@
/*
* 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.
*
*/
#if defined(EMOTIONFXANIMATION_EDITOR)
#pragma once
#include <SceneAPI/SceneCore/Components/SceneSystemComponent.h>
#include <EMotionFX/CommandSystem/Source/CommandManager.h>
#include <EMotionFX/Source/EMotionFXAllocatorInitializer.h>
#include <AzCore/Module/Environment.h>
namespace EMotionFX
{
namespace Pipeline
{
class PipelineComponent
: public AZ::SceneAPI::SceneCore::SceneSystemComponent
{
public:
AZ_COMPONENT(PipelineComponent, "{F74E0D7C-BF22-4BC0-897A-2D80DA960DB0}", AZ::SceneAPI::SceneCore::SceneSystemComponent);
PipelineComponent();
~PipelineComponent() override = default;
void Activate() override;
void Deactivate() override;
static void Reflect(AZ::ReflectContext* context);
private:
bool m_EMotionFXInited;
AZStd::unique_ptr<CommandSystem::CommandManager> m_commandManager;
// Creates a static shared pointer using the AZ EnvironmentVariable system.
// This will prevent the EMotionFXAllocator from destroying too early by the other component
static AZ::EnvironmentVariable<EMotionFXAllocatorInitializer> s_eMotionFXAllocatorInitializer;
};
} // Pipeline
} // EMotionFX
#endif
@@ -0,0 +1,202 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/Math/Vector2.h>
#include <AzCore/Math/Vector3.h>
#include <AzCore/Math/Transform.h>
#include <AzCore/Math/Quaternion.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/std/smart_ptr/intrusive_ptr.h>
#include <MCore/Source/Vector.h>
#include <MCore/Source/MemoryObject.h>
#include <EMotionFX/Source/Transform.h>
namespace AZStd
{
/**
* Intrusive ptr for EMotionFX-owned objects (uses EMotionFX's internal ref-counting MCore::Destroy()).
*/
template<>
struct IntrusivePtrCountPolicy<MCore::MemoryObject>
{
static AZ_FORCE_INLINE void add_ref(MCore::MemoryObject* ptr)
{
ptr->IncreaseReferenceCount();
}
static AZ_FORCE_INLINE void release(MCore::MemoryObject* ptr)
{
MCore::Destroy(ptr); // Calls DecreaseReferenceCount.
}
};
}
namespace EMotionFX
{
namespace Integration
{
/**
* System allocator to be used for all EMotionFX and EMotionFXAnimation gem persistent allocations.
*/
class EMotionFXAllocator
: public AZ::SimpleSchemaAllocator<AZ::ChildAllocatorSchema<AZ::SystemAllocator>>
{
public:
AZ_TYPE_INFO(EMotionFXAllocator, "{00AEC34F-4A00-4ECB-BC9C-7221E76337D6}");
using Base = AZ::SimpleSchemaAllocator<AZ::ChildAllocatorSchema<AZ::SystemAllocator>>;
using Descriptor = Base::Descriptor;
EMotionFXAllocator() : Base("EMotion FX System Allocator", "EMotion FX general memory allocator")
{
}
};
/**
* Intrusive ptr for EMotionFX-owned objects.
* Uses EMotionFX's internal ref-counting.
*/
template <typename ObjectType>
class EMotionFXPtr
{
public:
/// Use only to initialize a new EMotionFXPtr<> given an EMotionFX SDK object not currently owned by an EMotionFXPtr<>.
/// This is generally only appropriate for use when an EMotionFX object has just been constructed.
static EMotionFXPtr<ObjectType> MakeFromNew(ObjectType* object)
{
AZ_Assert(object, "CreateFromNew called with invalid object.");
AZ_Assert(object && object->GetReferenceCount() == 1, "Newly constructed EMotion FX objects are expected to have a referene count initialized to 1.");
// EMotionFX initializes objects with a ref count already at 1. So for newly-constructed objects that we're
// managing through smart pointers, it's not necessary to increment ref count during initial acquisition.
EMotionFXPtr<ObjectType> ptr;
ptr.m_ptr = object;
return ptr;
}
AZ_FORCE_INLINE explicit EMotionFXPtr(ObjectType* object = nullptr)
{
*this = object;
}
AZ_FORCE_INLINE EMotionFXPtr(const EMotionFXPtr<ObjectType>& rhs)
{
*this = rhs;
}
AZ_FORCE_INLINE ~EMotionFXPtr()
{
if (m_ptr)
{
MCore::Destroy(m_ptr); // Calls DecreaseReferenceCount.
}
}
AZ_FORCE_INLINE void reset(ObjectType* object = nullptr)
{
*this = object;
}
AZ_FORCE_INLINE void operator=(ObjectType* object)
{
if (m_ptr)
{
MCore::Destroy(m_ptr); // Calls DecreaseReferenceCount.
m_ptr = nullptr;
}
m_ptr = object;
if (m_ptr)
{
m_ptr->IncreaseReferenceCount();
}
}
AZ_FORCE_INLINE void operator=(const EMotionFXPtr<ObjectType>& rhs)
{
reset(rhs ? rhs.get() : nullptr);
}
AZ_FORCE_INLINE ObjectType* operator ->() const
{
AZ_Assert(m_ptr, "Attempting to dereference a null EMotion FX object pointer.");
return m_ptr;
}
AZ_FORCE_INLINE ObjectType* get() const
{
return m_ptr;
}
AZ_FORCE_INLINE operator bool() const
{
return m_ptr != nullptr;
}
AZ_FORCE_INLINE bool operator==(const EMotionFXPtr<ObjectType>& rhs) const
{
return (m_ptr == rhs.m_ptr);
}
AZ_FORCE_INLINE bool operator==(const ObjectType* rhs) const
{
return (m_ptr == rhs);
}
AZ_FORCE_INLINE bool operator!=(const EMotionFXPtr<ObjectType>& rhs) const
{
return !(m_ptr == rhs.m_ptr);
}
AZ_FORCE_INLINE bool operator!=(const ObjectType* rhs) const
{
return !(m_ptr == rhs);
}
private:
ObjectType* m_ptr = nullptr;
};
/**
* EMotionFX memory hooks
*/
AZ_FORCE_INLINE void* EMotionFXAlloc(size_t numBytes, AZ::u16 categoryID, AZ::u16 blockID, const char* filename, AZ::u32 lineNr)
{
(void)categoryID;
(void)blockID;
(void)lineNr;
return AZ::AllocatorInstance<EMotionFXAllocator>::Get().Allocate(numBytes, 8, 0, "EMotionFX", filename, lineNr);
}
AZ_FORCE_INLINE void* EMotionFXRealloc(void* memory, size_t numBytes, AZ::u16 categoryID, AZ::u16 blockID, const char* filename, AZ::u32 lineNr)
{
(void)categoryID;
(void)blockID;
(void)filename;
(void)lineNr;
return AZ::AllocatorInstance<EMotionFXAllocator>::Get().ReAllocate(memory, numBytes, 8);
}
AZ_FORCE_INLINE void EMotionFXFree(void* memory)
{
AZ::AllocatorInstance<EMotionFXAllocator>::Get().DeAllocate(memory);
}
} //namespace Integration
} // namespace EMotionFX
@@ -0,0 +1,918 @@
/*
* 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 "EMotionFX_precompiled.h"
#include <AzCore/Component/ComponentApplication.h>
#include <AzCore/Component/TransformBus.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzFramework/Physics/CharacterBus.h>
#include <EMotionFX/Source/Allocators.h>
#include <EMotionFX/Source/SingleThreadScheduler.h>
#include <EMotionFX/Source/EMotionFXManager.h>
#include <EMotionFX/Source/AnimGraphManager.h>
#include <EMotionFX/Source/AnimGraphObjectFactory.h>
#include <EMotionFX/Source/MotionSet.h>
#include <EMotionFX/Source/Recorder.h>
#include <EMotionFX/Source/ConstraintTransformRotationAngles.h>
#include <EMotionFX/Source/Parameter/ParameterFactory.h>
#include <EMotionFX/Source/TwoStringEventData.h>
#include <EMotionFX/Source/EventDataFootIK.h>
#include <EMotionFX/Source/PhysicsSetup.h>
#include <EMotionFX/Source/SimulatedObjectSetup.h>
#include <MCore/Source/Command.h>
#include <EMotionFX/CommandSystem/Source/MotionEventCommands.h>
#include <EMotionFX/CommandSystem/Source/SimulatedObjectCommands.h>
#include <EMotionFX/CommandSystem/Source/RagdollCommands.h>
#include <EMotionFX/Source/PoseData.h>
#include <EMotionFX/Source/PoseDataRagdoll.h>
#include <Integration/EMotionFXBus.h>
#include <Integration/Assets/ActorAsset.h>
#include <Integration/Assets/MotionAsset.h>
#include <Integration/Assets/MotionSetAsset.h>
#include <Integration/Assets/AnimGraphAsset.h>
#include <Integration/Rendering/Cry/CryRenderBackend.h>
#include <Integration/System/SystemComponent.h>
#include <Integration/System/CVars.h>
#include <AzFramework/Physics/World.h>
#include <Integration/MotionExtractionBus.h>
#if defined(EMOTIONFXANIMATION_EDITOR) // EMFX tools / editor includes
# include <IEditor.h>
// Qt
# include <QtGui/QSurfaceFormat>
// EMStudio tools and main window registration
# include <LyViewPaneNames.h>
# include <AzToolsFramework/API/ViewPaneOptions.h>
# include <AzCore/std/string/wildcard.h>
# include <QApplication>
# include <EMotionStudio/EMStudioSDK/Source/EMStudioManager.h>
# include <EMotionStudio/EMStudioSDK/Source/MainWindow.h>
# include <EMotionStudio/EMStudioSDK/Source/PluginManager.h>
// EMStudio plugins
# include <EMotionStudio/Plugins/StandardPlugins/Source/LogWindow/LogWindowPlugin.h>
# include <EMotionStudio/Plugins/StandardPlugins/Source/CommandBar/CommandBarPlugin.h>
# include <EMotionStudio/Plugins/StandardPlugins/Source/ActionHistory/ActionHistoryPlugin.h>
# include <EMotionStudio/Plugins/StandardPlugins/Source/MotionWindow/MotionWindowPlugin.h>
# include <EMotionStudio/Plugins/StandardPlugins/Source/MorphTargetsWindow/MorphTargetsWindowPlugin.h>
# include <EMotionStudio/Plugins/StandardPlugins/Source/TimeView/TimeViewPlugin.h>
# include <EMotionStudio/Plugins/StandardPlugins/Source/Attachments/AttachmentsPlugin.h>
# include <EMotionStudio/Plugins/StandardPlugins/Source/SceneManager/SceneManagerPlugin.h>
# include <EMotionStudio/Plugins/StandardPlugins/Source/NodeWindow/NodeWindowPlugin.h>
# include <EMotionStudio/Plugins/StandardPlugins/Source/MotionEvents/MotionEventsPlugin.h>
# include <EMotionStudio/Plugins/StandardPlugins/Source/MotionSetsWindow/MotionSetsWindowPlugin.h>
# include <EMotionStudio/Plugins/StandardPlugins/Source/NodeGroups/NodeGroupsPlugin.h>
# include <EMotionStudio/Plugins/StandardPlugins/Source/AnimGraph/AnimGraphPlugin.h>
# include <EMotionStudio/Plugins/RenderPlugins/Source/OpenGLRender/OpenGLRenderPlugin.h>
# include <Editor/Plugins/HitDetection/HitDetectionJointInspectorPlugin.h>
# include <Editor/Plugins/SkeletonOutliner/SkeletonOutlinerPlugin.h>
# include <Editor/Plugins/Ragdoll/RagdollNodeInspectorPlugin.h>
# include <Editor/Plugins/Cloth/ClothJointInspectorPlugin.h>
# include <Editor/Plugins/SimulatedObject/SimulatedObjectWidget.h>
# include <Source/Editor/PropertyWidgets/PropertyTypes.h>
# include <EMotionFX_Traits_Platform.h>
#endif // EMOTIONFXANIMATION_EDITOR
#include <IConsole.h>
#include <ISystem.h>
// include required AzCore headers
#include <AzCore/IO/FileIO.h>
#include <AzFramework/API/ApplicationAPI.h>
namespace EMotionFX
{
namespace Integration
{
//////////////////////////////////////////////////////////////////////////
class EMotionFXEventHandler
: public EMotionFX::EventHandler
{
public:
AZ_CLASS_ALLOCATOR(EMotionFXEventHandler, EMotionFXAllocator, 0);
const AZStd::vector<EventTypes> GetHandledEventTypes() const
{
return {
EVENT_TYPE_ON_EVENT,
EVENT_TYPE_ON_HAS_LOOPED,
EVENT_TYPE_ON_STATE_ENTERING,
EVENT_TYPE_ON_STATE_ENTER,
EVENT_TYPE_ON_STATE_END,
EVENT_TYPE_ON_STATE_EXIT,
EVENT_TYPE_ON_START_TRANSITION,
EVENT_TYPE_ON_END_TRANSITION
};
}
/// Dispatch motion events to listeners via ActorNotificationBus::OnMotionEvent.
void OnEvent(const EMotionFX::EventInfo& emfxInfo) override
{
const ActorInstance* actorInstance = emfxInfo.mActorInstance;
if (actorInstance)
{
const AZ::EntityId owningEntityId = actorInstance->GetEntityId();
// Fill engine-compatible structure to dispatch to game code.
MotionEvent motionEvent;
motionEvent.m_entityId = owningEntityId;
motionEvent.m_actorInstance = emfxInfo.mActorInstance;
motionEvent.m_motionInstance = emfxInfo.mMotionInstance;
motionEvent.m_time = emfxInfo.mTimeValue;
// TODO
for (const auto& eventData : emfxInfo.mEvent->GetEventDatas())
{
if (const EMotionFX::TwoStringEventData* twoStringEventData = azrtti_cast<const EMotionFX::TwoStringEventData*>(eventData.get()))
{
motionEvent.m_eventTypeName = twoStringEventData->GetSubject().c_str();
motionEvent.SetParameterString(twoStringEventData->GetParameters().c_str(), twoStringEventData->GetParameters().size());
break;
}
}
motionEvent.m_globalWeight = emfxInfo.mGlobalWeight;
motionEvent.m_localWeight = emfxInfo.mLocalWeight;
motionEvent.m_isEventStart = emfxInfo.IsEventStart();
// Queue the event to flush on the main thread.
ActorNotificationBus::QueueEvent(owningEntityId, &ActorNotificationBus::Events::OnMotionEvent, AZStd::move(motionEvent));
}
}
void OnHasLooped(EMotionFX::MotionInstance* motionInstance) override
{
const ActorInstance* actorInstance = motionInstance->GetActorInstance();
if (actorInstance)
{
const AZ::EntityId owningEntityId = actorInstance->GetEntityId();
ActorNotificationBus::QueueEvent(owningEntityId, &ActorNotificationBus::Events::OnMotionLoop, motionInstance->GetMotion()->GetName());
}
}
void OnStateEntering(EMotionFX::AnimGraphInstance* animGraphInstance, EMotionFX::AnimGraphNode* state) override
{
const ActorInstance* actorInstance = animGraphInstance->GetActorInstance();
if (actorInstance && state)
{
const AZ::EntityId owningEntityId = actorInstance->GetEntityId();
ActorNotificationBus::QueueEvent(owningEntityId, &ActorNotificationBus::Events::OnStateEntering, state->GetName());
}
}
void OnStateEnter(EMotionFX::AnimGraphInstance* animGraphInstance, EMotionFX::AnimGraphNode* state) override
{
const ActorInstance* actorInstance = animGraphInstance->GetActorInstance();
if (actorInstance && state)
{
const AZ::EntityId owningEntityId = actorInstance->GetEntityId();
ActorNotificationBus::QueueEvent(owningEntityId, &ActorNotificationBus::Events::OnStateEntered, state->GetName());
}
}
void OnStateEnd(EMotionFX::AnimGraphInstance* animGraphInstance, EMotionFX::AnimGraphNode* state) override
{
const ActorInstance* actorInstance = animGraphInstance->GetActorInstance();
if (actorInstance && state)
{
const AZ::EntityId owningEntityId = actorInstance->GetEntityId();
ActorNotificationBus::QueueEvent(owningEntityId, &ActorNotificationBus::Events::OnStateExiting, state->GetName());
}
}
void OnStateExit(EMotionFX::AnimGraphInstance* animGraphInstance, EMotionFX::AnimGraphNode* state) override
{
const ActorInstance* actorInstance = animGraphInstance->GetActorInstance();
if (actorInstance && state)
{
const AZ::EntityId owningEntityId = actorInstance->GetEntityId();
ActorNotificationBus::QueueEvent(owningEntityId, &ActorNotificationBus::Events::OnStateExited, state->GetName());
}
}
void OnStartTransition(EMotionFX::AnimGraphInstance* animGraphInstance, EMotionFX::AnimGraphStateTransition* transition) override
{
const ActorInstance* actorInstance = animGraphInstance->GetActorInstance();
if (actorInstance)
{
const AZ::EntityId owningEntityId = actorInstance->GetEntityId();
const char* sourceName = transition->GetSourceNode() ? transition->GetSourceNode()->GetName() : "";
const char* targetName = transition->GetTargetNode() ? transition->GetTargetNode()->GetName() : "";
ActorNotificationBus::QueueEvent(owningEntityId, &ActorNotificationBus::Events::OnStateTransitionStart, sourceName, targetName);
}
}
void OnEndTransition(EMotionFX::AnimGraphInstance* animGraphInstance, EMotionFX::AnimGraphStateTransition* transition) override
{
const ActorInstance* actorInstance = animGraphInstance->GetActorInstance();
if (actorInstance)
{
const AZ::EntityId owningEntityId = actorInstance->GetEntityId();
const char* sourceName = transition->GetSourceNode() ? transition->GetSourceNode()->GetName() : "";
const char* targetName = transition->GetTargetNode() ? transition->GetTargetNode()->GetName() : "";
ActorNotificationBus::QueueEvent(owningEntityId, &ActorNotificationBus::Events::OnStateTransitionEnd, sourceName, targetName);
}
}
};
//////////////////////////////////////////////////////////////////////////
class ActorNotificationBusHandler
: public ActorNotificationBus::Handler
, public AZ::BehaviorEBusHandler
{
public:
AZ_EBUS_BEHAVIOR_BINDER(ActorNotificationBusHandler, "{D2CD62E7-5FCF-4DC2-85DF-C205D5AB1E8B}", AZ::SystemAllocator,
OnMotionEvent,
OnMotionLoop,
OnStateEntering,
OnStateEntered,
OnStateExiting,
OnStateExited,
OnStateTransitionStart,
OnStateTransitionEnd);
void OnMotionEvent(MotionEvent motionEvent) override
{
Call(FN_OnMotionEvent, motionEvent);
}
void OnMotionLoop(const char* motionName) override
{
Call(FN_OnMotionLoop, motionName);
}
void OnStateEntering(const char* stateName) override
{
Call(FN_OnStateEntering, stateName);
}
void OnStateEntered(const char* stateName) override
{
Call(FN_OnStateEntered, stateName);
}
void OnStateExiting(const char* stateName) override
{
Call(FN_OnStateExiting, stateName);
}
void OnStateExited(const char* stateName) override
{
Call(FN_OnStateExited, stateName);
}
void OnStateTransitionStart(const char* fromState, const char* toState) override
{
Call(FN_OnStateTransitionStart, fromState, toState);
}
void OnStateTransitionEnd(const char* fromState, const char* toState) override
{
Call(FN_OnStateTransitionEnd, fromState, toState);
}
};
SystemComponent::~SystemComponent() = default;
void SystemComponent::ReflectEMotionFX(AZ::ReflectContext* context)
{
MCore::ReflectionSerializer::Reflect(context);
MCore::StringIdPoolIndex::Reflect(context);
EMotionFX::ConstraintTransformRotationAngles::Reflect(context);
// Actor
EMotionFX::PhysicsSetup::Reflect(context);
EMotionFX::SimulatedObjectSetup::Reflect(context);
EMotionFX::PoseData::Reflect(context);
EMotionFX::PoseDataRagdoll::Reflect(context);
// Motion set
EMotionFX::MotionSet::Reflect(context);
EMotionFX::MotionSet::MotionEntry::Reflect(context);
// Base AnimGraph objects
EMotionFX::AnimGraphObject::Reflect(context);
EMotionFX::AnimGraph::Reflect(context);
EMotionFX::AnimGraphNodeGroup::Reflect(context);
EMotionFX::AnimGraphGameControllerSettings::Reflect(context);
// Anim graph objects
EMotionFX::AnimGraphObjectFactory::ReflectTypes(context);
// Anim graph's parameters
EMotionFX::ParameterFactory::ReflectParameterTypes(context);
EMotionFX::MotionEventTable::Reflect(context);
EMotionFX::MotionEventTrack::Reflect(context);
EMotionFX::AnimGraphSyncTrack::Reflect(context);
EMotionFX::Event::Reflect(context);
EMotionFX::MotionEvent::Reflect(context);
EMotionFX::EventData::Reflect(context);
EMotionFX::EventDataSyncable::Reflect(context);
EMotionFX::TwoStringEventData::Reflect(context);
EMotionFX::EventDataFootIK::Reflect(context);
EMotionFX::Recorder::Reflect(context);
EMotionFX::KeyTrackLinearDynamic<AZ::Vector3>::Reflect(context);
EMotionFX::KeyTrackLinearDynamic<AZ::Quaternion>::Reflect(context);
EMotionFX::KeyFrame<AZ::Vector3>::Reflect(context);
EMotionFX::KeyFrame<AZ::Quaternion>::Reflect(context);
MCore::Command::Reflect(context);
CommandSystem::MotionIdCommandMixin::Reflect(context);
CommandSystem::CommandAdjustMotion::Reflect(context);
CommandSystem::CommandClearMotionEvents::Reflect(context);
CommandSystem::CommandCreateMotionEventTrack::Reflect(context);
CommandSystem::CommandAdjustMotionEventTrack::Reflect(context);
CommandSystem::CommandCreateMotionEvent::Reflect(context);
CommandSystem::CommandAdjustMotionEvent::Reflect(context);
EMotionFX::CommandAdjustSimulatedObject::Reflect(context);
EMotionFX::CommandAdjustSimulatedJoint::Reflect(context);
EMotionFX::CommandAddRagdollJoint::Reflect(context);
EMotionFX::CommandAdjustRagdollJoint::Reflect(context);
EMotionFX::CommandRemoveRagdollJoint::Reflect(context);
}
//////////////////////////////////////////////////////////////////////////
void SystemComponent::Reflect(AZ::ReflectContext* context)
{
ReflectEMotionFX(context);
// Reflect component for serialization.
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<SystemComponent, AZ::Component>()
->Version(1)
->Field("NumThreads", &SystemComponent::m_numThreads)
;
serializeContext->Class<MotionEvent>()
->Version(1)
;
if (AZ::EditContext* ec = serializeContext->GetEditContext())
{
ec->Class<SystemComponent>("EMotion FX Animation", "Enables the EMotion FX animation solution")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("System", 0xc94d118b))
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->DataElement(AZ::Edit::UIHandlers::Default, &SystemComponent::m_numThreads, "Number of threads", "Number of threads used internally by EMotion FX")
;
}
}
// Reflect system-level types and EBuses to behavior context.
AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context);
if (behaviorContext)
{
behaviorContext->EBus<SystemRequestBus>("SystemRequestBus")
;
behaviorContext->EBus<SystemNotificationBus>("SystemNotificationBus")
;
// In order for a property to be displayed in ScriptCanvas. Both a setter and a getter are necessary(both must be non-null).
// This is being worked on in dragon branch, once this is complete the dummy lambda functions can be removed.
behaviorContext->Class<MotionEvent>("MotionEvent")
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::Preview)
->Property("entityId", BehaviorValueGetter(&MotionEvent::m_entityId), [](MotionEvent*, const AZ::EntityId&) {})
->Property("parameter", BehaviorValueGetter(&MotionEvent::m_parameter), [](MotionEvent*, const char*) {})
->Property("eventType", BehaviorValueGetter(&MotionEvent::m_eventType), [](MotionEvent*, const AZ::u32&) {})
->Property("eventTypeName", BehaviorValueGetter(&MotionEvent::m_eventTypeName), [](MotionEvent*, const char*) {})
->Property("time", BehaviorValueGetter(&MotionEvent::m_time), [](MotionEvent*, const float&) {})
->Property("globalWeight", BehaviorValueGetter(&MotionEvent::m_globalWeight), [](MotionEvent*, const float&) {})
->Property("localWeight", BehaviorValueGetter(&MotionEvent::m_localWeight), [](MotionEvent*, const float&) {})
->Property("isEventStart", BehaviorValueGetter(&MotionEvent::m_isEventStart), [](MotionEvent*, const bool&) {})
;
behaviorContext->EBus<ActorNotificationBus>("ActorNotificationBus")
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::Preview)
->Handler<ActorNotificationBusHandler>()
->Event("OnMotionEvent", &ActorNotificationBus::Events::OnMotionEvent)
->Event("OnMotionLoop", &ActorNotificationBus::Events::OnMotionLoop)
->Event("OnStateEntering", &ActorNotificationBus::Events::OnStateEntering)
->Event("OnStateEntered", &ActorNotificationBus::Events::OnStateEntered)
->Event("OnStateExiting", &ActorNotificationBus::Events::OnStateExiting)
->Event("OnStateExited", &ActorNotificationBus::Events::OnStateExited)
->Event("OnStateTransitionStart", &ActorNotificationBus::Events::OnStateTransitionStart)
->Event("OnStateTransitionEnd", &ActorNotificationBus::Events::OnStateTransitionEnd)
;
}
}
//////////////////////////////////////////////////////////////////////////
void SystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC("EMotionFXAnimationService", 0x3f8a6369));
}
//////////////////////////////////////////////////////////////////////////
void SystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
{
incompatible.push_back(AZ_CRC("EMotionFXAnimationService", 0x3f8a6369));
}
//////////////////////////////////////////////////////////////////////////
void SystemComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
{
required.push_back(AZ_CRC("AssetDatabaseService", 0x3abf5601));
}
//////////////////////////////////////////////////////////////////////////
void SystemComponent::GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent)
{
dependent.push_back(AZ_CRC("AssetCatalogService", 0xc68ffc57));
dependent.push_back(AZ_CRC("JobsService", 0xd5ab5a50));
}
//////////////////////////////////////////////////////////////////////////
SystemComponent::SystemComponent()
: m_numThreads(1)
{
}
//////////////////////////////////////////////////////////////////////////
void SystemComponent::Init()
{
}
//////////////////////////////////////////////////////////////////////////
void SystemComponent::Activate()
{
// Start EMotionFX allocator.
AZ::AllocatorInstance<EMotionFXAllocator>::Create();
// Initialize MCore, which is EMotionFX's standard library of containers and systems.
MCore::Initializer::InitSettings coreSettings;
coreSettings.mMemAllocFunction = &EMotionFXAlloc;
coreSettings.mMemReallocFunction = &EMotionFXRealloc;
coreSettings.mMemFreeFunction = &EMotionFXFree;
if (!MCore::Initializer::Init(&coreSettings))
{
AZ_Error("EMotion FX Animation", false, "Failed to initialize EMotion FX SDK Core");
return;
}
// Initialize EMotionFX runtime.
EMotionFX::Initializer::InitSettings emfxSettings;
emfxSettings.mUnitType = MCore::Distance::UNITTYPE_METERS;
if (!EMotionFX::Initializer::Init(&emfxSettings))
{
AZ_Error("EMotion FX Animation", false, "Failed to initialize EMotion FX SDK Runtime");
return;
}
SetMediaRoot("@assets@");
// \todo Right now we're pointing at the @devassets@ location (source) and working from there, because .actor and .motion (motion) aren't yet processed through
// the FBX pipeline. Once they are, we'll need to update various segments of the Tool to always read from the @assets@ cache, but write to the @devassets@ data/metadata.
EMotionFX::GetEMotionFX().InitAssetFolderPaths();
// Register EMotionFX event handler
m_eventHandler.reset(aznew EMotionFXEventHandler());
EMotionFX::GetEventManager().AddEventHandler(m_eventHandler.get());
// Setup asset types.
RegisterAssetTypesAndHandlers();
SystemRequestBus::Handler::BusConnect();
AZ::TickBus::Handler::BusConnect();
CrySystemEventBus::Handler::BusConnect();
EMotionFXRequestBus::Handler::BusConnect();
EnableRayRequests();
m_renderBackendManager = AZStd::make_unique<RenderBackendManager>();
// Default to Cry render backend. The RenderBackendManager will manage the lifetime of the CryRenderBackend.
CryRenderBackend* cryRenderBackend = aznew CryRenderBackend();
m_renderBackendManager->SetRenderBackend(cryRenderBackend);
#if defined (EMOTIONFXANIMATION_EDITOR)
AzToolsFramework::EditorEvents::Bus::Handler::BusConnect();
AzToolsFramework::EditorAnimationSystemRequestsBus::Handler::BusConnect();
AzToolsFramework::AssetBrowser::AssetBrowserInteractionNotificationBus::Handler::BusConnect();
m_updateTimer.Stamp();
// Register custom property handlers for the reflected property editor.
m_propertyHandlers = RegisterPropertyTypes();
#endif // EMOTIONFXANIMATION_EDITOR
}
//////////////////////////////////////////////////////////////////////////
void SystemComponent::Deactivate()
{
#if defined(EMOTIONFXANIMATION_EDITOR)
// Unregister custom property handlers for the reflected property editor.
UnregisterPropertyTypes(m_propertyHandlers);
m_propertyHandlers.clear();
if (EMStudio::GetManager())
{
EMStudio::Initializer::Shutdown();
MysticQt::Initializer::Shutdown();
}
{
using namespace AzToolsFramework;
EditorRequests::Bus::Broadcast(&EditorRequests::UnregisterViewPane, EMStudio::MainWindow::GetEMotionFXPaneName());
}
AzToolsFramework::AssetBrowser::AssetBrowserInteractionNotificationBus::Handler::BusDisconnect();
AzToolsFramework::EditorAnimationSystemRequestsBus::Handler::BusDisconnect();
AzToolsFramework::EditorEvents::Bus::Handler::BusDisconnect();
#endif // EMOTIONFXANIMATION_EDITOR
m_renderBackendManager.reset();
EMotionFX::GetEventManager().RemoveEventHandler(m_eventHandler.get());
m_eventHandler.reset();
AZ::TickBus::Handler::BusDisconnect();
CrySystemEventBus::Handler::BusDisconnect();
EMotionFXRequestBus::Handler::BusDisconnect();
DisableRayRequests();
if (SystemRequestBus::Handler::BusIsConnected())
{
SystemRequestBus::Handler::BusDisconnect();
m_assetHandlers.resize(0);
EMotionFX::Initializer::Shutdown();
MCore::Initializer::Shutdown();
}
// Memory leaks will be reported.
AZ::AllocatorInstance<EMotionFXAllocator>::Destroy();
}
//////////////////////////////////////////////////////////////////////////
void SystemComponent::EnableRayRequests()
{
RaycastRequestBus::Handler::BusDisconnect();
RaycastRequestBus::Handler::BusConnect();
}
void SystemComponent::DisableRayRequests()
{
RaycastRequestBus::Handler::BusDisconnect();
}
//////////////////////////////////////////////////////////////////////////
void SystemComponent::OnCrySystemInitialized([[maybe_unused]] ISystem& system, const SSystemInitParams&)
{
#if !defined(AZ_MONOLITHIC_BUILD)
// When module is linked dynamically, we must set our gEnv pointer.
// When module is linked statically, we'll share the application's gEnv pointer.
gEnv = system.GetGlobalEnvironment();
#endif
REGISTER_CVAR2("emfx_updateEnabled", &CVars::emfx_updateEnabled, 1, VF_DEV_ONLY, "Enable main EMFX update");
REGISTER_CVAR2("emfx_actorRenderEnabled", &CVars::emfx_actorRenderEnabled, 1, VF_DEV_ONLY, "Enable ActorRenderNode rendering");
}
//////////////////////////////////////////////////////////////////////////
void SystemComponent::OnCrySystemShutdown(ISystem&)
{
gEnv->pConsole->UnregisterVariable("emfx_updateEnabled");
gEnv->pConsole->UnregisterVariable("emfx_actorRenderEnabled");
#if !defined(AZ_MONOLITHIC_BUILD)
gEnv = nullptr;
#endif
}
//////////////////////////////////////////////////////////////////////////
#if defined (EMOTIONFXANIMATION_EDITOR)
void SystemComponent::UpdateAnimationEditorPlugins(float delta)
{
if (!EMStudio::GetManager())
{
return;
}
EMStudio::PluginManager* pluginManager = EMStudio::GetPluginManager();
if (!pluginManager)
{
return;
}
// Process the plugins.
const AZ::u32 numPlugins = pluginManager->GetNumActivePlugins();
for (AZ::u32 i = 0; i < numPlugins; ++i)
{
EMStudio::EMStudioPlugin* plugin = pluginManager->GetActivePlugin(i);
plugin->ProcessFrame(delta);
}
}
#endif
//////////////////////////////////////////////////////////////////////////
void SystemComponent::OnTick(float delta, AZ::ScriptTimePoint timePoint)
{
AZ_UNUSED(timePoint);
#if defined (EMOTIONFXANIMATION_EDITOR)
AZ_UNUSED(delta);
const float realDelta = m_updateTimer.StampAndGetDeltaTimeInSeconds();
// Flush events prior to updating EMotion FX.
ActorNotificationBus::ExecuteQueuedEvents();
if (CVars::emfx_updateEnabled)
{
// Main EMotionFX runtime update.
GetEMotionFX().Update(realDelta);
}
// Check if we are in game mode.
IEditor* editor = nullptr;
EBUS_EVENT_RESULT(editor, AzToolsFramework::EditorRequests::Bus, GetEditor);
const bool inGameMode = editor ? editor->IsInGameMode() : false;
// Update all the animation editor plugins (redraw viewports, timeline, and graph windows etc).
// But only update this when the main window is visible and we are in game mode.
const bool isEditorActive =
EMotionFX::GetEMotionFX().GetIsInEditorMode() &&
EMStudio::GetManager() &&
EMStudio::HasMainWindow() &&
!EMStudio::GetMainWindow()->visibleRegion().isEmpty() &&
!inGameMode;
if (isEditorActive)
{
UpdateAnimationEditorPlugins(realDelta);
}
#else
// Flush events prior to updating EMotion FX.
ActorNotificationBus::ExecuteQueuedEvents();
if (CVars::emfx_updateEnabled)
{
// Main EMotionFX runtime update.
GetEMotionFX().Update(delta);
}
#endif
const float timeDelta = delta;
const ActorManager* actorManager = GetEMotionFX().GetActorManager();
const AZ::u32 numActorInstances = actorManager->GetNumActorInstances();
for (AZ::u32 i = 0; i < numActorInstances; ++i)
{
const ActorInstance* actorInstance = actorManager->GetActorInstance(i);
if (actorInstance && actorInstance->GetIsEnabled() && actorInstance->GetIsOwnedByRuntime())
{
AZ::Entity* entity = actorInstance->GetEntity();
const Actor* actor = actorInstance->GetActor();
if (entity && actor && actor->GetMotionExtractionNode())
{
const AZ::EntityId entityId = entity->GetId();
// Check if we have any physics character controllers.
bool hasCustomMotionExtractionController = false;
bool hasPhysicsController = false;
Physics::CharacterRequestBus::EventResult(hasPhysicsController, entityId, &Physics::CharacterRequests::IsPresent);
if (!hasPhysicsController)
{
hasCustomMotionExtractionController = MotionExtractionRequestBus::FindFirstHandler(entityId) != nullptr;
}
// If we have a physics controller.
if (hasCustomMotionExtractionController || hasPhysicsController)
{
const float deltaTimeInv = (timeDelta > 0.0f) ? (1.0f / timeDelta) : 0.0f;
AZ::Transform currentTransform = AZ::Transform::CreateIdentity();
AZ::TransformBus::EventResult(currentTransform, entityId, &AZ::TransformBus::Events::GetWorldTM);
const AZ::Vector3 actorInstancePosition = actorInstance->GetWorldSpaceTransform().mPosition;
const AZ::Vector3 positionDelta = actorInstancePosition - currentTransform.GetTranslation();
if (hasPhysicsController)
{
Physics::CharacterRequestBus::Event(
entityId, &Physics::CharacterRequests::AddVelocity, positionDelta * deltaTimeInv);
}
else if (hasCustomMotionExtractionController)
{
MotionExtractionRequestBus::Event(entityId, &MotionExtractionRequestBus::Events::ExtractMotion, positionDelta, timeDelta);
AZ::TransformBus::EventResult(currentTransform, entityId, &AZ::TransformBus::Events::GetWorldTM);
}
// Update the entity rotation.
const AZ::Quaternion actorInstanceRotation = actorInstance->GetWorldSpaceTransform().mRotation;
const AZ::Quaternion currentRotation = currentTransform.GetRotation();
if (!currentRotation.IsClose(actorInstanceRotation, AZ::Constants::FloatEpsilon))
{
AZ::Transform newTransform = currentTransform;
newTransform.SetRotation(actorInstanceRotation);
AZ::TransformBus::Event(entityId, &AZ::TransformBus::Events::SetWorldTM, newTransform);
}
}
else // There is no physics controller, just use EMotion FX's actor instance transform directly.
{
const AZ::Transform newTransform = actorInstance->GetWorldSpaceTransform().ToAZTransform();
AZ::TransformBus::Event(entityId, &AZ::TransformBus::Events::SetWorldTM, newTransform);
}
}
}
}
}
int SystemComponent::GetTickOrder()
{
return AZ::TICK_ANIMATION;
}
//////////////////////////////////////////////////////////////////////////
void SystemComponent::RegisterAnimGraphObjectType(EMotionFX::AnimGraphObject* objectTemplate)
{
EMotionFX::AnimGraphObjectFactory::GetUITypes().emplace(azrtti_typeid(objectTemplate));
}
//////////////////////////////////////////////////////////////////////////
void SystemComponent::RegisterAssetTypesAndHandlers()
{
// Initialize asset handlers.
m_assetHandlers.emplace_back(aznew ActorAssetHandler);
m_assetHandlers.emplace_back(aznew MotionAssetHandler);
m_assetHandlers.emplace_back(aznew MotionSetAssetHandler);
m_assetHandlers.emplace_back(aznew AnimGraphAssetHandler);
// Add asset types and extensions to AssetCatalog.
auto assetCatalog = AZ::Data::AssetCatalogRequestBus::FindFirstHandler();
if (assetCatalog)
{
assetCatalog->EnableCatalogForAsset(azrtti_typeid<ActorAsset>());
assetCatalog->EnableCatalogForAsset(azrtti_typeid<MotionAsset>());
assetCatalog->EnableCatalogForAsset(azrtti_typeid<MotionSetAsset>());
assetCatalog->EnableCatalogForAsset(azrtti_typeid<AnimGraphAsset>());
assetCatalog->AddExtension("actor"); // Actor
assetCatalog->AddExtension("motion"); // Motion
assetCatalog->AddExtension("motionset"); // Motion set
assetCatalog->AddExtension("animgraph"); // Anim graph
}
}
//////////////////////////////////////////////////////////////////////////
void SystemComponent::SetMediaRoot(const char* alias)
{
const char* rootPath = AZ::IO::FileIOBase::GetInstance()->GetAlias(alias);
if (rootPath)
{
AZStd::string mediaRootPath = rootPath;
EBUS_EVENT(AzFramework::ApplicationRequests::Bus, NormalizePathKeepCase, mediaRootPath);
EMotionFX::GetEMotionFX().SetMediaRootFolder(mediaRootPath.c_str());
}
else
{
AZ_Warning("EMotionFX", false, "Failed to set media root because alias \"%s\" could not be resolved.", alias);
}
}
//////////////////////////////////////////////////////////////////////////
RaycastRequests::RaycastResult SystemComponent::Raycast([[maybe_unused]] AZ::EntityId entityId, const RaycastRequests::RaycastRequest& rayRequest)
{
RaycastRequests::RaycastResult rayResult;
// Build the ray request in the physics system.
Physics::RayCastRequest physicsRayRequest;
physicsRayRequest.m_start = rayRequest.m_start;
physicsRayRequest.m_direction = rayRequest.m_direction;
physicsRayRequest.m_distance = rayRequest.m_distance;
physicsRayRequest.m_queryType = rayRequest.m_queryType;
// Cast the ray in the physics system.
Physics::RayCastHit physicsRayResult;
Physics::WorldRequestBus::EventResult(physicsRayResult, Physics::DefaultPhysicsWorldId, &Physics::WorldRequests::RayCast, physicsRayRequest);
if (physicsRayResult) // We intersected.
{
rayResult.m_position = physicsRayResult.m_position;
rayResult.m_normal = physicsRayResult.m_normal;
rayResult.m_intersected = true;
}
return rayResult;
}
#if defined (EMOTIONFXANIMATION_EDITOR)
//////////////////////////////////////////////////////////////////////////
void InitializeEMStudioPlugins()
{
// Register EMFX plugins.
EMStudio::PluginManager* pluginManager = EMStudio::GetPluginManager();
pluginManager->RegisterPlugin(new EMStudio::LogWindowPlugin());
pluginManager->RegisterPlugin(new EMStudio::CommandBarPlugin());
pluginManager->RegisterPlugin(new EMStudio::ActionHistoryPlugin());
pluginManager->RegisterPlugin(new EMStudio::MotionWindowPlugin());
pluginManager->RegisterPlugin(new EMStudio::MorphTargetsWindowPlugin());
pluginManager->RegisterPlugin(new EMStudio::TimeViewPlugin());
pluginManager->RegisterPlugin(new EMStudio::AttachmentsPlugin());
pluginManager->RegisterPlugin(new EMStudio::SceneManagerPlugin());
pluginManager->RegisterPlugin(new EMStudio::NodeWindowPlugin());
pluginManager->RegisterPlugin(new EMStudio::MotionEventsPlugin());
pluginManager->RegisterPlugin(new EMStudio::MotionSetsWindowPlugin());
pluginManager->RegisterPlugin(new EMStudio::NodeGroupsPlugin());
pluginManager->RegisterPlugin(new EMStudio::AnimGraphPlugin());
pluginManager->RegisterPlugin(new EMStudio::OpenGLRenderPlugin());
pluginManager->RegisterPlugin(new EMotionFX::HitDetectionJointInspectorPlugin());
pluginManager->RegisterPlugin(new EMotionFX::SkeletonOutlinerPlugin());
pluginManager->RegisterPlugin(new EMotionFX::RagdollNodeInspectorPlugin());
pluginManager->RegisterPlugin(new EMotionFX::ClothJointInspectorPlugin());
pluginManager->RegisterPlugin(new EMotionFX::SimulatedObjectWidget());
}
//////////////////////////////////////////////////////////////////////////
void SystemComponent::NotifyRegisterViews()
{
using namespace AzToolsFramework;
// Construct data folder that is used by the tool for loading assets (images etc.).
AZStd::string devRootPath;
AzFramework::ApplicationRequests::Bus::BroadcastResult(devRootPath, &AzFramework::ApplicationRequests::GetEngineRoot);
devRootPath += "Gems/EMotionFX/Assets/Editor/";
AzFramework::ApplicationRequests::Bus::Broadcast(&AzFramework::ApplicationRequests::NormalizePathKeepCase, devRootPath);
// Re-initialize EMStudio.
int argc = 0;
char** argv = nullptr;
MysticQt::Initializer::Init("", devRootPath.c_str());
EMStudio::Initializer::Init(qApp, argc, argv);
InitializeEMStudioPlugins();
// Get the MainWindow the first time so it is constructed
EMStudio::GetManager()->GetMainWindow();
EMStudio::GetManager()->ExecuteApp();
AZStd::function<QWidget*(QWidget*)> windowCreationFunc = []([[maybe_unused]] QWidget* parent = nullptr)
{
return EMStudio::GetMainWindow();
};
// Register EMotionFX window with the main editor.
AzToolsFramework::ViewPaneOptions emotionFXWindowOptions;
emotionFXWindowOptions.isPreview = true;
emotionFXWindowOptions.isDeletable = true;
emotionFXWindowOptions.isDockable = false;
#if AZ_TRAIT_EMOTIONFX_MAIN_WINDOW_DETACHED
emotionFXWindowOptions.detachedWindow = true;
#endif
emotionFXWindowOptions.optionalMenuText = "Animation Editor (PREVIEW)";
EditorRequests::Bus::Broadcast(&EditorRequests::RegisterViewPane, EMStudio::MainWindow::GetEMotionFXPaneName(), LyViewPane::CategoryTools, emotionFXWindowOptions, windowCreationFunc);
}
//////////////////////////////////////////////////////////////////////////
bool SystemComponent::IsSystemActive(EditorAnimationSystemRequests::AnimationSystem systemType)
{
return (systemType == AnimationSystem::EMotionFX);
}
// AzToolsFramework::AssetBrowser::AssetBrowserInteractionNotificationBus::Handler
AzToolsFramework::AssetBrowser::SourceFileDetails SystemComponent::GetSourceFileDetails(const char* fullSourceFileName)
{
using namespace AzToolsFramework::AssetBrowser;
if (AZStd::wildcard_match("*.motionset", fullSourceFileName))
{
return SourceFileDetails("Editor/Images/AssetBrowser/MotionSet_16.svg");
}
else if (AZStd::wildcard_match("*.animgraph", fullSourceFileName))
{
return SourceFileDetails("Editor/Images/AssetBrowser/AnimGraph_16.svg");
}
return SourceFileDetails(); // no result
}
#endif // EMOTIONFXANIMATION_EDITOR
}
}
@@ -0,0 +1,137 @@
/*
* 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.
*
*/
#pragma once
#include <AzCore/Component/Component.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <Integration/AnimationBus.h>
#include <Integration/EMotionFXBus.h>
#include <Integration/Rendering/RenderBackendManager.h>
#include <CrySystemBus.h> // Immediate-mode CryRendering only
#if defined (EMOTIONFXANIMATION_EDITOR)
# include <AzCore/Debug/Timer.h>
# include <AzToolsFramework/API/ToolsApplicationAPI.h>
# include <AzToolsFramework/API/EditorAnimationSystemRequestBus.h>
# include <AzToolsFramework/AssetBrowser/AssetBrowserBus.h>
#endif // EMOTIONFXANIMATION_EDITOR
namespace AZ
{
namespace Data
{
class AssetHandler;
}
}
namespace EMotionFX
{
namespace Integration
{
class EMotionFXEventHandler;
class CryRenderBackend;
class SystemComponent
: public AZ::Component
, private SystemRequestBus::Handler
, private AZ::TickBus::Handler
, private CrySystemEventBus::Handler
, private EMotionFXRequestBus::Handler
, private RaycastRequestBus::Handler
#if defined (EMOTIONFXANIMATION_EDITOR)
, private AzToolsFramework::EditorEvents::Bus::Handler
, private AzToolsFramework::EditorAnimationSystemRequestsBus::Handler
, private AzToolsFramework::AssetBrowser::AssetBrowserInteractionNotificationBus::Handler
#endif // EMOTIONFXANIMATION_EDITOR
{
public:
AZ_COMPONENT(SystemComponent, "{7AE4102B-387C-4157-B8C7-8D1EA3BCFD60}");
SystemComponent();
~SystemComponent() override;
static void ReflectEMotionFX(AZ::ReflectContext* context);
static void Reflect(AZ::ReflectContext* context);
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required);
static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent);
private:
//unique_ptr cannot be copied -> vector of unique_ptrs cannot be copied -> class cannot be copied
SystemComponent(const SystemComponent&) = delete;
////////////////////////////////////////////////////////////////////////
// AZ::Component interface implementation
void Init() override;
void Activate() override;
void Deactivate() override;
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
// AZ::TickBus::Handler
void OnTick(float delta, AZ::ScriptTimePoint timePoint) override;
int GetTickOrder() override;
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
// CrySystemEventBus
void OnCrySystemInitialized(ISystem&, const SSystemInitParams&) override;
void OnCrySystemShutdown(ISystem&) override;
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
// EMotionFXRequestBus
void RegisterAnimGraphObjectType(EMotionFX::AnimGraphObject* objectTemplate) override;
////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
// RaycastRequestBus
void EnableRayRequests() override;
void DisableRayRequests() override;
RaycastRequests::RaycastResult Raycast(AZ::EntityId entityId, const RaycastRequests::RaycastRequest& rayRequest) override;
////////////////////////////////////////////////////////////////////////
void RegisterAssetTypesAndHandlers();
void SetMediaRoot(const char* alias);
#if defined (EMOTIONFXANIMATION_EDITOR)
void UpdateAnimationEditorPlugins(float delta);
void NotifyRegisterViews() override;
bool IsSystemActive(EditorAnimationSystemRequests::AnimationSystem systemType);
//////////////////////////////////////////////////////////////////////////////////////
// AzToolsFramework::AssetBrowser::AssetBrowserInteractionNotificationBus::Handler
AzToolsFramework::AssetBrowser::SourceFileDetails GetSourceFileDetails(const char* fullSourceFileName) override;
//////////////////////////////////////////////////////////////////////////////////////
AZ::Debug::Timer m_updateTimer;
AZStd::vector<AzToolsFramework::PropertyHandlerBase*> m_propertyHandlers;
#endif // EMOTIONFXANIMATION_EDITOR
AZ::u32 m_numThreads;
private:
AZStd::vector<AZStd::unique_ptr<AZ::Data::AssetHandler> > m_assetHandlers;
AZStd::unique_ptr<EMotionFXEventHandler> m_eventHandler;
AZStd::unique_ptr<RenderBackendManager> m_renderBackendManager;
};
}
}
@@ -0,0 +1,14 @@
#
# 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.
#
set(FILES
AnimationModule.cpp
)