diff --git a/AutomatedTesting/TestAssets/ReportOneMissingDependency.txt b/AutomatedTesting/TestAssets/ReportOneMissingDependency.txt new file mode 100644 index 0000000000..24a8493ee5 --- /dev/null +++ b/AutomatedTesting/TestAssets/ReportOneMissingDependency.txt @@ -0,0 +1,5 @@ +This is the UUID for libs / particles / milestone2particles . xml. +6BDE282B49C957F7B0714B26579BCA9A +This isn an invalid UUID +33bdee92F3225688ABEE534F6058593F +This is another invalid UUID B076CDDC-14DK-50F4-A5E9-7518ABB3E851 \ No newline at end of file diff --git a/Code/Framework/AzFramework/AzFramework/Physics/Collision/CollisionEvents.cpp b/Code/Framework/AzFramework/AzFramework/Physics/Collision/CollisionEvents.cpp index 4c9124f594..9f9e5c51ad 100644 --- a/Code/Framework/AzFramework/AzFramework/Physics/Collision/CollisionEvents.cpp +++ b/Code/Framework/AzFramework/AzFramework/Physics/Collision/CollisionEvents.cpp @@ -37,9 +37,10 @@ namespace AzPhysics if (auto* behaviorContext = azdynamic_cast(context)) { behaviorContext->Class() - ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All) - ->Method("GetTriggerEntityId", &TriggerEvent::GetTriggerEntityId) - ->Method("GetOtherEntityId", &TriggerEvent::GetOtherEntityId) + ->Attribute(AZ::Script::Attributes::Module, "physics") + ->Attribute(AZ::Script::Attributes::Category, "Physics") + ->Method("Get Trigger EntityId", &TriggerEvent::GetTriggerEntityId) + ->Method("Get Other EntityId", &TriggerEvent::GetOtherEntityId) ; } } @@ -104,10 +105,11 @@ namespace AzPhysics if (auto* behaviorContext = azdynamic_cast(context)) { behaviorContext->Class() - ->Attribute(AZ::Script::Attributes::ExcludeFrom, AZ::Script::Attributes::ExcludeFlags::All) - ->Property("Contacts", BehaviorValueProperty(&CollisionEvent::m_contacts)) - ->Method("GetBody1EntityId", &CollisionEvent::GetBody1EntityId) - ->Method("GetBody2EntityId", &CollisionEvent::GetBody2EntityId) + ->Attribute(AZ::Script::Attributes::Module, "physics") + ->Attribute(AZ::Script::Attributes::Category, "Physics") + ->Property("Contacts", BehaviorValueGetter(&CollisionEvent::m_contacts), nullptr) + ->Method("Get Body 1 EntityId", &CollisionEvent::GetBody1EntityId) + ->Method("Get Body 2 EntityId", &CollisionEvent::GetBody2EntityId) ; } } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/API/ViewPaneOptions.h b/Code/Framework/AzToolsFramework/AzToolsFramework/API/ViewPaneOptions.h index fb86968ebb..4891d2ef85 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/API/ViewPaneOptions.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/API/ViewPaneOptions.h @@ -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 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 diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.h index d8eb81dd40..d8bc63cfc6 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Entity/PrefabEditorEntityOwnershipService.h @@ -197,7 +197,7 @@ namespace AzToolsFramework AZ::IO::PathView filePath, Prefab::InstanceOptionalReference instanceToParentUnder) override; Prefab::InstanceOptionalReference GetRootPrefabInstance() override; - + const AZStd::vector>& GetPlayInEditorAssetData() override; ////////////////////////////////////////////////////////////////////////// diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceToTemplateInterface.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceToTemplateInterface.h index c2ddbcf24f..c9b4b5acc3 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceToTemplateInterface.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceToTemplateInterface.h @@ -47,8 +47,13 @@ namespace AzToolsFramework virtual void AppendEntityAliasToPatchPaths(PrefabDom& providedPatch, const AZ::EntityId& entityId) = 0; - //! Updates the template links (updating instances) for the given templateId using the providedPatch - virtual bool PatchTemplate(PrefabDomValue& providedPatch, TemplateId templateId) = 0; + //! Updates the template links (updating instances) for the given template and triggers propagation on its instances. + //! @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; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceToTemplatePropagator.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceToTemplatePropagator.cpp index 6d3ddedd51..9fb6293b74 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceToTemplatePropagator.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceToTemplatePropagator.cpp @@ -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); @@ -184,7 +184,7 @@ namespace AzToolsFramework if (result.GetOutcome() == AZ::JsonSerializationResult::Outcomes::Success) { m_prefabSystemComponentInterface->SetTemplateDirtyFlag(templateId, true); - m_prefabSystemComponentInterface->PropagateTemplateChanges(templateId); + m_prefabSystemComponentInterface->PropagateTemplateChanges(templateId, instanceToExclude); return true; } else diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceToTemplatePropagator.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceToTemplatePropagator.h index 9a6aad8ac1..358494091d 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceToTemplatePropagator.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceToTemplatePropagator.h @@ -37,7 +37,7 @@ namespace AzToolsFramework 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; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceUpdateExecutor.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceUpdateExecutor.cpp index 6194adf784..b7a81a7f0c 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceUpdateExecutor.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceUpdateExecutor.cpp @@ -56,7 +56,7 @@ namespace AzToolsFramework AZ::Interface::Unregister(this); } - void InstanceUpdateExecutor::AddTemplateInstancesToQueue(TemplateId instanceTemplateId) + void InstanceUpdateExecutor::AddTemplateInstancesToQueue(TemplateId instanceTemplateId, InstanceOptionalReference instanceToExclude) { auto findInstancesResult = m_templateInstanceMapperInterface->FindInstancesOwnedByTemplate(instanceTemplateId); @@ -70,9 +70,18 @@ namespace AzToolsFramework return; } + Instance* instanceToExcludePtr = nullptr; + if (instanceToExclude.has_value()) + { + instanceToExcludePtr = &(instanceToExclude->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; ToolsApplicationRequestBus::BroadcastResult(selectedEntityIds, &ToolsApplicationRequests::GetSelectedEntities); - ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequests::SetSelectedEntities, EntityIdList()); + PrefabDom instanceDomFromRootDocument; // 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 @@ -148,13 +157,62 @@ namespace AzToolsFramework continue; } - Template& currentTemplate = currentTemplateReference->get(); 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 pathOfInstances; + + while (rootInstance->get().GetParentInstance() != AZStd::nullopt) { - // If a link was created for a nested instance before the changes were propagated, - // then we associate it correctly here - instanceToUpdate->GetNestedInstances([&](AZStd::unique_ptr& nestedInstance) { + 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)) + { + Template& currentTemplate = currentTemplateReference->get(); + instanceToUpdate->GetNestedInstances([&](AZStd::unique_ptr& nestedInstance) + { if (nestedInstance->GetLinkId() != InvalidLinkId) { return; @@ -179,22 +237,11 @@ namespace AzToolsFramework AzToolsFramework::EditorEntityContextRequestBus::Broadcast( &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++) { - // Since entities get recreated during propagation, we need to check whether the entities correspoding to the list - // of selected entity ids are present or not. + // Since entities get recreated during propagation, we need to check whether the entities + // corresponding to the list of selected entity ids are present or not. AZ::Entity* entity = GetEntityById(*entityIdIterator); if (entity == nullptr) { diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceUpdateExecutor.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceUpdateExecutor.h index fa13c34b98..a3fdd019c2 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceUpdateExecutor.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceUpdateExecutor.h @@ -35,7 +35,7 @@ namespace AzToolsFramework explicit InstanceUpdateExecutor(int instanceCountToUpdateInBatch = 0); - void AddTemplateInstancesToQueue(TemplateId instanceTemplateId) override; + void AddTemplateInstancesToQueue(TemplateId instanceTemplateId, InstanceOptionalReference instanceToExclude = AZStd::nullopt) override; bool UpdateTemplateInstancesInQueue() override; virtual void RemoveTemplateInstanceFromQueue(const Instance* instance) override; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceUpdateExecutorInterface.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceUpdateExecutorInterface.h index d794c4929d..2454a995cf 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceUpdateExecutorInterface.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/Instance/InstanceUpdateExecutorInterface.h @@ -27,7 +27,7 @@ namespace AzToolsFramework virtual ~InstanceUpdateExecutorInterface() = default; // 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. virtual bool UpdateTemplateInstancesInQueue() = 0; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp index 4656dcf48f..fcdbc5ce07 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.cpp @@ -242,11 +242,13 @@ namespace AzToolsFramework m_instanceToTemplateInterface->GenerateDomForEntity(containerAfterReset, *containerEntity); // Update the state of the entity - PrefabUndoEntityUpdate* state = aznew PrefabUndoEntityUpdate(AZStd::to_string(static_cast(containerEntityId))); - state->SetParent(undoBatch.GetUndoBatch()); - state->Capture(containerBeforeReset, containerAfterReset, containerEntityId); + auto templateId = instanceToCreate->get().GetTemplateId(); - 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. @@ -661,12 +663,12 @@ namespace AzToolsFramework else { Internal_HandleContainerOverride( - parentUndoBatch, entityId, patch, owningInstance->get().GetLinkId()); + parentUndoBatch, entityId, patch, owningInstance->get().GetLinkId(), owningInstance->get().GetParentInstance()); } } else { - Internal_HandleEntityChange(parentUndoBatch, entityId, beforeState, afterState); + Internal_HandleEntityChange(parentUndoBatch, entityId, beforeState, afterState, owningInstance); if (isNewParentOwnedByDifferentInstance) { @@ -679,25 +681,27 @@ namespace AzToolsFramework } 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 PrefabUndoLinkUpdate* linkUpdate = aznew PrefabUndoLinkUpdate(AZStd::to_string(static_cast(entityId))); linkUpdate->SetParent(undoBatch); linkUpdate->Capture(patch, linkId); - linkUpdate->Redo(); + linkUpdate->Redo(parentInstance); } 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 PrefabUndoEntityUpdate* state = aznew PrefabUndoEntityUpdate(AZStd::to_string(static_cast(entityId))); state->SetParent(undoBatch); state->Capture(beforeState, afterState, entityId); - state->Redo(); + state->Redo(instance); } void PrefabPublicHandler::Internal_HandleInstanceChange( diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h index 167791d1c1..f3d778d8de 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabPublicHandler.h @@ -162,9 +162,11 @@ namespace AzToolsFramework InstanceOptionalConstReference instance, const AZStd::unordered_set& templateSourcePaths); 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( - 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 UpdateLinkPatchesWithNewEntityAliases( diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponent.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponent.cpp index 5f5564b4e1..ab77d53283 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponent.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponent.cpp @@ -141,8 +141,10 @@ namespace AzToolsFramework return newInstance; } - void PrefabSystemComponent::PropagateTemplateChanges(TemplateId templateId) + void PrefabSystemComponent::PropagateTemplateChanges(TemplateId templateId, InstanceOptionalReference instanceToExclude) { + UpdatePrefabInstances(templateId, instanceToExclude); + auto templateIdToLinkIdsIterator = m_templateToLinkIdsMap.find(templateId); if (templateIdToLinkIdsIterator != m_templateToLinkIdsMap.end()) { @@ -153,10 +155,6 @@ namespace AzToolsFramework templateIdToLinkIdsIterator->second.end())); UpdateLinkedInstances(linkIdsToUpdateQueue); } - else - { - UpdatePrefabInstances(templateId); - } } 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& linkIdsQueue) @@ -250,8 +248,6 @@ namespace AzToolsFramework if (targetTemplateIdToLinkIdMap[targetTemplateId].first.empty() && targetTemplateIdToLinkIdMap[targetTemplateId].second) { - UpdatePrefabInstances(targetTemplateId); - auto templateToLinkIter = m_templateToLinkIdsMap.find(targetTemplateId); if (templateToLinkIter != m_templateToLinkIdsMap.end()) { diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponent.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponent.h index 0a9a450f64..a5170b8eef 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponent.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponent.h @@ -215,14 +215,14 @@ namespace AzToolsFramework */ 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. * * @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: AZ_DISABLE_COPY_MOVE(PrefabSystemComponent); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponentInterface.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponentInterface.h index f47941254a..8daf4e731b 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponentInterface.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabSystemComponentInterface.h @@ -56,7 +56,7 @@ namespace AzToolsFramework virtual PrefabDom& FindTemplateDom(TemplateId templateId) = 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 InstantiatePrefab(AZ::IO::PathView filePath) = 0; virtual AZStd::unique_ptr InstantiatePrefab(const TemplateId& templateId) = 0; diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabUndo.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabUndo.cpp index aadcdcdea0..d0b3426495 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabUndo.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabUndo.cpp @@ -70,10 +70,10 @@ namespace AzToolsFramework const AZ::EntityId& entityId) { //get the entity alias for future undo/redo - InstanceOptionalReference instanceOptionalReference = m_instanceEntityMapperInterface->FindOwningInstance(entityId); - AZ_Error("Prefab", instanceOptionalReference, + auto instanceReference = m_instanceEntityMapperInterface->FindOwningInstance(entityId); + AZ_Error("Prefab", instanceReference, "Failed to find an owning instance for the entity with id %llu.", static_cast(entityId)); - Instance& instance = instanceOptionalReference->get(); + Instance& instance = instanceReference->get(); m_templateId = instance.GetTemplateId(); m_entityAlias = (instance.GetEntityAlias(entityId)).value(); @@ -106,6 +106,17 @@ namespace AzToolsFramework 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 PrefabUndoInstanceLink::PrefabUndoInstanceLink(const AZStd::string& undoOperationName) : PrefabUndoBase(undoOperationName) @@ -290,7 +301,12 @@ namespace AzToolsFramework 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); @@ -304,7 +320,7 @@ namespace AzToolsFramework //propagate the link changes link->get().UpdateTarget(); - m_prefabSystemComponentInterface->PropagateTemplateChanges(link->get().GetTargetTemplateId()); + m_prefabSystemComponentInterface->PropagateTemplateChanges(link->get().GetTargetTemplateId(), instanceToExclude); //mark as dirty m_prefabSystemComponentInterface->SetTemplateDirtyFlag(link->get().GetTargetTemplateId(), true); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabUndo.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabUndo.h index 33d9e5ad33..7ae677571a 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabUndo.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Prefab/PrefabUndo.h @@ -71,11 +71,12 @@ namespace AzToolsFramework void Capture( PrefabDom& initialState, - PrefabDom& endState, - const AZ::EntityId& entity); + PrefabDom& endState, const AZ::EntityId& entity); void Undo() override; void Redo() override; + //! Overload to allow to apply the change, but prevent instanceToExclude from being refreshed. + void Redo(InstanceOptionalReference instanceToExclude); private: InstanceEntityMapperInterface* m_instanceEntityMapperInterface = nullptr; @@ -139,9 +140,11 @@ namespace AzToolsFramework void Undo() override; void Redo() override; + //! Overload to allow to apply the change, but prevent instanceToExclude from being refreshed. + void Redo(InstanceOptionalReference instanceToExclude); private: - void UpdateLink(PrefabDom& linkDom); + void UpdateLink(PrefabDom& linkDom, InstanceOptionalReference instanceToExclude = AZStd::nullopt); LinkId m_linkId; PrefabDom m_linkDomNext; //data for delete/update diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp index fee0267766..eadb870563 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp @@ -2240,42 +2240,31 @@ namespace AzToolsFramework RegenerateManipulators(); }); - bool isPrefabSystemEnabled = false; - AzFramework::ApplicationRequests::Bus::BroadcastResult( - isPrefabSystemEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled); + // duplicate selection + AddAction( + m_actions, { QKeySequence(Qt::CTRL + Qt::Key_D) }, + /*ID_EDIT_CLONE =*/33525, s_duplicateTitle, s_duplicateDesc, + []() + { + AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework); - bool prefabWipFeaturesEnabled = false; - AzFramework::ApplicationRequests::Bus::BroadcastResult( - prefabWipFeaturesEnabled, &AzFramework::ApplicationRequests::ArePrefabWipFeaturesEnabled); - - if (!isPrefabSystemEnabled || (isPrefabSystemEnabled && prefabWipFeaturesEnabled)) - { - // duplicate selection - AddAction( - m_actions, { QKeySequence(Qt::CTRL + Qt::Key_D) }, - /*ID_EDIT_CLONE =*/33525, s_duplicateTitle, s_duplicateDesc, - []() + // Clear Widget selection - Prevents issues caused by cloning entities while a property in the Reflected Property Editor + // is being edited. + if (QApplication::focusWidget()) { - 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); + auto selectionCommand = AZStd::make_unique(EntityIdList(), s_duplicateUndoRedoDesc); + selectionCommand->SetParent(undoBatch.GetUndoBatch()); + selectionCommand.release(); - ScopedUndoBatch undoBatch(s_duplicateUndoRedoDesc); - auto selectionCommand = AZStd::make_unique(EntityIdList(), s_duplicateUndoRedoDesc); - selectionCommand->SetParent(undoBatch.GetUndoBatch()); - selectionCommand.release(); + bool handled = false; + EditorRequestBus::Broadcast(&EditorRequests::CloneSelection, handled); - bool handled = false; - EditorRequestBus::Broadcast(&EditorRequests::CloneSelection, handled); - - // selection update handled in AfterEntitySelectionChanged - }); - } + // selection update handled in AfterEntitySelectionChanged + }); // delete selection AddAction( diff --git a/Code/Framework/AzToolsFramework/Tests/Prefab/PrefabEntityAliasTests.cpp b/Code/Framework/AzToolsFramework/Tests/Prefab/PrefabEntityAliasTests.cpp index 0cf39a3572..458625fa3f 100644 --- a/Code/Framework/AzToolsFramework/Tests/Prefab/PrefabEntityAliasTests.cpp +++ b/Code/Framework/AzToolsFramework/Tests/Prefab/PrefabEntityAliasTests.cpp @@ -242,6 +242,7 @@ namespace UnitTest // Patch the nested prefab to reference an entity in its parent ASSERT_TRUE(m_instanceToTemplateInterface->PatchEntityInTemplate(patch, newEntity->GetId())); + m_instanceUpdateExecutorInterface->AddTemplateInstancesToQueue(rootInstance->GetTemplateId()); m_instanceUpdateExecutorInterface->UpdateTemplateInstancesInQueue(); // Using the aliases we saved grab the updated entities so we can verify the entity reference is still preserved diff --git a/Code/Sandbox/Editor/Core/LevelEditorMenuHandler.cpp b/Code/Sandbox/Editor/Core/LevelEditorMenuHandler.cpp index 8f6e927a84..39c7ae43fd 100644 --- a/Code/Sandbox/Editor/Core/LevelEditorMenuHandler.cpp +++ b/Code/Sandbox/Editor/Core/LevelEditorMenuHandler.cpp @@ -473,18 +473,8 @@ void LevelEditorMenuHandler::PopulateEditMenu(ActionManager::MenuWrapper& editMe // editMenu->addAction(ID_EDIT_PASTE); // editMenu.AddSeparator(); - bool isPrefabSystemEnabled = false; - AzFramework::ApplicationRequests::Bus::BroadcastResult(isPrefabSystemEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled); - - bool prefabWipFeaturesEnabled = false; - AzFramework::ApplicationRequests::Bus::BroadcastResult( - prefabWipFeaturesEnabled, &AzFramework::ApplicationRequests::ArePrefabWipFeaturesEnabled); - - if (!isPrefabSystemEnabled || (isPrefabSystemEnabled && prefabWipFeaturesEnabled)) - { - // Duplicate - editMenu.AddAction(ID_EDIT_CLONE); - } + // Duplicate + editMenu.AddAction(ID_EDIT_CLONE); // Delete editMenu.AddAction(ID_EDIT_DELETE); @@ -912,6 +902,12 @@ QAction* LevelEditorMenuHandler::CreateViewPaneAction(const QtViewPane* view) action = new QAction(menuText, this); action->setObjectName(view->m_name); action->setCheckable(true); + + if (view->m_options.showOnToolsToolbar) + { + action->setIcon(QIcon(view->m_options.toolbarIcon)); + } + m_actionManager->AddAction(view->m_id, action); if (!view->m_options.shortcut.isEmpty()) @@ -941,6 +937,11 @@ QAction* LevelEditorMenuHandler::CreateViewPaneMenuItem( menu->addAction(action); + if (view->m_options.showOnToolsToolbar) + { + m_mainWindow->GetToolbarManager()->AddButtonToEditToolbar(action); + } + return action; } diff --git a/Code/Sandbox/Editor/MainWindow.cpp b/Code/Sandbox/Editor/MainWindow.cpp index 31eac05824..3753c29064 100644 --- a/Code/Sandbox/Editor/MainWindow.cpp +++ b/Code/Sandbox/Editor/MainWindow.cpp @@ -470,9 +470,11 @@ void MainWindow::Initialize() InitToolActionHandlers(); + // Initialize toolbars before we setup the menu so that any tools can be added to the toolbar as needed + InitToolBars(); + m_levelEditorMenuHandler->Initialize(); - InitToolBars(); InitStatusBar(); AzToolsFramework::SourceControlNotificationBus::Handler::BusConnect(); diff --git a/Code/Sandbox/Editor/ToolbarManager.cpp b/Code/Sandbox/Editor/ToolbarManager.cpp index 241b969da7..137057a4fa 100644 --- a/Code/Sandbox/Editor/ToolbarManager.cpp +++ b/Code/Sandbox/Editor/ToolbarManager.cpp @@ -623,6 +623,20 @@ AmazonToolbar ToolbarManager::GetMiscToolbar() const 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 { for (const AmazonToolbar& toolbar : m_standardToolbars) diff --git a/Code/Sandbox/Editor/ToolbarManager.h b/Code/Sandbox/Editor/ToolbarManager.h index 70228636f5..1867316d01 100644 --- a/Code/Sandbox/Editor/ToolbarManager.h +++ b/Code/Sandbox/Editor/ToolbarManager.h @@ -169,6 +169,8 @@ public: AmazonToolbar GetMiscToolbar() const; AmazonToolbar GetPlayConsoleToolbar() const; + void AddButtonToEditToolbar(QAction* action); + private: Q_DISABLE_COPY(ToolbarManager); void SaveToolbars(); diff --git a/Code/Sandbox/Plugins/ComponentEntityEditorPlugin/SandboxIntegration.cpp b/Code/Sandbox/Plugins/ComponentEntityEditorPlugin/SandboxIntegration.cpp index 8161d07547..694714cc6e 100644 --- a/Code/Sandbox/Plugins/ComponentEntityEditorPlugin/SandboxIntegration.cpp +++ b/Code/Sandbox/Plugins/ComponentEntityEditorPlugin/SandboxIntegration.cpp @@ -670,18 +670,11 @@ void SandboxIntegrationManager::PopulateEditorGlobalContextMenu(QMenu* menu, con AzToolsFramework::EditorContextMenuBus::Broadcast(&AzToolsFramework::EditorContextMenuEvents::PopulateEditorGlobalContextMenu, menu); } - bool prefabWipFeaturesEnabled = false; - AzFramework::ApplicationRequests::Bus::BroadcastResult( - prefabWipFeaturesEnabled, &AzFramework::ApplicationRequests::ArePrefabWipFeaturesEnabled); - - if (!prefabSystemEnabled || (prefabSystemEnabled && prefabWipFeaturesEnabled)) + action = menu->addAction(QObject::tr("Duplicate")); + QObject::connect(action, &QAction::triggered, action, [this] { ContextMenu_Duplicate(); }); + if (selected.size() == 0) { - action = menu->addAction(QObject::tr("Duplicate")); - QObject::connect(action, &QAction::triggered, action, [this] { ContextMenu_Duplicate(); }); - if (selected.size() == 0) - { - action->setDisabled(true); - } + action->setDisabled(true); } if (!prefabSystemEnabled) diff --git a/Gems/Atom/RPI/Code/CMakeLists.txt b/Gems/Atom/RPI/Code/CMakeLists.txt index 2898967add..8a2684347e 100644 --- a/Gems/Atom/RPI/Code/CMakeLists.txt +++ b/Gems/Atom/RPI/Code/CMakeLists.txt @@ -150,6 +150,7 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS) PRIVATE AZ::AtomCore AZ::AzTest + AZ::AzTestShared AZ::AzFramework AZ::AzToolsFramework Legacy::CryCommon diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Model/Model.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Model/Model.h index 35af200759..514e3e37a5 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Model/Model.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Public/Model/Model.h @@ -61,12 +61,13 @@ namespace AZ //! 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] //! - //! @param rayStart position where the ray starts - //! @param dir direction where the ray ends (does not have to be unit length) - //! @param distanceFactor if an intersection is detected, this will be set such that distanceFactor * dir.length == distance to intersection - //! @param normal if an intersection is detected, this will be set to the normal at the point of intersection - //! @return true if the ray intersects the mesh - bool LocalRayIntersection(const AZ::Vector3& rayStart, const AZ::Vector3& dir, float& distanceFactor, AZ::Vector3& normal) const; + //! @param rayStart The starting point of the ray. + //! @param rayDir The direction and length of the ray (magnitude is encoded in the direction). + //! @param[out] distanceNormalized If an intersection is found, will be set to the normalized distance of the intersection + //! (in the range 0.0-1.0) - to calculate the actual distance, multiply distanceNormalized by the magnitude of rayDir. + //! @param[out] normal If an intersection is found, will be set to the normal at the point of collision. + //! @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. //! 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 nonUniformScale Non-uniform scale applied in the model's local frame. - //! @param rayStart position where the ray starts - //! @param dir direction where the ray ends (does not have to be unit length) - //! @param distanceFactor if an intersection is detected, this will be set such that distanceFactor * dir.length == distance to intersection - //! @param normal if an intersection is detected, this will be set to the normal at the point of intersection - //! @return true if the ray intersects the mesh - bool RayIntersection(const AZ::Transform& modelTransform, const AZ::Vector3& nonUniformScale, const AZ::Vector3& rayStart, - const AZ::Vector3& dir, float& distanceFactor, AZ::Vector3& normal) const; + //! @param rayStart The starting point of the ray. + //! @param rayDir The direction and length of the ray (magnitude is encoded in the direction). + //! @param[out] distanceNormalized If an intersection is found, will be set to the normalized distance of the intersection + //! (in the range 0.0-1.0) - to calculate the actual distance, multiply distanceNormalized by the magnitude of rayDir. + //! @param[out] normal If an intersection is found, will be set to the normal at the point of collision. + //! @return True if the ray intersects the mesh. + 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. const AZStd::unordered_set& GetUvNames() const; diff --git a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Model/ModelAsset.h b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Model/ModelAsset.h index 8a773dc29e..f3da349195 100644 --- a/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Model/ModelAsset.h +++ b/Gems/Atom/RPI/Code/Include/Atom/RPI.Reflect/Model/ModelAsset.h @@ -63,12 +63,14 @@ namespace AZ //! 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] //! - //! @param rayStart position where the ray starts - //! @param dir direction where the ray ends (does not have to be unit length) - //! @param distance if an intersection is detected, this will be set such that distanceFactor * dir.length == distance to intersection - //! @param normal if an intersection is detected, this will be set to the normal at the point of collision - //! @return true if the ray intersects the mesh - virtual bool LocalRayIntersectionAgainstModel(const AZ::Vector3& rayStart, const AZ::Vector3& dir, float& distance, AZ::Vector3& normal) const; + //! @param rayStart The starting point of the ray. + //! @param rayDir The direction and length of the ray (magnitude is encoded in the direction). + //! @param[out] distanceNormalized If an intersection is found, will be set to the normalized distance of the intersection + //! (in the range 0.0-1.0) - to calculate the actual distance, multiply distanceNormalized by the magnitude of rayDir. + //! @param[out] normal If an intersection is found, will be set to the normal at the point of collision. + //! @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: void SetReady(); @@ -79,9 +81,15 @@ namespace AZ // mutable method 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 AZ::Name m_positionName{ "POSITION" }; diff --git a/Gems/Atom/RPI/Code/Source/RPI.Public/Model/Model.cpp b/Gems/Atom/RPI/Code/Source/RPI.Public/Model/Model.cpp index 86477bf785..17ff2c64c8 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Public/Model/Model.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Public/Model/Model.cpp @@ -137,12 +137,12 @@ namespace AZ 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); float start; 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 (ModelAsset* modelAssetPtr = m_modelAsset.Get()) @@ -151,7 +151,7 @@ namespace AZ AZ::Debug::Timer timer; timer.Stamp(); #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 (hit) { @@ -166,8 +166,12 @@ namespace AZ } bool Model::RayIntersection( - const AZ::Transform& modelTransform, const AZ::Vector3& nonUniformScale, const AZ::Vector3& rayStart, const AZ::Vector3& dir, - float& distanceFactor, AZ::Vector3& normal) const + const AZ::Transform& modelTransform, + const AZ::Vector3& nonUniformScale, + const AZ::Vector3& rayStart, + const AZ::Vector3& rayDir, + float& distanceNormalized, + AZ::Vector3& normal) const { AZ_PROFILE_FUNCTION(Debug::ProfileCategory::AzRender); const AZ::Vector3 clampedScale = nonUniformScale.GetMax(AZ::Vector3(AZ::MinTransformScale)); @@ -175,12 +179,13 @@ namespace AZ const AZ::Transform inverseTM = modelTransform.GetInverse(); 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. - const AZ::Vector3 rayDest = rayStart + dir; + // Instead of just rotating 'rayDir' we need it to be scaled too, so that 'distanceNormalized' will be in the target units rather + // than object local units. + const AZ::Vector3 rayDest = rayStart + rayDir; const AZ::Vector3 rayDestLocal = inverseTM.TransformPoint(rayDest) / clampedScale; 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(); return result; } diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelAsset.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelAsset.cpp index 988d07e66d..52fda0f56b 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelAsset.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelAsset.cpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -75,7 +76,8 @@ namespace AZ 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); @@ -85,7 +87,7 @@ namespace AZ 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_kdTree) @@ -97,11 +99,11 @@ namespace AZ } 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 @@ -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 if (GetLodAssets().empty() == false) @@ -144,27 +147,27 @@ namespace AZ // intersect against the highest level of detail if (ModelLodAsset* loadAssetPtr = GetLodAssets()[0].Get()) { - float shortestDistance = std::numeric_limits::max(); bool anyHit = false; - AZ::Vector3 intersectionNormal; - + float shortestDistanceNormalized = AZStd::numeric_limits::max(); 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; - if (distance < shortestDistance) + + if (currentDistanceNormalized < shortestDistanceNormalized) { normal = intersectionNormal; - shortestDistance = distance; + shortestDistanceNormalized = currentDistanceNormalized; } } } if (anyHit) { - distance = shortestDistance; + distanceNormalized = shortestDistanceNormalized; } return anyHit; @@ -174,7 +177,12 @@ namespace AZ 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 AZStd::array_view& streamBufferList = mesh.GetStreamBufferInfoList(); @@ -217,14 +225,13 @@ namespace AZ AZStd::array_view indexRawBuffer = indexAssetViewPtr->GetBuffer(); RHI::BufferViewDescriptor indexRawDesc = indexAssetViewPtr->GetBufferViewDescriptor(); - float closestNormalizedDistance = 1.f; bool anyHit = false; - const AZ::Vector3 rayEnd = rayStart + dir * distance; + const AZ::Vector3 rayEnd = rayStart + rayDir; AZ::Vector3 a, b, c; AZ::Vector3 intersectionNormal; - float normalizedDistance = 1.f; + float shortestDistanceNormalized = AZStd::numeric_limits::max(); const AZ::u32* indexPtr = reinterpret_cast(indexRawBuffer.data()); for (uint32_t indexIter = 0; indexIter <= indexRawDesc.m_elementCount - 3; indexIter += 3, indexPtr += 3) { @@ -247,20 +254,22 @@ namespace AZ p = reinterpret_cast(&positionRawBuffer[index2 * positionElementSize]); c.Set(const_cast(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; - closestNormalizedDistance = normalizedDistance; + shortestDistanceNormalized = currentDistanceNormalized; } - anyHit = true; } } if (anyHit) { - distance = closestNormalizedDistance * distance; + distanceNormalized = shortestDistanceNormalized; } return anyHit; diff --git a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelKdTree.cpp b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelKdTree.cpp index bee489c2fd..2ee6d93df3 100644 --- a/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelKdTree.cpp +++ b/Gems/Atom/RPI/Code/Source/RPI.Reflect/Model/ModelKdTree.cpp @@ -208,10 +208,10 @@ namespace AZ bool ModelKdTree::RayIntersection( const AZ::Vector3& raySrc, const AZ::Vector3& rayDir, float& distanceNormalized, AZ::Vector3& normal) const { - float closestDistanceNormalized = AZStd::numeric_limits::max(); - if (RayIntersectionRecursively(m_pRootNode.get(), raySrc, rayDir, closestDistanceNormalized, normal)) + float shortestDistanceNormalized = AZStd::numeric_limits::max(); + if (RayIntersectionRecursively(m_pRootNode.get(), raySrc, rayDir, shortestDistanceNormalized, normal)) { - distanceNormalized = closestDistanceNormalized; + distanceNormalized = shortestDistanceNormalized; return true; } diff --git a/Gems/Atom/RPI/Code/Tests/Model/ModelTests.cpp b/Gems/Atom/RPI/Code/Tests/Model/ModelTests.cpp index 3ce17bee8b..f039240ee0 100644 --- a/Gems/Atom/RPI/Code/Tests/Model/ModelTests.cpp +++ b/Gems/Atom/RPI/Code/Tests/Model/ModelTests.cpp @@ -22,6 +22,7 @@ #include #include +#include #include #include @@ -568,7 +569,7 @@ namespace UnitTest 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. TEST_F(ModelTests, SetNameNoBegin) { @@ -581,7 +582,7 @@ namespace UnitTest 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. TEST_F(ModelTests, AddLodNoBegin) { @@ -598,7 +599,7 @@ namespace UnitTest 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. TEST_F(ModelTests, CreateModelNoLods) { @@ -618,8 +619,8 @@ namespace UnitTest ASSERT_EQ(asset.Get(), nullptr); } - // Tests that if we call SetLodIndexBuffer without calling - // Begin first on the ModelLodAssetCreator that it + // Tests that if we call SetLodIndexBuffer without calling + // Begin first on the ModelLodAssetCreator that it // fails as expected. TEST_F(ModelTests, SetLodIndexBufferNoBegin) { @@ -633,8 +634,8 @@ namespace UnitTest creator.SetLodIndexBuffer(validIndexBuffer); } - // Tests that if we call AddLodStreamBuffer without calling - // Begin first on the ModelLodAssetCreator that it + // Tests that if we call AddLodStreamBuffer without calling + // Begin first on the ModelLodAssetCreator that it // fails as expected. TEST_F(ModelTests, AddLodStreamBufferNoBegin) { @@ -648,8 +649,8 @@ namespace UnitTest creator.AddLodStreamBuffer(validStreamBuffer); } - // Tests that if we call BeginMesh without calling - // Begin first on the ModelLodAssetCreator that it + // Tests that if we call BeginMesh without calling + // Begin first on the ModelLodAssetCreator that it // fails as expected. TEST_F(ModelTests, BeginMeshNoBegin) { @@ -662,13 +663,13 @@ namespace UnitTest } // 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* // called but BeginMesh is not. TEST_F(ModelTests, SetAabbNoBeginNoBeginMesh) { using namespace AZ; - + RPI::ModelLodAssetCreator creator; 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 - // 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* // called but BeginMesh is not. TEST_F(ModelTests, SetMaterialIdNoBeginNoBeginMesh) { using namespace AZ; - + RPI::ModelLodAssetCreator creator; { @@ -715,7 +716,7 @@ namespace UnitTest } // 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* // called but BeginMesh is not. TEST_F(ModelTests, SetIndexBufferNoBeginNoBeginMesh) @@ -751,7 +752,7 @@ namespace UnitTest } // 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* // called but BeginMesh is not. 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 // as expected. TEST_F(ModelTests, CreateLodNoMeshes) @@ -804,7 +805,7 @@ namespace UnitTest 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 // BeginMesh call TEST_F(ModelTests, SecondMeshFailureNoBeginMesh) @@ -862,8 +863,8 @@ namespace UnitTest ASSERT_EQ(asset->GetMeshes().size(), 1); } - // Tests that validation still fails when expected - // even after producing a valid mesh due to SetMeshX + // Tests that validation still fails when expected + // even after producing a valid mesh due to SetMeshX // calls coming after End TEST_F(ModelTests, SecondMeshAfterEnd) { @@ -907,7 +908,7 @@ namespace UnitTest AZ::Aabb aabb = AZ::Aabb::CreateCenterRadius(Vector3::CreateZero(), 1.0f); ErrorMessageFinder messageFinder("Begin() was not called", 6); - + creator.BeginMesh(); creator.SetMeshAabb(AZStd::move(aabb)); creator.SetMeshMaterialAsset(m_materialAsset); @@ -955,6 +956,20 @@ namespace UnitTest 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 // 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. @@ -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 TD; - class TwoSeparatedPlanesMesh + class TestMesh { public: - TwoSeparatedPlanesMesh() + TestMesh(const float* positions, size_t positionCount, const uint32_t* indices, size_t indicesCount) { - using namespace AZ; - - RPI::ModelLodAssetCreator lodCreator; - lodCreator.Begin(Data::AssetId(AZ::Uuid::CreateRandom())); + AZ::RPI::ModelLodAssetCreator lodCreator; + lodCreator.Begin(AZ::Data::AssetId(AZ::Uuid::CreateRandom())); 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( AZ::Data::Asset(AZ::Data::AssetId(AZ::Uuid::CreateRandom(), 0), AZ::AzTypeInfo::Uuid(), "") ); { - AZ::Data::Asset indexBuffer = BuildTestBuffer(s_indexes.size(), sizeof(uint32_t)); - AZStd::copy(s_indexes.begin(), s_indexes.end(), reinterpret_cast(const_cast(indexBuffer->GetBuffer().data()))); + AZ::Data::Asset indexBuffer = BuildTestBuffer(indicesCount, sizeof(uint32_t)); + AZStd::copy(indices, indices + indicesCount, reinterpret_cast(const_cast(indexBuffer->GetBuffer().data()))); lodCreator.SetMeshIndexBuffer({ indexBuffer, - RHI::BufferViewDescriptor::CreateStructured(0, s_indexes.size(), sizeof(uint32_t)) + AZ::RHI::BufferViewDescriptor::CreateStructured(0, indicesCount, sizeof(uint32_t)) }); } { - AZ::Data::Asset positionBuffer = BuildTestBuffer(s_positions.size() / 3, sizeof(float) * 3); - AZStd::copy(s_positions.begin(), s_positions.end(), reinterpret_cast(const_cast(positionBuffer->GetBuffer().data()))); + AZ::Data::Asset positionBuffer = BuildTestBuffer(positionCount / 3, sizeof(float) * 3); + AZStd::copy(positions, positions + positionCount, reinterpret_cast(const_cast(positionBuffer->GetBuffer().data()))); lodCreator.AddMeshStreamBuffer( AZ::RHI::ShaderSemantic(AZ::Name("POSITION")), AZ::Name(), { positionBuffer, - RHI::BufferViewDescriptor::CreateStructured(0, s_positions.size() / 3, sizeof(float) * 3) + AZ::RHI::BufferViewDescriptor::CreateStructured(0, positionCount / 3, sizeof(float) * 3) } ); } lodCreator.EndMesh(); - Data::Asset lodAsset; + AZ::Data::Asset lodAsset; lodCreator.End(lodAsset); - RPI::ModelAssetCreator modelCreator; - modelCreator.Begin(Data::AssetId(AZ::Uuid::CreateRandom())); + AZ::RPI::ModelAssetCreator modelCreator; + modelCreator.Begin(AZ::Data::AssetId(AZ::Uuid::CreateRandom())); modelCreator.SetName("TestModel"); modelCreator.AddLodAsset(AZStd::move(lodAsset)); modelCreator.End(m_modelAsset); @@ -1030,40 +1068,20 @@ namespace UnitTest private: AZ::Data::Asset 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 ypos; float zpos; + float xdir; + float ydir; + float zdir; float expectedDistance; bool expectedShouldIntersect; - friend std::ostream& operator<<(std::ostream& os, const KdTreeIntersectParams& param) + friend std::ostream& operator<<(std::ostream& os, const IntersectParams& param) { return os << "xpos:" << param.xpos @@ -1076,13 +1094,15 @@ namespace UnitTest class KdTreeIntersectsParameterizedFixture : public ModelTests - , public ::testing::WithParamInterface + , public ::testing::WithParamInterface { }; TEST_P(KdTreeIntersectsParameterizedFixture, KdTreeIntersects) { - TwoSeparatedPlanesMesh mesh; + TestMesh mesh( + TwoSeparatedPlanesPositions.data(), TwoSeparatedPlanesPositions.size(), TwoSeparatedPlanesIndices.data(), + TwoSeparatedPlanesIndices.size()); AZ::RPI::ModelKdTree kdTree; ASSERT_TRUE(kdTree.Build(mesh.GetModel().Get())); @@ -1092,38 +1112,40 @@ namespace UnitTest EXPECT_THAT( 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)); EXPECT_THAT(distance, testing::FloatEq(GetParam().expectedDistance)); } - static constexpr inline AZStd::array intersectTestData{ - KdTreeIntersectParams{ -0.1f, 0.0f, 1.0f, 0.5f, true }, - KdTreeIntersectParams{ 0.0f, 0.0f, 1.0f, 0.5f, true }, - KdTreeIntersectParams{ 0.1f, 0.0f, 1.0f, 0.5f, true }, + static constexpr AZStd::array KdTreeIntersectTestData{ + IntersectParams{ -0.1f, 0.0f, 1.0f, 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 }, + IntersectParams{ 0.1f, 0.0f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true }, // Test the center of each triangle - KdTreeIntersectParams{-0.111f, -0.111f, 1.0f, 0.5f, true}, - KdTreeIntersectParams{-0.111f, -0.778f, 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} - KdTreeIntersectParams{-0.555f, -0.555f, 1.0f, 0.5f, true}, - KdTreeIntersectParams{-0.555f, 0.111f, 1.0f, 0.5f, true}, - KdTreeIntersectParams{-0.555f, 0.778f, 1.0f, 0.5f, true}, - KdTreeIntersectParams{-0.778f, -0.111f, 1.0f, 0.5f, true}, - KdTreeIntersectParams{-0.778f, -0.778f, 1.0f, 0.5f, true}, - KdTreeIntersectParams{-0.778f, 0.555f, 1.0f, 0.5f, true}, - KdTreeIntersectParams{0.111f, -0.555f, 1.0f, 0.5f, true}, - KdTreeIntersectParams{0.111f, 0.111f, 1.0f, 0.5f, true}, - KdTreeIntersectParams{0.111f, 0.778f, 1.0f, 0.5f, true}, - KdTreeIntersectParams{0.555f, -0.111f, 1.0f, 0.5f, true}, - KdTreeIntersectParams{0.555f, -0.778f, 1.0f, 0.5f, true}, - KdTreeIntersectParams{0.555f, 0.555f, 1.0f, 0.5f, true}, - KdTreeIntersectParams{0.778f, -0.555f, 1.0f, 0.5f, true}, - KdTreeIntersectParams{0.778f, 0.111f, 1.0f, 0.5f, true}, - KdTreeIntersectParams{0.778f, 0.778f, 1.0f, 0.5f, true}, + IntersectParams{ -0.111f, -0.111f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true }, + IntersectParams{ -0.111f, -0.778f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true }, + IntersectParams{ -0.111f, 0.555f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, + true }, // Should intersect triangle with indices {29, 34, 27} and {11, 12, 10} + IntersectParams{ -0.555f, -0.555f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true }, + IntersectParams{ -0.555f, 0.111f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true }, + IntersectParams{ -0.555f, 0.778f, 1.0f, 0.0f, 0.0f, -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 }, + IntersectParams{ -0.778f, 0.555f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true }, + IntersectParams{ 0.111f, -0.555f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true }, + IntersectParams{ 0.111f, 0.111f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true }, + IntersectParams{ 0.111f, 0.778f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true }, + IntersectParams{ 0.555f, -0.111f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true }, + IntersectParams{ 0.555f, -0.778f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true }, + IntersectParams{ 0.555f, 0.555f, 1.0f, 0.0f, 0.0f, -1.0f, 0.5f, true }, + IntersectParams{ 0.778f, -0.555f, 1.0f, 0.0f, 0.0f, -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 : public ModelTests @@ -1133,7 +1155,10 @@ namespace UnitTest { ModelTests::SetUp(); - m_mesh = AZStd::make_unique(); + m_mesh = AZStd::make_unique( + TwoSeparatedPlanesPositions.data(), TwoSeparatedPlanesPositions.size(), TwoSeparatedPlanesIndices.data(), + TwoSeparatedPlanesIndices.size()); + m_kdTree = AZStd::make_unique(); ASSERT_TRUE(m_kdTree->Build(m_mesh->GetModel().Get())); } @@ -1146,7 +1171,7 @@ namespace UnitTest ModelTests::TearDown(); } - AZStd::unique_ptr m_mesh; + AZStd::unique_ptr m_mesh; AZStd::unique_ptr m_kdTree; }; @@ -1154,7 +1179,7 @@ namespace UnitTest { float t = AZStd::numeric_limits::max(); AZ::Vector3 normal; - + constexpr float rayLength = 100.0f; EXPECT_THAT( m_kdTree->RayIntersection( @@ -1181,4 +1206,85 @@ namespace UnitTest EXPECT_THAT( m_kdTree->RayIntersection(AZ::Vector3::CreateAxisZ(5.0f), -AZ::Vector3::CreateAxisZ(), t, normal), testing::Eq(false)); } + + class BruteForceIntersectsParameterizedFixture + : public ModelTests + , public ::testing::WithParamInterface + { + }; + + TEST_P(BruteForceIntersectsParameterizedFixture, BruteForceIntersectsCube) + { + TestMesh mesh(CubePositions.data(), CubePositions.size(), CubeIndices.data(), CubeIndices.size()); + + float distance = AZStd::numeric_limits::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::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(CubePositions.data(), CubePositions.size(), CubeIndices.data(), CubeIndices.size()); + } + + void TearDown() override + { + m_mesh.reset(); + ModelTests::TearDown(); + } + + AZStd::unique_ptr 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 diff --git a/Gems/GraphCanvas/Code/StaticLib/GraphCanvas/Widgets/NodePalette/Model/NodePaletteSortFilterProxyModel.cpp b/Gems/GraphCanvas/Code/StaticLib/GraphCanvas/Widgets/NodePalette/Model/NodePaletteSortFilterProxyModel.cpp index ca5e962685..2917642d66 100644 --- a/Gems/GraphCanvas/Code/StaticLib/GraphCanvas/Widgets/NodePalette/Model/NodePaletteSortFilterProxyModel.cpp +++ b/Gems/GraphCanvas/Code/StaticLib/GraphCanvas/Widgets/NodePalette/Model/NodePaletteSortFilterProxyModel.cpp @@ -147,16 +147,17 @@ namespace GraphCanvas return true; } - QString test = model->data(index).toString(); + QString test = model->data(index).toString(); + bool showRow = false; - int regexIndex = test.lastIndexOf(m_filterRegex); + int regexIndex = m_filterRegex.indexIn(test); if (regexIndex >= 0) { showRow = true; - - AZStd::pair highlight(regexIndex, m_filter.size()); + + AZStd::pair highlight(regexIndex, m_filterRegex.matchedLength()); currentItem->SetHighlight(highlight); } else @@ -283,8 +284,20 @@ namespace GraphCanvas void NodePaletteSortFilterProxyModel::SetFilter(const QString& filter) { - m_filter = QRegExp::escape(filter); - m_filterRegex = QRegExp(m_filter, Qt::CaseInsensitive); + // Remove whitespace and escape() so every regexp special character is escaped with a backslash + // Then ignore all whitespace by adding \s* (regex optional whitespace match) in between every other character. + // We use \s* instead of simply removing all whitespace from the filter and node-names in order to preserve the node-name and accurately highlight the matching portion. + // Example: "OnGraphStart" or "On Graph Start" + m_filter = QRegExp::escape(filter.simplified().replace(" ", "")); + + QString regExIgnoreWhitespace(m_filter[0]); + for (int i = 1; i < m_filter.size(); ++i) + { + regExIgnoreWhitespace.append("\\s*"); + regExIgnoreWhitespace.append(m_filter[i]); + } + + m_filterRegex = QRegExp(regExIgnoreWhitespace, Qt::CaseInsensitive); } void NodePaletteSortFilterProxyModel::ClearFilter() diff --git a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja index 259f469020..21bf6ab69b 100644 --- a/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja +++ b/Gems/Multiplayer/Code/Source/AutoGen/AutoComponent_Source.jinja @@ -368,26 +368,26 @@ void {{ ClassName }}::Signal{{ UpperFirst(Property.attrib['Name']) }}({{ ', '.jo ->Method("{{ UpperFirst(Property.attrib['Name']) }}", [](const {{ ClassName }}* self, {{ ', '.join(paramDefines) }}) { self->m_controller->{{ UpperFirst(Property.attrib['Name']) }}({{ ', '.join(paramNames) }}); }) - ->Method("{{ UpperFirst(Property.attrib['Name']) }}ByEntity", [](AZ::EntityId id, {{ ', '.join(paramDefines) }}) { + ->Method("{{ UpperFirst(Property.attrib['Name']) }}ByEntityId", [](AZ::EntityId id, {{ ', '.join(paramDefines) }}) { AZ::Entity* entity = AZ::Interface::Get()->FindEntity(id); if (!entity) { - AZ_Warning("Network Property", false, "{{ ClassName }} Get{{ UpperFirst(Property.attrib['Name']) }} failed. The entity with id %s doesn't exist, please provide a valid entity id.", id.ToString().c_str()) + AZ_Warning("Network Property", false, "{{ ClassName }} {{ UpperFirst(Property.attrib['Name']) }}ByEntityId failed. The entity with id %s doesn't exist, please provide a valid entity id.", id.ToString().c_str()) return; } {{ ClassName }}* networkComponent = entity->FindComponent<{{ ClassName }}>(); if (!networkComponent) { - AZ_Warning("Network Property", false, "{{ ClassName }} Get{{ UpperFirst(Property.attrib['Name']) }} failed. Entity '%s' (id: %s) is missing {{ ClassName }}, be sure to add {{ ClassName }} to this entity.", entity->GetName().c_str(), id.ToString().c_str()) + AZ_Warning("Network Property", false, "{{ ClassName }} {{ UpperFirst(Property.attrib['Name']) }}ByEntityId failed. Entity '%s' (id: %s) is missing {{ ClassName }}, be sure to add {{ ClassName }} to this entity.", entity->GetName().c_str(), id.ToString().c_str()) return; } {{ ClassName }}Controller* controller = static_cast<{{ ClassName }}Controller*>(networkComponent->GetController()); if (!controller) { - AZ_Warning("Network Property", false, "{{ ClassName }} Get{{ UpperFirst(Property.attrib['Name']) }} method failed. Entity '%s' (id: %s) {{ ClassName }} is missing the network controller. This RemoteProcedure can only be invoked from {{InvokeFrom}} network entities, because this entity doesn't have a controller, it must not be a {{InvokeFrom}} entity. Please check your network context before attempting to call {{ UpperFirst(Property.attrib['Name']) }}.", entity->GetName().c_str(), id.ToString().c_str()) + AZ_Warning("Network Property", false, "{{ ClassName }} {{ UpperFirst(Property.attrib['Name']) }}ByEntityId method failed. Entity '%s' (id: %s) {{ ClassName }} is missing the network controller. This RemoteProcedure can only be invoked from {{InvokeFrom}} network entities, because this entity doesn't have a controller, it must not be a {{InvokeFrom}} entity. Please check your network context before attempting to call {{ UpperFirst(Property.attrib['Name']) }}.", entity->GetName().c_str(), id.ToString().c_str()) return; } @@ -429,6 +429,32 @@ void {{ ClassName }}::Signal{{ UpperFirst(Property.attrib['Name']) }}({{ ', '.jo ->Method("Get{{ UpperFirst(Property.attrib['Name']) }}Event", [](const {{ ClassName }}* self) -> AZ::Event<{{ ', '.join(paramTypes) }}>& { return self->m_controller->Get{{ UpperFirst(Property.attrib['Name']) }}Event(); + }) + ->Attribute(AZ::Script::Attributes::AzEventDescription, {{ LowerFirst(Property.attrib['Name']) }}EventDesc) + ->Method("Get{{ UpperFirst(Property.attrib['Name']) }}EventByEntityId", [](AZ::EntityId id) -> AZ::Event<{{ ', '.join(paramTypes) }}>* + { + AZ::Entity* entity = AZ::Interface::Get()->FindEntity(id); + if (!entity) + { + AZ_Warning("Network Property", false, "{{ ClassName }} Get{{ UpperFirst(Property.attrib['Name']) }}EventByEntityId failed. The entity with id %s doesn't exist, please provide a valid entity id.", id.ToString().c_str()) + return nullptr; + } + + {{ ClassName }}* networkComponent = entity->FindComponent<{{ ClassName }}>(); + if (!networkComponent) + { + AZ_Warning("Network Property", false, "{{ ClassName }} Get{{ UpperFirst(Property.attrib['Name']) }}EventByEntityId failed. Entity '%s' (id: %s) is missing {{ ClassName }}, be sure to add {{ ClassName }} to this entity.", entity->GetName().c_str(), id.ToString().c_str()) + return nullptr; + } + + {{ ClassName }}Controller* controller = static_cast<{{ ClassName }}Controller*>(networkComponent->GetController()); + if (!controller) + { + AZ_Warning("Network Property", false, "{{ ClassName }} Get{{ UpperFirst(Property.attrib['Name']) }}EventByEntity method failed. Entity '%s' (id: %s) {{ ClassName }} is missing the network controller. This RemoteProcedure can only be received by {{InvokeTo}} network entities, because this entity doesn't have a controller, it must not be a {{InvokeTo}} entity. Please check your network context before attempting to Get{{ UpperFirst(Property.attrib['Name']) }}Event.", entity->GetName().c_str(), id.ToString().c_str()) + return nullptr; + } + + return &controller->Get{{ UpperFirst(Property.attrib['Name']) }}Event(); }) ->Attribute(AZ::Script::Attributes::AzEventDescription, AZStd::move({{ LowerFirst(Property.attrib['Name']) }}EventDesc)) {% endif %} diff --git a/Gems/PhysXDebug/Code/Source/SystemComponent.cpp b/Gems/PhysXDebug/Code/Source/SystemComponent.cpp index 34315eb11e..c693599a59 100644 --- a/Gems/PhysXDebug/Code/Source/SystemComponent.cpp +++ b/Gems/PhysXDebug/Code/Source/SystemComponent.cpp @@ -42,7 +42,7 @@ namespace PhysXDebug const float SystemComponent::m_maxCullingBoxSize = 150.0f; namespace Internal { - const AZ::Crc32 VewportId = 0; // was AzFramework::g_defaultSceneEntityDebugDisplayId but it didn't render to the viewport. + const AZ::Crc32 VewportId = AzFramework::g_defaultSceneEntityDebugDisplayId; } bool UseEditorPhysicsScene() @@ -565,9 +565,9 @@ namespace PhysXDebug static void physx_CullingBoxSize([[maybe_unused]] const AZ::ConsoleCommandContainer& arguments) { const int argumentCount = arguments.size(); - if (argumentCount == 2) + if (argumentCount == 1) { - float newCullingBoxSize = (float)strtol(AZ::CVarFixedString(arguments[1]).c_str(), nullptr, 10); + float newCullingBoxSize = (float)strtol(AZ::CVarFixedString(arguments[0]).c_str(), nullptr, 10); PhysXDebug::PhysXDebugRequestBus::Broadcast(&PhysXDebug::PhysXDebugRequestBus::Events::SetCullingBoxSize, newCullingBoxSize); } else @@ -584,9 +584,9 @@ namespace PhysXDebug const int argumentCount = arguments.size(); - if (argumentCount == 2) + if (argumentCount == 1) { - const auto userPreference = static_cast(strtol(AZ::CVarFixedString(arguments[1]).c_str(), nullptr, 10)); + const auto userPreference = static_cast(strtol(AZ::CVarFixedString(arguments[0]).c_str(), nullptr, 10)); switch (userPreference) { diff --git a/Tools/LyTestTools/ly_test_tools/o3de/asset_processor.py b/Tools/LyTestTools/ly_test_tools/o3de/asset_processor.py index 8e2b93c20a..a9c9a2fa5b 100644 --- a/Tools/LyTestTools/ly_test_tools/o3de/asset_processor.py +++ b/Tools/LyTestTools/ly_test_tools/o3de/asset_processor.py @@ -597,9 +597,10 @@ class AssetProcessor(object): run_result = subprocess.run(command, close_fds=True, timeout=timeout, capture_output=capture_output) output_list = None if capture_output: - output_list = run_result.stdout.splitlines() if decode: - output_list = [line.decode('utf-8') for line in output_list] + output_list = run_result.stdout.decode('utf-8').splitlines() + else: + output_list = run_result.stdout.splitlines() if run_result.returncode != 0: errorMessage = f"{command} returned error code: {run_result.returncode}" diff --git a/cmake/Packaging.cmake b/cmake/Packaging.cmake index 3e23511fa1..6c077ac617 100644 --- a/cmake/Packaging.cmake +++ b/cmake/Packaging.cmake @@ -16,6 +16,9 @@ endif() # public facing options will be used for conversion into cpack specific ones below. set(LY_INSTALLER_DOWNLOAD_URL "" CACHE STRING "URL embedded into the installer to download additional artifacts") set(LY_INSTALLER_LICENSE_URL "" CACHE STRING "Optionally embed a link to the license instead of raw text") +set(LY_INSTALLER_UPLOAD_URL "" CACHE STRING + "URL used to automatically upload the artifacts. Can also be set via LY_INSTALLER_UPLOAD_URL environment variable. Currently only accepts S3 URLs e.g. s3:///") +set(LY_INSTALLER_AWS_PROFILE "" CACHE STRING "AWS CLI profile for uploading artifacts. Can also be set via LY_INSTALLER_AWS_PROFILE environment variable.") set(CPACK_DESIRED_CMAKE_VERSION 3.20.2) @@ -103,6 +106,45 @@ install(FILES ${_cmake_package_dest} DESTINATION ./Tools/Redistributables/CMake ) +# checks for and removes trailing slash +function(strip_trailing_slash in_url out_url) + string(LENGTH ${in_url} _url_length) + MATH(EXPR _url_length "${_url_length}-1") + + string(SUBSTRING ${in_url} 0 ${_url_length} _clean_url) + if("${in_url}" STREQUAL "${_clean_url}/") + set(${out_url} ${_clean_url} PARENT_SCOPE) + else() + set(${out_url} ${in_url} PARENT_SCOPE) + endif() +endfunction() + +set(_versioned_target_url_tag ${LY_VERSION_STRING}/${PAL_HOST_PLATFORM_NAME}) + +if(NOT LY_INSTALLER_UPLOAD_URL AND DEFINED ENV{LY_INSTALLER_UPLOAD_URL}) + set(LY_INSTALLER_UPLOAD_URL $ENV{LY_INSTALLER_UPLOAD_URL}) +endif() + +if(LY_INSTALLER_UPLOAD_URL) + ly_is_s3_url(${LY_INSTALLER_UPLOAD_URL} _is_s3_bucket) + if(NOT _is_s3_bucket) + message(FATAL_ERROR "Only S3 installer uploading is supported at this time") + endif() + + if (LY_INSTALLER_AWS_PROFILE) + set(CPACK_AWS_PROFILE ${LY_INSTALLER_AWS_PROFILE}) + elseif (DEFINED ENV{LY_INSTALLER_AWS_PROFILE}) + set(CPACK_AWS_PROFILE $ENV{LY_INSTALLER_AWS_PROFILE}) + else() + message(FATAL_ERROR + "An AWS profile is required for installer S3 uploading. Please provide " + "one via LY_INSTALLER_AWS_PROFILE CLI argument or environment variable") + endif() + + strip_trailing_slash(${LY_INSTALLER_UPLOAD_URL} LY_INSTALLER_UPLOAD_URL) + set(CPACK_UPLOAD_URL ${LY_INSTALLER_UPLOAD_URL}/${_versioned_target_url_tag}) +endif() + # IMPORTANT: required to be included AFTER setting all property overrides include(CPack REQUIRED) @@ -146,9 +188,11 @@ ly_configure_cpack_component( ) if(LY_INSTALLER_DOWNLOAD_URL) - # this will set the following variables: CPACK_DOWNLOAD_SITE, CPACK_DOWNLOAD_ALL, and CPACK_UPLOAD_DIRECTORY + strip_trailing_slash(${LY_INSTALLER_DOWNLOAD_URL} LY_INSTALLER_DOWNLOAD_URL) + + # this will set the following variables: CPACK_DOWNLOAD_SITE, CPACK_DOWNLOAD_ALL, and CPACK_UPLOAD_DIRECTORY (local) cpack_configure_downloads( - ${LY_INSTALLER_DOWNLOAD_URL} + ${LY_INSTALLER_DOWNLOAD_URL}/${_versioned_target_url_tag} UPLOAD_DIRECTORY ${CMAKE_BINARY_DIR}/_CPack_Uploads # to match the _CPack_Packages directory ALL ) diff --git a/cmake/Platform/Windows/PackagingPostBuild.cmake b/cmake/Platform/Windows/PackagingPostBuild.cmake index d379358bf4..89b3efb44b 100644 --- a/cmake/Platform/Windows/PackagingPostBuild.cmake +++ b/cmake/Platform/Windows/PackagingPostBuild.cmake @@ -59,12 +59,21 @@ set(_light_command message(STATUS "Creating Bootstrap Installer...") execute_process( COMMAND ${_candle_command} - COMMAND_ERROR_IS_FATAL ANY + RESULT_VARIABLE _candle_result + ERROR_VARIABLE _candle_errors ) +if(NOT ${_candle_result} EQUAL 0) + message(FATAL_ERROR "An error occurred invoking candle.exe. ${_candle_errors}") +endif() + execute_process( COMMAND ${_light_command} - COMMAND_ERROR_IS_FATAL ANY + RESULT_VARIABLE _light_result + ERROR_VARIABLE _light_errors ) +if(NOT ${_light_result} EQUAL 0) + message(FATAL_ERROR "An error occurred invoking light.exe. ${_light_errors}") +endif() file(COPY ${_bootstrap_output_file} DESTINATION ${CPACK_PACKAGE_DIRECTORY} @@ -87,3 +96,42 @@ file(COPY ${_artifacts} DESTINATION ${CPACK_UPLOAD_DIRECTORY} ) message(STATUS "Artifacts copied to ${CPACK_UPLOAD_DIRECTORY}") + +if(NOT CPACK_UPLOAD_URL) + return() +endif() + +file(REAL_PATH "${CPACK_SOURCE_DIR}/.." _root_path) + +file(TO_NATIVE_PATH "${_root_path}/python/python.cmd" _python_cmd) +file(TO_NATIVE_PATH "${_root_path}/scripts/build/tools/upload_to_s3.py" _upload_script) +file(TO_NATIVE_PATH "${_cpack_wix_out_dir}" _cpack_wix_out_dir) + +# strip the scheme and extract the bucket/key prefix from the URL +string(REPLACE "s3://" "" _stripped_url ${CPACK_UPLOAD_URL}) +string(REPLACE "/" ";" _tokens ${_stripped_url}) + +list(POP_FRONT _tokens _bucket) +string(JOIN "/" _prefix ${_tokens}) + +set(_file_regex ".*(cab|exe|msi)$") + +set(_upload_command + ${_python_cmd} -s + -u ${_upload_script} + --base_dir ${_cpack_wix_out_dir} + --file_regex="${_file_regex}" + --bucket ${_bucket} + --key_prefix ${_prefix} + --profile ${CPACK_AWS_PROFILE} +) + +execute_process( + COMMAND ${_upload_command} + RESULT_VARIABLE _upload_result + ERROR_VARIABLE _upload_errors +) + +if (NOT ${_upload_result} EQUAL 0) + message(FATAL_ERROR "An error occurred uploading artifacts. ${_upload_errors}") +endif() diff --git a/scripts/build/tools/upload_to_s3.py b/scripts/build/tools/upload_to_s3.py index d6d6d8ddb5..5dfe5eb66e 100755 --- a/scripts/build/tools/upload_to_s3.py +++ b/scripts/build/tools/upload_to_s3.py @@ -65,6 +65,11 @@ def get_client(service_name, profile_name): def get_files_to_upload(base_dir, regex): # Get all file names in base directory files = [x for x in os.listdir(base_dir) if os.path.isfile(os.path.join(base_dir, x))] + # strip the surround quotes, if they exist + try: + regex = json.loads(regex) + except: + pass # Get all file names matching the regular expression, those file will be uploaded to S3 files_to_upload = [x for x in files if re.match(regex, x)] return files_to_upload