Merge branch 'stabilization/2106' into JsonSerialization/UnsupportedWarnings

main
AMZN-koppersr 5 years ago
commit ccbb0f45f5

@ -41,7 +41,7 @@ def PrefabLevel_OpensLevelWithEntities():
EXPECTED_EMPTY_ENTITY_POS = Vector3(10.00, 20.0, 30.0) EXPECTED_EMPTY_ENTITY_POS = Vector3(10.00, 20.0, 30.0)
helper.init_idle() helper.init_idle()
helper.open_level("prefab", "PrefabLevel_OpensLevelWithEntities") helper.open_level("Prefab", "PrefabLevel_OpensLevelWithEntities")
def find_entity(entity_name): def find_entity(entity_name):
searchFilter = entity.SearchFilter() searchFilter = entity.SearchFilter()

@ -1,5 +1,5 @@
{ {
"Source": "Levels/PrefabLevel_OpensLevelWithEntities/PrefabLevel_OpensLevelWithEntities.prefab", "Source": "Levels/Prefab/PrefabLevel_OpensLevelWithEntities/PrefabLevel_OpensLevelWithEntities.prefab",
"ContainerEntity": { "ContainerEntity": {
"Id": "Entity_[403811863694]", "Id": "Entity_[403811863694]",
"Name": "Level", "Name": "Level",
@ -15,7 +15,8 @@
"Component_[13764860261821571747]": { "Component_[13764860261821571747]": {
"$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent",
"Id": 13764860261821571747, "Id": 13764860261821571747,
"Parent Entity": "" "Parent Entity": "",
"Cached World Transform Parent": ""
}, },
"Component_[15844324401733835865]": { "Component_[15844324401733835865]": {
"$type": "EditorEntitySortComponent", "$type": "EditorEntitySortComponent",

@ -88,28 +88,28 @@ if(NOT INSTALLED_ENGINE)
# Add external subdirectories listed in the engine.json. LY_EXTERNAL_SUBDIRS is a cache variable so the user can add extra # Add external subdirectories listed in the engine.json. LY_EXTERNAL_SUBDIRS is a cache variable so the user can add extra
# external subdirectories # external subdirectories
add_engine_json_external_subdirectories() add_engine_json_external_subdirectories()
get_property(external_subdirs GLOBAL PROPERTY LY_EXTERNAL_SUBDIRS)
list(APPEND LY_EXTERNAL_SUBDIRS ${external_subdirs})
# Loop over the additional external subdirectories and invoke add_subdirectory on them
foreach(external_directory ${LY_EXTERNAL_SUBDIRS})
# Hash the extenal_directory name and append it to the Binary Directory section of add_subdirectory
# This is to deal with potential situations where multiple external directories has the same last directory name
# For example if D:/Company1/RayTracingGem and F:/Company2/Path/RayTracingGem were both added as a subdirectory
file(REAL_PATH ${external_directory} full_directory_path)
string(SHA256 full_directory_hash ${full_directory_path})
# Truncate the full_directory_hash down to 8 characters to avoid hitting the Windows 260 character path limit
# when the external subdirectory contains relative paths of significant length
string(SUBSTRING ${full_directory_hash} 0 8 full_directory_hash)
# Use the last directory as the suffix path to use for the Binary Directory
get_filename_component(directory_name ${external_directory} NAME)
add_subdirectory(${external_directory} ${CMAKE_BINARY_DIR}/External/${directory_name}-${full_directory_hash})
endforeach()
else() else()
ly_find_o3de_packages() ly_find_o3de_packages()
endif() endif()
get_property(external_subdirs GLOBAL PROPERTY LY_EXTERNAL_SUBDIRS)
list(APPEND LY_EXTERNAL_SUBDIRS ${external_subdirs})
# Loop over the additional external subdirectories and invoke add_subdirectory on them
foreach(external_directory ${LY_EXTERNAL_SUBDIRS})
# Hash the extenal_directory name and append it to the Binary Directory section of add_subdirectory
# This is to deal with potential situations where multiple external directories has the same last directory name
# For example if D:/Company1/RayTracingGem and F:/Company2/Path/RayTracingGem were both added as a subdirectory
file(REAL_PATH ${external_directory} full_directory_path)
string(SHA256 full_directory_hash ${full_directory_path})
# Truncate the full_directory_hash down to 8 characters to avoid hitting the Windows 260 character path limit
# when the external subdirectory contains relative paths of significant length
string(SUBSTRING ${full_directory_hash} 0 8 full_directory_hash)
# Use the last directory as the suffix path to use for the Binary Directory
get_filename_component(directory_name ${external_directory} NAME)
add_subdirectory(${external_directory} ${CMAKE_BINARY_DIR}/External/${directory_name}-${full_directory_hash})
endforeach()
################################################################################ ################################################################################
# Post-processing # Post-processing
################################################################################ ################################################################################

@ -161,7 +161,56 @@ namespace AZ
return "A pair is an fixed size collection of two elements."; return "A pair is an fixed size collection of two elements.";
} }
}; };
template<typename T>
void GetTypeNamesFold(AZStd::vector<AZStd::string>& result, AZ::BehaviorContext& context)
{
result.push_back(OnDemandPrettyName<T>::Get(context));
};
template<typename... T>
void GetTypeNames(AZStd::vector<AZStd::string>& result, AZ::BehaviorContext& context)
{
(GetTypeNamesFold<T>(result, context), ...);
};
template<typename T>
void GetTypeNamesFold(AZStd::string& result, AZ::BehaviorContext& context)
{
if (!result.empty())
{
result += ", ";
}
result += OnDemandPrettyName<T>::Get(context);
};
template<typename... T>
void GetTypeNames(AZStd::string& result, AZ::BehaviorContext& context)
{
(GetTypeNamesFold<T>(result, context), ...);
};
template<typename... T>
struct OnDemandPrettyName<AZStd::tuple<T...>>
{
static AZStd::string Get(AZ::BehaviorContext& context)
{
AZStd::string typeNames;
GetTypeNames<T...>(typeNames, context);
return AZStd::string::format("Tuple<%s>", typeNames.c_str());
}
};
template<typename... T>
struct OnDemandToolTip<AZStd::tuple<T...>>
{
static AZStd::string Get(AZ::BehaviorContext&)
{
return "A tuple is an fixed size collection of any number of any type of element.";
}
};
template<class Key, class MappedType, class Hasher, class EqualKey, class Allocator> template<class Key, class MappedType, class Hasher, class EqualKey, class Allocator>
struct OnDemandPrettyName< AZStd::unordered_map<Key, MappedType, Hasher, EqualKey, Allocator> > struct OnDemandPrettyName< AZStd::unordered_map<Key, MappedType, Hasher, EqualKey, Allocator> >
{ {

@ -813,20 +813,27 @@ namespace AZ
{ {
using ContainerType = AZStd::tuple<T...>; using ContainerType = AZStd::tuple<T...>;
template<size_t Index> template<typename Targ, size_t Index>
static void ReflectUnpackMethodFold(BehaviorContext::ClassBuilder<ContainerType>& builder) static void ReflectUnpackMethodFold(BehaviorContext::ClassBuilder<ContainerType>& builder, const AZStd::vector<AZStd::string>& typeNames)
{ {
const AZStd::string methodName = AZStd::string::format("Get%zu", Index); const AZStd::string methodName = AZStd::string::format("Get%zu", Index);
builder->Method(methodName.data(), [](ContainerType& value) { return AZStd::get<Index>(value); }) builder->Method(methodName.data(), [](ContainerType& thisPointer) { return AZStd::get<Index>(thisPointer); })
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All) ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::List)
->Attribute(AZ::ScriptCanvasAttributes::TupleGetFunctionIndex, Index) ->Attribute(AZ::ScriptCanvasAttributes::TupleGetFunctionIndex, Index)
; ;
builder->Property
( AZStd::string::format("element_%zu_%s", Index, typeNames[Index].c_str()).c_str()
, [](ContainerType& thisPointer) { return AZStd::get<Index>(thisPointer); }
, [](ContainerType& thisPointer, const Targ& element) { AZStd::get<Index>(thisPointer) = element; });
} }
template<size_t... Indices> template<typename... Targ, size_t... Indices>
static void ReflectUnpackMethods(BehaviorContext::ClassBuilder<ContainerType>& builder, AZStd::index_sequence<Indices...>) static void ReflectUnpackMethods(BehaviorContext::ClassBuilder<ContainerType>& builder, AZStd::index_sequence<Indices...>)
{ {
(ReflectUnpackMethodFold<Indices>(builder), ...); AZStd::vector<AZStd::string> typeNames;
ScriptCanvasOnDemandReflection::GetTypeNames<T...>(typeNames, *builder.m_context);
(ReflectUnpackMethodFold<Targ, Indices>(builder, typeNames), ...);
} }
static void Reflect(ReflectContext* context) static void Reflect(ReflectContext* context)
@ -851,9 +858,10 @@ namespace AZ
->Attribute(AZ::ScriptCanvasAttributes::TupleConstructorFunction, constructorHolder) ->Attribute(AZ::ScriptCanvasAttributes::TupleConstructorFunction, constructorHolder)
; ;
ReflectUnpackMethods(builder, AZStd::make_index_sequence<sizeof...(T)>{}); ReflectUnpackMethods<T...>(builder, AZStd::make_index_sequence<sizeof...(T)>{});
builder->Method("GetSize", []() { return AZStd::tuple_size<ContainerType>::value; }) builder->Method("GetSize", []() { return AZStd::tuple_size<ContainerType>::value; })
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All) ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::List)
; ;
} }
} }

@ -113,7 +113,8 @@ namespace AZ
//! Save a string to a file. Otherwise returns a failure with error message. //! Save a string to a file. Otherwise returns a failure with error message.
AZ::Outcome<void, AZStd::string> WriteFile(AZStd::string_view content, AZStd::string_view filePath); AZ::Outcome<void, AZStd::string> WriteFile(AZStd::string_view content, AZStd::string_view filePath);
//! Read a file into a string. Returns a failure with error message if the content could not be loaded. //! Read a file into a string. Returns a failure with error message if the content could not be loaded or if
//! the file size is larger than the max file size provided.
template<typename Container = AZStd::string> template<typename Container = AZStd::string>
AZ::Outcome<Container, AZStd::string> ReadFile(AZStd::string_view filePath, size_t maxFileSize = DefaultMaxFileSize); AZ::Outcome<Container, AZStd::string> ReadFile(AZStd::string_view filePath, size_t maxFileSize = DefaultMaxFileSize);
} }

@ -37,9 +37,10 @@ namespace AzPhysics
if (auto* behaviorContext = azdynamic_cast<AZ::BehaviorContext*>(context)) if (auto* behaviorContext = azdynamic_cast<AZ::BehaviorContext*>(context))
{ {
behaviorContext->Class<TriggerEvent>() behaviorContext->Class<TriggerEvent>()
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All) ->Attribute(AZ::Script::Attributes::Module, "physics")
->Method("GetTriggerEntityId", &TriggerEvent::GetTriggerEntityId) ->Attribute(AZ::Script::Attributes::Category, "Physics")
->Method("GetOtherEntityId", &TriggerEvent::GetOtherEntityId) ->Method("Get Trigger EntityId", &TriggerEvent::GetTriggerEntityId)
->Method("Get Other EntityId", &TriggerEvent::GetOtherEntityId)
; ;
} }
} }
@ -104,10 +105,11 @@ namespace AzPhysics
if (auto* behaviorContext = azdynamic_cast<AZ::BehaviorContext*>(context)) if (auto* behaviorContext = azdynamic_cast<AZ::BehaviorContext*>(context))
{ {
behaviorContext->Class<CollisionEvent>() behaviorContext->Class<CollisionEvent>()
->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All) ->Attribute(AZ::Script::Attributes::Module, "physics")
->Property("Contacts", BehaviorValueProperty(&CollisionEvent::m_contacts)) ->Attribute(AZ::Script::Attributes::Category, "Physics")
->Method("GetBody1EntityId", &CollisionEvent::GetBody1EntityId) ->Property("Contacts", BehaviorValueGetter(&CollisionEvent::m_contacts), nullptr)
->Method("GetBody2EntityId", &CollisionEvent::GetBody2EntityId) ->Method("Get Body 1 EntityId", &CollisionEvent::GetBody1EntityId)
->Method("Get Body 2 EntityId", &CollisionEvent::GetBody2EntityId)
; ;
} }
} }

@ -8,29 +8,25 @@
# remove or modify any license notices. This file is distributed on an "AS IS" BASIS, # 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. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# #
ly_get_list_relative_pal_filename(pal_dir ${CMAKE_CURRENT_LIST_DIR}/AzTest/Platform/${PAL_PLATFORM_NAME})
if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) ly_add_target(
NAME AzTest STATIC
ly_get_list_relative_pal_filename(pal_dir ${CMAKE_CURRENT_LIST_DIR}/AzTest/Platform/${PAL_PLATFORM_NAME}) NAMESPACE AZ
FILES_CMAKE
ly_add_target( AzTest/aztest_files.cmake
NAME AzTest STATIC ${pal_dir}/platform_${PAL_PLATFORM_NAME_LOWERCASE}_files.cmake
NAMESPACE AZ INCLUDE_DIRECTORIES
FILES_CMAKE PUBLIC
AzTest/aztest_files.cmake .
${pal_dir}/platform_${PAL_PLATFORM_NAME_LOWERCASE}_files.cmake ${pal_dir}
INCLUDE_DIRECTORIES BUILD_DEPENDENCIES
PUBLIC PUBLIC
. 3rdParty::googletest::GMock
${pal_dir} 3rdParty::googletest::GTest
BUILD_DEPENDENCIES 3rdParty::GoogleBenchmark
PUBLIC AZ::AzCore
3rdParty::googletest::GMock PLATFORM_INCLUDE_FILES
3rdParty::googletest::GTest ${pal_dir}/platform_${PAL_PLATFORM_NAME_LOWERCASE}.cmake
3rdParty::GoogleBenchmark )
AZ::AzCore
PLATFORM_INCLUDE_FILES
${pal_dir}/platform_${PAL_PLATFORM_NAME_LOWERCASE}.cmake
)
endif()

@ -42,6 +42,9 @@ namespace AzToolsFramework
bool detachedWindow = false; ///< set to true if the view pane should use a detached, non-dockable widget. This is to workaround a problem with QOpenGLWidget on macOS. Currently this has no effect on other platforms. bool detachedWindow = false; ///< set to true if the view pane should use a detached, non-dockable widget. This is to workaround a problem with QOpenGLWidget on macOS. Currently this has no effect on other platforms.
bool isDisabledInSimMode = false; ///< set to true if the view pane should not be openable from level editor menu when editor is in simulation mode. bool isDisabledInSimMode = false; ///< set to true if the view pane should not be openable from level editor menu when editor is in simulation mode.
bool showOnToolsToolbar = false; ///< set to true if the view pane should create a button on the tools toolbar to open/close the pane
QString toolbarIcon; ///< path to the icon to use for the toolbar button - only used if showOnToolsToolbar is set to true
}; };
} // namespace AzToolsFramework } // namespace AzToolsFramework

@ -185,13 +185,7 @@ namespace AzToolsFramework
bool PrefabEditorEntityOwnershipService::LoadFromStream(AZ::IO::GenericStream& stream, AZStd::string_view filename) bool PrefabEditorEntityOwnershipService::LoadFromStream(AZ::IO::GenericStream& stream, AZStd::string_view filename)
{ {
Reset(); Reset();
// Make loading from stream to behave the same in terms of filesize as regular loading of prefabs
// This may need to be revisited in the future for supporting higher sizes along with prefab loading
if (stream.GetLength() > Prefab::MaxPrefabFileSize)
{
AZ_Error("Prefab", false, "'%.*s' prefab content is bigger than the max supported size (%f MB)", AZ_STRING_ARG(filename), Prefab::MaxPrefabFileSize / (1024.f * 1024.f));
return false;
}
const size_t bufSize = stream.GetLength(); const size_t bufSize = stream.GetLength();
AZStd::unique_ptr<char[]> buf(new char[bufSize]); AZStd::unique_ptr<char[]> buf(new char[bufSize]);
AZ::IO::SizeType bytes = stream.Read(bufSize, buf.get()); AZ::IO::SizeType bytes = stream.Read(bufSize, buf.get());

@ -197,7 +197,7 @@ namespace AzToolsFramework
AZ::IO::PathView filePath, Prefab::InstanceOptionalReference instanceToParentUnder) override; AZ::IO::PathView filePath, Prefab::InstanceOptionalReference instanceToParentUnder) override;
Prefab::InstanceOptionalReference GetRootPrefabInstance() override; Prefab::InstanceOptionalReference GetRootPrefabInstance() override;
const AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>>& GetPlayInEditorAssetData() override; const AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>>& GetPlayInEditorAssetData() override;
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////

