Merge branch 'main' into TIF/Runtime

This commit is contained in:
John
2021-06-15 17:32:21 +01:00
38 changed files with 645 additions and 287 deletions
@@ -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
@@ -37,9 +37,10 @@ namespace AzPhysics
if (auto* behaviorContext = azdynamic_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->Class<TriggerEvent>()
->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<AZ::BehaviorContext*>(context))
{
behaviorContext->Class<CollisionEvent>()
->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)
;
}
}
@@ -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
@@ -197,7 +197,7 @@ namespace AzToolsFramework
AZ::IO::PathView filePath, Prefab::InstanceOptionalReference instanceToParentUnder) override;
Prefab::InstanceOptionalReference GetRootPrefabInstance() 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;
//! 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;
@@ -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
@@ -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;
@@ -56,7 +56,7 @@ namespace AzToolsFramework
AZ::Interface<InstanceUpdateExecutorInterface>::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<InstanceOptionalConstReference> 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<Instance>& 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<Instance>& 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)
{
@@ -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;
@@ -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;
@@ -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<AZ::u64>(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<AZ::u64>(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<AZ::u64>(entityId)));
state->SetParent(undoBatch);
state->Capture(beforeState, afterState, entityId);
state->Redo();
state->Redo(instance);
}
void PrefabPublicHandler::Internal_HandleInstanceChange(
@@ -162,9 +162,11 @@ namespace AzToolsFramework
InstanceOptionalConstReference instance, const AZStd::unordered_set<AZ::IO::Path>& 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(
@@ -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<LinkIds>& 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())
{
@@ -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);
@@ -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<Instance> InstantiatePrefab(AZ::IO::PathView filePath) = 0;
virtual AZStd::unique_ptr<Instance> InstantiatePrefab(const TemplateId& templateId) = 0;
@@ -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<AZ::u64>(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);
@@ -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
@@ -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<SelectionCommand>(EntityIdList(), s_duplicateUndoRedoDesc);
selectionCommand->SetParent(undoBatch.GetUndoBatch());
selectionCommand.release();
ScopedUndoBatch undoBatch(s_duplicateUndoRedoDesc);
auto selectionCommand = AZStd::make_unique<SelectionCommand>(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(
@@ -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
@@ -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;
}
+3 -1
View File
@@ -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();
+14
View File
@@ -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)
+2
View File
@@ -169,6 +169,8 @@ public:
AmazonToolbar GetMiscToolbar() const;
AmazonToolbar GetPlayConsoleToolbar() const;
void AddButtonToEditToolbar(QAction* action);
private:
Q_DISABLE_COPY(ToolbarManager);
void SaveToolbars();
@@ -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)
+1
View File
@@ -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
@@ -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<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.
//! [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" };
@@ -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;
}
@@ -15,6 +15,7 @@
#include <AzCore/Debug/EventTrace.h>
#include <AzCore/Jobs/JobFunction.h>
#include <AzCore/Math/IntersectSegment.h>
#include <AzCore/std/limits.h>
#include <AzCore/RTTI/ReflectContext.h>
#include <AzCore/Serialization/SerializeContext.h>
@@ -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<float>::max();
bool anyHit = false;
AZ::Vector3 intersectionNormal;
float shortestDistanceNormalized = AZStd::numeric_limits<float>::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<ModelLodAsset::Mesh::StreamBufferInfo>& streamBufferList = mesh.GetStreamBufferInfoList();
@@ -217,14 +225,13 @@ namespace AZ
AZStd::array_view<uint8_t> 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<float>::max();
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)
{
@@ -247,20 +254,22 @@ namespace AZ
p = reinterpret_cast<const float*>(&positionRawBuffer[index2 * positionElementSize]);
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;
closestNormalizedDistance = normalizedDistance;
shortestDistanceNormalized = currentDistanceNormalized;
}
anyHit = true;
}
}
if (anyHit)
{
distance = closestNormalizedDistance * distance;
distanceNormalized = shortestDistanceNormalized;
}
return anyHit;
@@ -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<float>::max();
if (RayIntersectionRecursively(m_pRootNode.get(), raySrc, rayDir, closestDistanceNormalized, normal))
float shortestDistanceNormalized = AZStd::numeric_limits<float>::max();
if (RayIntersectionRecursively(m_pRootNode.get(), raySrc, rayDir, shortestDistanceNormalized, normal))
{
distanceNormalized = closestDistanceNormalized;
distanceNormalized = shortestDistanceNormalized;
return true;
}
+196 -90
View File
@@ -22,6 +22,7 @@
#include <AzCore/std/limits.h>
#include <AzCore/Component/Entity.h>
#include <AZTestShared/Math/MathTestHelpers.h>
#include <AzTest/AzTest.h>
#include <Common/RPITestFixture.h>
@@ -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 x> 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::RPI::MaterialAsset>(AZ::Data::AssetId(AZ::Uuid::CreateRandom(), 0),
AZ::AzTypeInfo<AZ::RPI::MaterialAsset>::Uuid(), "")
);
{
AZ::Data::Asset<AZ::RPI::BufferAsset> indexBuffer = BuildTestBuffer(s_indexes.size(), sizeof(uint32_t));
AZStd::copy(s_indexes.begin(), s_indexes.end(), reinterpret_cast<uint32_t*>(const_cast<uint8_t*>(indexBuffer->GetBuffer().data())));
AZ::Data::Asset<AZ::RPI::BufferAsset> indexBuffer = BuildTestBuffer(indicesCount, sizeof(uint32_t));
AZStd::copy(indices, indices + indicesCount, reinterpret_cast<uint32_t*>(const_cast<uint8_t*>(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<AZ::RPI::BufferAsset> positionBuffer = BuildTestBuffer(s_positions.size() / 3, sizeof(float) * 3);
AZStd::copy(s_positions.begin(), s_positions.end(), reinterpret_cast<float*>(const_cast<uint8_t*>(positionBuffer->GetBuffer().data())));
AZ::Data::Asset<AZ::RPI::BufferAsset> positionBuffer = BuildTestBuffer(positionCount / 3, sizeof(float) * 3);
AZStd::copy(positions, positions + positionCount, reinterpret_cast<float*>(const_cast<uint8_t*>(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<RPI::ModelLodAsset> lodAsset;
AZ::Data::Asset<AZ::RPI::ModelLodAsset> 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<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 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<KdTreeIntersectParams>
, public ::testing::WithParamInterface<IntersectParams>
{
};
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<KdTreeIntersectParams, 21> 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<TwoSeparatedPlanesMesh>();
m_mesh = AZStd::make_unique<TestMesh>(
TwoSeparatedPlanesPositions.data(), TwoSeparatedPlanesPositions.size(), TwoSeparatedPlanesIndices.data(),
TwoSeparatedPlanesIndices.size());
m_kdTree = AZStd::make_unique<AZ::RPI::ModelKdTree>();
ASSERT_TRUE(m_kdTree->Build(m_mesh->GetModel().Get()));
}
@@ -1146,7 +1171,7 @@ namespace UnitTest
ModelTests::TearDown();
}
AZStd::unique_ptr<TwoSeparatedPlanesMesh> m_mesh;
AZStd::unique_ptr<TestMesh> m_mesh;
AZStd::unique_ptr<AZ::RPI::ModelKdTree> m_kdTree;
};
@@ -1154,7 +1179,7 @@ namespace UnitTest
{
float t = AZStd::numeric_limits<float>::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<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
@@ -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<int, int> highlight(regexIndex, m_filter.size());
AZStd::pair<int, int> 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()
@@ -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<AZ::ComponentApplicationRequests>::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<AZ::ComponentApplicationRequests>::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 %}
@@ -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<DebugCVarValues>(strtol(AZ::CVarFixedString(arguments[1]).c_str(), nullptr, 10));
const auto userPreference = static_cast<DebugCVarValues>(strtol(AZ::CVarFixedString(arguments[0]).c_str(), nullptr, 10));
switch (userPreference)
{
@@ -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}"
+46 -2
View File
@@ -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://<bucket>/<prefix>")
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
)
@@ -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()
+5
View File
@@ -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