Merge branch 'development' into Atom/jromnoa/ATOM-14767-update-build-config-and-s3-uploader

This commit is contained in:
jromnoa
2021-06-15 11:11:19 -07:00
20 changed files with 225 additions and 36 deletions
@@ -274,7 +274,7 @@ namespace AZ
{
using Callable = AZStd::conditional_t<AZStd::function_traits<Invocable>::value, AZStd::function<typename AZStd::function_traits<Invocable>::function_type>, Invocable>;
public:
AZ_RTTI((AttributeInvocable<Invocable>, "{60D5804F-9AF4-4EB1-8F5A-62AFB4883F9D}"), AZ::Attribute);
AZ_RTTI((AttributeInvocable<Invocable>, "{60D5804F-9AF4-4EB1-8F5A-62AFB4883F9D}", Invocable), AZ::Attribute);
AZ_CLASS_ALLOCATOR(AttributeInvocable<Invocable>, SystemAllocator, 0);
template<typename CallableType>
explicit AttributeInvocable(CallableType&& invocable)
@@ -2010,6 +2010,28 @@ TEST_F(SerializeBasicTest, BasicTypeTest_Succeed)
}
/*
This test will dynamic cast (azrtti_cast) between incompatible types, which should always result in nullptr.
If this test fails, the RTTI declaration for the relevant type is incorrect.
*/
TEST_F(Serialization, AttributeRTTI)
{
{
AttributeInvocable<AZStd::function<AZStd::string(AZStd::string)>> fn([](AZStd::string x) { return x + x; });
Attribute* fnDownCast = &fn;
auto fnUpCast = azrtti_cast<AttributeInvocable<AZStd::function<int(int)>>*>(fnDownCast);
EXPECT_EQ(fnUpCast, nullptr);
}
{
AttributeFunction<AZStd::string(AZStd::string)> fn([](AZStd::string x) { return x + x; });
Attribute* fnDownCast = &fn;
auto fnUpCast = azrtti_cast<AttributeFunction<int(int)>*>(fnDownCast);
EXPECT_EQ(fnUpCast, nullptr);
}
}
/*
* Deprecation
*/
+2 -1
View File
@@ -181,7 +181,8 @@ void CViewportTitleDlg::SetupCameraDropdownMenu()
auto comboBoxTextChanged = static_cast<void (QComboBox::*)(const QString&)>(&QComboBox::currentTextChanged);
SetSpeedComboBox(cameraMoveSpeed);
m_cameraSpeed->setInsertPolicy(QComboBox::NoInsert);
m_cameraSpeed->setInsertPolicy(QComboBox::InsertAtBottom);
m_cameraSpeed->setDuplicatesEnabled(false);
connect(m_cameraSpeed, comboBoxTextChanged, this, &CViewportTitleDlg::OnUpdateMoveSpeedText);
connect(m_cameraSpeed->lineEdit(), &QLineEdit::returnPressed, this, &CViewportTitleDlg::OnSpeedComboBoxEnter);
+2 -2
View File
@@ -117,11 +117,11 @@ protected:
// Speed combobox/lineEdit settings
double m_minSpeed = 0.01;
double m_maxSpeed = 100.0;
double m_speedStep = 0.01;
double m_speedStep = 0.001;
int m_numDecimals = 3;
// Speed presets
float m_speedPresetValues[3] = { 0.1f, 1.0f, 10.0f };
float m_speedPresetValues[4] = { 0.01f, 0.1f, 1.0f, 10.0f };
double m_fieldWidthMultiplier = 1.8;
@@ -109,14 +109,7 @@ namespace ImageProcessingAtom
while (!m_process->IsFinished() && !IsJobCancelled())
{
m_process->UpdateProcess();
if (m_isPreview)
{
m_output->SetProgress(m_process->GetProgress() / static_cast<float>(m_previewProcessStep));
}
else
{
m_output->SetProgress(m_process->GetProgress());
}
m_output->SetProgress(m_process->GetProgress() / static_cast<float>(m_previewProcessStep));
}
IImageObjectPtr outputImage = m_process->GetOutputImage();
@@ -125,7 +118,7 @@ namespace ImageProcessingAtom
m_output->SetOutputImage(outputImage, ImageConvertOutput::Base);
m_output->SetOutputImage(outputImageAlpha, ImageConvertOutput::Alpha);
if (m_isPreview && !IsJobCancelled())
if (!IsJobCancelled())
{
// For preview, combine image output with alpha if any
m_output->SetProgress(1.0f / static_cast<float>(m_previewProcessStep));
@@ -72,7 +72,6 @@ namespace ImageProcessingAtom
static const int m_previewProcessStep = 2;
AZStd::unique_ptr<ImageConvertProcess> m_process;
bool m_isPreview;
AZStd::atomic_bool m_isCancelled;
ImageConvertOutput* m_output;
};
@@ -95,6 +95,8 @@ namespace AZ
TransformServiceFeatureProcessorInterface::ObjectId m_objectId;
Aabb m_aabb = Aabb::CreateNull();
bool m_cullBoundsNeedsUpdate = false;
bool m_cullableNeedsRebuild = false;
bool m_objectSrgNeedsUpdate = true;
@@ -160,6 +162,9 @@ namespace AZ
Transform GetTransform(const MeshHandle& meshHandle) override;
Vector3 GetNonUniformScale(const MeshHandle& meshHandle) override;
void SetLocalAabb(const MeshHandle& meshHandle, const AZ::Aabb& localAabb) override;
AZ::Aabb GetLocalAabb(const MeshHandle& meshHandle) const override;
void SetSortKey(const MeshHandle& meshHandle, RHI::DrawItemSortKey sortKey) override;
RHI::DrawItemSortKey GetSortKey(const MeshHandle& meshHandle) override;
@@ -86,6 +86,10 @@ namespace AZ
virtual Transform GetTransform(const MeshHandle& meshHandle) = 0;
//! Gets the non-uniform scale for a given mesh handle.
virtual Vector3 GetNonUniformScale(const MeshHandle& meshHandle) = 0;
//! Sets the local space bbox for a given mesh handle. You don't need to call this for static models, only skinned/animated models
virtual void SetLocalAabb(const MeshHandle& meshHandle, const AZ::Aabb& localAabb) = 0;
//! Gets the local space bbox for a given mesh handle. Unless SetLocalAabb has been called before, this will be the bbox of the model asset
virtual AZ::Aabb GetLocalAabb(const MeshHandle& meshHandle) const = 0;
//! Sets the sort key for a given mesh handle.
virtual void SetSortKey(const MeshHandle& meshHandle, RHI::DrawItemSortKey sortKey) = 0;
//! Gets the sort key for a given mesh handle.
@@ -33,6 +33,8 @@ namespace UnitTest
MOCK_METHOD2(SetMaterialAssignmentMap, void(const MeshHandle&, const AZ::Render::MaterialAssignmentMap&));
MOCK_METHOD1(GetTransform, AZ::Transform(const MeshHandle&));
MOCK_METHOD1(GetNonUniformScale, AZ::Vector3(const MeshHandle&));
MOCK_METHOD2(SetLocalAabb, void(const MeshHandle&, const AZ::Aabb&));
MOCK_CONST_METHOD1(GetLocalAabb, AZ::Aabb(const MeshHandle&));
MOCK_METHOD2(SetSortKey, void (const MeshHandle&, AZ::RHI::DrawItemSortKey));
MOCK_METHOD1(GetSortKey, AZ::RHI::DrawItemSortKey(const MeshHandle&));
MOCK_METHOD2(SetLodOverride, void(const MeshHandle&, AZ::RPI::Cullable::LodOverride));
@@ -304,6 +304,30 @@ namespace AZ
}
}
void MeshFeatureProcessor::SetLocalAabb(const MeshHandle& meshHandle, const AZ::Aabb& localAabb)
{
if (meshHandle.IsValid())
{
MeshDataInstance& meshData = *meshHandle;
meshData.m_aabb = localAabb;
meshData.m_cullBoundsNeedsUpdate = true;
meshData.m_objectSrgNeedsUpdate = true;
}
};
AZ::Aabb MeshFeatureProcessor::GetLocalAabb(const MeshHandle& meshHandle) const
{
if (meshHandle.IsValid())
{
return meshHandle->m_aabb;
}
else
{
AZ_Assert(false, "Invalid mesh handle");
return Aabb::CreateNull();
}
}
Transform MeshFeatureProcessor::GetTransform(const MeshHandle& meshHandle)
{
if (meshHandle.IsValid())
@@ -603,6 +627,8 @@ namespace AZ
SetRayTracingData();
}
m_aabb = model->GetModelAsset()->GetAabb();
m_cullableNeedsRebuild = true;
m_cullBoundsNeedsUpdate = true;
m_objectSrgNeedsUpdate = true;
@@ -996,7 +1022,7 @@ namespace AZ
RPI::Cullable::CullData& cullData = m_cullable.m_cullData;
RPI::Cullable::LodData& lodData = m_cullable.m_lodData;
const Aabb& localAabb = m_model->GetAabb();
const Aabb& localAabb = m_aabb;
lodData.m_lodSelectionRadius = 0.5f*localAabb.GetExtents().GetMaxElement();
const size_t modelLodCount = m_model->GetLodCount();
@@ -1077,7 +1103,7 @@ namespace AZ
Vector3 center;
float radius;
Aabb localAabb = m_model->GetAabb();
Aabb localAabb = m_aabb;
localAabb.MultiplyByScale(nonUniformScale);
localAabb.GetTransformedAabb(localToWorld).GetAsSphere(center, radius);
@@ -32,6 +32,7 @@ namespace AZ
: public Data::InstanceData
{
friend class ModelSystem;
public:
AZ_INSTANCE_DATA(Model, "{C30F5522-B381-4B38-BBAF-6E0B1885C8B9}");
AZ_CLASS_ALLOCATOR(Model, AZ::SystemAllocator, 0);
@@ -53,8 +54,6 @@ namespace AZ
//! Returns whether a buffer upload is pending.
bool IsUploadPending() const;
const AZ::Aabb& GetAabb() const;
const Data::Asset<ModelAsset>& GetModelAsset() const;
//! Checks a ray for intersection against this model. The ray must be in the same coordinate space as the model.
@@ -105,8 +104,6 @@ namespace AZ
// Tracks whether buffers have all been streamed up to the GPU.
bool m_isUploadPending = false;
AZ::Aabb m_aabb;
};
} // namespace RPI
} // namespace AZ
@@ -62,8 +62,6 @@ namespace AZ
{
AZ_PROFILE_FUNCTION(Debug::ProfileCategory::AzRender);
m_aabb = modelAsset.GetAabb();
m_lods.resize(modelAsset.GetLodAssets().size());
for (size_t lodIndex = 0; lodIndex < m_lods.size(); ++lodIndex)
@@ -127,11 +125,6 @@ namespace AZ
return m_isUploadPending;
}
const AZ::Aabb& Model::GetAabb() const
{
return m_aabb;
}
const Data::Asset<ModelAsset>& Model::GetModelAsset() const
{
return m_modelAsset;
@@ -140,9 +133,16 @@ namespace AZ
bool Model::LocalRayIntersection(const AZ::Vector3& rayStart, const AZ::Vector3& rayDir, float& distanceNormalized, AZ::Vector3& normal) const
{
AZ_PROFILE_FUNCTION(Debug::ProfileCategory::AzRender);
if (!GetModelAsset())
{
AZ_Assert(false, "Invalid Model - not created from a ModelAsset?");
return false;
}
float start;
float end;
const int result = Intersect::IntersectRayAABB2(rayStart, rayDir.GetReciprocal(), m_aabb, start, end);
const int result = Intersect::IntersectRayAABB2(rayStart, rayDir.GetReciprocal(), GetModelAsset()->GetAabb(), start, end);
if (Intersect::ISECT_RAY_AABB_NONE != result)
{
if (ModelAsset* modelAssetPtr = m_modelAsset.Get())
@@ -49,7 +49,7 @@ namespace AZ
With that percentage we can determine which Lod we want to use.
*/
Aabb modelAabb = model.GetAabb();
Aabb modelAabb = model.GetModelAsset()->GetAabb();
modelAabb.Translate(position);
Vector3 center;
@@ -460,10 +460,10 @@ namespace AZ
Aabb MeshComponentController::GetLocalBounds()
{
const Data::Instance<RPI::Model> model = GetModel();
if (model)
if (m_meshHandle.IsValid() && m_meshFeatureProcessor)
{
Aabb aabb = model->GetAabb();
Aabb aabb = m_meshFeatureProcessor->GetLocalAabb(m_meshHandle);
aabb.MultiplyByScale(m_cachedNonUniformScale);
return aabb;
}
@@ -83,12 +83,20 @@ namespace AZ
void AtomActorInstance::UpdateBounds()
{
// Update RenderActorInstance world bounding box
// The bounding box is moving with the actor instance. It is static in the way that it does not change shape.
// The bounding box is moving with the actor instance.
// The entity and actor transforms are kept in sync already.
m_worldAABB = AZ::Aabb::CreateFromMinMax(m_actorInstance->GetAABB().GetMin(), m_actorInstance->GetAABB().GetMax());
// Update RenderActorInstance local bounding box
m_localAABB = AZ::Aabb::CreateFromMinMax(m_actorInstance->GetStaticBasedAABB().GetMin(), m_actorInstance->GetStaticBasedAABB().GetMax());
// NB: computing the local bbox from the world bbox makes the local bbox artifically larger than it should be
// instead EMFX should support getting the local bbox from the actor instance directly
m_localAABB = m_worldAABB.GetTransformedAabb(m_transformInterface->GetWorldTM().GetInverse());
// Update bbox on mesh instance if it exists
if (m_meshFeatureProcessor && m_meshHandle && m_meshHandle->IsValid() && m_skinnedMeshInstance)
{
m_meshFeatureProcessor->SetLocalAabb(*m_meshHandle, m_localAABB);
}
AZ::Interface<AzFramework::IEntityBoundsUnion>::Get()->RefreshEntityLocalBoundsUnion(m_entityId);
}
@@ -57,14 +57,68 @@ namespace EMotionFX
}
};
//////////////////////////////////////////////////////////////////////////
void ActorComponent::BoundingBoxConfiguration::Set(ActorInstance* actor) const
{
if (m_autoUpdateBounds)
{
actor->SetupAutoBoundsUpdate(m_updateTimeFrequency, m_boundsType, m_updateItemFrequency);
}
else
{
actor->SetBoundsUpdateType(m_boundsType);
actor->SetBoundsUpdateEnabled(false);
}
}
void ActorComponent::BoundingBoxConfiguration::SetAndUpdate(ActorInstance* actor) const
{
Set(actor);
const AZ::u32 freq = actor->GetBoundsUpdateEnabled() ? actor->GetBoundsUpdateItemFrequency() : 1;
actor->UpdateBounds(0, actor->GetBoundsUpdateType(), freq);
}
void ActorComponent::BoundingBoxConfiguration::Reflect(AZ::ReflectContext * context)
{
if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<BoundingBoxConfiguration>()
->Version(2, [](AZ::SerializeContext& sc, AZ::SerializeContext::DataElementNode& node)
{
if (node.GetVersion() < 2)
{
// m_boundsType used to be an enum class with `int' underlying type, is now `u8'
static const char* m_boundsType_name = "m_boundsType";
static AZ::Crc32 m_boundsType_nameCrc(m_boundsType_name);
int m_boundsType_as_int;
if (!node.GetChildData(m_boundsType_nameCrc, m_boundsType_as_int))
{
return false;
}
if (!node.RemoveElementByName(m_boundsType_nameCrc)) return false;
if (node.AddElementWithData(sc, m_boundsType_name, (AZ::u8)m_boundsType_as_int) == -1) return false;
}
return true;
})
->Field("m_boundsType", &BoundingBoxConfiguration::m_boundsType)
->Field("m_autoUpdateBounds", &BoundingBoxConfiguration::m_autoUpdateBounds)
->Field("m_updateTimeFrequency", &BoundingBoxConfiguration::m_updateTimeFrequency)
->Field("m_updateItemFrequency", &BoundingBoxConfiguration::m_updateItemFrequency)
;
}
}
//////////////////////////////////////////////////////////////////////////
void ActorComponent::Configuration::Reflect(AZ::ReflectContext* context)
{
BoundingBoxConfiguration::Reflect(context);
auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<Configuration>()
->Version(3)
->Version(4)
->Field("ActorAsset", &Configuration::m_actorAsset)
->Field("MaterialPerLOD", &Configuration::m_materialPerLOD)
->Field("RenderSkeleton", &Configuration::m_renderSkeleton)
@@ -74,6 +128,7 @@ namespace EMotionFX
->Field("AttachmentTarget", &Configuration::m_attachmentTarget)
->Field("SkinningMethod", &Configuration::m_skinningMethod)
->Field("LODLevel", &Configuration::m_lodLevel)
->Field("BoundingBoxConfig", &Configuration::m_bboxConfig)
->Field("ForceJointsUpdateOOV", &Configuration::m_forceUpdateJointsOOV)
;
}
@@ -349,6 +404,8 @@ namespace EMotionFX
AZ::TransformNotificationBus::MultiHandler::BusConnect(GetEntityId());
m_actorInstance->UpdateWorldTransform();
// Set bounds update mode and compute bbox first time
m_configuration.m_bboxConfig.SetAndUpdate(m_actorInstance.get());
m_actorInstance->UpdateBounds(0, ActorInstance::EBoundsType::BOUNDS_STATIC_BASED);
// Creating the render actor AFTER both actor asset and mesh asset loaded.
@@ -49,6 +49,26 @@ namespace EMotionFX
AZ_COMPONENT(ActorComponent, "{BDC97E7F-A054-448B-A26F-EA2B5D78E377}");
friend class EditorActorComponent;
struct BoundingBoxConfiguration
{
AZ_TYPE_INFO(BoundingBoxConfiguration, "{EBCFF975-00A5-4578-85C7-59909F52067C}");
BoundingBoxConfiguration() = default;
EMotionFX::ActorInstance::EBoundsType m_boundsType = EMotionFX::ActorInstance::BOUNDS_STATIC_BASED;
bool m_autoUpdateBounds = true;
float m_updateTimeFrequency = 0.f;
AZ::u32 m_updateItemFrequency = 1;
// Set the bounding box configuration of the given actor instance to the parameters given by `this'. The actor instance must not be null (this is not checked).
void Set(ActorInstance* inst) const;
// Set the bounding box configuration, then update the bounds of the actor instance
void SetAndUpdate(ActorInstance* inst) const;
static void Reflect(AZ::ReflectContext* context);
};
/**
* Configuration struct for procedural configuration of Actor Components.
*/
@@ -71,6 +91,7 @@ namespace EMotionFX
// default, joints level update (beside the root joint) on
// actor are disabled when the actor is out of view.
bool m_forceUpdateJointsOOV = false;
BoundingBoxConfiguration m_bboxConfig; ///< Configuration for bounding box type and updates
static void Reflect(AZ::ReflectContext* context);
};
@@ -58,11 +58,49 @@ namespace EMotionFX
->Field("SkinningMethod", &EditorActorComponent::m_skinningMethod)
->Field("UpdateJointTransformsWhenOutOfView", &EditorActorComponent::m_forceUpdateJointsOOV)
->Field("LodLevel", &EditorActorComponent::m_lodLevel)
->Field("BBoxConfig", &EditorActorComponent::m_bboxConfig)
;
AZ::EditContext* editContext = serializeContext->GetEditContext();
if (editContext)
{
editContext->Class<ActorComponent::BoundingBoxConfiguration>("Actor Bounding Box Config", "")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->DataElement(AZ::Edit::UIHandlers::ComboBox, &ActorComponent::BoundingBoxConfiguration::m_boundsType,
"Bounds type",
"The method used to compute the Actor bounding box. NOTE: ordered by least expensive to compute to most expensive to compute."
)
->EnumAttribute(ActorInstance::BOUNDS_STATIC_BASED, "Static bounds (source-asset bounds)")
->EnumAttribute(ActorInstance::BOUNDS_NODE_BASED, "Bone position-based")
->EnumAttribute(ActorInstance::BOUNDS_NODEOBB_BASED, "Bone local bounding box-based")
->EnumAttribute(ActorInstance::BOUNDS_MESH_BASED, "Render mesh vertex position-based (VERY EXPENSIVE)")
->DataElement(0, &ActorComponent::BoundingBoxConfiguration::m_autoUpdateBounds,
"Automatically update bounds?",
"If true, bounds are automatically updated based on some frequency. Otherwise bounds are computed only at creation or when triggered manually"
)
->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::AttributesAndValues)
->DataElement(0, &ActorComponent::BoundingBoxConfiguration::m_updateTimeFrequency,
"Update frequency",
"How often to update bounds automatically"
)
->Attribute(AZ::Edit::Attributes::Suffix, " Hz")
->Attribute(AZ::Edit::Attributes::Min, 0.f)
->Attribute(AZ::Edit::Attributes::Step, 0.001f)
->Attribute(AZ::Edit::Attributes::Visibility, &ActorComponent::BoundingBoxConfiguration::m_autoUpdateBounds)
->DataElement(0, &ActorComponent::BoundingBoxConfiguration::m_updateItemFrequency,
"Update item skip factor",
"How many items (bones or vertices) to skip when automatically updating bounds."
" <br> i.e. =1 uses every single item, =2 uses every 2nd item, =3 uses every 3rd item... "
)
->Attribute(AZ::Edit::Attributes::Suffix, " items")
->Attribute(AZ::Edit::Attributes::Min, (AZ::u32)1)
->Attribute(AZ::Edit::Attributes::Visibility, &ActorComponent::BoundingBoxConfiguration::m_autoUpdateBounds)
;
editContext->Class<EditorActorComponent>("Actor", "The Actor component manages an instance of an Actor")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Category, "Animation")
@@ -118,6 +156,9 @@ namespace EMotionFX
->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.")
->DataElement(0, &EditorActorComponent::m_bboxConfig,
"Bounding box configuration", "")
->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorActorComponent::OnBBoxConfigChanged)
;
}
}
@@ -433,6 +474,14 @@ namespace EMotionFX
return refreshLevel;
}
void EditorActorComponent::OnBBoxConfigChanged()
{
if (m_actorInstance)
{
m_bboxConfig.SetAndUpdate(m_actorInstance.get());
}
}
void EditorActorComponent::LaunchAnimationEditor(const AZ::Data::AssetId& assetId, const AZ::Data::AssetType&)
{
// call to open must be done before LoadCharacter
@@ -571,6 +620,7 @@ namespace EMotionFX
cfg.m_attachmentJointIndex = m_attachmentJointIndex;
cfg.m_lodLevel = m_lodLevel;
cfg.m_skinningMethod = m_skinningMethod;
cfg.m_bboxConfig = m_bboxConfig;
cfg.m_forceUpdateJointsOOV = m_forceUpdateJointsOOV;
gameEntity->AddComponent(aznew ActorComponent(&cfg));
@@ -846,6 +896,7 @@ namespace EMotionFX
// Force an update of node transforms so we can get an accurate bounding box.
m_actorInstance->UpdateTransformations(0.0f, true, false);
OnBBoxConfigChanged(); // Apply BBox config
// Creating the render actor AFTER both actor asset and mesh asset loaded.
RenderBackend* renderBackend = AZ::Interface<RenderBackendManager>::Get()->GetRenderBackend();
@@ -898,4 +949,3 @@ namespace EMotionFX
}
} //namespace Integration
} // namespace EMotionFX
@@ -121,6 +121,7 @@ namespace EMotionFX
AZ::Crc32 OnAttachmentTypeChanged();
AZ::Crc32 OnAttachmentTargetChanged();
AZ::Crc32 OnAttachmentTargetJointSelect();
void OnBBoxConfigChanged();
bool AttachmentTargetVisibility();
bool AttachmentTargetJointVisibility();
AZStd::string AttachmentJointButtonText();
@@ -163,6 +164,7 @@ namespace EMotionFX
AZStd::string m_attachmentJointName; ///< Joint name on target to which to attach (if ActorAttachment).
AZ::u32 m_attachmentJointIndex;
AZ::u32 m_lodLevel;
ActorComponent::BoundingBoxConfiguration m_bboxConfig;
bool m_forceUpdateJointsOOV = false;
// \todo attachmentTarget node nr
+2
View File
@@ -123,6 +123,8 @@ if (PAL_TRAIT_BUILD_HOST_TOOLS)
BUILD_DEPENDENCIES
PRIVATE
Gem::Multiplayer.Builders.Static
RUNTIME_DEPENDENCIES
Gem::Multiplayer.Editor
)
ly_add_target(