@ -47,8 +47,13 @@ namespace AzToolsFramework
virtual void AppendEntityAliasToPatchPaths(PrefabDom& providedPatch, const AZ::EntityId& entityId) = 0; virtual void AppendEntityAliasToPatchPaths(PrefabDom& providedPatch, const AZ::EntityId& entityId) = 0;
//! Updates the template links (updating instances) for the given templateId using the providedPatch //! Updates the template links (updating instances) for the given template and triggers propagation on its instances.
virtual bool PatchTemplate(PrefabDomValue& providedPatch, TemplateId templateId) = 0; //! @param providedPatch The patch to apply to the template.
//! @param templateId The id of the template to update.
//! @param instanceToExclude An optional reference to an instance of the template being updated that should not be refreshes as part of propagation.
//! Defaults to nullopt, which means that all instances will be refreshed.
//! @return True if the template was patched correctly, false if the operation failed.
virtual bool PatchTemplate(PrefabDomValue& providedPatch, TemplateId templateId, InstanceOptionalReference instanceToExclude = AZStd::nullopt) = 0;
virtual void ApplyPatchesToInstance(const AZ::EntityId& entityId, PrefabDom& patches, const Instance& instanceToAddPatches) = 0; virtual void ApplyPatchesToInstance(const AZ::EntityId& entityId, PrefabDom& patches, const Instance& instanceToAddPatches) = 0;

@ -172,7 +172,7 @@ namespace AzToolsFramework
} }
} }
bool InstanceToTemplatePropagator::PatchTemplate(PrefabDomValue& providedPatch, TemplateId templateId) bool InstanceToTemplatePropagator::PatchTemplate(PrefabDomValue& providedPatch, TemplateId templateId, InstanceOptionalReference instanceToExclude)
{ {
PrefabDom& templateDomReference = m_prefabSystemComponentInterface->FindTemplateDom(templateId); PrefabDom& templateDomReference = m_prefabSystemComponentInterface->FindTemplateDom(templateId);
@ -184,7 +184,7 @@ namespace AzToolsFramework
if (result.GetOutcome() == AZ::JsonSerializationResult::Outcomes::Success) if (result.GetOutcome() == AZ::JsonSerializationResult::Outcomes::Success)
{ {
m_prefabSystemComponentInterface->SetTemplateDirtyFlag(templateId, true); m_prefabSystemComponentInterface->SetTemplateDirtyFlag(templateId, true);
m_prefabSystemComponentInterface->PropagateTemplateChanges(templateId); m_prefabSystemComponentInterface->PropagateTemplateChanges(templateId, instanceToExclude);
return true; return true;
} }
else else

@ -37,7 +37,7 @@ namespace AzToolsFramework
InstanceOptionalReference GetTopMostInstanceInHierarchy(AZ::EntityId entityId); InstanceOptionalReference GetTopMostInstanceInHierarchy(AZ::EntityId entityId);
bool PatchTemplate(PrefabDomValue& providedPatch, TemplateId templateId) override; bool PatchTemplate(PrefabDomValue& providedPatch, TemplateId templateId, InstanceOptionalReference instanceToExclude = AZStd::nullopt) override;
void ApplyPatchesToInstance(const AZ::EntityId& entityId, PrefabDom& patches, const Instance& instanceToAddPatches) override; void ApplyPatchesToInstance(const AZ::EntityId& entityId, PrefabDom& patches, const Instance& instanceToAddPatches) override;

@ -56,7 +56,7 @@ namespace AzToolsFramework
AZ::Interface<InstanceUpdateExecutorInterface>::Unregister(this); AZ::Interface<InstanceUpdateExecutorInterface>::Unregister(this);
} }
void InstanceUpdateExecutor::AddTemplateInstancesToQueue(TemplateId instanceTemplateId) void InstanceUpdateExecutor::AddTemplateInstancesToQueue(TemplateId instanceTemplateId, InstanceOptionalReference instanceToExclude)
{ {
auto findInstancesResult = auto findInstancesResult =
m_templateInstanceMapperInterface->FindInstancesOwnedByTemplate(instanceTemplateId); m_templateInstanceMapperInterface->FindInstancesOwnedByTemplate(instanceTemplateId);
@ -70,9 +70,18 @@ namespace AzToolsFramework
return; return;
} }
Instance* instanceToExcludePtr = nullptr;
if (instanceToExclude.has_value())
{
instanceToExcludePtr = &(instanceToExclude->get());
}
for (auto instance : findInstancesResult->get()) for (auto instance : findInstancesResult->get())
{ {
m_instancesUpdateQueue.emplace_back(instance); if (instance != instanceToExcludePtr)
{
m_instancesUpdateQueue.emplace_back(instance);
}
} }
} }
@ -103,7 +112,7 @@ namespace AzToolsFramework
EntityIdList selectedEntityIds; EntityIdList selectedEntityIds;
ToolsApplicationRequestBus::BroadcastResult(selectedEntityIds, &ToolsApplicationRequests::GetSelectedEntities); ToolsApplicationRequestBus::BroadcastResult(selectedEntityIds, &ToolsApplicationRequests::GetSelectedEntities);
ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequests::SetSelectedEntities, EntityIdList()); PrefabDom instanceDomFromRootDocument;
// Process all instances in the queue, capped to the batch size. // Process all instances in the queue, capped to the batch size.
// Even though we potentially initialized the batch size to the queue, it's possible for the queue size to shrink // Even though we potentially initialized the batch size to the queue, it's possible for the queue size to shrink
@ -148,13 +157,62 @@ namespace AzToolsFramework
continue; continue;
} }
Template& currentTemplate = currentTemplateReference->get();
Instance::EntityList newEntities; Instance::EntityList newEntities;
if (PrefabDomUtils::LoadInstanceFromPrefabDom(*instanceToUpdate, newEntities, currentTemplate.GetPrefabDom()))
// Climb up to the root of the instance hierarchy from this instance
InstanceOptionalConstReference rootInstance = *instanceToUpdate;
AZStd::vector<InstanceOptionalConstReference> pathOfInstances;
while (rootInstance->get().GetParentInstance() != AZStd::nullopt)
{
pathOfInstances.emplace_back(rootInstance);
rootInstance = rootInstance->get().GetParentInstance();
}
AZStd::string aliasPathResult = "";
for (auto instanceIter = pathOfInstances.rbegin(); instanceIter != pathOfInstances.rend(); ++instanceIter)
{
aliasPathResult.append("/Instances/");
aliasPathResult.append((*instanceIter)->get().GetInstanceAlias());
}
PrefabDomPath rootPrefabDomPath(aliasPathResult.c_str());
PrefabDom& rootPrefabTemplateDom =
m_prefabSystemComponentInterface->FindTemplateDom(rootInstance->get().GetTemplateId());
auto instanceDomFromRootValue = rootPrefabDomPath.Get(rootPrefabTemplateDom);
if (!instanceDomFromRootValue)
{
AZ_Assert(
false,
"InstanceUpdateExecutor::UpdateTemplateInstancesInQueue - "
"Could not load Instance DOM from the top level ancestor's DOM.");
isUpdateSuccessful = false;
continue;
}
PrefabDomValueReference instanceDomFromRoot = *instanceDomFromRootValue;
if (!instanceDomFromRoot.has_value())
{
AZ_Assert(
false,
"InstanceUpdateExecutor::UpdateTemplateInstancesInQueue - "
"Could not load Instance DOM from the top level ancestor's DOM.");
isUpdateSuccessful = false;
continue;
}
// If a link was created for a nested instance before the changes were propagated,
// then we associate it correctly here
instanceDomFromRootDocument.CopyFrom(instanceDomFromRoot->get(), instanceDomFromRootDocument.GetAllocator());
if (PrefabDomUtils::LoadInstanceFromPrefabDom(*instanceToUpdate, newEntities, instanceDomFromRootDocument))
{ {
// If a link was created for a nested instance before the changes were propagated, Template& currentTemplate = currentTemplateReference->get();
// then we associate it correctly here instanceToUpdate->GetNestedInstances([&](AZStd::unique_ptr<Instance>& nestedInstance)
instanceToUpdate->GetNestedInstances([&](AZStd::unique_ptr<Instance>& nestedInstance) { {
if (nestedInstance->GetLinkId() != InvalidLinkId) if (nestedInstance->GetLinkId() != InvalidLinkId)
{ {
return; return;
@ -179,22 +237,11 @@ namespace AzToolsFramework
AzToolsFramework::EditorEntityContextRequestBus::Broadcast( AzToolsFramework::EditorEntityContextRequestBus::Broadcast(
&AzToolsFramework::EditorEntityContextRequests::HandleEntitiesAdded, newEntities); &AzToolsFramework::EditorEntityContextRequests::HandleEntitiesAdded, newEntities);
} }
else
{
AZ_Error(
"Prefab", false,
"InstanceUpdateExecutor::UpdateTemplateInstancesInQueue - "
"Could not load Instance from Prefab DOM of Template with Id '%llu' on file path '%s'.",
currentTemplateId, currentTemplate.GetFilePath().c_str());
isUpdateSuccessful = false;
}
} }
for (auto entityIdIterator = selectedEntityIds.begin(); entityIdIterator != selectedEntityIds.end(); entityIdIterator++) for (auto entityIdIterator = selectedEntityIds.begin(); entityIdIterator != selectedEntityIds.end(); entityIdIterator++)
{ {
// Since entities get recreated during propagation, we need to check whether the entities correspoding to the list // Since entities get recreated during propagation, we need to check whether the entities
// of selected entity ids are present or not. // corresponding to the list of selected entity ids are present or not.
AZ::Entity* entity = GetEntityById(*entityIdIterator); AZ::Entity* entity = GetEntityById(*entityIdIterator);
if (entity == nullptr) if (entity == nullptr)
{ {

@ -35,7 +35,7 @@ namespace AzToolsFramework
explicit InstanceUpdateExecutor(int instanceCountToUpdateInBatch = 0); explicit InstanceUpdateExecutor(int instanceCountToUpdateInBatch = 0);
void AddTemplateInstancesToQueue(TemplateId instanceTemplateId) override; void AddTemplateInstancesToQueue(TemplateId instanceTemplateId, InstanceOptionalReference instanceToExclude = AZStd::nullopt) override;
bool UpdateTemplateInstancesInQueue() override; bool UpdateTemplateInstancesInQueue() override;
virtual void RemoveTemplateInstanceFromQueue(const Instance* instance) override; virtual void RemoveTemplateInstanceFromQueue(const Instance* instance) override;

@ -27,7 +27,7 @@ namespace AzToolsFramework
virtual ~InstanceUpdateExecutorInterface() = default; virtual ~InstanceUpdateExecutorInterface() = default;
// Add all Instances of Template with given Id into a queue for updating them later. // Add all Instances of Template with given Id into a queue for updating them later.
virtual void AddTemplateInstancesToQueue(TemplateId instanceTemplateId) = 0; virtual void AddTemplateInstancesToQueue(TemplateId instanceTemplateId, InstanceOptionalReference instanceToExclude = AZStd::nullopt) = 0;
// Update Instances in the waiting queue. // Update Instances in the waiting queue.
virtual bool UpdateTemplateInstancesInQueue() = 0; virtual bool UpdateTemplateInstancesInQueue() = 0;

@ -74,7 +74,7 @@ namespace AzToolsFramework
return InvalidTemplateId; return InvalidTemplateId;
} }
auto readResult = AZ::Utils::ReadFile(GetFullPath(filePath).Native(), MaxPrefabFileSize); auto readResult = AZ::Utils::ReadFile(GetFullPath(filePath).Native(), AZStd::numeric_limits<size_t>::max());
if (!readResult.IsSuccess()) if (!readResult.IsSuccess())
{ {
AZ_Error( AZ_Error(

@ -21,8 +21,6 @@ namespace AzToolsFramework
{ {
namespace Prefab namespace Prefab
{ {
constexpr size_t MaxPrefabFileSize = 1024 * 1024;
/*! /*!
* PrefabLoaderInterface * PrefabLoaderInterface
* Interface for saving/loading Prefab files. * Interface for saving/loading Prefab files.

@ -242,11 +242,13 @@ namespace AzToolsFramework
m_instanceToTemplateInterface->GenerateDomForEntity(containerAfterReset, *containerEntity); m_instanceToTemplateInterface->GenerateDomForEntity(containerAfterReset, *containerEntity);
// Update the state of the entity // Update the state of the entity
PrefabUndoEntityUpdate* state = aznew PrefabUndoEntityUpdate(AZStd::to_string(static_cast<AZ::u64>(containerEntityId))); auto templateId = instanceToCreate->get().GetTemplateId();
state->SetParent(undoBatch.GetUndoBatch());
state->Capture(containerBeforeReset, containerAfterReset, containerEntityId);
state->Redo(); PrefabDom transformPatch;
m_instanceToTemplateInterface->GeneratePatch(transformPatch, containerBeforeReset, containerAfterReset);
m_instanceToTemplateInterface->AppendEntityAliasToPatchPaths(transformPatch, containerEntityId);
m_instanceToTemplateInterface->PatchTemplate(transformPatch, templateId);
} }
// This clears any entities marked as dirty due to reparenting of entities during the process of creating a prefab. // This clears any entities marked as dirty due to reparenting of entities during the process of creating a prefab.
@ -661,12 +663,12 @@ namespace AzToolsFramework
else else
{ {
Internal_HandleContainerOverride( Internal_HandleContainerOverride(
parentUndoBatch, entityId, patch, owningInstance->get().GetLinkId()); parentUndoBatch, entityId, patch, owningInstance->get().GetLinkId(), owningInstance->get().GetParentInstance());
} }
} }
else else
{ {
Internal_HandleEntityChange(parentUndoBatch, entityId, beforeState, afterState); Internal_HandleEntityChange(parentUndoBatch, entityId, beforeState, afterState, owningInstance);
if (isNewParentOwnedByDifferentInstance) if (isNewParentOwnedByDifferentInstance)
{ {
@ -679,25 +681,27 @@ namespace AzToolsFramework
} }
void PrefabPublicHandler::Internal_HandleContainerOverride( void PrefabPublicHandler::Internal_HandleContainerOverride(
UndoSystem::URSequencePoint* undoBatch, AZ::EntityId entityId, const PrefabDom& patch, const LinkId linkId) UndoSystem::URSequencePoint* undoBatch, AZ::EntityId entityId, const PrefabDom& patch,
const LinkId linkId, InstanceOptionalReference parentInstance)
{ {
// Save these changes as patches to the link // Save these changes as patches to the link
PrefabUndoLinkUpdate* linkUpdate = aznew PrefabUndoLinkUpdate(AZStd::to_string(static_cast<AZ::u64>(entityId))); PrefabUndoLinkUpdate* linkUpdate = aznew PrefabUndoLinkUpdate(AZStd::to_string(static_cast<AZ::u64>(entityId)));
linkUpdate->SetParent(undoBatch); linkUpdate->SetParent(undoBatch);
linkUpdate->Capture(patch, linkId); linkUpdate->Capture(patch, linkId);
linkUpdate->Redo(); linkUpdate->Redo(parentInstance);
} }
void PrefabPublicHandler::Internal_HandleEntityChange( void PrefabPublicHandler::Internal_HandleEntityChange(
UndoSystem::URSequencePoint* undoBatch, AZ::EntityId entityId, PrefabDom& beforeState, PrefabDom& afterState) UndoSystem::URSequencePoint* undoBatch, AZ::EntityId entityId, PrefabDom& beforeState,
PrefabDom& afterState, InstanceOptionalReference instance)
{ {
// Update the state of the entity // Update the state of the entity
PrefabUndoEntityUpdate* state = aznew PrefabUndoEntityUpdate(AZStd::to_string(static_cast<AZ::u64>(entityId))); PrefabUndoEntityUpdate* state = aznew PrefabUndoEntityUpdate(AZStd::to_string(static_cast<AZ::u64>(entityId)));
state->SetParent(undoBatch); state->SetParent(undoBatch);
state->Capture(beforeState, afterState, entityId); state->Capture(beforeState, afterState, entityId);
state->Redo(); state->Redo(instance);
} }
void PrefabPublicHandler::Internal_HandleInstanceChange( void PrefabPublicHandler::Internal_HandleInstanceChange(
@ -897,7 +901,13 @@ namespace AzToolsFramework
return AZ::Failure(AZStd::string("No entities to duplicate.")); return AZ::Failure(AZStd::string("No entities to duplicate."));
} }
if (!EntitiesBelongToSameInstance(entityIds)) const EntityIdList entityIdsNoLevelInstance = GenerateEntityIdListWithoutLevelInstance(entityIds);
if (entityIdsNoLevelInstance.empty())
{
return AZ::Failure(AZStd::string("No entities to duplicate because only instance selected is the level instance."));
}
if (!EntitiesBelongToSameInstance(entityIdsNoLevelInstance))
{ {
return AZ::Failure(AZStd::string("Cannot duplicate multiple entities belonging to different instances with one operation." return AZ::Failure(AZStd::string("Cannot duplicate multiple entities belonging to different instances with one operation."
"Change your selection to contain entities in the same instance.")); "Change your selection to contain entities in the same instance."));
@ -905,7 +915,7 @@ namespace AzToolsFramework
// We've already verified the entities are all owned by the same instance, // We've already verified the entities are all owned by the same instance,
// so we can just retrieve our instance from the first entity in the list. // so we can just retrieve our instance from the first entity in the list.
AZ::EntityId firstEntityIdToDuplicate = entityIds[0]; AZ::EntityId firstEntityIdToDuplicate = entityIdsNoLevelInstance[0];
InstanceOptionalReference commonOwningInstance = GetOwnerInstanceByEntityId(firstEntityIdToDuplicate); InstanceOptionalReference commonOwningInstance = GetOwnerInstanceByEntityId(firstEntityIdToDuplicate);
if (!commonOwningInstance.has_value()) if (!commonOwningInstance.has_value())
{ {
@ -925,7 +935,7 @@ namespace AzToolsFramework
// This will cull out any entities that have ancestors in the list, since we will end up duplicating // This will cull out any entities that have ancestors in the list, since we will end up duplicating
// the full nested hierarchy with what is returned from RetrieveAndSortPrefabEntitiesAndInstances // the full nested hierarchy with what is returned from RetrieveAndSortPrefabEntitiesAndInstances
AzToolsFramework::EntityIdSet duplicationSet = AzToolsFramework::GetCulledEntityHierarchy(entityIds); AzToolsFramework::EntityIdSet duplicationSet = AzToolsFramework::GetCulledEntityHierarchy(entityIdsNoLevelInstance);
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework); AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
@ -1000,17 +1010,19 @@ namespace AzToolsFramework
PrefabOperationResult PrefabPublicHandler::DeleteFromInstance(const EntityIdList& entityIds, bool deleteDescendants) PrefabOperationResult PrefabPublicHandler::DeleteFromInstance(const EntityIdList& entityIds, bool deleteDescendants)
{ {
if (entityIds.empty()) const EntityIdList entityIdsNoLevelInstance = GenerateEntityIdListWithoutLevelInstance(entityIds);
if (entityIdsNoLevelInstance.empty())
{ {
return AZ::Success(); return AZ::Success();
} }
if (!EntitiesBelongToSameInstance(entityIds)) if (!EntitiesBelongToSameInstance(entityIdsNoLevelInstance))
{ {
return AZ::Failure(AZStd::string("Cannot delete multiple entities belonging to different instances with one operation.")); return AZ::Failure(AZStd::string("Cannot delete multiple entities belonging to different instances with one operation."));
} }
AZ::EntityId firstEntityIdToDelete = entityIds[0]; AZ::EntityId firstEntityIdToDelete = entityIdsNoLevelInstance[0];
InstanceOptionalReference commonOwningInstance = GetOwnerInstanceByEntityId(firstEntityIdToDelete); InstanceOptionalReference commonOwningInstance = GetOwnerInstanceByEntityId(firstEntityIdToDelete);
// If the first entity id is a container entity id, then we need to mark its parent as the common owning instance because you // If the first entity id is a container entity id, then we need to mark its parent as the common owning instance because you
@ -1021,7 +1033,7 @@ namespace AzToolsFramework
} }
// Retrieve entityList from entityIds // Retrieve entityList from entityIds
EntityList inputEntityList = EntityIdListToEntityList(entityIds); EntityList inputEntityList = EntityIdListToEntityList(entityIdsNoLevelInstance);
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework); AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
@ -1077,7 +1089,7 @@ namespace AzToolsFramework
} }
else else
{ {
for (AZ::EntityId entityId : entityIds) for (AZ::EntityId entityId : entityIdsNoLevelInstance)
{ {
InstanceOptionalReference owningInstance = m_instanceEntityMapperInterface->FindOwningInstance(entityId); InstanceOptionalReference owningInstance = m_instanceEntityMapperInterface->FindOwningInstance(entityId);
// If this is the container entity, it actually represents the instance so get its owner // If this is the container entity, it actually represents the instance so get its owner
@ -1433,6 +1445,22 @@ namespace AzToolsFramework
return (outEntities.size() + outInstances.size()) > 0; return (outEntities.size() + outInstances.size()) > 0;
} }
EntityIdList PrefabPublicHandler::GenerateEntityIdListWithoutLevelInstance(
const EntityIdList& entityIds) const
{
EntityIdList outEntityIds;
outEntityIds.reserve(entityIds.size()); // Actual size could be smaller.
for (const AZ::EntityId& entityId : entityIds)
{
if (!IsLevelInstanceContainerEntity(entityId))
{
outEntityIds.emplace_back(entityId);
}
}
return outEntityIds;
}
bool PrefabPublicHandler::EntitiesBelongToSameInstance(const EntityIdList& entityIds) const bool PrefabPublicHandler::EntitiesBelongToSameInstance(const EntityIdList& entityIds) const
{ {
if (entityIds.size() <= 1) if (entityIds.size() <= 1)

@ -70,6 +70,7 @@ namespace AzToolsFramework
PrefabOperationResult DeleteFromInstance(const EntityIdList& entityIds, bool deleteDescendants); PrefabOperationResult DeleteFromInstance(const EntityIdList& entityIds, bool deleteDescendants);
bool RetrieveAndSortPrefabEntitiesAndInstances(const EntityList& inputEntities, Instance& commonRootEntityOwningInstance, bool RetrieveAndSortPrefabEntitiesAndInstances(const EntityList& inputEntities, Instance& commonRootEntityOwningInstance,
EntityList& outEntities, AZStd::vector<Instance*>& outInstances) const; EntityList& outEntities, AZStd::vector<Instance*>& outInstances) const;
EntityIdList GenerateEntityIdListWithoutLevelInstance(const EntityIdList& entityIds) const;
InstanceOptionalReference GetOwnerInstanceByEntityId(AZ::EntityId entityId) const; InstanceOptionalReference GetOwnerInstanceByEntityId(AZ::EntityId entityId) const;
bool EntitiesBelongToSameInstance(const EntityIdList& entityIds) const; bool EntitiesBelongToSameInstance(const EntityIdList& entityIds) const;
@ -162,9 +163,11 @@ namespace AzToolsFramework
InstanceOptionalConstReference instance, const AZStd::unordered_set<AZ::IO::Path>& templateSourcePaths); InstanceOptionalConstReference instance, const AZStd::unordered_set<AZ::IO::Path>& templateSourcePaths);
static void Internal_HandleContainerOverride( static void Internal_HandleContainerOverride(
UndoSystem::URSequencePoint* undoBatch, AZ::EntityId entityId, const PrefabDom& patch, const LinkId linkId); UndoSystem::URSequencePoint* undoBatch, AZ::EntityId entityId, const PrefabDom& patch,
const LinkId linkId, InstanceOptionalReference parentInstance = AZStd::nullopt);
static void Internal_HandleEntityChange( static void Internal_HandleEntityChange(
UndoSystem::URSequencePoint* undoBatch, AZ::EntityId entityId, PrefabDom& beforeState, PrefabDom& afterState); UndoSystem::URSequencePoint* undoBatch, AZ::EntityId entityId, PrefabDom& beforeState,
PrefabDom& afterState, InstanceOptionalReference instance = AZStd::nullopt);
void Internal_HandleInstanceChange(UndoSystem::URSequencePoint* undoBatch, AZ::Entity* entity, AZ::EntityId beforeParentId, AZ::EntityId afterParentId); void Internal_HandleInstanceChange(UndoSystem::URSequencePoint* undoBatch, AZ::Entity* entity, AZ::EntityId beforeParentId, AZ::EntityId afterParentId);
void UpdateLinkPatchesWithNewEntityAliases( void UpdateLinkPatchesWithNewEntityAliases(

@ -141,8 +141,10 @@ namespace AzToolsFramework
return newInstance; return newInstance;
} }
void PrefabSystemComponent::PropagateTemplateChanges(TemplateId templateId) void PrefabSystemComponent::PropagateTemplateChanges(TemplateId templateId, InstanceOptionalReference instanceToExclude)
{ {
UpdatePrefabInstances(templateId, instanceToExclude);
auto templateIdToLinkIdsIterator = m_templateToLinkIdsMap.find(templateId); auto templateIdToLinkIdsIterator = m_templateToLinkIdsMap.find(templateId);
if (templateIdToLinkIdsIterator != m_templateToLinkIdsMap.end()) if (templateIdToLinkIdsIterator != m_templateToLinkIdsMap.end())
{ {
@ -153,10 +155,6 @@ namespace AzToolsFramework
templateIdToLinkIdsIterator->second.end())); templateIdToLinkIdsIterator->second.end()));
UpdateLinkedInstances(linkIdsToUpdateQueue); UpdateLinkedInstances(linkIdsToUpdateQueue);
} }
else
{
UpdatePrefabInstances(templateId);
}
} }
void PrefabSystemComponent::UpdatePrefabTemplate(TemplateId templateId, const PrefabDom& updatedDom) void PrefabSystemComponent::UpdatePrefabTemplate(TemplateId templateId, const PrefabDom& updatedDom)
@ -174,9 +172,9 @@ namespace AzToolsFramework
} }
} }
void PrefabSystemComponent::UpdatePrefabInstances(const TemplateId& templateId) void PrefabSystemComponent::UpdatePrefabInstances(const TemplateId& templateId, InstanceOptionalReference instanceToExclude)
{ {
m_instanceUpdateExecutor.AddTemplateInstancesToQueue(templateId); m_instanceUpdateExecutor.AddTemplateInstancesToQueue(templateId, instanceToExclude);
} }
void PrefabSystemComponent::UpdateLinkedInstances(AZStd::queue<LinkIds>& linkIdsQueue) void PrefabSystemComponent::UpdateLinkedInstances(AZStd::queue<LinkIds>& linkIdsQueue)
@ -250,8 +248,6 @@ namespace AzToolsFramework
if (targetTemplateIdToLinkIdMap[targetTemplateId].first.empty() && if (targetTemplateIdToLinkIdMap[targetTemplateId].first.empty() &&
targetTemplateIdToLinkIdMap[targetTemplateId].second) targetTemplateIdToLinkIdMap[targetTemplateId].second)
{ {
UpdatePrefabInstances(targetTemplateId);
auto templateToLinkIter = m_templateToLinkIdsMap.find(targetTemplateId); auto templateToLinkIter = m_templateToLinkIdsMap.find(targetTemplateId);
if (templateToLinkIter != m_templateToLinkIdsMap.end()) if (templateToLinkIter != m_templateToLinkIdsMap.end())
{ {

@ -215,14 +215,14 @@ namespace AzToolsFramework
*/ */
void UpdatePrefabTemplate(TemplateId templateId, const PrefabDom& updatedDom) override; void UpdatePrefabTemplate(TemplateId templateId, const PrefabDom& updatedDom) override;
void PropagateTemplateChanges(TemplateId templateId) override; void PropagateTemplateChanges(TemplateId templateId, InstanceOptionalReference instanceToExclude = AZStd::nullopt) override;
/** /**
* Updates all Instances owned by a Template. * Updates all Instances owned by a Template.
* *
* @param templateId The id of the Template owning Instances to update. * @param templateId The id of the Template owning Instances to update.
*/ */
void UpdatePrefabInstances(const TemplateId& templateId); void UpdatePrefabInstances(const TemplateId& templateId, InstanceOptionalReference instanceToExclude = AZStd::nullopt);
private: private:
AZ_DISABLE_COPY_MOVE(PrefabSystemComponent); AZ_DISABLE_COPY_MOVE(PrefabSystemComponent);

@ -56,7 +56,7 @@ namespace AzToolsFramework
virtual PrefabDom& FindTemplateDom(TemplateId templateId) = 0; virtual PrefabDom& FindTemplateDom(TemplateId templateId) = 0;
virtual void UpdatePrefabTemplate(TemplateId templateId, const PrefabDom& updatedDom) = 0; virtual void UpdatePrefabTemplate(TemplateId templateId, const PrefabDom& updatedDom) = 0;
virtual void PropagateTemplateChanges(TemplateId templateId) = 0; virtual void PropagateTemplateChanges(TemplateId templateId, InstanceOptionalReference instanceToExclude = AZStd::nullopt) = 0;
virtual AZStd::unique_ptr<Instance> InstantiatePrefab(AZ::IO::PathView filePath) = 0; virtual AZStd::unique_ptr<Instance> InstantiatePrefab(AZ::IO::PathView filePath) = 0;
virtual AZStd::unique_ptr<Instance> InstantiatePrefab(const TemplateId& templateId) = 0; virtual AZStd::unique_ptr<Instance> InstantiatePrefab(const TemplateId& templateId) = 0;

@ -70,10 +70,10 @@ namespace AzToolsFramework
const AZ::EntityId& entityId) const AZ::EntityId& entityId)
{ {
//get the entity alias for future undo/redo //get the entity alias for future undo/redo
InstanceOptionalReference instanceOptionalReference = m_instanceEntityMapperInterface->FindOwningInstance(entityId); auto instanceReference = m_instanceEntityMapperInterface->FindOwningInstance(entityId);
AZ_Error("Prefab", instanceOptionalReference, AZ_Error("Prefab", instanceReference,
"Failed to find an owning instance for the entity with id %llu.", static_cast<AZ::u64>(entityId)); "Failed to find an owning instance for the entity with id %llu.", static_cast<AZ::u64>(entityId));
Instance& instance = instanceOptionalReference->get(); Instance& instance = instanceReference->get();
m_templateId = instance.GetTemplateId(); m_templateId = instance.GetTemplateId();
m_entityAlias = (instance.GetEntityAlias(entityId)).value(); m_entityAlias = (instance.GetEntityAlias(entityId)).value();
@ -106,6 +106,17 @@ namespace AzToolsFramework
m_templateId); m_templateId);
} }
void PrefabUndoEntityUpdate::Redo(InstanceOptionalReference instanceToExclude)
{
[[maybe_unused]] bool isPatchApplicationSuccessful =
m_instanceToTemplateInterface->PatchTemplate(m_redoPatch, m_templateId, instanceToExclude);
AZ_Error(
"Prefab", isPatchApplicationSuccessful,
"Applying the patch on the entity with alias '%s' in template with id '%llu' was unsuccessful", m_entityAlias.c_str(),
m_templateId);
}
//PrefabInstanceLinkUndo //PrefabInstanceLinkUndo
PrefabUndoInstanceLink::PrefabUndoInstanceLink(const AZStd::string& undoOperationName) PrefabUndoInstanceLink::PrefabUndoInstanceLink(const AZStd::string& undoOperationName)
: PrefabUndoBase(undoOperationName) : PrefabUndoBase(undoOperationName)
@ -290,7 +301,12 @@ namespace AzToolsFramework
UpdateLink(m_linkDomNext); UpdateLink(m_linkDomNext);
} }
void PrefabUndoLinkUpdate::UpdateLink(PrefabDom& linkDom) void PrefabUndoLinkUpdate::Redo(InstanceOptionalReference instanceToExclude)
{
UpdateLink(m_linkDomNext, instanceToExclude);
}
void PrefabUndoLinkUpdate::UpdateLink(PrefabDom& linkDom, InstanceOptionalReference instanceToExclude)
{ {
LinkReference link = m_prefabSystemComponentInterface->FindLink(m_linkId); LinkReference link = m_prefabSystemComponentInterface->FindLink(m_linkId);
@ -304,7 +320,7 @@ namespace AzToolsFramework
//propagate the link changes //propagate the link changes
link->get().UpdateTarget(); link->get().UpdateTarget();
m_prefabSystemComponentInterface->PropagateTemplateChanges(link->get().GetTargetTemplateId()); m_prefabSystemComponentInterface->PropagateTemplateChanges(link->get().GetTargetTemplateId(), instanceToExclude);
//mark as dirty //mark as dirty
m_prefabSystemComponentInterface->SetTemplateDirtyFlag(link->get().GetTargetTemplateId(), true); m_prefabSystemComponentInterface->SetTemplateDirtyFlag(link->get().GetTargetTemplateId(), true);

@ -71,11 +71,12 @@ namespace AzToolsFramework
void Capture( void Capture(
PrefabDom& initialState, PrefabDom& initialState,
PrefabDom& endState, PrefabDom& endState, const AZ::EntityId& entity);
const AZ::EntityId& entity);
void Undo() override; void Undo() override;
void Redo() override; void Redo() override;
//! Overload to allow to apply the change, but prevent instanceToExclude from being refreshed.
void Redo(InstanceOptionalReference instanceToExclude);
private: private:
InstanceEntityMapperInterface* m_instanceEntityMapperInterface = nullptr; InstanceEntityMapperInterface* m_instanceEntityMapperInterface = nullptr;
@ -139,9 +140,11 @@ namespace AzToolsFramework
void Undo() override; void Undo() override;
void Redo() override; void Redo() override;
//! Overload to allow to apply the change, but prevent instanceToExclude from being refreshed.
void Redo(InstanceOptionalReference instanceToExclude);
private: private:
void UpdateLink(PrefabDom& linkDom); void UpdateLink(PrefabDom& linkDom, InstanceOptionalReference instanceToExclude = AZStd::nullopt);
LinkId m_linkId; LinkId m_linkId;
PrefabDom m_linkDomNext; //data for delete/update PrefabDom m_linkDomNext; //data for delete/update

@ -2240,42 +2240,31 @@ namespace AzToolsFramework
RegenerateManipulators(); RegenerateManipulators();
}); });
bool isPrefabSystemEnabled = false; // duplicate selection
AzFramework::ApplicationRequests::Bus::BroadcastResult( AddAction(
isPrefabSystemEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled); m_actions, { QKeySequence(Qt::CTRL + Qt::Key_D) },
/*ID_EDIT_CLONE =*/33525, s_duplicateTitle, s_duplicateDesc,
bool prefabWipFeaturesEnabled = false; []()
AzFramework::ApplicationRequests::Bus::BroadcastResult( {
prefabWipFeaturesEnabled, &AzFramework::ApplicationRequests::ArePrefabWipFeaturesEnabled); AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
if (!isPrefabSystemEnabled || (isPrefabSystemEnabled && prefabWipFeaturesEnabled)) // Clear Widget selection - Prevents issues caused by cloning entities while a property in the Reflected Property Editor
{ // is being edited.
// duplicate selection if (QApplication::focusWidget())
AddAction(
m_actions, { QKeySequence(Qt::CTRL + Qt::Key_D) },
/*ID_EDIT_CLONE =*/33525, s_duplicateTitle, s_duplicateDesc,
[]()
{ {
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework); QApplication::focusWidget()->clearFocus();
}
// Clear Widget selection - Prevents issues caused by cloning entities while a property in the Reflected Property Editor
// is being edited.
if (QApplication::focusWidget())
{
QApplication::focusWidget()->clearFocus();
}
ScopedUndoBatch undoBatch(s_duplicateUndoRedoDesc); ScopedUndoBatch undoBatch(s_duplicateUndoRedoDesc);
auto selectionCommand = AZStd::make_unique<SelectionCommand>(EntityIdList(), s_duplicateUndoRedoDesc); auto selectionCommand = AZStd::make_unique<SelectionCommand>(EntityIdList(), s_duplicateUndoRedoDesc);
selectionCommand->SetParent(undoBatch.GetUndoBatch()); selectionCommand->SetParent(undoBatch.GetUndoBatch());
selectionCommand.release(); selectionCommand.release();
bool handled = false; bool handled = false;
EditorRequestBus::Broadcast(&EditorRequests::CloneSelection, handled); EditorRequestBus::Broadcast(&EditorRequests::CloneSelection, handled);
// selection update handled in AfterEntitySelectionChanged // selection update handled in AfterEntitySelectionChanged
}); });
}
// delete selection // delete selection
AddAction( AddAction(

@ -242,6 +242,7 @@ namespace UnitTest
// Patch the nested prefab to reference an entity in its parent // Patch the nested prefab to reference an entity in its parent
ASSERT_TRUE(m_instanceToTemplateInterface->PatchEntityInTemplate(patch, newEntity->GetId())); ASSERT_TRUE(m_instanceToTemplateInterface->PatchEntityInTemplate(patch, newEntity->GetId()));
m_instanceUpdateExecutorInterface->AddTemplateInstancesToQueue(rootInstance->GetTemplateId());
m_instanceUpdateExecutorInterface->UpdateTemplateInstancesInQueue(); m_instanceUpdateExecutorInterface->UpdateTemplateInstancesInQueue();
// Using the aliases we saved grab the updated entities so we can verify the entity reference is still preserved // Using the aliases we saved grab the updated entities so we can verify the entity reference is still preserved

@ -16,6 +16,7 @@ set_property(GLOBAL PROPERTY LAUNCHER_UNIFIED_BINARY_DIR ${CMAKE_CURRENT_BINARY_
# When using an installed engine, this file will be included by the FindLauncherGenerator.cmake script # When using an installed engine, this file will be included by the FindLauncherGenerator.cmake script
get_property(LY_PROJECTS_TARGET_NAME GLOBAL PROPERTY LY_PROJECTS_TARGET_NAME) get_property(LY_PROJECTS_TARGET_NAME GLOBAL PROPERTY LY_PROJECTS_TARGET_NAME)
foreach(project_name project_path IN ZIP_LISTS LY_PROJECTS_TARGET_NAME LY_PROJECTS) foreach(project_name project_path IN ZIP_LISTS LY_PROJECTS_TARGET_NAME LY_PROJECTS)
# Computes the realpath to the project # Computes the realpath to the project
# If the project_path is relative, it is evaluated relative to the ${LY_ROOT_FOLDER} # If the project_path is relative, it is evaluated relative to the ${LY_ROOT_FOLDER}
# Otherwise the the absolute project_path is returned with symlinks resolved # Otherwise the the absolute project_path is returned with symlinks resolved
@ -35,6 +36,28 @@ foreach(project_name project_path IN ZIP_LISTS LY_PROJECTS_TARGET_NAME LY_PROJEC
"to the LY_PROJECTS_TARGET_NAME global property. Other configuration errors might occur") "to the LY_PROJECTS_TARGET_NAME global property. Other configuration errors might occur")
endif() endif()
endif() endif()
################################################################################
# Assets
################################################################################
if(PAL_TRAIT_BUILD_HOST_TOOLS)
add_custom_target(${project_name}.Assets
COMMENT "Processing ${project_name} assets..."
COMMAND "${CMAKE_COMMAND}"
-DLY_LOCK_FILE=$<TARGET_FILE_DIR:AZ::AssetProcessorBatch>/project_assets.lock
-P ${LY_ROOT_FOLDER}/cmake/CommandExecution.cmake
EXEC_COMMAND $<TARGET_FILE:AZ::AssetProcessorBatch>
--zeroAnalysisMode
--project-path=${project_real_path}
--platforms=${LY_ASSET_DEPLOY_ASSET_TYPE}
)
set_target_properties(${project_name}.Assets
PROPERTIES
EXCLUDE_FROM_ALL TRUE
FOLDER ${project_name}
)
endif()
################################################################################ ################################################################################
# Monolithic game # Monolithic game
################################################################################ ################################################################################

@ -473,18 +473,8 @@ void LevelEditorMenuHandler::PopulateEditMenu(ActionManager::MenuWrapper& editMe
// editMenu->addAction(ID_EDIT_PASTE); // editMenu->addAction(ID_EDIT_PASTE);
// editMenu.AddSeparator(); // editMenu.AddSeparator();
bool isPrefabSystemEnabled = false; // Duplicate
AzFramework::ApplicationRequests::Bus::BroadcastResult(isPrefabSystemEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled); editMenu.AddAction(ID_EDIT_CLONE);
bool prefabWipFeaturesEnabled = false;
AzFramework::ApplicationRequests::Bus::BroadcastResult(
prefabWipFeaturesEnabled, &AzFramework::ApplicationRequests::ArePrefabWipFeaturesEnabled);
if (!isPrefabSystemEnabled || (isPrefabSystemEnabled && prefabWipFeaturesEnabled))
{
// Duplicate
editMenu.AddAction(ID_EDIT_CLONE);
}
// Delete // Delete
editMenu.AddAction(ID_EDIT_DELETE); editMenu.AddAction(ID_EDIT_DELETE);
@ -912,6 +902,12 @@ QAction* LevelEditorMenuHandler::CreateViewPaneAction(const QtViewPane* view)
action = new QAction(menuText, this); action = new QAction(menuText, this);
action->setObjectName(view->m_name); action->setObjectName(view->m_name);
action->setCheckable(true); action->setCheckable(true);
if (view->m_options.showOnToolsToolbar)
{
action->setIcon(QIcon(view->m_options.toolbarIcon));
}
m_actionManager->AddAction(view->m_id, action); m_actionManager->AddAction(view->m_id, action);
if (!view->m_options.shortcut.isEmpty()) if (!view->m_options.shortcut.isEmpty())
@ -941,6 +937,11 @@ QAction* LevelEditorMenuHandler::CreateViewPaneMenuItem(
menu->addAction(action); menu->addAction(action);
if (view->m_options.showOnToolsToolbar)
{
m_mainWindow->GetToolbarManager()->AddButtonToEditToolbar(action);
}
return action; return action;
} }

@ -470,9 +470,11 @@ void MainWindow::Initialize()
InitToolActionHandlers(); InitToolActionHandlers();
// Initialize toolbars before we setup the menu so that any tools can be added to the toolbar as needed
InitToolBars();
m_levelEditorMenuHandler->Initialize(); m_levelEditorMenuHandler->Initialize();
InitToolBars();
InitStatusBar(); InitStatusBar();
AzToolsFramework::SourceControlNotificationBus::Handler::BusConnect(); AzToolsFramework::SourceControlNotificationBus::Handler::BusConnect();

@ -623,6 +623,20 @@ AmazonToolbar ToolbarManager::GetMiscToolbar() const
return t; return t;
} }
void ToolbarManager::AddButtonToEditToolbar(QAction* action)
{
QString toolbarName = "EditMode";
const AmazonToolbar* toolbar = FindToolbar(toolbarName);
if (toolbar)
{
if (toolbar->Toolbar())
{
toolbar->Toolbar()->addAction(action);
}
}
}
const AmazonToolbar* ToolbarManager::FindDefaultToolbar(const QString& toolbarName) const const AmazonToolbar* ToolbarManager::FindDefaultToolbar(const QString& toolbarName) const
{ {
for (const AmazonToolbar& toolbar : m_standardToolbars) for (const AmazonToolbar& toolbar : m_standardToolbars)

@ -169,6 +169,8 @@ public:
AmazonToolbar GetMiscToolbar() const; AmazonToolbar GetMiscToolbar() const;
AmazonToolbar GetPlayConsoleToolbar() const; AmazonToolbar GetPlayConsoleToolbar() const;
void AddButtonToEditToolbar(QAction* action);
private: private:
Q_DISABLE_COPY(ToolbarManager); Q_DISABLE_COPY(ToolbarManager);
void SaveToolbars(); void SaveToolbars();

@ -670,18 +670,11 @@ void SandboxIntegrationManager::PopulateEditorGlobalContextMenu(QMenu* menu, con
AzToolsFramework::EditorContextMenuBus::Broadcast(&AzToolsFramework::EditorContextMenuEvents::PopulateEditorGlobalContextMenu, menu); AzToolsFramework::EditorContextMenuBus::Broadcast(&AzToolsFramework::EditorContextMenuEvents::PopulateEditorGlobalContextMenu, menu);
} }
bool prefabWipFeaturesEnabled = false; action = menu->addAction(QObject::tr("Duplicate"));
AzFramework::ApplicationRequests::Bus::BroadcastResult( QObject::connect(action, &QAction::triggered, action, [this] { ContextMenu_Duplicate(); });
prefabWipFeaturesEnabled, &AzFramework::ApplicationRequests::ArePrefabWipFeaturesEnabled); if (selected.size() == 0)
if (!prefabSystemEnabled || (prefabSystemEnabled && prefabWipFeaturesEnabled))
{ {
action = menu->addAction(QObject::tr("Duplicate")); action->setDisabled(true);
QObject::connect(action, &QAction::triggered, action, [this] { ContextMenu_Duplicate(); });
if (selected.size() == 0)
{
action->setDisabled(true);
}
} }
if (!prefabSystemEnabled) if (!prefabSystemEnabled)

@ -9,12 +9,12 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# #
if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) ly_get_list_relative_pal_filename(pal_dir ${CMAKE_CURRENT_LIST_DIR}/Platform/${PAL_PLATFORM_NAME})
ly_get_list_relative_pal_filename(pal_dir ${CMAKE_CURRENT_LIST_DIR}/Platform/${PAL_PLATFORM_NAME}) include(${pal_dir}/platform_traits_${PAL_PLATFORM_NAME_LOWERCASE}.cmake)
include(${pal_dir}/platform_traits_${PAL_PLATFORM_NAME_LOWERCASE}.cmake)
if(PAL_TRAIT_AZTESTRUNNER_SUPPORTED)
ly_add_target( ly_add_target(
NAME AzTestRunner ${PAL_TRAIT_AZTESTRUNNER_LAUNCHER_TYPE} NAME AzTestRunner ${PAL_TRAIT_AZTESTRUNNER_LAUNCHER_TYPE}
NAMESPACE AZ NAMESPACE AZ
@ -32,19 +32,23 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
AZ::AzTest AZ::AzTest
AZ::AzFramework AZ::AzFramework
) )
if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
ly_add_target(
NAME AzTestRunner.Tests ${PAL_TRAIT_TEST_TARGET_TYPE}
NAMESPACE AZ
FILES_CMAKE
aztestrunner_test_files.cmake
BUILD_DEPENDENCIES
PRIVATE
AZ::AzTest
)
ly_add_target( ly_add_googletest(
NAME AzTestRunner.Tests ${PAL_TRAIT_TEST_TARGET_TYPE} NAME AZ::AzTestRunner.Tests
NAMESPACE AZ )
FILES_CMAKE
aztestrunner_test_files.cmake
BUILD_DEPENDENCIES
PRIVATE
AZ::AzTest
)
ly_add_googletest( endif()
NAME AZ::AzTestRunner.Tests
)
endif() endif()

@ -9,5 +9,5 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# #
set(PAL_TRAIT_AZTESTRUNNER_SUPPORTED TRUE)
set(PAL_TRAIT_AZTESTRUNNER_LAUNCHER_TYPE MODULE) set(PAL_TRAIT_AZTESTRUNNER_LAUNCHER_TYPE MODULE)

@ -9,5 +9,5 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# #
set(PAL_TRAIT_AZTESTRUNNER_SUPPORTED TRUE)
set(PAL_TRAIT_AZTESTRUNNER_LAUNCHER_TYPE EXECUTABLE) set(PAL_TRAIT_AZTESTRUNNER_LAUNCHER_TYPE EXECUTABLE)

@ -9,5 +9,5 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# #
set(PAL_TRAIT_AZTESTRUNNER_SUPPORTED TRUE)
set(PAL_TRAIT_AZTESTRUNNER_LAUNCHER_TYPE EXECUTABLE) set(PAL_TRAIT_AZTESTRUNNER_LAUNCHER_TYPE EXECUTABLE)

@ -9,5 +9,5 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# #
set(PAL_TRAIT_AZTESTRUNNER_SUPPORTED TRUE)
set(PAL_TRAIT_AZTESTRUNNER_LAUNCHER_TYPE EXECUTABLE) set(PAL_TRAIT_AZTESTRUNNER_LAUNCHER_TYPE EXECUTABLE)

@ -9,5 +9,5 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# #
set(PAL_TRAIT_AZTESTRUNNER_SUPPORTED TRUE)
set(PAL_TRAIT_AZTESTRUNNER_LAUNCHER_TYPE EXECUTABLE) set(PAL_TRAIT_AZTESTRUNNER_LAUNCHER_TYPE EXECUTABLE)

@ -151,7 +151,6 @@ namespace O3DE::ProjectManager
void ProjectButton::ReadySetup() void ProjectButton::ReadySetup()
{ {
connect(m_projectImageLabel, &LabelButton::triggered, [this]() { emit OpenProject(m_projectInfo.m_path); });
connect(m_projectImageLabel->GetBuildButton(), &QPushButton::clicked, [this](){ emit BuildProject(m_projectInfo); }); connect(m_projectImageLabel->GetBuildButton(), &QPushButton::clicked, [this](){ emit BuildProject(m_projectInfo); });
QMenu* menu = new QMenu(this); QMenu* menu = new QMenu(this);

@ -135,6 +135,12 @@ namespace AZ
{ {
const RHI::ShaderInputBufferUnboundedArrayIndex index(groupIndex); const RHI::ShaderInputBufferUnboundedArrayIndex index(groupIndex);
auto bufViews = groupData.GetBufferViewUnboundedArray(index); auto bufViews = groupData.GetBufferViewUnboundedArray(index);
if (bufViews.empty())
{
// skip empty unbounded arrays
continue;
}
uint32_t layoutIndex = m_descriptorSetLayout->GetLayoutIndexFromGroupIndex(groupIndex, DescriptorSetLayout::ResourceType::BufferViewUnboundedArray); uint32_t layoutIndex = m_descriptorSetLayout->GetLayoutIndexFromGroupIndex(groupIndex, DescriptorSetLayout::ResourceType::BufferViewUnboundedArray);
descriptorSet.UpdateBufferViews(layoutIndex, bufViews); descriptorSet.UpdateBufferViews(layoutIndex, bufViews);
} }
@ -144,6 +150,12 @@ namespace AZ
{ {
const RHI::ShaderInputImageUnboundedArrayIndex index(groupIndex); const RHI::ShaderInputImageUnboundedArrayIndex index(groupIndex);
auto imgViews = groupData.GetImageViewUnboundedArray(index); auto imgViews = groupData.GetImageViewUnboundedArray(index);
if (imgViews.empty())
{
// skip empty unbounded arrays
continue;
}
uint32_t layoutIndex = m_descriptorSetLayout->GetLayoutIndexFromGroupIndex(groupIndex, DescriptorSetLayout::ResourceType::ImageViewUnboundedArray); uint32_t layoutIndex = m_descriptorSetLayout->GetLayoutIndexFromGroupIndex(groupIndex, DescriptorSetLayout::ResourceType::ImageViewUnboundedArray);
descriptorSet.UpdateImageViews(layoutIndex, imgViews, shaderImageUnboundeArrayList[groupIndex].m_type); descriptorSet.UpdateImageViews(layoutIndex, imgViews, shaderImageUnboundeArrayList[groupIndex].m_type);
} }

@ -150,6 +150,7 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS)
PRIVATE PRIVATE
AZ::AtomCore AZ::AtomCore
AZ::AzTest AZ::AzTest
AZ::AzTestShared
AZ::AzFramework AZ::AzFramework
AZ::AzToolsFramework AZ::AzToolsFramework
Legacy::CryCommon Legacy::CryCommon

@ -61,12 +61,13 @@ namespace AZ
//! Important: only to be used in the Editor, it may kick off a job to calculate spatial information. //! Important: only to be used in the Editor, it may kick off a job to calculate spatial information.
//! [GFX TODO][ATOM-4343 Bake mesh spatial during AP processing] //! [GFX TODO][ATOM-4343 Bake mesh spatial during AP processing]
//! //!
//! @param rayStart position where the ray starts //! @param rayStart The starting point of the ray.
//! @param dir direction where the ray ends (does not have to be unit length) //! @param rayDir The direction and length of the ray (magnitude is encoded in the direction).
//! @param distanceFactor if an intersection is detected, this will be set such that distanceFactor * dir.length == distance to intersection //! @param[out] distanceNormalized If an intersection is found, will be set to the normalized distance of the intersection
//! @param normal if an intersection is detected, this will be set to the normal at the point of intersection //! (in the range 0.0-1.0) - to calculate the actual distance, multiply distanceNormalized by the magnitude of rayDir.
//! @return true if the ray intersects the mesh //! @param[out] normal If an intersection is found, will be set to the normal at the point of collision.
bool LocalRayIntersection(const AZ::Vector3& rayStart, const AZ::Vector3& dir, float& distanceFactor, AZ::Vector3& normal) const; //! @return True if the ray intersects the mesh.
bool LocalRayIntersection(const AZ::Vector3& rayStart, const AZ::Vector3& rayDir, float& distanceNormalized, AZ::Vector3& normal) const;
//! Checks a ray for intersection against this model, where the ray is in a different coordinate space. //! Checks a ray for intersection against this model, where the ray is in a different coordinate space.
//! Important: only to be used in the Editor, it may kick off a job to calculate spatial information. //! Important: only to be used in the Editor, it may kick off a job to calculate spatial information.
@ -74,13 +75,19 @@ namespace AZ
//! //!
//! @param modelTransform a transform that puts the model into the ray's coordinate space //! @param modelTransform a transform that puts the model into the ray's coordinate space
//! @param nonUniformScale Non-uniform scale applied in the model's local frame. //! @param nonUniformScale Non-uniform scale applied in the model's local frame.
//! @param rayStart position where the ray starts //! @param rayStart The starting point of the ray.
//! @param dir direction where the ray ends (does not have to be unit length) //! @param rayDir The direction and length of the ray (magnitude is encoded in the direction).
//! @param distanceFactor if an intersection is detected, this will be set such that distanceFactor * dir.length == distance to intersection //! @param[out] distanceNormalized If an intersection is found, will be set to the normalized distance of the intersection
//! @param normal if an intersection is detected, this will be set to the normal at the point of intersection //! (in the range 0.0-1.0) - to calculate the actual distance, multiply distanceNormalized by the magnitude of rayDir.
//! @return true if the ray intersects the mesh //! @param[out] normal If an intersection is found, will be set to the normal at the point of collision.
bool RayIntersection(const AZ::Transform& modelTransform, const AZ::Vector3& nonUniformScale, const AZ::Vector3& rayStart, //! @return True if the ray intersects the mesh.
const AZ::Vector3& dir, float& distanceFactor, AZ::Vector3& normal) const; bool RayIntersection(
const AZ::Transform& modelTransform,
const AZ::Vector3& nonUniformScale,
const AZ::Vector3& rayStart,
const AZ::Vector3& rayDir,
float& distanceNormalized,
AZ::Vector3& normal) const;
//! Get available UV names from the model and its lods. //! Get available UV names from the model and its lods.
const AZStd::unordered_set<AZ::Name>& GetUvNames() const; const AZStd::unordered_set<AZ::Name>& GetUvNames() const;

@ -63,12 +63,14 @@ namespace AZ
//! Important: only to be used in the Editor, it may kick off a job to calculate spatial information. //! Important: only to be used in the Editor, it may kick off a job to calculate spatial information.
//! [GFX TODO][ATOM-4343 Bake mesh spatial information during AP processing] //! [GFX TODO][ATOM-4343 Bake mesh spatial information during AP processing]
//! //!
//! @param rayStart position where the ray starts //! @param rayStart The starting point of the ray.
//! @param dir direction where the ray ends (does not have to be unit length) //! @param rayDir The direction and length of the ray (magnitude is encoded in the direction).
//! @param distance if an intersection is detected, this will be set such that distanceFactor * dir.length == distance to intersection //! @param[out] distanceNormalized If an intersection is found, will be set to the normalized distance of the intersection
//! @param normal if an intersection is detected, this will be set to the normal at the point of collision //! (in the range 0.0-1.0) - to calculate the actual distance, multiply distanceNormalized by the magnitude of rayDir.
//! @return true if the ray intersects the mesh //! @param[out] normal If an intersection is found, will be set to the normal at the point of collision.
virtual bool LocalRayIntersectionAgainstModel(const AZ::Vector3& rayStart, const AZ::Vector3& dir, float& distance, AZ::Vector3& normal) const; //! @return True if the ray intersects the mesh.
virtual bool LocalRayIntersectionAgainstModel(
const AZ::Vector3& rayStart, const AZ::Vector3& rayDir, float& distanceNormalized, AZ::Vector3& normal) const;
private: private:
void SetReady(); void SetReady();
@ -79,9 +81,15 @@ namespace AZ
// mutable method // mutable method
void BuildKdTree() const; void BuildKdTree() const;
bool BruteForceRayIntersect(const AZ::Vector3& rayStart, const AZ::Vector3& dir, float& distance, AZ::Vector3& normal) const; bool BruteForceRayIntersect(
const AZ::Vector3& rayStart, const AZ::Vector3& rayDir, float& distanceNormalized, AZ::Vector3& normal) const;
bool LocalRayIntersectionAgainstMesh(const ModelLodAsset::Mesh& mesh, const AZ::Vector3& rayStart, const AZ::Vector3& dir, float& distance, AZ::Vector3& normal) const;
bool LocalRayIntersectionAgainstMesh(
const ModelLodAsset::Mesh& mesh,
const AZ::Vector3& rayStart,
const AZ::Vector3& rayDir,
float& distanceNormalized,
AZ::Vector3& normal) const;
// Various model information used in raycasting // Various model information used in raycasting
AZ::Name m_positionName{ "POSITION" }; AZ::Name m_positionName{ "POSITION" };

@ -137,12 +137,12 @@ namespace AZ
return m_modelAsset; return m_modelAsset;
} }
bool Model::LocalRayIntersection(const AZ::Vector3& rayStart, const AZ::Vector3& dir, float& distance, AZ::Vector3& normal) const bool Model::LocalRayIntersection(const AZ::Vector3& rayStart, const AZ::Vector3& rayDir, float& distanceNormalized, AZ::Vector3& normal) const
{ {
AZ_PROFILE_FUNCTION(Debug::ProfileCategory::AzRender); AZ_PROFILE_FUNCTION(Debug::ProfileCategory::AzRender);
float start; float start;
float end; float end;
const int result = Intersect::IntersectRayAABB2(rayStart, dir.GetReciprocal(), m_aabb, start, end); const int result = Intersect::IntersectRayAABB2(rayStart, rayDir.GetReciprocal(), m_aabb, start, end);
if (Intersect::ISECT_RAY_AABB_NONE != result) if (Intersect::ISECT_RAY_AABB_NONE != result)
{ {
if (ModelAsset* modelAssetPtr = m_modelAsset.Get()) if (ModelAsset* modelAssetPtr = m_modelAsset.Get())
@ -151,7 +151,7 @@ namespace AZ
AZ::Debug::Timer timer; AZ::Debug::Timer timer;
timer.Stamp(); timer.Stamp();
#endif #endif
const bool hit = modelAssetPtr->LocalRayIntersectionAgainstModel(rayStart, dir, distance, normal); const bool hit = modelAssetPtr->LocalRayIntersectionAgainstModel(rayStart, rayDir, distanceNormalized, normal);
#if defined(AZ_RPI_PROFILE_RAYCASTING_AGAINST_MODELS) #if defined(AZ_RPI_PROFILE_RAYCASTING_AGAINST_MODELS)
if (hit) if (hit)
{ {
@ -166,8 +166,12 @@ namespace AZ
} }
bool Model::RayIntersection( bool Model::RayIntersection(
const AZ::Transform& modelTransform, const AZ::Vector3& nonUniformScale, const AZ::Vector3& rayStart, const AZ::Vector3& dir, const AZ::Transform& modelTransform,
float& distanceFactor, AZ::Vector3& normal) const const AZ::Vector3& nonUniformScale,
const AZ::Vector3& rayStart,
const AZ::Vector3& rayDir,
float& distanceNormalized,
AZ::Vector3& normal) const
{ {
AZ_PROFILE_FUNCTION(Debug::ProfileCategory::AzRender); AZ_PROFILE_FUNCTION(Debug::ProfileCategory::AzRender);
const AZ::Vector3 clampedScale = nonUniformScale.GetMax(AZ::Vector3(AZ::MinTransformScale)); const AZ::Vector3 clampedScale = nonUniformScale.GetMax(AZ::Vector3(AZ::MinTransformScale));
@ -175,12 +179,13 @@ namespace AZ
const AZ::Transform inverseTM = modelTransform.GetInverse(); const AZ::Transform inverseTM = modelTransform.GetInverse();
const AZ::Vector3 raySrcLocal = inverseTM.TransformPoint(rayStart) / clampedScale; const AZ::Vector3 raySrcLocal = inverseTM.TransformPoint(rayStart) / clampedScale;
// Instead of just rotating 'dir' we need it to be scaled too, so that 'distanceFactor' will be in the target units rather than object local units. // Instead of just rotating 'rayDir' we need it to be scaled too, so that 'distanceNormalized' will be in the target units rather
const AZ::Vector3 rayDest = rayStart + dir; // than object local units.
const AZ::Vector3 rayDest = rayStart + rayDir;
const AZ::Vector3 rayDestLocal = inverseTM.TransformPoint(rayDest) / clampedScale; const AZ::Vector3 rayDestLocal = inverseTM.TransformPoint(rayDest) / clampedScale;
const AZ::Vector3 rayDirLocal = rayDestLocal - raySrcLocal; const AZ::Vector3 rayDirLocal = rayDestLocal - raySrcLocal;
bool result = LocalRayIntersection(raySrcLocal, rayDirLocal, distanceFactor, normal); const bool result = LocalRayIntersection(raySrcLocal, rayDirLocal, distanceNormalized, normal);
normal = (normal * clampedScale).GetNormalized(); normal = (normal * clampedScale).GetNormalized();
return result; return result;
} }

@ -15,6 +15,7 @@
#include <AzCore/Debug/EventTrace.h> #include <AzCore/Debug/EventTrace.h>
#include <AzCore/Jobs/JobFunction.h> #include <AzCore/Jobs/JobFunction.h>
#include <AzCore/Math/IntersectSegment.h> #include <AzCore/Math/IntersectSegment.h>
#include <AzCore/std/limits.h>
#include <AzCore/RTTI/ReflectContext.h> #include <AzCore/RTTI/ReflectContext.h>
#include <AzCore/Serialization/SerializeContext.h> #include <AzCore/Serialization/SerializeContext.h>
@ -75,7 +76,8 @@ namespace AZ
m_status = Data::AssetData::AssetStatus::Ready; m_status = Data::AssetData::AssetStatus::Ready;
} }
bool ModelAsset::LocalRayIntersectionAgainstModel(const AZ::Vector3& rayStart, const AZ::Vector3& dir, float& distance, AZ::Vector3& normal) const bool ModelAsset::LocalRayIntersectionAgainstModel(
const AZ::Vector3& rayStart, const AZ::Vector3& rayDir, float& distanceNormalized, AZ::Vector3& normal) const
{ {
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzRender); AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzRender);
@ -85,7 +87,7 @@ namespace AZ
m_modelTriangleCount = CalculateTriangleCount(); m_modelTriangleCount = CalculateTriangleCount();
} }
// check the total vertex count for this model and skip kdtree if the model is simple enough // check the total vertex count for this model and skip kd-tree if the model is simple enough
if (*m_modelTriangleCount > s_minimumModelTriangleCountToOptimize) if (*m_modelTriangleCount > s_minimumModelTriangleCountToOptimize)
{ {
if (!m_kdTree) if (!m_kdTree)
@ -97,11 +99,11 @@ namespace AZ
} }
else else
{ {
return m_kdTree->RayIntersection(rayStart, dir, distance, normal); return m_kdTree->RayIntersection(rayStart, rayDir, distanceNormalized, normal);
} }
} }
return BruteForceRayIntersect(rayStart, dir, distance, normal); return BruteForceRayIntersect(rayStart, rayDir, distanceNormalized, normal);
} }
void ModelAsset::BuildKdTree() const void ModelAsset::BuildKdTree() const
@ -136,7 +138,8 @@ namespace AZ
} }
} }
bool ModelAsset::BruteForceRayIntersect(const AZ::Vector3& rayStart, const AZ::Vector3& dir, float& distance, AZ::Vector3& normal) const bool ModelAsset::BruteForceRayIntersect(
const AZ::Vector3& rayStart, const AZ::Vector3& rayDir, float& distanceNormalized, AZ::Vector3& normal) const
{ {
// brute force - check every triangle // brute force - check every triangle
if (GetLodAssets().empty() == false) if (GetLodAssets().empty() == false)
@ -144,27 +147,27 @@ namespace AZ
// intersect against the highest level of detail // intersect against the highest level of detail
if (ModelLodAsset* loadAssetPtr = GetLodAssets()[0].Get()) if (ModelLodAsset* loadAssetPtr = GetLodAssets()[0].Get())
{ {
float shortestDistance = std::numeric_limits<float>::max();
bool anyHit = false; bool anyHit = false;
AZ::Vector3 intersectionNormal; AZ::Vector3 intersectionNormal;
float shortestDistanceNormalized = AZStd::numeric_limits<float>::max();
for (const ModelLodAsset::Mesh& mesh : loadAssetPtr->GetMeshes()) for (const ModelLodAsset::Mesh& mesh : loadAssetPtr->GetMeshes())
{ {
if (LocalRayIntersectionAgainstMesh(mesh, rayStart, dir, distance, intersectionNormal)) float currentDistanceNormalized;
if (LocalRayIntersectionAgainstMesh(mesh, rayStart, rayDir, currentDistanceNormalized, intersectionNormal))
{ {
anyHit = true; anyHit = true;
if (distance < shortestDistance)
if (currentDistanceNormalized < shortestDistanceNormalized)
{ {
normal = intersectionNormal; normal = intersectionNormal;
shortestDistance = distance; shortestDistanceNormalized = currentDistanceNormalized;
} }
} }
} }
if (anyHit) if (anyHit)
{ {
distance = shortestDistance; distanceNormalized = shortestDistanceNormalized;
} }
return anyHit; return anyHit;
@ -174,7 +177,12 @@ namespace AZ
return false; return false;
} }
bool ModelAsset::LocalRayIntersectionAgainstMesh(const ModelLodAsset::Mesh& mesh, const AZ::Vector3& rayStart, const AZ::Vector3& dir, float& distance, AZ::Vector3& normal) const bool ModelAsset::LocalRayIntersectionAgainstMesh(
const ModelLodAsset::Mesh& mesh,
const AZ::Vector3& rayStart,
const AZ::Vector3& rayDir,
float& distanceNormalized,
AZ::Vector3& normal) const
{ {
const BufferAssetView& indexBufferView = mesh.GetIndexBufferAssetView(); const BufferAssetView& indexBufferView = mesh.GetIndexBufferAssetView();
const AZStd::array_view<ModelLodAsset::Mesh::StreamBufferInfo>& streamBufferList = mesh.GetStreamBufferInfoList(); const AZStd::array_view<ModelLodAsset::Mesh::StreamBufferInfo>& streamBufferList = mesh.GetStreamBufferInfoList();
@ -217,14 +225,13 @@ namespace AZ
AZStd::array_view<uint8_t> indexRawBuffer = indexAssetViewPtr->GetBuffer(); AZStd::array_view<uint8_t> indexRawBuffer = indexAssetViewPtr->GetBuffer();
RHI::BufferViewDescriptor indexRawDesc = indexAssetViewPtr->GetBufferViewDescriptor(); RHI::BufferViewDescriptor indexRawDesc = indexAssetViewPtr->GetBufferViewDescriptor();
float closestNormalizedDistance = 1.f;
bool anyHit = false; bool anyHit = false;
const AZ::Vector3 rayEnd = rayStart + dir * distance; const AZ::Vector3 rayEnd = rayStart + rayDir;
AZ::Vector3 a, b, c; AZ::Vector3 a, b, c;
AZ::Vector3 intersectionNormal; AZ::Vector3 intersectionNormal;
float normalizedDistance = 1.f;
float shortestDistanceNormalized = AZStd::numeric_limits<float>::max();
const AZ::u32* indexPtr = reinterpret_cast<const AZ::u32*>(indexRawBuffer.data()); const AZ::u32* indexPtr = reinterpret_cast<const AZ::u32*>(indexRawBuffer.data());
for (uint32_t indexIter = 0; indexIter <= indexRawDesc.m_elementCount - 3; indexIter += 3, indexPtr += 3) for (uint32_t indexIter = 0; indexIter <= indexRawDesc.m_elementCount - 3; indexIter += 3, indexPtr += 3)
{ {
@ -247,20 +254,22 @@ namespace AZ
p = reinterpret_cast<const float*>(&positionRawBuffer[index2 * positionElementSize]); p = reinterpret_cast<const float*>(&positionRawBuffer[index2 * positionElementSize]);
c.Set(const_cast<float*>(p)); c.Set(const_cast<float*>(p));
if (AZ::Intersect::IntersectSegmentTriangleCCW(rayStart, rayEnd, a, b, c, intersectionNormal, normalizedDistance)) float currentDistanceNormalized;
if (AZ::Intersect::IntersectSegmentTriangleCCW(rayStart, rayEnd, a, b, c, intersectionNormal, currentDistanceNormalized))
{ {
if (normalizedDistance < closestNormalizedDistance) anyHit = true;
if (currentDistanceNormalized < shortestDistanceNormalized)
{ {
normal = intersectionNormal; normal = intersectionNormal;
closestNormalizedDistance = normalizedDistance; shortestDistanceNormalized = currentDistanceNormalized;
} }
anyHit = true;
} }
} }
if (anyHit) if (anyHit)
{ {
distance = closestNormalizedDistance * distance; distanceNormalized = shortestDistanceNormalized;
} }
return anyHit; return anyHit;

@ -208,10 +208,10 @@ namespace AZ
bool ModelKdTree::RayIntersection( bool ModelKdTree::RayIntersection(
const AZ::Vector3& raySrc, const AZ::Vector3& rayDir, float& distanceNormalized, AZ::Vector3& normal) const const AZ::Vector3& raySrc, const AZ::Vector3& rayDir, float& distanceNormalized, AZ::Vector3& normal) const
{ {
float closestDistanceNormalized = AZStd::numeric_limits<float>::max(); float shortestDistanceNormalized = AZStd::numeric_limits<float>::max();
if (RayIntersectionRecursively(m_pRootNode.get(), raySrc, rayDir, closestDistanceNormalized, normal)) if (RayIntersectionRecursively(m_pRootNode.get(), raySrc, rayDir, shortestDistanceNormalized, normal))
{ {
distanceNormalized = closestDistanceNormalized; distanceNormalized = shortestDistanceNormalized;
return true; return true;
} }

@ -22,6 +22,7 @@
#include <AzCore/std/limits.h> #include <AzCore/std/limits.h>
#include <AzCore/Component/Entity.h> #include <AzCore/Component/Entity.h>
#include <AZTestShared/Math/MathTestHelpers.h>
#include <AzTest/AzTest.h> #include <AzTest/AzTest.h>
#include <Common/RPITestFixture.h> #include <Common/RPITestFixture.h>
@ -568,7 +569,7 @@ namespace UnitTest
ValidateModelAsset(serializedModelAsset.Get(), expectedModel); ValidateModelAsset(serializedModelAsset.Get(), expectedModel);
} }
// Tests that if we try to set the name on a Model // Tests that if we try to set the name on a Model
// before calling Begin that it will fail. // before calling Begin that it will fail.
TEST_F(ModelTests, SetNameNoBegin) TEST_F(ModelTests, SetNameNoBegin)
{ {
@ -581,7 +582,7 @@ namespace UnitTest
creator.SetName("TestName"); creator.SetName("TestName");
} }
// Tests that if we try to add a ModelLod to a Model // Tests that if we try to add a ModelLod to a Model
// before calling Begin that it will fail. // before calling Begin that it will fail.
TEST_F(ModelTests, AddLodNoBegin) TEST_F(ModelTests, AddLodNoBegin)
{ {
@ -598,7 +599,7 @@ namespace UnitTest
creator.AddLodAsset(AZStd::move(lod)); creator.AddLodAsset(AZStd::move(lod));
} }
// Tests that if we create a ModelAsset without adding // Tests that if we create a ModelAsset without adding
// any ModelLodAssets that the creator will properly fail to produce an asset. // any ModelLodAssets that the creator will properly fail to produce an asset.
TEST_F(ModelTests, CreateModelNoLods) TEST_F(ModelTests, CreateModelNoLods)
{ {
@ -618,8 +619,8 @@ namespace UnitTest
ASSERT_EQ(asset.Get(), nullptr); ASSERT_EQ(asset.Get(), nullptr);
} }
// Tests that if we call SetLodIndexBuffer without calling // Tests that if we call SetLodIndexBuffer without calling
// Begin first on the ModelLodAssetCreator that it // Begin first on the ModelLodAssetCreator that it
// fails as expected. // fails as expected.
TEST_F(ModelTests, SetLodIndexBufferNoBegin) TEST_F(ModelTests, SetLodIndexBufferNoBegin)
{ {
@ -633,8 +634,8 @@ namespace UnitTest
creator.SetLodIndexBuffer(validIndexBuffer); creator.SetLodIndexBuffer(validIndexBuffer);
} }
// Tests that if we call AddLodStreamBuffer without calling // Tests that if we call AddLodStreamBuffer without calling
// Begin first on the ModelLodAssetCreator that it // Begin first on the ModelLodAssetCreator that it
// fails as expected. // fails as expected.
TEST_F(ModelTests, AddLodStreamBufferNoBegin) TEST_F(ModelTests, AddLodStreamBufferNoBegin)
{ {
@ -648,8 +649,8 @@ namespace UnitTest
creator.AddLodStreamBuffer(validStreamBuffer); creator.AddLodStreamBuffer(validStreamBuffer);
} }
// Tests that if we call BeginMesh without calling // Tests that if we call BeginMesh without calling
// Begin first on the ModelLodAssetCreator that it // Begin first on the ModelLodAssetCreator that it
// fails as expected. // fails as expected.
TEST_F(ModelTests, BeginMeshNoBegin) TEST_F(ModelTests, BeginMeshNoBegin)
{ {
@ -662,13 +663,13 @@ namespace UnitTest
} }
// Tests that if we try to set an AABB on a mesh // Tests that if we try to set an AABB on a mesh
// without calling Begin or BeginMesh that it fails // without calling Begin or BeginMesh that it fails
// as expected. Also tests the case that Begin *is* // as expected. Also tests the case that Begin *is*
// called but BeginMesh is not. // called but BeginMesh is not.
TEST_F(ModelTests, SetAabbNoBeginNoBeginMesh) TEST_F(ModelTests, SetAabbNoBeginNoBeginMesh)
{ {
using namespace AZ; using namespace AZ;
RPI::ModelLodAssetCreator creator; RPI::ModelLodAssetCreator creator;
AZ::Aabb aabb = AZ::Aabb::CreateCenterRadius(AZ::Vector3::CreateZero(), 1.0f); AZ::Aabb aabb = AZ::Aabb::CreateCenterRadius(AZ::Vector3::CreateZero(), 1.0f);
@ -691,13 +692,13 @@ namespace UnitTest
} }
// Tests that if we try to set the material id on a mesh // Tests that if we try to set the material id on a mesh
// without calling Begin or BeginMesh that it fails // without calling Begin or BeginMesh that it fails
// as expected. Also tests the case that Begin *is* // as expected. Also tests the case that Begin *is*
// called but BeginMesh is not. // called but BeginMesh is not.
TEST_F(ModelTests, SetMaterialIdNoBeginNoBeginMesh) TEST_F(ModelTests, SetMaterialIdNoBeginNoBeginMesh)
{ {
using namespace AZ; using namespace AZ;
RPI::ModelLodAssetCreator creator; RPI::ModelLodAssetCreator creator;
{ {
@ -715,7 +716,7 @@ namespace UnitTest
} }
// Tests that if we try to set the index buffer on a mesh // Tests that if we try to set the index buffer on a mesh
// without calling Begin or BeginMesh that it fails // without calling Begin or BeginMesh that it fails
// as expected. Also tests the case that Begin *is* // as expected. Also tests the case that Begin *is*
// called but BeginMesh is not. // called but BeginMesh is not.
TEST_F(ModelTests, SetIndexBufferNoBeginNoBeginMesh) TEST_F(ModelTests, SetIndexBufferNoBeginNoBeginMesh)
@ -751,7 +752,7 @@ namespace UnitTest
} }
// Tests that if we try to add a stream buffer on a mesh // Tests that if we try to add a stream buffer on a mesh
// without calling Begin or BeginMesh that it fails // without calling Begin or BeginMesh that it fails
// as expected. Also tests the case that Begin *is* // as expected. Also tests the case that Begin *is*
// called but BeginMesh is not. // called but BeginMesh is not.
TEST_F(ModelTests, AddStreamBufferNoBeginNoBeginMesh) TEST_F(ModelTests, AddStreamBufferNoBeginNoBeginMesh)
@ -785,7 +786,7 @@ namespace UnitTest
} }
} }
// Tests that if we try to end the creation of a // Tests that if we try to end the creation of a
// ModelLodAsset that has no meshes that it fails // ModelLodAsset that has no meshes that it fails
// as expected. // as expected.
TEST_F(ModelTests, CreateLodNoMeshes) TEST_F(ModelTests, CreateLodNoMeshes)
@ -804,7 +805,7 @@ namespace UnitTest
ASSERT_EQ(asset.Get(), nullptr); ASSERT_EQ(asset.Get(), nullptr);
} }
// Tests that validation still fails when expected // Tests that validation still fails when expected
// even after producing a valid mesh due to a missing // even after producing a valid mesh due to a missing
// BeginMesh call // BeginMesh call
TEST_F(ModelTests, SecondMeshFailureNoBeginMesh) TEST_F(ModelTests, SecondMeshFailureNoBeginMesh)
@ -862,8 +863,8 @@ namespace UnitTest
ASSERT_EQ(asset->GetMeshes().size(), 1); ASSERT_EQ(asset->GetMeshes().size(), 1);
} }
// Tests that validation still fails when expected // Tests that validation still fails when expected
// even after producing a valid mesh due to SetMeshX // even after producing a valid mesh due to SetMeshX
// calls coming after End // calls coming after End
TEST_F(ModelTests, SecondMeshAfterEnd) TEST_F(ModelTests, SecondMeshAfterEnd)
{ {
@ -907,7 +908,7 @@ namespace UnitTest
AZ::Aabb aabb = AZ::Aabb::CreateCenterRadius(Vector3::CreateZero(), 1.0f); AZ::Aabb aabb = AZ::Aabb::CreateCenterRadius(Vector3::CreateZero(), 1.0f);
ErrorMessageFinder messageFinder("Begin() was not called", 6); ErrorMessageFinder messageFinder("Begin() was not called", 6);
creator.BeginMesh(); creator.BeginMesh();
creator.SetMeshAabb(AZStd::move(aabb)); creator.SetMeshAabb(AZStd::move(aabb));
creator.SetMeshMaterialAsset(m_materialAsset); creator.SetMeshMaterialAsset(m_materialAsset);
@ -955,6 +956,20 @@ namespace UnitTest
EXPECT_EQ(uvStreamTangentBitmask.GetFullTangentBitmask(), 0x70000F51); EXPECT_EQ(uvStreamTangentBitmask.GetFullTangentBitmask(), 0x70000F51);
} }
//
// +----+
// / /|
// +----+ |
// | | +
// | |/
// +----+
//
static constexpr AZStd::array CubePositions = { -1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, -1.0f, 1.0f,
-1.0f, 1.0f, -1.0f, 1.0f, 1.0f, -1.0f, -1.0f, -1.0f, -1.0f, 1.0f, -1.0f, -1.0f };
static constexpr AZStd::array CubeIndices = {
uint32_t{ 0 }, 2, 1, 1, 2, 3, 4, 5, 6, 5, 7, 6, 0, 4, 2, 4, 6, 2, 1, 3, 5, 5, 3, 7, 0, 1, 4, 4, 1, 5, 2, 6, 3, 6, 7, 3,
};
// This class creates a Model with one LOD, whose mesh contains 2 planes. Plane 1 is in the XY plane at Z=-0.5, and // This class creates a Model with one LOD, whose mesh contains 2 planes. Plane 1 is in the XY plane at Z=-0.5, and
// plane 2 is in the XY plane at Z=0.5. The two planes each have 9 quads which have been triangulated. It only has // plane 2 is in the XY plane at Z=0.5. The two planes each have 9 quads which have been triangulated. It only has
// a position and index buffer. // a position and index buffer.
@ -972,52 +987,75 @@ namespace UnitTest
// *---*---*---* // *---*---*---*
// \ / \ / \ / \ // \ / \ / \ / \
// *---*---*---* // *---*---*---*
static constexpr AZStd::array TwoSeparatedPlanesPositions{
-1.0f, -0.333f, -0.5f, -0.333f, -1.0f, -0.5f, -0.333f, -0.333f, -0.5f, 0.333f, -0.333f, -0.5f, 1.0f, -1.0f, -0.5f,
1.0f, -0.333f, -0.5f, 0.333f, -1.0f, -0.5f, 0.333f, 1.0f, -0.5f, 1.0f, 0.333f, -0.5f, 1.0f, 1.0f, -0.5f,
0.333f, 0.333f, -0.5f, -0.333f, 1.0f, -0.5f, -0.333f, 0.333f, -0.5f, -1.0f, 1.0f, -0.5f, -1.0f, 0.333f, -0.5f,
-1.0f, -0.333f, 0.5f, -0.333f, -1.0f, 0.5f, -0.333f, -0.333f, 0.5f, 0.333f, -0.333f, 0.5f, 1.0f, -1.0f, 0.5f,
1.0f, -0.333f, 0.5f, -0.333f, -0.333f, 0.5f, 0.333f, -1.0f, 0.5f, 0.333f, -0.333f, 0.5f, 0.333f, 1.0f, 0.5f,
1.0f, 0.333f, 0.5f, 1.0f, 1.0f, 0.5f, 0.333f, 0.333f, 0.5f, 1.0f, -0.333f, 0.5f, -0.333f, 1.0f, 0.5f,
-0.333f, 0.333f, 0.5f, 0.333f, -0.333f, 0.5f, 0.333f, 0.333f, 0.5f, -1.0f, 1.0f, 0.5f, -0.333f, 0.333f, 0.5f,
-1.0f, 0.333f, 0.5f, -1.0f, -1.0f, -0.5f, -1.0f, -1.0f, 0.5f, 0.333f, -0.333f, 0.5f, 0.333f, -1.0f, 0.5f,
1.0f, -1.0f, 0.5f, 0.333f, -1.0f, 0.5f, 0.333f, 0.333f, 0.5f, 0.333f, -0.333f, 0.5f, 1.0f, -0.333f, 0.5f,
-0.333f, 0.333f, 0.5f, -0.333f, -0.333f, 0.5f, 0.333f, -0.333f, 0.5f,
};
// clang-format off
static constexpr AZStd::array TwoSeparatedPlanesIndices{
uint32_t{ 0 }, 1, 2, 3, 4, 5, 2, 6, 3, 7, 8, 9, 10, 5, 8, 11, 10, 7, 12, 3, 10, 13, 12, 11, 14, 2, 12,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 25, 29, 27, 24, 30, 31, 32, 33, 34, 29, 35, 17, 34,
0, 36, 1, 3, 6, 4, 2, 1, 6, 7, 10, 8, 10, 3, 5, 11, 12, 10, 12, 2, 3, 13, 14, 12, 14, 0, 2,
15, 37, 16, 38, 39, 40, 17, 16, 41, 24, 27, 25, 42, 43, 44, 29, 34, 27, 45, 46, 47, 33, 35, 34, 35, 15, 17,
};
// clang-format on
// Ensure that the index buffer references all the positions in the position buffer
static constexpr inline auto minmaxElement = AZStd::minmax_element(begin(TwoSeparatedPlanesIndices), end(TwoSeparatedPlanesIndices));
static_assert(*minmaxElement.second == (TwoSeparatedPlanesPositions.size() / 3) - 1);
template<class x> class TD; template<class x> class TD;
class TwoSeparatedPlanesMesh class TestMesh
{ {
public: public:
TwoSeparatedPlanesMesh() TestMesh(const float* positions, size_t positionCount, const uint32_t* indices, size_t indicesCount)
{ {
using namespace AZ; AZ::RPI::ModelLodAssetCreator lodCreator;
lodCreator.Begin(AZ::Data::AssetId(AZ::Uuid::CreateRandom()));
RPI::ModelLodAssetCreator lodCreator;
lodCreator.Begin(Data::AssetId(AZ::Uuid::CreateRandom()));
lodCreator.BeginMesh(); lodCreator.BeginMesh();
lodCreator.SetMeshAabb(Aabb::CreateFromMinMax({-1.0f, -1.0f, -0.5f}, {1.0f, 1.0f, 0.5f})); lodCreator.SetMeshAabb(AZ::Aabb::CreateFromMinMax({-1.0f, -1.0f, -0.5f}, {1.0f, 1.0f, 0.5f}));
lodCreator.SetMeshMaterialAsset( lodCreator.SetMeshMaterialAsset(
AZ::Data::Asset<AZ::RPI::MaterialAsset>(AZ::Data::AssetId(AZ::Uuid::CreateRandom(), 0), AZ::Data::Asset<AZ::RPI::MaterialAsset>(AZ::Data::AssetId(AZ::Uuid::CreateRandom(), 0),
AZ::AzTypeInfo<AZ::RPI::MaterialAsset>::Uuid(), "") AZ::AzTypeInfo<AZ::RPI::MaterialAsset>::Uuid(), "")
); );
{ {
AZ::Data::Asset<AZ::RPI::BufferAsset> indexBuffer = BuildTestBuffer(s_indexes.size(), sizeof(uint32_t)); AZ::Data::Asset<AZ::RPI::BufferAsset> indexBuffer = BuildTestBuffer(indicesCount, sizeof(uint32_t));
AZStd::copy(s_indexes.begin(), s_indexes.end(), reinterpret_cast<uint32_t*>(const_cast<uint8_t*>(indexBuffer->GetBuffer().data()))); AZStd::copy(indices, indices + indicesCount, reinterpret_cast<uint32_t*>(const_cast<uint8_t*>(indexBuffer->GetBuffer().data())));
lodCreator.SetMeshIndexBuffer({ lodCreator.SetMeshIndexBuffer({
indexBuffer, indexBuffer,
RHI::BufferViewDescriptor::CreateStructured(0, s_indexes.size(), sizeof(uint32_t)) AZ::RHI::BufferViewDescriptor::CreateStructured(0, indicesCount, sizeof(uint32_t))
}); });
} }
{ {
AZ::Data::Asset<AZ::RPI::BufferAsset> positionBuffer = BuildTestBuffer(s_positions.size() / 3, sizeof(float) * 3); AZ::Data::Asset<AZ::RPI::BufferAsset> positionBuffer = BuildTestBuffer(positionCount / 3, sizeof(float) * 3);
AZStd::copy(s_positions.begin(), s_positions.end(), reinterpret_cast<float*>(const_cast<uint8_t*>(positionBuffer->GetBuffer().data()))); AZStd::copy(positions, positions + positionCount, reinterpret_cast<float*>(const_cast<uint8_t*>(positionBuffer->GetBuffer().data())));
lodCreator.AddMeshStreamBuffer( lodCreator.AddMeshStreamBuffer(
AZ::RHI::ShaderSemantic(AZ::Name("POSITION")), AZ::RHI::ShaderSemantic(AZ::Name("POSITION")),
AZ::Name(), AZ::Name(),
{ {
positionBuffer, positionBuffer,
RHI::BufferViewDescriptor::CreateStructured(0, s_positions.size() / 3, sizeof(float) * 3) AZ::RHI::BufferViewDescriptor::CreateStructured(0, positionCount / 3, sizeof(float) * 3)
} }
); );
} }
lodCreator.EndMesh(); lodCreator.EndMesh();
Data::Asset<RPI::ModelLodAsset> lodAsset; AZ::Data::Asset<AZ::RPI::ModelLodAsset> lodAsset;
lodCreator.End(lodAsset); lodCreator.End(lodAsset);
RPI::ModelAssetCreator modelCreator; AZ::RPI::ModelAssetCreator modelCreator;
modelCreator.Begin(Data::AssetId(AZ::Uuid::CreateRandom())); modelCreator.Begin(AZ::Data::AssetId(AZ::Uuid::CreateRandom()));
modelCreator.SetName("TestModel"); modelCreator.SetName("TestModel");
modelCreator.AddLodAsset(AZStd::move(lodAsset)); modelCreator.AddLodAsset(AZStd::move(lodAsset));
modelCreator.End(m_modelAsset); modelCreator.End(m_modelAsset);
@ -1030,40 +1068,20 @@ namespace UnitTest
private: private:
AZ::Data::Asset<AZ::RPI::ModelAsset> m_modelAsset; AZ::Data::Asset<AZ::RPI::ModelAsset> m_modelAsset;
static constexpr AZStd::array s_positions{
-1.0f, -0.333f, -0.5f, -0.333f, -1.0f, -0.5f, -0.333f, -0.333f, -0.5f, 0.333f, -0.333f, -0.5f, 1.0f, -1.0f, -0.5f,
1.0f, -0.333f, -0.5f, 0.333f, -1.0f, -0.5f, 0.333f, 1.0f, -0.5f, 1.0f, 0.333f, -0.5f, 1.0f, 1.0f, -0.5f,
0.333f, 0.333f, -0.5f, -0.333f, 1.0f, -0.5f, -0.333f, 0.333f, -0.5f, -1.0f, 1.0f, -0.5f, -1.0f, 0.333f, -0.5f,
-1.0f, -0.333f, 0.5f, -0.333f, -1.0f, 0.5f, -0.333f, -0.333f, 0.5f, 0.333f, -0.333f, 0.5f, 1.0f, -1.0f, 0.5f,
1.0f, -0.333f, 0.5f, -0.333f, -0.333f, 0.5f, 0.333f, -1.0f, 0.5f, 0.333f, -0.333f, 0.5f, 0.333f, 1.0f, 0.5f,
1.0f, 0.333f, 0.5f, 1.0f, 1.0f, 0.5f, 0.333f, 0.333f, 0.5f, 1.0f, -0.333f, 0.5f, -0.333f, 1.0f, 0.5f,
-0.333f, 0.333f, 0.5f, 0.333f, -0.333f, 0.5f, 0.333f, 0.333f, 0.5f, -1.0f, 1.0f, 0.5f, -0.333f, 0.333f, 0.5f,
-1.0f, 0.333f, 0.5f, -1.0f, -1.0f, -0.5f, -1.0f, -1.0f, 0.5f, 0.333f, -0.333f, 0.5f, 0.333f, -1.0f, 0.5f,
1.0f, -1.0f, 0.5f, 0.333f, -1.0f, 0.5f, 0.333f, 0.333f, 0.5f, 0.333f, -0.333f, 0.5f, 1.0f, -0.333f, 0.5f,
-0.333f, 0.333f, 0.5f, -0.333f, -0.333f, 0.5f, 0.333f, -0.333f, 0.5f,
};
static constexpr AZStd::array s_indexes{
uint32_t{0}, 1, 2, 3, 4, 5, 2, 6, 3, 7, 8, 9, 10, 5, 8, 11, 10, 7, 12, 3, 10, 13, 12, 11, 14, 2, 12,
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 25, 29, 27, 24, 30, 31, 32, 33, 34, 29, 35, 17, 34,
0, 36, 1, 3, 6, 4, 2, 1, 6, 7, 10, 8, 10, 3, 5, 11, 12, 10, 12, 2, 3, 13, 14, 12, 14, 0, 2,
15, 37, 16, 38, 39, 40, 17, 16, 41, 24, 27, 25, 42, 43, 44, 29, 34, 27, 45, 46, 47, 33, 35, 34, 35, 15, 17,
};
// Ensure that the index buffer references all the positions in the position buffer
static constexpr inline auto minmaxElement = AZStd::minmax_element(begin(s_indexes), end(s_indexes));
static_assert(*minmaxElement.second == (s_positions.size() / 3) - 1);
}; };
struct KdTreeIntersectParams struct IntersectParams
{ {
float xpos; float xpos;
float ypos; float ypos;
float zpos; float zpos;
float xdir;
float ydir;
float zdir;
float expectedDistance; float expectedDistance;
bool expectedShouldIntersect; bool expectedShouldIntersect;
friend std::ostream& operator<<(std::ostream& os, const KdTreeIntersectParams& param) friend std::ostream& operator<<(std::ostream& os, const IntersectParams& param)
{ {
return os return os
<< "xpos:" << param.xpos << "xpos:" << param.xpos
@ -1076,13 +1094,15 @@ namespace UnitTest
class KdTreeIntersectsParameterizedFixture class KdTreeIntersectsParameterizedFixture
: public ModelTests : public ModelTests
, public ::testing::WithParamInterface<KdTreeIntersectParams> , public ::testing::WithParamInterface<IntersectParams>
{ {
}; };
TEST_P(KdTreeIntersectsParameterizedFixture, KdTreeIntersects) TEST_P(KdTreeIntersectsParameterizedFixture, KdTreeIntersects)
{ {
TwoSeparatedPlanesMesh mesh; TestMesh mesh(
TwoSeparatedPlanesPositions.data(), TwoSeparatedPlanesPositions.size(), TwoSeparatedPlanesIndices.data(),
TwoSeparatedPlanesIndices.size());
AZ::RPI::ModelKdTree kdTree; AZ::RPI::ModelKdTree kdTree;
ASSERT_TRUE(kdTree.Build(mesh.GetModel().Get())); ASSERT_TRUE(kdTree.Build(mesh.GetModel().Get()));
@ -1092,38 +1112,40 @@ namespace UnitTest
EXPECT_THAT( EXPECT_THAT(
kdTree.RayIntersection( kdTree.RayIntersection(
AZ::Vector3(GetParam().xpos, GetParam().ypos, GetParam().zpos), AZ::Vector3::CreateAxisZ(-1.0f), distance, normal), AZ::Vector3(GetParam().xpos, GetParam().ypos, GetParam().zpos),
AZ::Vector3(GetParam().xdir, GetParam().ydir, GetParam().zdir), distance, normal),
testing::Eq(GetParam().expectedShouldIntersect)); testing::Eq(GetParam().expectedShouldIntersect));
EXPECT_THAT(distance, testing::FloatEq(GetParam().expectedDistance)); EXPECT_THAT(distance, testing::FloatEq(GetParam().expectedDistance));
} }
static constexpr inline AZStd::array<KdTreeIntersectParams, 21> intersectTestData{ static constexpr AZStd::array KdTreeIntersectTestData{
KdTreeIntersectParams{ -0.1f, 0.0f, 1.0f, 0.5f, true }, IntersectParams{ -0.1f, 0.0f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
KdTreeIntersectParams{ 0.0f, 0.0f, 1.0f, 0.5f, true }, IntersectParams{ 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
KdTreeIntersectParams{ 0.1f, 0.0f, 1.0f, 0.5f, true }, IntersectParams{ 0.1f, 0.0f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
// Test the center of each triangle // Test the center of each triangle
KdTreeIntersectParams{-0.111f, -0.111f, 1.0f, 0.5f, true}, IntersectParams{ -0.111f, -0.111f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
KdTreeIntersectParams{-0.111f, -0.778f, 1.0f, 0.5f, true}, IntersectParams{ -0.111f, -0.778f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
KdTreeIntersectParams{-0.111f, 0.555f, 1.0f, 0.5f, true}, // Should intersect triangle with indices {29, 34, 27} and {11, 12, 10} IntersectParams{ -0.111f, 0.555f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f,
KdTreeIntersectParams{-0.555f, -0.555f, 1.0f, 0.5f, true}, true }, // Should intersect triangle with indices {29, 34, 27} and {11, 12, 10}
KdTreeIntersectParams{-0.555f, 0.111f, 1.0f, 0.5f, true}, IntersectParams{ -0.555f, -0.555f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
KdTreeIntersectParams{-0.555f, 0.778f, 1.0f, 0.5f, true}, IntersectParams{ -0.555f, 0.111f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
KdTreeIntersectParams{-0.778f, -0.111f, 1.0f, 0.5f, true}, IntersectParams{ -0.555f, 0.778f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
KdTreeIntersectParams{-0.778f, -0.778f, 1.0f, 0.5f, true}, IntersectParams{ -0.778f, -0.111f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
KdTreeIntersectParams{-0.778f, 0.555f, 1.0f, 0.5f, true}, IntersectParams{ -0.778f, -0.778f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
KdTreeIntersectParams{0.111f, -0.555f, 1.0f, 0.5f, true}, IntersectParams{ -0.778f, 0.555f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
KdTreeIntersectParams{0.111f, 0.111f, 1.0f, 0.5f, true}, IntersectParams{ 0.111f, -0.555f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
KdTreeIntersectParams{0.111f, 0.778f, 1.0f, 0.5f, true}, IntersectParams{ 0.111f, 0.111f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
KdTreeIntersectParams{0.555f, -0.111f, 1.0f, 0.5f, true}, IntersectParams{ 0.111f, 0.778f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
KdTreeIntersectParams{0.555f, -0.778f, 1.0f, 0.5f, true}, IntersectParams{ 0.555f, -0.111f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
KdTreeIntersectParams{0.555f, 0.555f, 1.0f, 0.5f, true}, IntersectParams{ 0.555f, -0.778f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
KdTreeIntersectParams{0.778f, -0.555f, 1.0f, 0.5f, true}, IntersectParams{ 0.555f, 0.555f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
KdTreeIntersectParams{0.778f, 0.111f, 1.0f, 0.5f, true}, IntersectParams{ 0.778f, -0.555f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
KdTreeIntersectParams{0.778f, 0.778f, 1.0f, 0.5f, true}, IntersectParams{ 0.778f, 0.111f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
IntersectParams{ 0.778f, 0.778f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true },
}; };
INSTANTIATE_TEST_CASE_P(KdTreeIntersectsPlane, KdTreeIntersectsParameterizedFixture, ::testing::ValuesIn(intersectTestData)); INSTANTIATE_TEST_CASE_P(KdTreeIntersectsPlane, KdTreeIntersectsParameterizedFixture, ::testing::ValuesIn(KdTreeIntersectTestData));
class KdTreeIntersectsFixture class KdTreeIntersectsFixture
: public ModelTests : public ModelTests
@ -1133,7 +1155,10 @@ namespace UnitTest
{ {
ModelTests::SetUp(); ModelTests::SetUp();
m_mesh = AZStd::make_unique<TwoSeparatedPlanesMesh>(); m_mesh = AZStd::make_unique<TestMesh>(
TwoSeparatedPlanesPositions.data(), TwoSeparatedPlanesPositions.size(), TwoSeparatedPlanesIndices.data(),
TwoSeparatedPlanesIndices.size());
m_kdTree = AZStd::make_unique<AZ::RPI::ModelKdTree>(); m_kdTree = AZStd::make_unique<AZ::RPI::ModelKdTree>();
ASSERT_TRUE(m_kdTree->Build(m_mesh->GetModel().Get())); ASSERT_TRUE(m_kdTree->Build(m_mesh->GetModel().Get()));
} }
@ -1146,7 +1171,7 @@ namespace UnitTest
ModelTests::TearDown(); ModelTests::TearDown();
} }
AZStd::unique_ptr<TwoSeparatedPlanesMesh> m_mesh; AZStd::unique_ptr<TestMesh> m_mesh;
AZStd::unique_ptr<AZ::RPI::ModelKdTree> m_kdTree; AZStd::unique_ptr<AZ::RPI::ModelKdTree> m_kdTree;
}; };
@ -1154,7 +1179,7 @@ namespace UnitTest
{ {
float t = AZStd::numeric_limits<float>::max(); float t = AZStd::numeric_limits<float>::max();
AZ::Vector3 normal; AZ::Vector3 normal;
constexpr float rayLength = 100.0f; constexpr float rayLength = 100.0f;
EXPECT_THAT( EXPECT_THAT(
m_kdTree->RayIntersection( m_kdTree->RayIntersection(
@ -1181,4 +1206,85 @@ namespace UnitTest
EXPECT_THAT( EXPECT_THAT(
m_kdTree->RayIntersection(AZ::Vector3::CreateAxisZ(5.0f), -AZ::Vector3::CreateAxisZ(), t, normal), testing::Eq(false)); m_kdTree->RayIntersection(AZ::Vector3::CreateAxisZ(5.0f), -AZ::Vector3::CreateAxisZ(), t, normal), testing::Eq(false));
} }
class BruteForceIntersectsParameterizedFixture
: public ModelTests
, public ::testing::WithParamInterface<IntersectParams>
{
};
TEST_P(BruteForceIntersectsParameterizedFixture, BruteForceIntersectsCube)
{
TestMesh mesh(CubePositions.data(), CubePositions.size(), CubeIndices.data(), CubeIndices.size());
float distance = AZStd::numeric_limits<float>::max();
AZ::Vector3 normal;
EXPECT_THAT(
mesh.GetModel()->LocalRayIntersectionAgainstModel(
AZ::Vector3(GetParam().xpos, GetParam().ypos, GetParam().zpos),
AZ::Vector3(GetParam().xdir, GetParam().ydir, GetParam().zdir), distance, normal),
testing::Eq(GetParam().expectedShouldIntersect));
EXPECT_THAT(distance, testing::FloatEq(GetParam().expectedDistance));
}
static constexpr AZStd::array BruteForceIntersectTestData{
IntersectParams{ 5.0f, 0.0f, 5.0f, 0.0f, 0.0f, -1.0f, AZStd::numeric_limits<float>::max(), false },
IntersectParams{ 0.0f, 0.0f, 1.5f, 0.0f, 0.0f, -1.0f, 0.5f, true },
IntersectParams{ 5.0f, 0.0f, 0.0f, -10.0f, 0.0f, 0.0f, 0.4f, true },
IntersectParams{ -5.0f, 0.0f, 0.0f, 20.0f, 0.0f, 0.0f, 0.2f, true },
IntersectParams{ 0.0f, -10.0f, 0.0f, 0.0f, 20.0f, 0.0f, 0.45f, true },
IntersectParams{ 0.0f, 20.0f, 0.0f, 0.0f, -40.0f, 0.0f, 0.475f, true },
IntersectParams{ 0.0f, 20.0f, 0.0f, 0.0f, -19.0f, 0.0f, 1.0f, true },
};
INSTANTIATE_TEST_CASE_P(
BruteForceIntersects, BruteForceIntersectsParameterizedFixture, ::testing::ValuesIn(BruteForceIntersectTestData));
class BruteForceModelIntersectsFixture
: public ModelTests
{
public:
void SetUp() override
{
ModelTests::SetUp();
m_mesh = AZStd::make_unique<TestMesh>(CubePositions.data(), CubePositions.size(), CubeIndices.data(), CubeIndices.size());
}
void TearDown() override
{
m_mesh.reset();
ModelTests::TearDown();
}
AZStd::unique_ptr<TestMesh> m_mesh;
};
TEST_F(BruteForceModelIntersectsFixture, BruteForceIntersectionDetectedWithCube)
{
float t = 0.0f;
AZ::Vector3 normal;
// firing down the negative z axis, positioned 5 units from cube (cube is 2x2x2 so intersection
// happens at 1 in z)
EXPECT_THAT(
m_mesh->GetModel()->LocalRayIntersectionAgainstModel(
AZ::Vector3::CreateAxisZ(5.0f), -AZ::Vector3::CreateAxisZ(10.0f), t, normal),
testing::Eq(true));
EXPECT_THAT(t, testing::FloatEq(0.4f));
}
TEST_F(BruteForceModelIntersectsFixture, BruteForceIntersectionDetectedAndNormalSetAtEndOfRay)
{
float t = 0.0f;
AZ::Vector3 normal = AZ::Vector3::CreateOne(); // invalid starting normal
// ensure the intersection happens right at the end of the ray
EXPECT_THAT(
m_mesh->GetModel()->LocalRayIntersectionAgainstModel(
AZ::Vector3::CreateAxisY(10.0f), -AZ::Vector3::CreateAxisY(9.0f), t, normal),
testing::Eq(true));
EXPECT_THAT(t, testing::FloatEq(1.0f));
EXPECT_THAT(normal, IsClose(AZ::Vector3::CreateAxisY()));
}
} // namespace UnitTest } // namespace UnitTest

@ -693,7 +693,7 @@ namespace ImGui
ImGui::TextColored(ImGui::Colors::s_PlainLabelColor, "Left Stick"); ImGui::TextColored(ImGui::Colors::s_PlainLabelColor, "Left Stick");
ImGui::NextColumn(); ImGui::NextColumn();
ImGui::Bullet(); ImGui::Bullet();
ImGui::TextColored(ImGui::Colors::s_PlainLabelColor, "Mova Mouse Pointer"); ImGui::TextColored(ImGui::Colors::s_PlainLabelColor, "Move Mouse Pointer");
ImGui::Separator(); ImGui::Separator();
ImGui::NextColumn(); ImGui::NextColumn();

@ -13,6 +13,7 @@
#include <PhysX_precompiled.h> #include <PhysX_precompiled.h>
#include <AzCore/Serialization/EditContext.h> #include <AzCore/Serialization/EditContext.h>
#include <AzCore/std/smart_ptr/make_shared.h> #include <AzCore/std/smart_ptr/make_shared.h>
#include <AzCore/std/parallel/scoped_lock.h>
#include <AzFramework/Physics/PhysicsScene.h> #include <AzFramework/Physics/PhysicsScene.h>
#include <AzFramework/Physics/PhysicsSystem.h> #include <AzFramework/Physics/PhysicsSystem.h>
#include <AzFramework/Physics/SystemBus.h> #include <AzFramework/Physics/SystemBus.h>
@ -40,6 +41,8 @@ namespace PhysX
} // namespace Internal } // namespace Internal
// PhysX::Ragdoll // PhysX::Ragdoll
/*static*/ AZStd::mutex Ragdoll::m_sceneEventMutex;
void Ragdoll::Reflect(AZ::ReflectContext* context) void Ragdoll::Reflect(AZ::ReflectContext* context)
{ {
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context); AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
@ -114,7 +117,10 @@ namespace PhysX
Ragdoll::~Ragdoll() Ragdoll::~Ragdoll()
{ {
m_sceneStartSimHandler.Disconnect(); {
AZStd::scoped_lock lock(m_sceneEventMutex);
m_sceneStartSimHandler.Disconnect();
}
m_nodes.clear(); //the nodes destructor will remove the simulated body from the scene. m_nodes.clear(); //the nodes destructor will remove the simulated body from the scene.
} }
@ -212,7 +218,13 @@ namespace PhysX
} }
} }
sceneInterface->RegisterSceneSimulationStartHandler(m_sceneOwner, m_sceneStartSimHandler); // the handler is also connected in EnableSimulationQueued(),
// which will call this function, so if called from that path dont connect here.
if (!m_sceneStartSimHandler.IsConnected())
{
AZStd::scoped_lock lock(m_sceneEventMutex);
sceneInterface->RegisterSceneSimulationStartHandler(m_sceneOwner, m_sceneStartSimHandler);
}
sceneInterface->EnableSimulationOfBody(m_sceneOwner, m_bodyHandle); sceneInterface->EnableSimulationOfBody(m_sceneOwner, m_bodyHandle);
} }
@ -225,6 +237,7 @@ namespace PhysX
if (auto* sceneInterface = AZ::Interface<AzPhysics::SceneInterface>::Get()) if (auto* sceneInterface = AZ::Interface<AzPhysics::SceneInterface>::Get())
{ {
AZStd::scoped_lock lock(m_sceneEventMutex);
sceneInterface->RegisterSceneSimulationStartHandler(m_sceneOwner, m_sceneStartSimHandler); sceneInterface->RegisterSceneSimulationStartHandler(m_sceneOwner, m_sceneStartSimHandler);
} }
@ -244,7 +257,10 @@ namespace PhysX
return; return;
} }
m_sceneStartSimHandler.Disconnect(); {
AZStd::scoped_lock lock(m_sceneEventMutex);
m_sceneStartSimHandler.Disconnect();
}
physx::PxScene* pxScene = Internal::GetPxScene(m_sceneOwner); physx::PxScene* pxScene = Internal::GetPxScene(m_sceneOwner);
const size_t numNodes = m_nodes.size(); const size_t numNodes = m_nodes.size();

@ -12,6 +12,7 @@
#pragma once #pragma once
#include <AzCore/std/parallel/mutex.h>
#include <AzFramework/Physics/RagdollPhysicsBus.h> #include <AzFramework/Physics/RagdollPhysicsBus.h>
#include <AzFramework/Physics/Ragdoll.h> #include <AzFramework/Physics/Ragdoll.h>
#include <AzFramework/Physics/Common/PhysicsEvents.h> #include <AzFramework/Physics/Common/PhysicsEvents.h>
@ -84,5 +85,6 @@ namespace PhysX
bool m_queuedDisableSimulation = false; bool m_queuedDisableSimulation = false;
AzPhysics::SceneEvents::OnSceneSimulationStartHandler m_sceneStartSimHandler; AzPhysics::SceneEvents::OnSceneSimulationStartHandler m_sceneStartSimHandler;
static AZStd::mutex m_sceneEventMutex;
}; };
} // namespace PhysX } // namespace PhysX

@ -3356,22 +3356,26 @@ namespace ScriptCanvas
if (executionIf->GetId().m_node->IsIfBranchPrefacedWithBooleanExpression()) if (executionIf->GetId().m_node->IsIfBranchPrefacedWithBooleanExpression())
{ {
auto removeChildOutcome = RemoveChild(executionIf->ModParent(), executionIf); ExecutionTreePtr booleanExpression;
if (!removeChildOutcome.IsSuccess())
{
AddError(executionIf->GetNodeId(), executionIf, ScriptCanvas::ParseErrors::FailedToRemoveChild);
}
if (!IsErrorFree())
{ {
return; auto removeChildOutcome = RemoveChild(executionIf->ModParent(), executionIf);
} if (!removeChildOutcome.IsSuccess())
{
AddError(executionIf->GetNodeId(), executionIf, ScriptCanvas::ParseErrors::FailedToRemoveChild);
}
if (!IsErrorFree())
{
return;
}
const auto indexAndChild = removeChildOutcome.TakeValue(); const auto indexAndChild = removeChildOutcome.TakeValue();
ExecutionTreePtr booleanExpression = CreateChild(executionIf->ModParent(), executionIf->GetId().m_node, executionIf->GetId().m_slot); booleanExpression = CreateChild(executionIf->ModParent(), executionIf->GetId().m_node, executionIf->GetId().m_slot);
executionIf->ModParent()->InsertChild(indexAndChild.first, { indexAndChild.second.m_slot, indexAndChild.second.m_output, booleanExpression }); executionIf->ModParent()->InsertChild(indexAndChild.first, { indexAndChild.second.m_slot, indexAndChild.second.m_output, booleanExpression });
executionIf->SetParent(booleanExpression); executionIf->SetParent(booleanExpression);
}
// make a condition here // make a condition here
auto symbol = CheckLogicalExpressionSymbol(booleanExpression); auto symbol = CheckLogicalExpressionSymbol(booleanExpression);
@ -3402,7 +3406,7 @@ namespace ScriptCanvas
return; return;
} }
const auto indexAndChild2 = removeChildOutcome.TakeValue(); const auto indexAndChild2 = removeChildOutcome2.TakeValue();
// parse if statement internal function // parse if statement internal function
ExecutionTreePtr internalFunction = CreateChild(booleanExpression->ModParent(), booleanExpression->GetId().m_node, booleanExpression->GetId().m_slot); ExecutionTreePtr internalFunction = CreateChild(booleanExpression->ModParent(), booleanExpression->GetId().m_node, booleanExpression->GetId().m_slot);
@ -4782,7 +4786,7 @@ namespace ScriptCanvas
{ {
PropertyExtractionPtr extraction = AZStd::make_shared<PropertyExtraction>(); PropertyExtractionPtr extraction = AZStd::make_shared<PropertyExtraction>();
extraction->m_slot = slot; extraction->m_slot = slot;
extraction->m_name = propertyField.first; extraction->m_name = AZ::ReplaceCppArtifacts(propertyField.first);
execution->AddPropertyExtractionSource(slot, extraction); execution->AddPropertyExtractionSource(slot, extraction);
} }
else else

@ -90,6 +90,11 @@ public:
} }
}; };
TEST_F(ScriptCanvasTestFixture, ParseFunctionIfBranchWithConnectedInput)
{
RunUnitTestGraph("LY_SC_UnitTest_ParseFunctionIfBranchWithConnectedInput");
}
TEST_F(ScriptCanvasTestFixture, UseRawBehaviorProperties) TEST_F(ScriptCanvasTestFixture, UseRawBehaviorProperties)
{ {
RunUnitTestGraph("LY_SC_UnitTest_UseRawBehaviorProperties"); RunUnitTestGraph("LY_SC_UnitTest_UseRawBehaviorProperties");

@ -162,7 +162,18 @@ function(ly_setup_target OUTPUT_CONFIGURED_TARGET ALIAS_TARGET_NAME)
elseif(target_type STREQUAL MODULE_LIBRARY) elseif(target_type STREQUAL MODULE_LIBRARY)
set(target_location "\${LY_ROOT_FOLDER}/${library_output_directory}/${PAL_PLATFORM_NAME}/$<CONFIG>/${target_library_output_subdirectory}/$<TARGET_FILE_NAME:${TARGET_NAME}>") set(target_location "\${LY_ROOT_FOLDER}/${library_output_directory}/${PAL_PLATFORM_NAME}/$<CONFIG>/${target_library_output_subdirectory}/$<TARGET_FILE_NAME:${TARGET_NAME}>")
elseif(target_type STREQUAL SHARED_LIBRARY) elseif(target_type STREQUAL SHARED_LIBRARY)
string(APPEND target_file_contents "set_property(TARGET ${TARGET_NAME} PROPERTY IMPORTED_IMPLIB_$<CONFIG> \"\${LY_ROOT_FOLDER}/${archive_output_directory}/${PAL_PLATFORM_NAME}/$<CONFIG>/$<TARGET_LINKER_FILE_NAME:${TARGET_NAME}>\")\n") string(APPEND target_file_contents
"set_property(TARGET ${TARGET_NAME}
APPEND_STRING PROPERTY IMPORTED_IMPLIB
$<$<CONFIG:$<CONFIG>$<ANGLE-R>:\"\${LY_ROOT_FOLDER}/${archive_output_directory}/${PAL_PLATFORM_NAME}/$<CONFIG>/$<TARGET_LINKER_FILE_NAME:${TARGET_NAME}>\"$<ANGLE-R>
)
")
string(APPEND target_file_contents
"set_property(TARGET ${TARGET_NAME}
PROPERTY IMPORTED_IMPLIB_$<UPPER_CASE:$<CONFIG>>
\"\${LY_ROOT_FOLDER}/${archive_output_directory}/${PAL_PLATFORM_NAME}/$<CONFIG>/$<TARGET_LINKER_FILE_NAME:${TARGET_NAME}>\"
)
")
set(target_location "\${LY_ROOT_FOLDER}/${library_output_directory}/${PAL_PLATFORM_NAME}/$<CONFIG>/${target_library_output_subdirectory}/$<TARGET_FILE_NAME:${TARGET_NAME}>") set(target_location "\${LY_ROOT_FOLDER}/${library_output_directory}/${PAL_PLATFORM_NAME}/$<CONFIG>/${target_library_output_subdirectory}/$<TARGET_FILE_NAME:${TARGET_NAME}>")
else() # STATIC_LIBRARY, OBJECT_LIBRARY, INTERFACE_LIBRARY else() # STATIC_LIBRARY, OBJECT_LIBRARY, INTERFACE_LIBRARY
set(target_location "\${LY_ROOT_FOLDER}/${archive_output_directory}/${PAL_PLATFORM_NAME}/$<CONFIG>/$<TARGET_LINKER_FILE_NAME:${TARGET_NAME}>") set(target_location "\${LY_ROOT_FOLDER}/${archive_output_directory}/${PAL_PLATFORM_NAME}/$<CONFIG>/$<TARGET_LINKER_FILE_NAME:${TARGET_NAME}>")

@ -10,5 +10,5 @@
# #
if(CMAKE_GENERATOR MATCHES "Visual Studio 16") if(CMAKE_GENERATOR MATCHES "Visual Studio 16")
configure_file("${CMAKE_CURRENT_LIST_DIR}/Directory.Build.props" "${CMAKE_CURRENT_BINARY_DIR}/Directory.Build.props" COPYONLY) configure_file("${CMAKE_CURRENT_LIST_DIR}/Directory.Build.props" "${CMAKE_BINARY_DIR}/Directory.Build.props" COPYONLY)
endif() endif()

@ -16,35 +16,6 @@ if(NOT PAL_TRAIT_BUILD_TESTS_SUPPORTED)
return() return()
endif() endif()
################################################################################
# Asset Processing Target
# i.e. Tests depend on AutomatedTesting.Assets
################################################################################
if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS)
get_property(LY_PROJECTS_TARGET_NAME GLOBAL PROPERTY LY_PROJECTS_TARGET_NAME)
foreach(project_target_name project_path IN ZIP_LISTS LY_PROJECTS_TARGET_NAME LY_PROJECTS)
file(REAL_PATH ${project_path} project_real_path BASE_DIRECTORY ${LY_ROOT_FOLDER})
# With the lock file, asset processing jobs are serialized to avoid race conditions
# on files that are created temporarily in source folders during shader processing.
add_custom_target(${project_target_name}.Assets
COMMENT "Processing ${project_target_name} assets..."
COMMAND "${CMAKE_COMMAND}"
-DLY_LOCK_FILE=$<TARGET_FILE_DIR:AZ::AssetProcessorBatch>/project_assets.lock
-P ${LY_ROOT_FOLDER}/cmake/CommandExecution.cmake
EXEC_COMMAND $<TARGET_FILE:AZ::AssetProcessorBatch>
--zeroAnalysisMode
--project-path=${project_real_path}
--platforms=${LY_ASSET_DEPLOY_ASSET_TYPE}
)
set_target_properties(${project_target_name}.Assets
PROPERTIES
EXCLUDE_FROM_ALL TRUE
FOLDER ${project_target_name}
)
endforeach()
endif()
################################################################################ ################################################################################
# Tests # Tests
################################################################################ ################################################################################

Loading…
Cancel
Save