diff --git a/Gems/SimulationInterfaces/.clang-format b/Gems/SimulationInterfaces/.clang-format new file mode 100644 index 0000000000..fddf244031 --- /dev/null +++ b/Gems/SimulationInterfaces/.clang-format @@ -0,0 +1,61 @@ +Language: Cpp + +AccessModifierOffset: -4 +AlignAfterOpenBracket: AlwaysBreak +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Right +AlignOperands: false +AlignTrailingComments: false +AllowAllArgumentsOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortFunctionsOnASingleLine: None +AllowShortLambdasOnASingleLine: None +AlwaysBreakAfterReturnType: None +AlwaysBreakTemplateDeclarations: true +BinPackArguments: false +BinPackParameters: false +BreakBeforeBraces: Custom +BraceWrapping: + AfterClass: true + AfterControlStatement: true + AfterEnum: true + AfterFunction: true + AfterNamespace: true + BeforeLambdaBody: true + AfterStruct: true + BeforeElse: true + SplitEmptyFunction: true +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: BeforeComma +BreakInheritanceList: BeforeComma +ColumnLimit: 140 +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: false +FixNamespaceComments: true +IncludeBlocks: Preserve +IndentCaseBlocks: true +IndentCaseLabels: false +IndentPPDirectives: None +IndentWidth: 4 +KeepEmptyLinesAtTheStartOfBlocks: false +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: All +PenaltyReturnTypeOnItsOwnLine: 1000 +PointerAlignment: Left +SortIncludes: true +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyParentheses: false +SpacesInAngles: false +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +Standard: c++17 +UseTab: Never \ No newline at end of file diff --git a/Gems/SimulationInterfaces/.gitignore b/Gems/SimulationInterfaces/.gitignore new file mode 100644 index 0000000000..e69de29bb2 diff --git a/Gems/SimulationInterfaces/Assets/SampleAsset/TestSimulationEntity.prefab b/Gems/SimulationInterfaces/Assets/SampleAsset/TestSimulationEntity.prefab new file mode 100755 index 0000000000..37351e162f --- /dev/null +++ b/Gems/SimulationInterfaces/Assets/SampleAsset/TestSimulationEntity.prefab @@ -0,0 +1,137 @@ +{ + "ContainerEntity": { + "Id": "ContainerEntity", + "Name": "TestSimulationEntity", + "Components": { + "EditorDisabledCompositionComponent": { + "$type": "EditorDisabledCompositionComponent", + "Id": 13342689700908152991 + }, + "EditorEntityIconComponent": { + "$type": "EditorEntityIconComponent", + "Id": 8584796089140035515 + }, + "EditorEntitySortComponent": { + "$type": "EditorEntitySortComponent", + "Id": 17941228157017035486, + "Child Entity Order": [ + "Entity_[2555577822056]" + ] + }, + "EditorInspectorComponent": { + "$type": "EditorInspectorComponent", + "Id": 16387168034938951622 + }, + "EditorLockComponent": { + "$type": "EditorLockComponent", + "Id": 5868718090530092403 + }, + "EditorOnlyEntityComponent": { + "$type": "EditorOnlyEntityComponent", + "Id": 10685569871653723108 + }, + "EditorPendingCompositionComponent": { + "$type": "EditorPendingCompositionComponent", + "Id": 13486227769083255940 + }, + "EditorPrefabComponent": { + "$type": "EditorPrefabComponent", + "Id": 9974721317876760075 + }, + "EditorVisibilityComponent": { + "$type": "EditorVisibilityComponent", + "Id": 18186949152932686714 + }, + "TransformComponent": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 13473127497085401640, + "Parent Entity": "" + } + } + }, + "Entities": { + "Entity_[2555577822056]": { + "Id": "Entity_[2555577822056]", + "Name": "TestSimulationEntity", + "Components": { + "EditorBoxShapeComponent": { + "$type": "EditorBoxShapeComponent", + "Id": 17815707635495439549, + "GameView": true + }, + "EditorColliderComponent": { + "$type": "EditorColliderComponent", + "Id": 7164453177612248133, + "ColliderConfiguration": { + "MaterialSlots": { + "Slots": [ + { + "Name": "Entire object" + } + ] + } + }, + "ShapeConfiguration": { + "ShapeType": 0 + } + }, + "EditorDisabledCompositionComponent": { + "$type": "EditorDisabledCompositionComponent", + "Id": 14547453815095477983 + }, + "EditorEntityIconComponent": { + "$type": "EditorEntityIconComponent", + "Id": 11928217918748032436 + }, + "EditorEntitySortComponent": { + "$type": "EditorEntitySortComponent", + "Id": 16175661524427525128 + }, + "EditorInspectorComponent": { + "$type": "EditorInspectorComponent", + "Id": 7165286389418841970 + }, + "EditorLockComponent": { + "$type": "EditorLockComponent", + "Id": 18134844107097174864 + }, + "EditorOnlyEntityComponent": { + "$type": "EditorOnlyEntityComponent", + "Id": 3157917745503616515 + }, + "EditorPendingCompositionComponent": { + "$type": "EditorPendingCompositionComponent", + "Id": 10603658429884530304 + }, + "EditorRigidBodyComponent": { + "$type": "EditorRigidBodyComponent", + "Id": 11637898985208574279, + "Configuration": { + "entityId": "", + "Mass": 523.5988159179688, + "Inertia tensor": [ + 52.35987854003906, + 0.0, + 0.0, + 0.0, + 52.35987854003906, + 0.0, + 0.0, + 0.0, + 52.35987854003906 + ] + } + }, + "EditorVisibilityComponent": { + "$type": "EditorVisibilityComponent", + "Id": 6275443825220336439 + }, + "TransformComponent": { + "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent", + "Id": 14994870681931426785, + "Parent Entity": "ContainerEntity" + } + } + } + } +} \ No newline at end of file diff --git a/Gems/SimulationInterfaces/CMakeLists.txt b/Gems/SimulationInterfaces/CMakeLists.txt new file mode 100644 index 0000000000..d759cbf1a3 --- /dev/null +++ b/Gems/SimulationInterfaces/CMakeLists.txt @@ -0,0 +1,11 @@ +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# + +o3de_gem_setup("SimulationInterfaces") + +ly_add_external_target_path(${CMAKE_CURRENT_SOURCE_DIR}/3rdParty) + +add_subdirectory(Code) diff --git a/Gems/SimulationInterfaces/Code/CMakeLists.txt b/Gems/SimulationInterfaces/Code/CMakeLists.txt new file mode 100644 index 0000000000..47785f66fe --- /dev/null +++ b/Gems/SimulationInterfaces/Code/CMakeLists.txt @@ -0,0 +1,287 @@ +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# + +# Currently we are in the Code folder: ${CMAKE_CURRENT_LIST_DIR} +# Get the platform specific folder ${pal_dir} for the current folder: ${CMAKE_CURRENT_LIST_DIR}/Platform/${PAL_PLATFORM_NAME} +# Note: o3de_pal_dir will take care of the details for us, as this may be a restricted platform +# in which case it will see if that platform is present here or in the restricted folder. +# i.e. It could here in our gem : Gems/SimulationInterfaces/Code/Platform/ or +# //Gems/SimulationInterfaces/Code +o3de_pal_dir(pal_dir ${CMAKE_CURRENT_LIST_DIR}/Platform/${PAL_PLATFORM_NAME} "${gem_restricted_path}" "${gem_path}" "${gem_parent_relative_path}") + +# Now that we have the platform abstraction layer (PAL) folder for this folder, thats where we will find the +# traits for this platform. Traits for a platform are defines for things like whether or not something in this gem +# is supported by this platform. +include(${pal_dir}/PAL_${PAL_PLATFORM_NAME_LOWERCASE}.cmake) + +# Check to see if building the Gem Modules are supported for the current platform +if(NOT PAL_TRAIT_SIMULATIONINTERFACES_SUPPORTED) + return() +endif() + +# The ${gem_name}.API target declares the common interface that users of this gem should depend on in their targets +ly_add_target( + NAME ${gem_name}.API INTERFACE + NAMESPACE Gem + FILES_CMAKE + simulationinterfaces_api_files.cmake + ${pal_dir}/simulationinterfaces_api_files.cmake + INCLUDE_DIRECTORIES + INTERFACE + Include + BUILD_DEPENDENCIES + INTERFACE + AZ::AzCore +) + +# The ${gem_name}.Private.Object target is an internal target +# It should not be used outside of this Gems CMakeLists.txt +ly_add_target( + NAME ${gem_name}.Private.Object STATIC + NAMESPACE Gem + FILES_CMAKE + simulationinterfaces_private_files.cmake + ${pal_dir}/simulationinterfaces_private_files.cmake + TARGET_PROPERTIES + O3DE_PRIVATE_TARGET TRUE + INCLUDE_DIRECTORIES + PRIVATE + Include + Source + BUILD_DEPENDENCIES + PUBLIC + AZ::AzCore + AZ::AzFramework +) + +# Here add ${gem_name} target, it depends on the Private Object library and Public API interface +ly_add_target( + NAME ${gem_name} ${PAL_TRAIT_MONOLITHIC_DRIVEN_MODULE_TYPE} + NAMESPACE Gem + FILES_CMAKE + simulationinterfaces_shared_files.cmake + ${pal_dir}/simulationinterfaces_shared_files.cmake + INCLUDE_DIRECTORIES + PUBLIC + Include + PRIVATE + Source + BUILD_DEPENDENCIES + PUBLIC + Gem::${gem_name}.API + PRIVATE + Gem::${gem_name}.Private.Object +) + +# Include the gem name into the Client Module source file +# for use with the AZ_DECLARE_MODULE_CLASS macro +# This is to allow renaming of the gem to also cause +# the CreateModuleClass_Gem_ function which +# is used to bootstrap the gem in monolithic builds to link to the new gem name +ly_add_source_properties( +SOURCES + Source/Clients/SimulationInterfacesModule.cpp +PROPERTY COMPILE_DEFINITIONS + VALUES + O3DE_GEM_NAME=${gem_name} + O3DE_GEM_VERSION=${gem_version}) + +# By default, we will specify that the above target ${gem_name} would be used by +# Client and Server type targets when this gem is enabled. If you don't want it +# active in Clients or Servers by default, delete one of both of the following lines: +ly_create_alias(NAME ${gem_name}.Clients NAMESPACE Gem TARGETS Gem::${gem_name}) +ly_create_alias(NAME ${gem_name}.Servers NAMESPACE Gem TARGETS Gem::${gem_name}) +ly_create_alias(NAME ${gem_name}.Unified NAMESPACE Gem TARGETS Gem::${gem_name}) + +# For the Client and Server variants of ${gem_name} Gem, an alias to the ${gem_name}.API target will be made +ly_create_alias(NAME ${gem_name}.Clients.API NAMESPACE Gem TARGETS Gem::${gem_name}.API) +ly_create_alias(NAME ${gem_name}.Servers.API NAMESPACE Gem TARGETS Gem::${gem_name}.API) +ly_create_alias(NAME ${gem_name}.Unified.API NAMESPACE Gem TARGETS Gem::${gem_name}.API) + +# Add in CMake dependencies for each gem dependency listed in this gem's gem.json file +# for the Clients, Servers, Unified gem variants +o3de_add_variant_dependencies_for_gem_dependencies(GEM_NAME ${gem_name} VARIANTS Clients Servers Unified) + +# If we are on a host platform, we want to add the host tools targets like the ${gem_name}.Editor MODULE target +if(PAL_TRAIT_BUILD_HOST_TOOLS) + # The ${gem_name}.Editor.API target can be used by other gems that want to interact with the ${gem_name}.Editor module + ly_add_target( + NAME ${gem_name}.Editor.API INTERFACE + NAMESPACE Gem + FILES_CMAKE + simulationinterfaces_editor_api_files.cmake + ${pal_dir}/simulationinterfaces_editor_api_files.cmake + INCLUDE_DIRECTORIES + INTERFACE + Include + BUILD_DEPENDENCIES + INTERFACE + AZ::AzToolsFramework + ) + + # The ${gem_name}.Editor.Private.Object target is an internal target + # which is only to be used by this gems CMakeLists.txt and any subdirectories + # Other gems should not use this target + ly_add_target( + NAME ${gem_name}.Editor.Private.Object STATIC + NAMESPACE Gem + FILES_CMAKE + simulationinterfaces_editor_private_files.cmake + TARGET_PROPERTIES + O3DE_PRIVATE_TARGET TRUE + INCLUDE_DIRECTORIES + PRIVATE + Include + Source + BUILD_DEPENDENCIES + PUBLIC + AZ::AzToolsFramework + ${gem_name}.Private.Object + ) + + ly_add_target( + NAME ${gem_name}.Editor GEM_MODULE + NAMESPACE Gem + AUTOMOC + FILES_CMAKE + simulationinterfaces_editor_shared_files.cmake + INCLUDE_DIRECTORIES + PRIVATE + Source + PUBLIC + Include + BUILD_DEPENDENCIES + PUBLIC + Gem::${gem_name}.Editor.API + PRIVATE + Gem::${gem_name}.Editor.Private.Object + + + ) + + # Include the gem name into the Editor Module source file + # for use with the AZ_DECLARE_MODULE_CLASS macro + # This is to allow renaming of the gem to also cause + # the CreateModuleClass_Gem_ function which + # is used to bootstrap the gem in monolithic builds to link to the new gem name + ly_add_source_properties( + SOURCES + Source/Tools/SimulationInterfacesEditorModule.cpp + PROPERTY COMPILE_DEFINITIONS + VALUES + O3DE_GEM_NAME=${gem_name} + O3DE_GEM_VERSION=${gem_version}) + + # By default, we will specify that the above target ${gem_name} would be used by + # Tool and Builder type targets when this gem is enabled. If you don't want it + # active in Tools or Builders by default, delete one of both of the following lines: + ly_create_alias(NAME ${gem_name}.Tools NAMESPACE Gem TARGETS Gem::${gem_name}.Editor) + ly_create_alias(NAME ${gem_name}.Builders NAMESPACE Gem TARGETS Gem::${gem_name}.Editor) + + # For the Tools and Builders variants of ${gem_name} Gem, an alias to the ${gem_name}.Editor API target will be made + ly_create_alias(NAME ${gem_name}.Tools.API NAMESPACE Gem TARGETS Gem::${gem_name}.Editor.API) + ly_create_alias(NAME ${gem_name}.Builders.API NAMESPACE Gem TARGETS Gem::${gem_name}.Editor.API) + + # Add in CMake dependencies for each gem dependency listed in this gem's gem.json file + # for the Tools and Builders gem variants + o3de_add_variant_dependencies_for_gem_dependencies(GEM_NAME ${gem_name} VARIANTS Tools Builders) +endif() + +################################################################################ +# Tests +################################################################################ +# See if globally, tests are supported +if(PAL_TRAIT_BUILD_TESTS_SUPPORTED) + # We globally support tests, see if we support tests on this platform for ${gem_name}.Tests + if(PAL_TRAIT_SIMULATIONINTERFACES_TEST_SUPPORTED) + # We support ${gem_name}.Tests on this platform, add dependency on the Private Object target + ly_add_target( + NAME ${gem_name}.Tests ${PAL_TRAIT_TEST_TARGET_TYPE} + NAMESPACE Gem + FILES_CMAKE + simulationinterfaces_tests_files.cmake + INCLUDE_DIRECTORIES + PRIVATE + Tests + Source + Include + BUILD_DEPENDENCIES + PRIVATE + AZ::AzTest + AZ::AzFramework + Gem::${gem_name}.Private.Object + ) + + # Add ${gem_name}.Tests to googletest + ly_add_googletest( + NAME Gem::${gem_name}.Tests + ) + endif() + + # If we are a host platform we want to add tools test like editor tests here + if(PAL_TRAIT_BUILD_HOST_TOOLS) + # We are a host platform, see if Editor tests are supported on this platform + if(PAL_TRAIT_SIMULATIONINTERFACES_EDITOR_TEST_SUPPORTED) + # We support ${gem_name}.Editor.Tests on this platform, add ${gem_name}.Editor.Tests target which depends on + # private ${gem_name}.Editor.Private.Object target + ly_add_target( + NAME ${gem_name}.Editor.Tests ${PAL_TRAIT_TEST_TARGET_TYPE} + NAMESPACE Gem + FILES_CMAKE + simulationinterfaces_editor_tests_files.cmake + INCLUDE_DIRECTORIES + PRIVATE + Tests + Source + Include + BUILD_DEPENDENCIES + PRIVATE + AZ::AzTest + AZ::AzTestShared + AZ::AzToolsFramework + Legacy::CryCommon + Legacy::EditorCommon + Legacy::Editor.Headers + AZ::AzManipulatorTestFramework.Static + Gem::${gem_name}.API + Gem::${gem_name}.Editor.Private.Object + ) + + # Add ${gem_name}.Editor.Tests to googletest + ly_add_googletest( + NAME Gem::${gem_name}.Editor.Tests + ) + + ly_add_target( + NAME ${gem_name}.TestApp ${PAL_TRAIT_TEST_TARGET_TYPE} + NAMESPACE Gem + FILES_CMAKE + simulationinterfaces_editor_app_test.cmake + INCLUDE_DIRECTORIES + PRIVATE + Tests + Source + Include + BUILD_DEPENDENCIES + PRIVATE + AZ::AzTest + AZ::AzTestShared + AZ::AzToolsFramework + Legacy::CryCommon + Legacy::EditorCommon + Legacy::Editor.Headers + AZ::AzManipulatorTestFramework.Static + Gem::${gem_name}.API + Gem::${gem_name}.Editor.Private.Object + ) + + # Add ${gem_name}.Editor.Tests to googletest + ly_add_googletest( + NAME Gem::${gem_name}.TestApp + ) + endif() + endif() +endif() diff --git a/Gems/SimulationInterfaces/Code/Include/SimulationInterfaces/SimulationEntityManagerRequestBus.h b/Gems/SimulationInterfaces/Code/Include/SimulationInterfaces/SimulationEntityManagerRequestBus.h new file mode 100644 index 0000000000..f576420b7b --- /dev/null +++ b/Gems/SimulationInterfaces/Code/Include/SimulationInterfaces/SimulationEntityManagerRequestBus.h @@ -0,0 +1,102 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include + +#include +#include +#include +#include + +namespace SimulationInterfaces +{ + //! # A set of filters to apply to entity queries. See GetEntities, GetEntitiesStates. + //! # The filters are combined with a logical AND. + //! @see EntityFilters.msg + struct EntityFilters + { + AZStd::string m_filter; //! A posix regular expression to match against entity names + AZStd::shared_ptr + m_bounds_shape; //! A shape to use for filtering entities, null means no bounds filtering + AZ::Transform m_bounds_pose{ AZ::Transform::CreateIdentity() }; + }; + + //! @see EntityState.msg + struct EntityState + { + AZ::Transform m_pose; //! The pose of the entity + AZ::Vector3 m_twist_linear; //! The linear velocity of the entity (in the entity frame) + AZ::Vector3 m_twist_angular; //! The angular velocity of the entity (in the entity frame) + }; + + struct Spawnable + { + AZStd::string m_uri; + AZStd::string m_description; + AZStd::string m_bounds_sphere; + }; + + class SimulationEntityManagerRequests + { + public: + AZ_RTTI(SimulationEntityManagerRequests, SimulationInterfacesRequestsTypeId); + virtual ~SimulationEntityManagerRequests() = default; + + //! # Get a list of entities that match the filter. + //! Supported filters: + //! - name : a posix regular expression to match against entity names + //! - bounds : a shape to use for filtering entities, null means no bounds filtering + //! @see GetEntities.srv + virtual AZStd::vector GetEntities(const EntityFilters& filter) = 0; + + //! Get the state of an entity. + //! @see GetEntityState.srv + virtual EntityState GetEntityState(const AZStd::string& name) = 0; + + //! Get the state of all entities that match the filter. + //! @see GetEntitiesStates.srv + virtual AZStd::unordered_map GetEntitiesStates(const EntityFilters& filter) = 0; + + //! Set the state of an entity. + //! @see SetEntityState.srv + virtual bool SetEntityState(const AZStd::string& name, const EntityState& state) = 0; + + //! Remove previously spawned entity from the simulation. + //! @see DeleteEntity.srv + virtual bool DeleteEntity(const AZStd::string& name) = 0; + + virtual AZStd::vector GetSpawnables() = 0; + + //! Callback for when an entity has been spawned and registered. The string is the name of the entity in the simulation interface. + //! Note : The names is empty, if the entity could not be registered (e.g. prefab has no simulated entities) + using SpawnCompletedCb = AZStd::function&)>; + + virtual void SpawnEntity( + const AZStd::string& name, + const AZStd::string& uri, + const AZStd::string& entityNamespace, + const AZ::Transform& initialPose, + SpawnCompletedCb completedCb) = 0; + }; + + class SimulationInterfacesBusTraits : public AZ::EBusTraits + { + public: + ////////////////////////////////////////////////////////////////////////// + // EBusTraits overrides + static constexpr AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single; + static constexpr AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; + ////////////////////////////////////////////////////////////////////////// + }; + + using SimulationEntityManagerRequestBus = AZ::EBus; + using SimulationEntityManagerInterface = AZ::Interface; + +} // namespace SimulationInterfaces diff --git a/Gems/SimulationInterfaces/Code/Include/SimulationInterfaces/SimulationInterfacesTypeIds.h b/Gems/SimulationInterfaces/Code/Include/SimulationInterfaces/SimulationInterfacesTypeIds.h new file mode 100644 index 0000000000..7d165b3ed6 --- /dev/null +++ b/Gems/SimulationInterfaces/Code/Include/SimulationInterfaces/SimulationInterfacesTypeIds.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +namespace SimulationInterfaces +{ + // System Component TypeIds + inline constexpr const char* SimulationEntitiesManagerTypeId = "{4BF53AF2-A295-4F99-A166-F85FBFBDC077}"; + inline constexpr const char* SimulationEntitiesManagerEditorTypeId = "{B035007B-BAD3-40FA-880F-F45054A4C232}"; + + inline constexpr const char* SimulationManagerTypeId = "{5BB34EB0-1263-4DA1-A35C-CE613A088F4B}"; + inline constexpr const char* SimulationManagerEditorTypeId = "{2CC8D67B-CFD3-4E89-AAF0-8935640B51C1}"; + + // Module derived classes TypeIds + inline constexpr const char* SimulationInterfacesModuleInterfaceTypeId = "{675797BF-E5D5-438A-BF86-4B4554F09CEF}"; + inline constexpr const char* SimulationInterfacesModuleTypeId = "{8D6741FD-3105-4CB0-9700-152123B6D135}"; + // The Editor Module by default is mutually exclusive with the Client Module + // so they use the Same TypeId + inline constexpr const char* SimulationInterfacesEditorModuleTypeId = SimulationInterfacesModuleTypeId; + + // Interface TypeIds + inline constexpr const char* SimulationInterfacesRequestsTypeId = "{6818E5E3-BBF5-41BD-96BB-7AF57CCC7528}"; + inline constexpr const char* SimulationManagerRequestsTypeId = "{056477BA-8153-4901-9401-0146A5E3E9ED}"; + +} // namespace SimulationInterfaces diff --git a/Gems/SimulationInterfaces/Code/Include/SimulationInterfaces/SimulationMangerRequestBus.h b/Gems/SimulationInterfaces/Code/Include/SimulationInterfaces/SimulationMangerRequestBus.h new file mode 100644 index 0000000000..2b622c912c --- /dev/null +++ b/Gems/SimulationInterfaces/Code/Include/SimulationInterfaces/SimulationMangerRequestBus.h @@ -0,0 +1,45 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include + +#include +#include +#include +#include + +namespace SimulationInterfaces +{ + + class SimulationManagerRequests + { + public: + AZ_RTTI(SimulationManagerRequests, SimulationManagerRequestsTypeId); + virtual ~SimulationManagerRequests() = default; + + virtual void SetSimulationPaused(bool paused) = 0; + virtual void StepSimulation(AZ::u32 steps) = 0; + + }; + + class SimulationMangerRequestBusTraits : public AZ::EBusTraits + { + public: + ////////////////////////////////////////////////////////////////////////// + // EBusTraits overrides + static constexpr AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single; + static constexpr AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; + ////////////////////////////////////////////////////////////////////////// + }; + + using SimulationManagerRequestBus = AZ::EBus; + using SimulationManagerRequestBusInterface = AZ::Interface; + +} // namespace SimulationInterfaces diff --git a/Gems/SimulationInterfaces/Code/Platform/Android/PAL_android.cmake b/Gems/SimulationInterfaces/Code/Platform/Android/PAL_android.cmake new file mode 100644 index 0000000000..0bd6d7cf35 --- /dev/null +++ b/Gems/SimulationInterfaces/Code/Platform/Android/PAL_android.cmake @@ -0,0 +1,9 @@ +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# + +set(PAL_TRAIT_SIMULATIONINTERFACES_SUPPORTED TRUE) +set(PAL_TRAIT_SIMULATIONINTERFACES_TEST_SUPPORTED FALSE) +set(PAL_TRAIT_SIMULATIONINTERFACES_EDITOR_TEST_SUPPORTED FALSE) diff --git a/Gems/SimulationInterfaces/Code/Platform/Android/simulationinterfaces_api_files.cmake b/Gems/SimulationInterfaces/Code/Platform/Android/simulationinterfaces_api_files.cmake new file mode 100644 index 0000000000..17b4532de0 --- /dev/null +++ b/Gems/SimulationInterfaces/Code/Platform/Android/simulationinterfaces_api_files.cmake @@ -0,0 +1,8 @@ +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# + +set(FILES +) diff --git a/Gems/SimulationInterfaces/Code/Platform/Android/simulationinterfaces_private_files.cmake b/Gems/SimulationInterfaces/Code/Platform/Android/simulationinterfaces_private_files.cmake new file mode 100644 index 0000000000..7fbe5c9178 --- /dev/null +++ b/Gems/SimulationInterfaces/Code/Platform/Android/simulationinterfaces_private_files.cmake @@ -0,0 +1,13 @@ +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# + +# Platform specific files for Android +# i.e. ../Source/Android/SimulationInterfacesAndroid.cpp +# ../Source/Android/SimulationInterfacesAndroid.h +# ../Include/Android/SimulationInterfacesAndroid.h + +set(FILES +) diff --git a/Gems/SimulationInterfaces/Code/Platform/Android/simulationinterfaces_shared_files.cmake b/Gems/SimulationInterfaces/Code/Platform/Android/simulationinterfaces_shared_files.cmake new file mode 100644 index 0000000000..7fbe5c9178 --- /dev/null +++ b/Gems/SimulationInterfaces/Code/Platform/Android/simulationinterfaces_shared_files.cmake @@ -0,0 +1,13 @@ +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# + +# Platform specific files for Android +# i.e. ../Source/Android/SimulationInterfacesAndroid.cpp +# ../Source/Android/SimulationInterfacesAndroid.h +# ../Include/Android/SimulationInterfacesAndroid.h + +set(FILES +) diff --git a/Gems/SimulationInterfaces/Code/Platform/Linux/PAL_linux.cmake b/Gems/SimulationInterfaces/Code/Platform/Linux/PAL_linux.cmake new file mode 100644 index 0000000000..c79341aef2 --- /dev/null +++ b/Gems/SimulationInterfaces/Code/Platform/Linux/PAL_linux.cmake @@ -0,0 +1,9 @@ +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# + +set(PAL_TRAIT_SIMULATIONINTERFACES_SUPPORTED TRUE) +set(PAL_TRAIT_SIMULATIONINTERFACES_TEST_SUPPORTED TRUE) +set(PAL_TRAIT_SIMULATIONINTERFACES_EDITOR_TEST_SUPPORTED TRUE) diff --git a/Gems/SimulationInterfaces/Code/Platform/Linux/simulationinterfaces_api_files.cmake b/Gems/SimulationInterfaces/Code/Platform/Linux/simulationinterfaces_api_files.cmake new file mode 100644 index 0000000000..17b4532de0 --- /dev/null +++ b/Gems/SimulationInterfaces/Code/Platform/Linux/simulationinterfaces_api_files.cmake @@ -0,0 +1,8 @@ +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# + +set(FILES +) diff --git a/Gems/SimulationInterfaces/Code/Platform/Linux/simulationinterfaces_editor_api_files.cmake b/Gems/SimulationInterfaces/Code/Platform/Linux/simulationinterfaces_editor_api_files.cmake new file mode 100644 index 0000000000..17b4532de0 --- /dev/null +++ b/Gems/SimulationInterfaces/Code/Platform/Linux/simulationinterfaces_editor_api_files.cmake @@ -0,0 +1,8 @@ +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# + +set(FILES +) diff --git a/Gems/SimulationInterfaces/Code/Platform/Linux/simulationinterfaces_private_files.cmake b/Gems/SimulationInterfaces/Code/Platform/Linux/simulationinterfaces_private_files.cmake new file mode 100644 index 0000000000..e9d0093855 --- /dev/null +++ b/Gems/SimulationInterfaces/Code/Platform/Linux/simulationinterfaces_private_files.cmake @@ -0,0 +1,13 @@ +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# + +# Platform specific files for Linux +# i.e. ../Source/Linux/SimulationInterfacesLinux.cpp +# ../Source/Linux/SimulationInterfacesLinux.h +# ../Include/Linux/SimulationInterfacesLinux.h + +set(FILES +) diff --git a/Gems/SimulationInterfaces/Code/Platform/Linux/simulationinterfaces_shared_files.cmake b/Gems/SimulationInterfaces/Code/Platform/Linux/simulationinterfaces_shared_files.cmake new file mode 100644 index 0000000000..e9d0093855 --- /dev/null +++ b/Gems/SimulationInterfaces/Code/Platform/Linux/simulationinterfaces_shared_files.cmake @@ -0,0 +1,13 @@ +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# + +# Platform specific files for Linux +# i.e. ../Source/Linux/SimulationInterfacesLinux.cpp +# ../Source/Linux/SimulationInterfacesLinux.h +# ../Include/Linux/SimulationInterfacesLinux.h + +set(FILES +) diff --git a/Gems/SimulationInterfaces/Code/Platform/Mac/PAL_mac.cmake b/Gems/SimulationInterfaces/Code/Platform/Mac/PAL_mac.cmake new file mode 100644 index 0000000000..0bd6d7cf35 --- /dev/null +++ b/Gems/SimulationInterfaces/Code/Platform/Mac/PAL_mac.cmake @@ -0,0 +1,9 @@ +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# + +set(PAL_TRAIT_SIMULATIONINTERFACES_SUPPORTED TRUE) +set(PAL_TRAIT_SIMULATIONINTERFACES_TEST_SUPPORTED FALSE) +set(PAL_TRAIT_SIMULATIONINTERFACES_EDITOR_TEST_SUPPORTED FALSE) diff --git a/Gems/SimulationInterfaces/Code/Platform/Mac/simulationinterfaces_api_files.cmake b/Gems/SimulationInterfaces/Code/Platform/Mac/simulationinterfaces_api_files.cmake new file mode 100644 index 0000000000..17b4532de0 --- /dev/null +++ b/Gems/SimulationInterfaces/Code/Platform/Mac/simulationinterfaces_api_files.cmake @@ -0,0 +1,8 @@ +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# + +set(FILES +) diff --git a/Gems/SimulationInterfaces/Code/Platform/Mac/simulationinterfaces_editor_api_files.cmake b/Gems/SimulationInterfaces/Code/Platform/Mac/simulationinterfaces_editor_api_files.cmake new file mode 100644 index 0000000000..17b4532de0 --- /dev/null +++ b/Gems/SimulationInterfaces/Code/Platform/Mac/simulationinterfaces_editor_api_files.cmake @@ -0,0 +1,8 @@ +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# + +set(FILES +) diff --git a/Gems/SimulationInterfaces/Code/Platform/Mac/simulationinterfaces_private_files.cmake b/Gems/SimulationInterfaces/Code/Platform/Mac/simulationinterfaces_private_files.cmake new file mode 100644 index 0000000000..e333821649 --- /dev/null +++ b/Gems/SimulationInterfaces/Code/Platform/Mac/simulationinterfaces_private_files.cmake @@ -0,0 +1,13 @@ +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# + +# Platform specific files for Mac +# i.e. ../Source/Mac/SimulationInterfacesMac.cpp +# ../Source/Mac/SimulationInterfacesMac.h +# ../Include/Mac/SimulationInterfacesMac.h + +set(FILES +) diff --git a/Gems/SimulationInterfaces/Code/Platform/Mac/simulationinterfaces_shared_files.cmake b/Gems/SimulationInterfaces/Code/Platform/Mac/simulationinterfaces_shared_files.cmake new file mode 100644 index 0000000000..e333821649 --- /dev/null +++ b/Gems/SimulationInterfaces/Code/Platform/Mac/simulationinterfaces_shared_files.cmake @@ -0,0 +1,13 @@ +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# + +# Platform specific files for Mac +# i.e. ../Source/Mac/SimulationInterfacesMac.cpp +# ../Source/Mac/SimulationInterfacesMac.h +# ../Include/Mac/SimulationInterfacesMac.h + +set(FILES +) diff --git a/Gems/SimulationInterfaces/Code/Platform/Windows/PAL_windows.cmake b/Gems/SimulationInterfaces/Code/Platform/Windows/PAL_windows.cmake new file mode 100644 index 0000000000..0bd6d7cf35 --- /dev/null +++ b/Gems/SimulationInterfaces/Code/Platform/Windows/PAL_windows.cmake @@ -0,0 +1,9 @@ +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# + +set(PAL_TRAIT_SIMULATIONINTERFACES_SUPPORTED TRUE) +set(PAL_TRAIT_SIMULATIONINTERFACES_TEST_SUPPORTED FALSE) +set(PAL_TRAIT_SIMULATIONINTERFACES_EDITOR_TEST_SUPPORTED FALSE) diff --git a/Gems/SimulationInterfaces/Code/Platform/Windows/simulationinterfaces_api_files.cmake b/Gems/SimulationInterfaces/Code/Platform/Windows/simulationinterfaces_api_files.cmake new file mode 100644 index 0000000000..17b4532de0 --- /dev/null +++ b/Gems/SimulationInterfaces/Code/Platform/Windows/simulationinterfaces_api_files.cmake @@ -0,0 +1,8 @@ +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# + +set(FILES +) diff --git a/Gems/SimulationInterfaces/Code/Platform/Windows/simulationinterfaces_editor_api_files.cmake b/Gems/SimulationInterfaces/Code/Platform/Windows/simulationinterfaces_editor_api_files.cmake new file mode 100644 index 0000000000..17b4532de0 --- /dev/null +++ b/Gems/SimulationInterfaces/Code/Platform/Windows/simulationinterfaces_editor_api_files.cmake @@ -0,0 +1,8 @@ +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# + +set(FILES +) diff --git a/Gems/SimulationInterfaces/Code/Platform/Windows/simulationinterfaces_private_files.cmake b/Gems/SimulationInterfaces/Code/Platform/Windows/simulationinterfaces_private_files.cmake new file mode 100644 index 0000000000..c1fc94516a --- /dev/null +++ b/Gems/SimulationInterfaces/Code/Platform/Windows/simulationinterfaces_private_files.cmake @@ -0,0 +1,13 @@ +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# + +# Platform specific files for Windows +# i.e. ../Source/Windows/SimulationInterfacesWindows.cpp +# ../Source/Windows/SimulationInterfacesWindows.h +# ../Include/Windows/SimulationInterfacesWindows.h + +set(FILES +) diff --git a/Gems/SimulationInterfaces/Code/Platform/Windows/simulationinterfaces_shared_files.cmake b/Gems/SimulationInterfaces/Code/Platform/Windows/simulationinterfaces_shared_files.cmake new file mode 100644 index 0000000000..c1fc94516a --- /dev/null +++ b/Gems/SimulationInterfaces/Code/Platform/Windows/simulationinterfaces_shared_files.cmake @@ -0,0 +1,13 @@ +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# + +# Platform specific files for Windows +# i.e. ../Source/Windows/SimulationInterfacesWindows.cpp +# ../Source/Windows/SimulationInterfacesWindows.h +# ../Include/Windows/SimulationInterfacesWindows.h + +set(FILES +) diff --git a/Gems/SimulationInterfaces/Code/Platform/iOS/PAL_ios.cmake b/Gems/SimulationInterfaces/Code/Platform/iOS/PAL_ios.cmake new file mode 100644 index 0000000000..0bd6d7cf35 --- /dev/null +++ b/Gems/SimulationInterfaces/Code/Platform/iOS/PAL_ios.cmake @@ -0,0 +1,9 @@ +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# + +set(PAL_TRAIT_SIMULATIONINTERFACES_SUPPORTED TRUE) +set(PAL_TRAIT_SIMULATIONINTERFACES_TEST_SUPPORTED FALSE) +set(PAL_TRAIT_SIMULATIONINTERFACES_EDITOR_TEST_SUPPORTED FALSE) diff --git a/Gems/SimulationInterfaces/Code/Platform/iOS/simulationinterfaces_api_files.cmake b/Gems/SimulationInterfaces/Code/Platform/iOS/simulationinterfaces_api_files.cmake new file mode 100644 index 0000000000..17b4532de0 --- /dev/null +++ b/Gems/SimulationInterfaces/Code/Platform/iOS/simulationinterfaces_api_files.cmake @@ -0,0 +1,8 @@ +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# + +set(FILES +) diff --git a/Gems/SimulationInterfaces/Code/Platform/iOS/simulationinterfaces_private_files.cmake b/Gems/SimulationInterfaces/Code/Platform/iOS/simulationinterfaces_private_files.cmake new file mode 100644 index 0000000000..3c2adb03d2 --- /dev/null +++ b/Gems/SimulationInterfaces/Code/Platform/iOS/simulationinterfaces_private_files.cmake @@ -0,0 +1,13 @@ +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# + +# Platform specific files for iOS +# i.e. ../Source/iOS/SimulationInterfacesiOS.cpp +# ../Source/iOS/SimulationInterfacesiOS.h +# ../Include/iOS/SimulationInterfacesiOS.h + +set(FILES +) diff --git a/Gems/SimulationInterfaces/Code/Platform/iOS/simulationinterfaces_shared_files.cmake b/Gems/SimulationInterfaces/Code/Platform/iOS/simulationinterfaces_shared_files.cmake new file mode 100644 index 0000000000..3c2adb03d2 --- /dev/null +++ b/Gems/SimulationInterfaces/Code/Platform/iOS/simulationinterfaces_shared_files.cmake @@ -0,0 +1,13 @@ +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# + +# Platform specific files for iOS +# i.e. ../Source/iOS/SimulationInterfacesiOS.cpp +# ../Source/iOS/SimulationInterfacesiOS.h +# ../Include/iOS/SimulationInterfacesiOS.h + +set(FILES +) diff --git a/Gems/SimulationInterfaces/Code/Source/Clients/CommonUtilities.cpp b/Gems/SimulationInterfaces/Code/Source/Clients/CommonUtilities.cpp new file mode 100644 index 0000000000..d50ffb57e1 --- /dev/null +++ b/Gems/SimulationInterfaces/Code/Source/Clients/CommonUtilities.cpp @@ -0,0 +1,31 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include "CommonUtilities.h" + +namespace SimulationInterfaces::Utils +{ + const char* const ProductAssetPrefix = "product_asset:///"; + AZStd::string RelPathToUri(AZStd::string_view relPath) + { + AZStd::string uri = relPath; + AZStd::replace(uri.begin(), uri.end(), '\\', '/'); + uri.insert(0, ProductAssetPrefix); + return uri; + } + + AZStd::string UriToRelPath(AZStd::string_view uri) + { + if (uri.starts_with(ProductAssetPrefix)) + { + const AZStd::string_view productAssetPrefix{ ProductAssetPrefix }; + return uri.substr(productAssetPrefix.length()); + } + return {}; + } +} // namespace SimulationInterfaces::Utils \ No newline at end of file diff --git a/Gems/SimulationInterfaces/Code/Source/Clients/CommonUtilities.h b/Gems/SimulationInterfaces/Code/Source/Clients/CommonUtilities.h new file mode 100644 index 0000000000..0d0a96a802 --- /dev/null +++ b/Gems/SimulationInterfaces/Code/Source/Clients/CommonUtilities.h @@ -0,0 +1,20 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +namespace SimulationInterfaces::Utils +{ + //! Convert a relative path to a URI + //! relative path: "path/to/file.txt" + //! URI: "product_asset:///path/to/file.txt" + AZStd::string RelPathToUri(AZStd::string_view relPath); + AZStd::string UriToRelPath(AZStd::string_view relPath); + +} // namespace SimulationInterfaces::Utils \ No newline at end of file diff --git a/Gems/SimulationInterfaces/Code/Source/Clients/ConsoleCommands.inl b/Gems/SimulationInterfaces/Code/Source/Clients/ConsoleCommands.inl new file mode 100644 index 0000000000..59e010960d --- /dev/null +++ b/Gems/SimulationInterfaces/Code/Source/Clients/ConsoleCommands.inl @@ -0,0 +1,224 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include +#include +#include +#include + +namespace SimulationInterfacesCommands +{ + + using namespace SimulationInterfaces; + static void simulationinterfaces_GetEntities(const AZ::ConsoleCommandContainer& arguments) + { + AZStd::vector entities; + SimulationEntityManagerRequestBus::BroadcastResult(entities, &SimulationEntityManagerRequestBus::Events::GetEntities, EntityFilters()); + AZ_Printf("SimulationInterfacesConsole", "Number of simulation entities: %d\n", entities.size()); + for (const auto& entity : entities) + { + AZ_Printf("SimulationInterfacesConsole", " - %s\n", entity.c_str()); + } + } + + static void simulationinterfaces_Pause(const AZ::ConsoleCommandContainer& arguments) + { + SimulationManagerRequestBus::Broadcast(&SimulationManagerRequestBus::Events::SetSimulationPaused, true); + } + + static void simulationinterfaces_Resume(const AZ::ConsoleCommandContainer& arguments) + { + SimulationManagerRequestBus::Broadcast(&SimulationManagerRequestBus::Events::SetSimulationPaused, false); + } + + + static void simulationinterfaces_Step(const AZ::ConsoleCommandContainer& arguments) + { + if (arguments.empty()) + { + AZ_Printf("SimulationInterfacesConsole", "simulationinterfaces_Step \n"); + return; + } + uint32_t steps = AZStd::stoi(AZStd::string(arguments[0])); + + SimulationManagerRequestBus::Broadcast(&SimulationManagerRequestBus::Events::StepSimulation, steps); + } + + + + static void simulationinterfaces_GetEntitiesSphere(const AZ::ConsoleCommandContainer& arguments) + { + float sphereShape = 10.f; + AZ::Vector3 position = AZ::Vector3::CreateZero(); + sphereShape = arguments.empty() ? 10.f : (AZStd::stof(AZStd::string(arguments[0]))); + position.SetX(arguments.size() > 1 ? (AZStd::stof(AZStd::string(arguments[1]))) : 0.f); + position.SetY(arguments.size() > 2 ? (AZStd::stof(AZStd::string(arguments[2]))) : 0.f); + position.SetZ(arguments.size() > 3 ? (AZStd::stof(AZStd::string(arguments[3]))) : 0.f); + + AZ_Printf("SimulationInterfacesConsole", "simulationinterfaces_GetEntities in radius %f \n", sphereShape); + AZ_Printf("SimulationInterfacesConsole", "position %f %f %f \n", position.GetX(), position.GetY(), position.GetZ()); + EntityFilters filter; + filter.m_bounds_shape = AZStd::make_shared(sphereShape); + + AZStd::vector entities; + SimulationEntityManagerRequestBus::BroadcastResult(entities, &SimulationEntityManagerRequestBus::Events::GetEntities, filter); + AZ_Printf("SimulationInterfacesConsole", "Number of simulation entities: %d\n", entities.size()); + for (const auto& entity : entities) + { + AZ_Printf("SimulationInterfacesConsole", " - %s\n", entity.c_str()); + } + } + + static void simulationinterfaces_GetEntityState(const AZ::ConsoleCommandContainer& arguments) + { + if (arguments.empty()) + { + AZ_Printf("SimulationInterfacesConsole", "simulationinterfaces_GetEntityState requires entity name\n"); + return; + } + const AZStd::string entityName = arguments[0]; + AZ_Printf("SimulationInterfacesConsole", "simulationinterfaces_GetEntityState %s\n", entityName.c_str()); + EntityState entityState; + SimulationEntityManagerRequestBus::BroadcastResult(entityState, &SimulationEntityManagerRequestBus::Events::GetEntityState, entityName); + AZ_Printf("SimulationInterfacesConsole", "Entity %s\n", entityName.c_str()); + AZ_Printf( + "SimulationInterfacesConsole", + "Pose %s\n", + AZ::Vector3ToString(entityState.m_pose.GetTranslation()).c_str()); + AZ_Printf( + "SimulationInterfacesConsole", + "Rotation %s \n", + AZ::QuaternionToString(entityState.m_pose.GetRotation()).c_str()); + + const AZ::Vector3 euler = entityState.m_pose.GetRotation().GetEulerDegrees(); + AZ_Printf("SimulationInterfacesConsole", "Rotation (euler) %s\n", AZ::Vector3ToString(euler).c_str()); + AZ_Printf( + "SimulationInterfacesConsole", + "Twist Linear %s\n", + AZ::Vector3ToString(entityState.m_twist_linear).c_str()); + AZ_Printf( + "SimulationInterfacesConsole", + "Twist Angular %s\n", + AZ::Vector3ToString(entityState.m_twist_angular).c_str()); + } + + static void simulationinterfaces_SetStateXYZ(const AZ::ConsoleCommandContainer& arguments) + { + if (arguments.empty()) + { + AZ_Printf("SimulationInterfacesConsole", "simulationinterfaces_GetEntityState requires entity name\n"); + return; + } + const AZStd::string entityName = arguments[0]; + AZ::Vector3 position = AZ::Vector3::CreateZero(); + position.SetX(arguments.size() > 1 ? (AZStd::stof(AZStd::string(arguments[1]))) : 0.f); + position.SetY(arguments.size() > 2 ? (AZStd::stof(AZStd::string(arguments[2]))) : 0.f); + position.SetZ(arguments.size() > 3 ? (AZStd::stof(AZStd::string(arguments[3]))) : 0.f); + EntityState entityState{}; + entityState.m_pose = AZ::Transform::CreateIdentity(); + entityState.m_pose.SetTranslation(position); + bool isOk = false; + SimulationEntityManagerRequestBus::BroadcastResult( + isOk, &SimulationEntityManagerRequestBus::Events::SetEntityState, entityName, entityState); + if (isOk) + { + AZ_Printf("SimulationInterfacesConsole", "Entity %s state set\n", entityName.c_str()); + } + else + { + AZ_Printf("SimulationInterfacesConsole", "Entity %s state NOT set\n", entityName.c_str()); + } + } + + static void simulationinterfaces_DeleteEntity(const AZ::ConsoleCommandContainer& arguments) + { + if (arguments.empty()) + { + AZ_Printf("SimulationInterfacesConsole", "simulationinterfaces_DeleteEntity requires entity name\n"); + return; + } + const AZStd::string entityName = arguments[0]; + AZ_Printf("SimulationInterfacesConsole", "simulationinterfaces_DeleteEntity %s\n", entityName.c_str()); + bool isOk = false; + SimulationEntityManagerRequestBus::BroadcastResult(isOk, &SimulationEntityManagerRequestBus::Events::DeleteEntity, entityName); + if (isOk) + { + AZ_Printf("SimulationInterfacesConsole", "Entity %s deleted\n", entityName.c_str()); + } + else + { + AZ_Printf("SimulationInterfacesConsole", "Entity %s NOT deleted\n", entityName.c_str()); + } + } + + static void simulationinterfaces_GetSpawnables(const AZ::ConsoleCommandContainer& arguments) + { + AZ_Printf("SimulationInterfacesConsole", "simulationinterfaces_GetSpawnables\n"); + AZStd::vector spawnables; + SimulationEntityManagerRequestBus::BroadcastResult(spawnables, &SimulationEntityManagerRequestBus::Events::GetSpawnables); + AZ_Printf("SimulationInterfacesConsole", "Number of spawnables: %d\n", spawnables.size()); + for (const auto& spawnable : spawnables) + { + AZ_Printf("SimulationInterfaces", " - %s\n", spawnable.m_uri.c_str()); + } + } + + static void simulationinterfaces_Spawn(const AZ::ConsoleCommandContainer& arguments) + { + if (arguments.size() < 2) + { + AZ_Printf("SimulationInterfacesConsole", "simulationinterface_Spawn minimal :\n"); + AZ_Printf("SimulationInterfacesConsole", " simulationinterface_Spawn \n"); + AZ_Printf("SimulationInterfacesConsole", "simulationinterface_Spawn optional :\n"); + AZ_Printf("SimulationInterfacesConsole", " simulationinterface_Spawn \n"); + return; + } + AZStd::string name = arguments[0]; + AZStd::string uri = arguments[1]; + AZStd::string entityNamespace = arguments.size() > 2 ? arguments[2] : ""; + AZ::Transform initialPose = AZ::Transform::CreateIdentity(); + if (arguments.size() > 5) + { + initialPose.SetTranslation( + AZ::Vector3( + AZStd::stof(AZStd::string(arguments[3])), + AZStd::stof(AZStd::string(arguments[4])), + AZStd::stof(AZStd::string(arguments[5])))); + } + SimulationEntityManagerRequests::SpawnCompletedCb completedCb = [](const AZ::Outcome& name) + { + if (name.IsSuccess()) + { + AZ_Printf("SimulationInterfacesConsole", "Entity %s spawned and registered\n", name.GetValue().c_str()); + } + else + { + AZ_Printf("SimulationInterfacesConsole", "Entity NOT spawned. Error : %s\n", name.GetError().c_str()); + } + }; + SimulationEntityManagerRequestBus::Broadcast(&SimulationEntityManagerRequestBus::Events::SpawnEntity, name, uri, entityNamespace, initialPose, completedCb); + AZ_Printf("SimulationInterfacesConsole", "simulationinterface_Spawn %s %s\n", name.c_str(), uri.c_str()); + } + + AZ_CONSOLEFREEFUNC(simulationinterfaces_Pause, AZ::ConsoleFunctorFlags::DontReplicate, "Pause simulation."); + AZ_CONSOLEFREEFUNC(simulationinterfaces_Resume, AZ::ConsoleFunctorFlags::DontReplicate, "Resume simulation."); + AZ_CONSOLEFREEFUNC(simulationinterfaces_Step, AZ::ConsoleFunctorFlags::DontReplicate, "Step simulation."); + + AZ_CONSOLEFREEFUNC( + simulationinterfaces_GetEntities, AZ::ConsoleFunctorFlags::DontReplicate, "Get all simulated entities in the scene."); + AZ_CONSOLEFREEFUNC( + simulationinterfaces_GetEntitiesSphere, AZ::ConsoleFunctorFlags::DontReplicate, "Get all simulated entities in the radius."); + AZ_CONSOLEFREEFUNC(simulationinterfaces_GetEntityState, AZ::ConsoleFunctorFlags::DontReplicate, "Get state of the entity."); + AZ_CONSOLEFREEFUNC(simulationinterfaces_SetStateXYZ, AZ::ConsoleFunctorFlags::DontReplicate, "Set state of the entity."); + AZ_CONSOLEFREEFUNC(simulationinterfaces_DeleteEntity, AZ::ConsoleFunctorFlags::DontReplicate, "Delete entity."); + AZ_CONSOLEFREEFUNC( + simulationinterfaces_GetSpawnables, AZ::ConsoleFunctorFlags::DontReplicate, "Get all spawnable entities in the scene."); + AZ_CONSOLEFREEFUNC(simulationinterfaces_Spawn, AZ::ConsoleFunctorFlags::DontReplicate, "Spawn entity."); +} // namespace SimulationInterfacesCommands diff --git a/Gems/SimulationInterfaces/Code/Source/Clients/SimulationEntitiesManager.cpp b/Gems/SimulationInterfaces/Code/Source/Clients/SimulationEntitiesManager.cpp new file mode 100644 index 0000000000..54ac45bd2e --- /dev/null +++ b/Gems/SimulationInterfaces/Code/Source/Clients/SimulationEntitiesManager.cpp @@ -0,0 +1,586 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include "SimulationEntitiesManager.h" + +#include + +#include "CommonUtilities.h" +#include "ConsoleCommands.inl" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace SimulationInterfaces +{ + void SetRigidBodyVelocities(AzPhysics::RigidBody* rigidBody, const EntityState& state) + { + if (!state.m_twist_angular.IsClose(AZ::Vector3::CreateZero(), AZ::Constants::FloatEpsilon)) + { + // get transform + AZ::Vector3 angularVelWorld = rigidBody->GetTransform().TransformVector(state.m_twist_angular); + rigidBody->SetAngularVelocity(angularVelWorld); + } + + if (!state.m_twist_linear.IsClose(AZ::Vector3::CreateZero(), AZ::Constants::FloatEpsilon)) + { + // get transform + AZ::Vector3 linearVelWorld = rigidBody->GetTransform().TransformVector(state.m_twist_linear); + rigidBody->SetAngularVelocity(linearVelWorld); + } + } + + AZ_COMPONENT_IMPL(SimulationEntitiesManager, "SimulationEntitiesManager", SimulationEntitiesManagerTypeId); + + void SimulationEntitiesManager::Reflect(AZ::ReflectContext* context) + { + if (auto serializeContext = azrtti_cast(context)) + { + serializeContext->Class()->Version(0); + } + } + + void SimulationEntitiesManager::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided) + { + provided.push_back(AZ_CRC_CE("SimulationInterfacesService")); + } + + void SimulationEntitiesManager::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible) + { + incompatible.push_back(AZ_CRC_CE("SimulationInterfacesService")); + } + + void SimulationEntitiesManager::GetRequiredServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& required) + { + required.push_back(AZ_CRC_CE("AssetCatalogService")); + } + + void SimulationEntitiesManager::GetDependentServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& dependent) + { + dependent.push_back(AZ_CRC_CE("PhysicsService")); + } + + SimulationEntitiesManager::SimulationEntitiesManager() + { + if (SimulationEntityManagerInterface::Get() == nullptr) + { + SimulationEntityManagerInterface::Register(this); + } + } + + SimulationEntitiesManager::~SimulationEntitiesManager() + { + if (SimulationEntityManagerInterface::Get() == this) + { + SimulationEntityManagerInterface::Unregister(this); + } + } + + void SimulationEntitiesManager::Init() + { + } + + AzPhysics::Scene* GetSceneHelper(AzPhysics::SceneHandle sceneHandle) + { + AzPhysics::SystemInterface* physicsSystem = AZ::Interface::Get(); + AZ_Assert(physicsSystem, "Physics system is not available."); + AzPhysics::Scene* scene = physicsSystem->GetScene(sceneHandle); + return scene; + } + + void SimulationEntitiesManager::Activate() + { + m_simulationBodyAddedHandler = AzPhysics::SceneEvents::OnSimulationBodyAdded::Handler( + [this](AzPhysics::SceneHandle sceneHandle, AzPhysics::SimulatedBodyHandle bodyHandle) + { + auto* scene = GetSceneHelper(sceneHandle); + if (scene == nullptr) + { + return; + } + auto* body = scene->GetSimulatedBodyFromHandle(bodyHandle); + AZ_Assert(body, "Simulated body is not available."); + auto* rigidBody = azdynamic_cast(body); + if (rigidBody != nullptr) + { + [[maybe_unused]] auto shapeCount = rigidBody->GetShapeCount(); + AZ_Warning( + "SimulationInterfaces", + shapeCount > 0, + "Entity %s has no collider shapes, it won't be available by bound search", + rigidBody->GetEntityId().ToString().c_str()); + } + const AZ::EntityId entityId = body->GetEntityId(); + AZ::Entity* entity = nullptr; + AZ::ComponentApplicationBus::BroadcastResult(entity, &AZ::ComponentApplicationRequests::FindEntity, entityId); + AZ_Assert(entity, "Entity is not available."); + // check if entity is not spawned by this component + const auto ticketId = entity->GetEntitySpawnTicketId(); + AZStd::string proposedName{}; + // check if ticket is in the unregistered list + + auto spawnData = m_spawnCompletedCallbacks.find(ticketId); + if (spawnData != m_spawnCompletedCallbacks.end()) + { + proposedName = spawnData->second.m_userProposedName; + } + + const AZStd::string registeredName = this->AddSimulatedEntity(entityId, proposedName); + // call the callback + if (spawnData != m_spawnCompletedCallbacks.end()) + { + // call and remove the callback + spawnData->second.m_completedCb(AZ::Success(registeredName)); + m_spawnCompletedCallbacks.erase(spawnData); + } + }); + m_simulationBodyRemovedHandler = AzPhysics::SceneEvents::OnSimulationBodyRemoved::Handler( + [this](AzPhysics::SceneHandle sceneHandle, AzPhysics::SimulatedBodyHandle bodyHandle) + { + auto* scene = GetSceneHelper(sceneHandle); + if (scene == nullptr) + { + return; + } + const auto* body = scene->GetSimulatedBodyFromHandle(bodyHandle); + AZ_Assert(body, "Simulated body is not available."); + const AZ::EntityId entityId = body->GetEntityId(); + // remove simulated entity + this->RemoveSimulatedEntity(entityId); + }); + + m_sceneAddedHandler = AzPhysics::SystemEvents::OnSceneAddedEvent::Handler( + [this](AzPhysics::SceneHandle sceneHandle) + { + AZ_Warning("SimulationInterfaces", m_physicsScenesHandle == AzPhysics::InvalidSceneHandle, "Hmm, we already have a scene"); + auto* scene = GetSceneHelper(sceneHandle); + AZ_Assert(scene, "Scene is not available."); + if (scene == nullptr) + { + return; + } + scene->RegisterSimulationBodyAddedHandler(m_simulationBodyAddedHandler); + scene->RegisterSimulationBodyRemovedHandler(m_simulationBodyRemovedHandler); + + AZ_Printf("SimulationInterfaces", "Registered simulation body added handler\n"); + m_physicsScenesHandle = sceneHandle; + }); + m_sceneRemovedHandler = AzPhysics::SystemEvents::OnSceneRemovedEvent::Handler( + [this](AzPhysics::SceneHandle sceneHandle) + { + if (m_physicsScenesHandle == sceneHandle) + { + m_entityIdToSimulatedEntityMap.clear(); + m_simulatedEntityToEntityIdMap.clear(); + m_simulationBodyAddedHandler.Disconnect(); + m_simulationBodyRemovedHandler.Disconnect(); + m_physicsScenesHandle = AzPhysics::InvalidSceneHandle; + } + }); + AzPhysics::SystemInterface* physicsSystem = AZ::Interface::Get(); + if (physicsSystem) + { + physicsSystem->RegisterSceneAddedEvent(m_sceneAddedHandler); + physicsSystem->RegisterSceneRemovedEvent(m_sceneRemovedHandler); + SimulationEntityManagerRequestBus::Handler::BusConnect(); + } + } + + void SimulationEntitiesManager::Deactivate() + { + SimulationEntityManagerRequestBus::Handler::BusDisconnect(); + if (m_simulationBodyAddedHandler.IsConnected()) + { + m_simulationBodyAddedHandler.Disconnect(); + } + if (m_simulationBodyRemovedHandler.IsConnected()) + { + m_simulationBodyRemovedHandler.Disconnect(); + } + m_physicsScenesHandle = AzPhysics::InvalidSceneHandle; + if (m_sceneAddedHandler.IsConnected()) + { + m_sceneAddedHandler.Disconnect(); + } + if (m_sceneAddedHandler.IsConnected()) + { + m_sceneAddedHandler.Disconnect(); + } + } + + AZStd::string SimulationEntitiesManager::AddSimulatedEntity(AZ::EntityId entityId, const AZStd::string& userProposedName) + { + if (!entityId.IsValid()) + { + return ""; + } + // check if entity is already registered + auto findIt = m_entityIdToSimulatedEntityMap.find(entityId); + if (findIt != m_entityIdToSimulatedEntityMap.end()) + { + return findIt->second; + } + // register entity under unique name + AZStd::string simulatedEntityName = GetSimulatedEntityName(entityId, userProposedName); + m_simulatedEntityToEntityIdMap[simulatedEntityName] = entityId; + m_entityIdToSimulatedEntityMap[entityId] = simulatedEntityName; + AZ_Printf("SimulationInterfaces", "Registered entity %s\n", simulatedEntityName.c_str()); + return simulatedEntityName; + } + + void SimulationEntitiesManager::RemoveSimulatedEntity(AZ::EntityId entityId) + { + auto findIt = m_entityIdToSimulatedEntityMap.find(entityId); + if (findIt != m_entityIdToSimulatedEntityMap.end()) + { + const auto& simulatedEntityName = findIt->second; + m_entityIdToSimulatedEntityMap.erase(findIt); + m_simulatedEntityToEntityIdMap.erase(simulatedEntityName); + } + } + + AZStd::vector SimulationEntitiesManager::GetEntities(const EntityFilters& filter) + { + const bool reFilter = !filter.m_filter.empty(); + const bool shapeCastFilter = filter.m_bounds_shape != nullptr; + + AZStd::vector entities; + if (!shapeCastFilter) + { + // get all entities from the map + entities.reserve(m_entityIdToSimulatedEntityMap.size()); + AZStd::transform( + m_entityIdToSimulatedEntityMap.begin(), + m_entityIdToSimulatedEntityMap.end(), + AZStd::back_inserter(entities), + [](const auto& pair) + { + return pair.second; + }); + } + else + { + auto* sceneInterface = AZ::Interface::Get(); + AZ_Assert(sceneInterface, "Physics scene interface is not available."); + + if (m_physicsScenesHandle == AzPhysics::InvalidSceneHandle) + { + return entities; + } + + AzPhysics::OverlapRequest request; + request.m_shapeConfiguration = filter.m_bounds_shape; + request.m_pose = filter.m_bounds_pose; + request.m_maxResults = AZStd::numeric_limits::max(); + + AzPhysics::SceneQueryHits result = sceneInterface->QueryScene(m_physicsScenesHandle, &request); + for (const auto& hit : result.m_hits) + { + const AZ::EntityId entityId = hit.m_entityId; + auto findIt = m_entityIdToSimulatedEntityMap.find(entityId); + if (findIt != m_entityIdToSimulatedEntityMap.end()) + { + entities.push_back(findIt->second); + } + } + } + if (reFilter) + { + const AZStd::vector prefilteredEntities = AZStd::move(entities); + entities.clear(); + const AZStd::regex regex(filter.m_filter); + if (regex.Valid()) + { + AZStd::ranges::copy_if( + prefilteredEntities, + AZStd::back_inserter(entities), + [®ex](const AZStd::string& entityName) + { + return AZStd::regex_search(entityName, regex); + }); + } + } + return entities; + } + + EntityState SimulationEntitiesManager::GetEntityState(const AZStd::string& name) + { + const auto findIt = m_simulatedEntityToEntityIdMap.find(name); + AZ_Error("SimulationInterfaces", findIt != m_simulatedEntityToEntityIdMap.end(), "Entity %s not found", name.c_str()); + if (findIt != m_simulatedEntityToEntityIdMap.end()) + { + EntityState entityState{}; + const AZ::EntityId entityId = findIt->second; + AZ_Assert(entityId.IsValid(), "EntityId is not valid"); + AZ::TransformBus::EventResult(entityState.m_pose, entityId, &AZ::TransformBus::Events::GetWorldTM); + + AZ::Vector3 linearVelocity = AZ::Vector3::CreateZero(); + Physics::RigidBodyRequestBus::EventResult(linearVelocity, entityId, &Physics::RigidBodyRequests::GetLinearVelocity); + + AZ::Vector3 angularVelocity = AZ::Vector3::CreateZero(); + Physics::RigidBodyRequestBus::EventResult(angularVelocity, entityId, &Physics::RigidBodyRequests::GetAngularVelocity); + + // transform linear and angular velocities to entity frame + AZ::Transform entityTransformInv = entityState.m_pose.GetInverse(); + entityState.m_twist_linear = entityTransformInv.TransformVector(linearVelocity); + entityState.m_twist_angular = entityTransformInv.TransformVector(angularVelocity); + return entityState; + } + return {}; + } + + bool SimulationEntitiesManager::SetEntityState(const AZStd::string& name, const EntityState& state) + { + const auto findIt = m_simulatedEntityToEntityIdMap.find(name); + if (findIt != m_simulatedEntityToEntityIdMap.end()) + { + const AZ::EntityId entityId = findIt->second; + AZ_Assert(entityId.IsValid(), "EntityId is not valid"); + + // get entity and all descendants + AZStd::vector entityAndDescendants; + AZ::TransformBus::EventResult(entityAndDescendants, entityId, &AZ::TransformBus::Events::GetEntityAndAllDescendants); + + if (state.m_pose.IsOrthogonal()) + { + // disable simulation for all entities + AZStd::map entityTransforms; + for (const auto& descendant : entityAndDescendants) + { + // get name + AZStd::string entityName = "Unknown"; + AZ::ComponentApplicationBus::BroadcastResult(entityName, &AZ::ComponentApplicationRequests::GetEntityName, descendant); + AZ_Printf("SimulationInterfaces", "Disable physics for entity %s\n", entityName.c_str()); + Physics::RigidBodyRequestBus::Event(descendant, &Physics::RigidBodyRequests::DisablePhysics); + } + + AZ::TransformBus::Event(entityId, &AZ::TransformBus::Events::SetLocalTM, state.m_pose); + + for (const auto& descendant : entityAndDescendants) + { + Physics::RigidBodyRequestBus::Event(descendant, &Physics::RigidBodyRequests::EnablePhysics); + Physics::RigidBodyRequestBus::Event( + descendant, &Physics::RigidBodyRequests::SetAngularVelocity, AZ::Vector3::CreateZero()); + Physics::RigidBodyRequestBus::Event( + descendant, &Physics::RigidBodyRequests::SetLinearVelocity, AZ::Vector3::CreateZero()); + } + } + if (!state.m_twist_linear.IsZero(AZ::Constants::FloatEpsilon) || !state.m_twist_angular.IsZero(AZ::Constants::FloatEpsilon)) + { + // get rigid body + AzPhysics::RigidBody* rigidBody = nullptr; + Physics::RigidBodyRequestBus::EventResult(rigidBody, entityId, &Physics::RigidBodyRequests::GetRigidBody); + if (rigidBody != nullptr) + { + SetRigidBodyVelocities(rigidBody, state); + } + } + } + return false; + } + + bool SimulationEntitiesManager::DeleteEntity(const AZStd::string& name) + { + const auto findIt = m_simulatedEntityToEntityIdMap.find(name); + + if (findIt == m_simulatedEntityToEntityIdMap.end()) + { + return false; + } + + const AZ::EntityId entityId = findIt->second; + AZ_Assert(entityId.IsValid(), "EntityId is not valid"); + // get entity + AZ::Entity* entity = nullptr; + AZ::ComponentApplicationBus::BroadcastResult(entity, &AZ::ComponentApplicationRequests::FindEntity, entityId); + AZ_Assert(entity, "Entity is not available."); + // check if entity is spawned by this component + const auto ticketId = entity->GetEntitySpawnTicketId(); + if (m_spawnedTickets.find(ticketId) != m_spawnedTickets.end()) + { + // remove the ticket + m_spawnedTickets.erase(ticketId); + } + else + { + AZ_Warning("SimulationInterfaces", false, "Entity %s was not spawned by this component, wont delete it", name.c_str()); + return false; + } +#ifdef POTENTIALY_UNSAFE + if (findIt != m_simulatedEntityToEntityIdMap.end()) + { + const AZ::EntityId entityId = findIt->second; + AZ_Assert(entityId.IsValid(), "EntityId is not valid"); + // get all descendants + AZStd::vector entityAndDescendants; + AZ::TransformBus::EventResult(entityAndDescendants, entityId, &AZ::TransformBus::Events::GetEntityAndAllDescendants); + for (const auto& descendant : entityAndDescendants) + { + // I am not sure if this is the safe way to delete an entity + AZ::ComponentApplicationBus::Broadcast(&AZ::ComponentApplicationRequests::DeleteEntity, descendant); + } + + return true; + } +#endif + return false; + } + + AZStd::unordered_map SimulationEntitiesManager::GetEntitiesStates(const EntityFilters& filter) + { + AZStd::unordered_map entitiesStates; + const auto& entities = GetEntities(filter); + for (const auto& entity : entities) + { + entitiesStates.emplace(AZStd::make_pair(entity, GetEntityState(entity))); + } + return entitiesStates; + } + + AZStd::vector SimulationEntitiesManager::GetSpawnables() + { + AZStd::vector spawnables; + + const auto enumCallback = [&spawnables](const AZ::Data::AssetId assetId, const AZ::Data::AssetInfo& assetInfo) + { + bool isSpawnable = false; + AZ::Data::AssetCatalogRequestBus::BroadcastResult( + isSpawnable, &AZ::Data::AssetCatalogRequests::DoesAssetIdMatchWildcardPattern, assetId, "*.spawnable"); + + if (isSpawnable) + { + Spawnable spawnable; + spawnable.m_uri = Utils::RelPathToUri(assetInfo.m_relativePath); + spawnables.push_back(spawnable); + } + }; + + AZ::Data::AssetCatalogRequestBus::Broadcast(&AZ::Data::AssetCatalogRequests::EnumerateAssets, nullptr, enumCallback, nullptr); + return spawnables; + } + + void SimulationEntitiesManager::SpawnEntity( + const AZStd::string& name, + const AZStd::string& uri, + const AZStd::string& entityNamespace, + const AZ::Transform& initialPose, + SpawnCompletedCb completedCb) + { + // get rel path from uri + const AZStd::string relPath = Utils::UriToRelPath(uri); + + // create spawnnable + AZ::Data::AssetId assetId; + AZ::Data::AssetCatalogRequestBus::BroadcastResult( + assetId, + &AZ::Data::AssetCatalogRequestBus::Events::GetAssetIdByPath, + relPath.c_str(), + azrtti_typeid(), + false); + AZ_Warning("SimulationInterfaces", assetId.IsValid(), "AssetId is not valid, relative path %s", relPath.c_str()); + + auto spawner = AZ::Interface::Get(); + AZ_Assert(spawner, "SpawnableEntitiesDefinition is not available."); + + AZ::Data::Asset spawnableAsset = + AZ::Data::AssetManager::Instance().GetAsset(assetId, AZ::Data::AssetLoadBehavior::NoLoad); + if (!spawnableAsset) + { + completedCb(AZ::Failure("Failed to get spawnable asset - incorrect uri")); + return; + } + + auto ticket = AzFramework::EntitySpawnTicket(spawnableAsset); + AzFramework::SpawnAllEntitiesOptionalArgs optionalArgs; + + optionalArgs.m_preInsertionCallback = [initialPose, entityNamespace, name](auto id, auto view) + { + if (view.empty()) + { + return; + } + const AZ::Entity* root = *view.begin(); + + // change names for all entites + for (auto* entity : view) + { + AZStd::string entityName = AZStd::string::format("%s_%s", name.c_str(), entity->GetName().c_str()); + entity->SetName(entityName); + } + + auto* transformInterface = root->FindComponent(); + if (transformInterface) + { + transformInterface->SetWorldTM(initialPose); + } + + if (!entityNamespace.empty()) + { + // TODO: Mpelka set ROS 2 namespace here + AZ_Error("SimulationInterfaces", false, "ROS 2 namespace is not implemented yet in spawning"); + } + }; + optionalArgs.m_completionCallback = + [this](AzFramework::EntitySpawnTicket::Id ticketId, AzFramework::SpawnableConstEntityContainerView view) + { + // at this point the entities are spawned and should be registered in simulation interface and callback should be called + // if that is not a case, it means that the AZFrameworrk::Pshysics::OnSimulationBodyAdded event was not called. + // That means the prefab has no physics component or the physics component is not enabled - we need to call the callback here + // and return the error. + auto spawnData = m_spawnCompletedCallbacks.find(ticketId); + if (spawnData != m_spawnCompletedCallbacks.end()) + { + // call and remove the callback + spawnData->second.m_completedCb(AZ::Failure( + "Entity was not registered in simulation interface - no physics component or physics component is not enabled.")); + m_spawnCompletedCallbacks.erase(spawnData); + } + }; + + spawner->SpawnAllEntities(ticket, optionalArgs); + + auto ticketId = ticket.GetId(); + AZ_Printf("SimulationInterfaces", "Spawning uri %s with ticket id %d\n", uri.c_str(), ticketId); + + SpawnCompletedCbData data; + data.m_userProposedName = name; + data.m_completedCb = completedCb; + m_spawnCompletedCallbacks[ticketId] = data; + m_spawnedTickets[ticketId] = ticket; + } + + AZStd::string SimulationEntitiesManager::GetSimulatedEntityName(AZ::EntityId entityId, const AZStd::string& proposedName) const + { + // Get O3DE entity name + AZStd::string entityName = proposedName; + if (entityName.empty()) + { + AZ::ComponentApplicationBus::BroadcastResult(entityName, &AZ::ComponentApplicationRequests::GetEntityName, entityId); + } + // Generate unique simulated entity name + AZStd::string simulatedEntityName = entityName; + // check if name is unique + auto otherEntityIt = m_simulatedEntityToEntityIdMap.find(simulatedEntityName); + if (otherEntityIt != m_simulatedEntityToEntityIdMap.end()) + { + // name is not unique, add entityId to name + simulatedEntityName = AZStd::string::format("%s_%s", entityName.c_str(), entityId.ToString().c_str()); + } + return simulatedEntityName; + } +} // namespace SimulationInterfaces diff --git a/Gems/SimulationInterfaces/Code/Source/Clients/SimulationEntitiesManager.h b/Gems/SimulationInterfaces/Code/Source/Clients/SimulationEntitiesManager.h new file mode 100644 index 0000000000..25412f21b3 --- /dev/null +++ b/Gems/SimulationInterfaces/Code/Source/Clients/SimulationEntitiesManager.h @@ -0,0 +1,94 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace SimulationInterfaces +{ + class SimulationEntitiesManager + : public AZ::Component + , protected SimulationEntityManagerRequestBus::Handler + { + public: + AZ_COMPONENT_DECL(SimulationEntitiesManager); + + static void Reflect(AZ::ReflectContext* context); + + static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided); + static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible); + static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required); + static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent); + + SimulationEntitiesManager(); + ~SimulationEntitiesManager(); + + protected: + // SimulationEntityManagerRequestBus interface implementation + AZStd::vector GetEntities(const EntityFilters& filter) override; + EntityState GetEntityState(const AZStd::string& name) override; + AZStd::unordered_map GetEntitiesStates(const EntityFilters& filter) override; + bool SetEntityState(const AZStd::string& name, const EntityState& state) override; + bool DeleteEntity(const AZStd::string& name) override; + AZStd::vector GetSpawnables() override; + void SpawnEntity( + const AZStd::string& name, + const AZStd::string& uri, + const AZStd::string& entityNamespace, + const AZ::Transform& initialPose, + SpawnCompletedCb completedCb) override; + + // AZ::Component interface implementation + void Init() override; + void Activate() override; + void Deactivate() override; + + private: + //! Registers simulated entity to entity id mapping. + //! Note that the entityId will be registered under unique name. + //! \param entityId The entity id to register + //! \param proposedName Optional user proposed name for the simulated entity + //! \return returns the simulated entity name + AZStd::string AddSimulatedEntity(AZ::EntityId entityId, const AZStd::string& proposedName); + + //! Removes simulated entity from the mapping. + void RemoveSimulatedEntity(AZ::EntityId entityId); + + //! Returns the simulated entity name for the given entity id. + AZStd::string GetSimulatedEntityName(AZ::EntityId entityId, const AZStd::string& proposedName) const; + + AzPhysics::SceneEvents::OnSimulationBodyAdded::Handler m_simulationBodyAddedHandler; + AzPhysics::SceneEvents::OnSimulationBodyRemoved::Handler m_simulationBodyRemovedHandler; + + AzPhysics::SystemEvents::OnSceneAddedEvent::Handler m_sceneAddedHandler; + AzPhysics::SystemEvents::OnSceneRemovedEvent::Handler m_sceneRemovedHandler; + AzPhysics::SceneHandle m_physicsScenesHandle = AzPhysics::InvalidSceneHandle; + AZStd::unordered_map m_simulatedEntityToEntityIdMap; + AZStd::unordered_map m_entityIdToSimulatedEntityMap; + AZStd::unordered_set m_disabledBodies; + + AZStd::unordered_map m_spawnedTickets; + + struct SpawnCompletedCbData + { + AZStd::string m_userProposedName; //! Name proposed by the User in spawn request + SpawnCompletedCb m_completedCb; //! User callback to be called when the entity is registered + AZ::ScriptTimePoint m_spawnCompletedTime; //! Time at which the entity was spawned + }; + AZStd::unordered_map + m_spawnCompletedCallbacks; //! Callbacks to be called when the entity is registered + }; + +} // namespace SimulationInterfaces diff --git a/Gems/SimulationInterfaces/Code/Source/Clients/SimulationInterfacesModule.cpp b/Gems/SimulationInterfaces/Code/Source/Clients/SimulationInterfacesModule.cpp new file mode 100644 index 0000000000..119dec747c --- /dev/null +++ b/Gems/SimulationInterfaces/Code/Source/Clients/SimulationInterfacesModule.cpp @@ -0,0 +1,27 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include "SimulationEntitiesManager.h" +#include +#include + +namespace SimulationInterfaces +{ + class SimulationInterfacesModule : public SimulationInterfacesModuleInterface + { + public: + AZ_RTTI(SimulationInterfacesModule, SimulationInterfacesModuleTypeId, SimulationInterfacesModuleInterface); + AZ_CLASS_ALLOCATOR(SimulationInterfacesModule, AZ::SystemAllocator); + }; +} // namespace SimulationInterfaces + +#if defined(O3DE_GEM_NAME) +AZ_DECLARE_MODULE_CLASS(AZ_JOIN(Gem_, O3DE_GEM_NAME), SimulationInterfaces::SimulationInterfacesModule) +#else +AZ_DECLARE_MODULE_CLASS(Gem_SimulationInterfaces, SimulationInterfaces::SimulationInterfacesModule) +#endif diff --git a/Gems/SimulationInterfaces/Code/Source/Clients/SimulationManager.cpp b/Gems/SimulationInterfaces/Code/Source/Clients/SimulationManager.cpp new file mode 100644 index 0000000000..67233c9a45 --- /dev/null +++ b/Gems/SimulationInterfaces/Code/Source/Clients/SimulationManager.cpp @@ -0,0 +1,127 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include "SimulationManager.h" + +#include +#include +#include +#include + +namespace SimulationInterfaces +{ + + AZ_COMPONENT_IMPL(SimulationManager, "SimulationManager", SimulationManagerTypeId); + + void SimulationManager::Reflect(AZ::ReflectContext* context) + { + if (auto serializeContext = azrtti_cast(context)) + { + serializeContext->Class()->Version(0); + } + } + + void SimulationManager::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided) + { + provided.push_back(AZ_CRC_CE("SimulationManagerService")); + } + + void SimulationManager::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible) + { + incompatible.push_back(AZ_CRC_CE("SimulationManagerService")); + } + + void SimulationManager::GetRequiredServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& required) + { + required.push_back(AZ_CRC_CE("PhysicsService")); + } + + void SimulationManager::GetDependentServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& dependent) + { + } + + SimulationManager::SimulationManager() + { + if (SimulationManagerRequestBusInterface::Get() == nullptr) + { + SimulationManagerRequestBusInterface::Register(this); + } + } + + SimulationManager::~SimulationManager() + { + if (SimulationManagerRequestBusInterface::Get() == this) + { + SimulationManagerRequestBusInterface::Unregister(this); + } + } + + void SimulationManager::Init() + { + } + + void SimulationManager::Activate() + { + SimulationManagerRequestBus::Handler::BusConnect(); + } + + void SimulationManager::Deactivate() + { + SimulationManagerRequestBus::Handler::BusDisconnect(); + } + + void SimulationManager::SetSimulationPaused(bool paused) + { + // get az physics system + auto* physicsSystem = AZ::Interface::Get(); + AZ_Assert(physicsSystem, "Physics system is not available"); + const auto& sceneHandlers = physicsSystem->GetAllScenes(); + auto* sceneInterface = AZ::Interface::Get(); + AZ_Assert(sceneInterface, "Physics scene interface is not available"); + for (auto& scene : sceneHandlers) + { + AZ_Assert(scene, "Physics scene is not available"); + scene->SetEnabled(!paused); + } + } + + void SimulationManager::StepSimulation(AZ::u32 steps) + { + m_numberOfPhysicsSteps = steps; + + // install handler + m_simulationFinishEvent = AzPhysics::SceneEvents::OnSceneSimulationFinishHandler( + [this](AzPhysics::SceneHandle sceneHandle, float) + { + m_numberOfPhysicsSteps--; + AZ_Printf("SimulationManager", "Physics simulation step finished. Remaining steps: %d", m_numberOfPhysicsSteps); + if (m_numberOfPhysicsSteps <= 0) + { + SetSimulationPaused(true); + // remove handler + m_simulationFinishEvent.Disconnect(); + } + }); + + // get default scene + auto* physicsSystem = AZ::Interface::Get(); + AZ_Assert(physicsSystem, "Physics system is not available"); + auto* sceneInterface = AZ::Interface::Get(); + AZ_Assert(sceneInterface, "Physics scene interface is not available"); + AzPhysics::SceneHandle defaultScene = sceneInterface->GetSceneHandle(AzPhysics::DefaultPhysicsSceneName); + + auto scene = sceneInterface->GetScene(defaultScene); + AZ_Assert(scene, "Default physics scene is not available"); + + // install handler + scene->RegisterSceneSimulationFinishHandler(m_simulationFinishEvent); + SetSimulationPaused(false); + + } + +} // namespace SimulationInterfaces diff --git a/Gems/SimulationInterfaces/Code/Source/Clients/SimulationManager.h b/Gems/SimulationInterfaces/Code/Source/Clients/SimulationManager.h new file mode 100644 index 0000000000..8212e8e128 --- /dev/null +++ b/Gems/SimulationInterfaces/Code/Source/Clients/SimulationManager.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +namespace SimulationInterfaces +{ + class SimulationManager : + public AZ::Component, + protected SimulationManagerRequestBus::Handler + { + public: + AZ_COMPONENT_DECL(SimulationManager); + + static void Reflect(AZ::ReflectContext* context); + + static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided); + static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible); + static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required); + static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent); + + SimulationManager(); + ~SimulationManager(); + + // AZ::Component + void Init() override; + void Activate() override; + void Deactivate() override; + protected: + void SetSimulationPaused(bool paused) override; + void StepSimulation(AZ::u32 steps) override; + uint32_t m_numberOfPhysicsSteps = 0; + AzPhysics::SceneEvents::OnSceneSimulationFinishHandler m_simulationFinishEvent; + + }; +} // namespace SimulationInterfaces \ No newline at end of file diff --git a/Gems/SimulationInterfaces/Code/Source/SimulationInterfacesModuleInterface.cpp b/Gems/SimulationInterfaces/Code/Source/SimulationInterfacesModuleInterface.cpp new file mode 100644 index 0000000000..ec2ff7c74f --- /dev/null +++ b/Gems/SimulationInterfaces/Code/Source/SimulationInterfacesModuleInterface.cpp @@ -0,0 +1,41 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include "SimulationInterfacesModuleInterface.h" +#include + +#include + +#include +#include "Clients/SimulationManager.h" + +namespace SimulationInterfaces +{ + AZ_TYPE_INFO_WITH_NAME_IMPL( + SimulationInterfacesModuleInterface, "SimulationInterfacesModuleInterface", SimulationInterfacesModuleInterfaceTypeId); + AZ_RTTI_NO_TYPE_INFO_IMPL(SimulationInterfacesModuleInterface, AZ::Module); + AZ_CLASS_ALLOCATOR_IMPL(SimulationInterfacesModuleInterface, AZ::SystemAllocator); + + SimulationInterfacesModuleInterface::SimulationInterfacesModuleInterface() + { + m_descriptors.insert( + m_descriptors.end(), + { + SimulationEntitiesManager::CreateDescriptor(), + SimulationManager::CreateDescriptor(), + }); + } + + AZ::ComponentTypeList SimulationInterfacesModuleInterface::GetRequiredSystemComponents() const + { + return AZ::ComponentTypeList{ + azrtti_typeid(), + azrtti_typeid(), + }; + } +} // namespace SimulationInterfaces diff --git a/Gems/SimulationInterfaces/Code/Source/SimulationInterfacesModuleInterface.h b/Gems/SimulationInterfaces/Code/Source/SimulationInterfacesModuleInterface.h new file mode 100644 index 0000000000..6e9825a67e --- /dev/null +++ b/Gems/SimulationInterfaces/Code/Source/SimulationInterfacesModuleInterface.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include +#include + +namespace SimulationInterfaces +{ + class SimulationInterfacesModuleInterface : public AZ::Module + { + public: + AZ_TYPE_INFO_WITH_NAME_DECL(SimulationInterfacesModuleInterface) + AZ_RTTI_NO_TYPE_INFO_DECL() + AZ_CLASS_ALLOCATOR_DECL + + SimulationInterfacesModuleInterface(); + + /** + * Add required SystemComponents to the SystemEntity. + */ + AZ::ComponentTypeList GetRequiredSystemComponents() const override; + }; +} // namespace SimulationInterfaces diff --git a/Gems/SimulationInterfaces/Code/Source/Tools/SimulationEntitiesManagerEditor.cpp b/Gems/SimulationInterfaces/Code/Source/Tools/SimulationEntitiesManagerEditor.cpp new file mode 100644 index 0000000000..c20439b84b --- /dev/null +++ b/Gems/SimulationInterfaces/Code/Source/Tools/SimulationEntitiesManagerEditor.cpp @@ -0,0 +1,70 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include "SimulationEntitiesManagerEditor.h" +#include + +#include + +namespace SimulationInterfaces +{ + AZ_COMPONENT_IMPL( + SimulationEntitiesManagerEditor, + "SimulationEntitiesManagerEditor", + SimulationEntitiesManagerEditorTypeId, + BaseSystemComponent); + + void SimulationEntitiesManagerEditor::Reflect(AZ::ReflectContext* context) + { + if (auto serializeContext = azrtti_cast(context)) + { + serializeContext->Class()->Version(0); + } + } + + SimulationEntitiesManagerEditor::SimulationEntitiesManagerEditor() = default; + + SimulationEntitiesManagerEditor::~SimulationEntitiesManagerEditor() = default; + + void SimulationEntitiesManagerEditor::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided) + { + BaseSystemComponent::GetProvidedServices(provided); + provided.push_back(AZ_CRC_CE("SimulationInterfacesEditorService")); + } + + void SimulationEntitiesManagerEditor::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible) + { + BaseSystemComponent::GetIncompatibleServices(incompatible); + incompatible.push_back(AZ_CRC_CE("SimulationInterfacesEditorService")); + } + + void SimulationEntitiesManagerEditor::GetRequiredServices( + [[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& required) + { + BaseSystemComponent::GetRequiredServices(required); + } + + void SimulationEntitiesManagerEditor::GetDependentServices( + [[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& dependent) + { + BaseSystemComponent::GetDependentServices(dependent); + } + + void SimulationEntitiesManagerEditor::Activate() + { + SimulationEntitiesManager::Activate(); + AzToolsFramework::EditorEvents::Bus::Handler::BusConnect(); + } + + void SimulationEntitiesManagerEditor::Deactivate() + { + AzToolsFramework::EditorEvents::Bus::Handler::BusDisconnect(); + SimulationEntitiesManager::Deactivate(); + } + +} // namespace SimulationInterfaces diff --git a/Gems/SimulationInterfaces/Code/Source/Tools/SimulationEntitiesManagerEditor.h b/Gems/SimulationInterfaces/Code/Source/Tools/SimulationEntitiesManagerEditor.h new file mode 100644 index 0000000000..b04108822e --- /dev/null +++ b/Gems/SimulationInterfaces/Code/Source/Tools/SimulationEntitiesManagerEditor.h @@ -0,0 +1,42 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#pragma once + +#include + +#include + +namespace SimulationInterfaces +{ + /// System component for SimulationInterfaces editor + class SimulationEntitiesManagerEditor + : public SimulationEntitiesManager + , protected AzToolsFramework::EditorEvents::Bus::Handler + { + using BaseSystemComponent = SimulationEntitiesManager; + + public: + AZ_COMPONENT_DECL(SimulationEntitiesManagerEditor); + + static void Reflect(AZ::ReflectContext* context); + + SimulationEntitiesManagerEditor(); + ~SimulationEntitiesManagerEditor(); + + private: + static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided); + static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible); + static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required); + static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent); + + // AZ::Component + void Activate() override; + void Deactivate() override; + }; +} // namespace SimulationInterfaces diff --git a/Gems/SimulationInterfaces/Code/Source/Tools/SimulationInterfacesEditorModule.cpp b/Gems/SimulationInterfaces/Code/Source/Tools/SimulationInterfacesEditorModule.cpp new file mode 100644 index 0000000000..62eb71feb4 --- /dev/null +++ b/Gems/SimulationInterfaces/Code/Source/Tools/SimulationInterfacesEditorModule.cpp @@ -0,0 +1,48 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include "SimulationEntitiesManagerEditor.h" +#include "SimulationManagerEditor.h" +#include +#include +namespace SimulationInterfaces +{ + class SimulationInterfacesEditorModule : public SimulationInterfacesModuleInterface + { + public: + AZ_RTTI(SimulationInterfacesEditorModule, SimulationInterfacesEditorModuleTypeId, SimulationInterfacesModuleInterface); + AZ_CLASS_ALLOCATOR(SimulationInterfacesEditorModule, AZ::SystemAllocator); + + SimulationInterfacesEditorModule() + { + m_descriptors.insert( + m_descriptors.end(), + { + SimulationEntitiesManagerEditor::CreateDescriptor(), + SimulationManagerEditor::CreateDescriptor(), + }); + } + + /** + * Add required SystemComponents to the SystemEntity. + * Non-SystemComponents should not be added here + */ + AZ::ComponentTypeList GetRequiredSystemComponents() const override + { + return AZ::ComponentTypeList{ + azrtti_typeid(), + }; + } + }; +} // namespace SimulationInterfaces + +#if defined(O3DE_GEM_NAME) +AZ_DECLARE_MODULE_CLASS(AZ_JOIN(Gem_, O3DE_GEM_NAME, _Editor), SimulationInterfaces::SimulationInterfacesEditorModule) +#else +AZ_DECLARE_MODULE_CLASS(Gem_SimulationInterfaces_Editor, SimulationInterfaces::SimulationInterfacesEditorModule) +#endif diff --git a/Gems/SimulationInterfaces/Code/Source/Tools/SimulationManagerEditor.cpp b/Gems/SimulationInterfaces/Code/Source/Tools/SimulationManagerEditor.cpp new file mode 100644 index 0000000000..d0366d858d --- /dev/null +++ b/Gems/SimulationInterfaces/Code/Source/Tools/SimulationManagerEditor.cpp @@ -0,0 +1,68 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include "SimulationManagerEditor.h" +#include + +#include + +namespace SimulationInterfaces +{ + AZ_COMPONENT_IMPL(SimulationManagerEditor, "SimulationMangerEditor", SimulationManagerEditorTypeId, BaseSystemComponent); + + void SimulationManagerEditor::Reflect(AZ::ReflectContext* context) + { + if (auto serializeContext = azrtti_cast(context)) + { + serializeContext->Class()->Version(0); + } + } + + SimulationManagerEditor::SimulationManagerEditor() = default; + + SimulationManagerEditor::~SimulationManagerEditor() = default; + + void SimulationManagerEditor::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided) + { + BaseSystemComponent::GetProvidedServices(provided); + provided.push_back(AZ_CRC_CE("SimulationManagerEditorService")); + } + + void SimulationManagerEditor::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible) + { + BaseSystemComponent::GetIncompatibleServices(incompatible); + incompatible.push_back(AZ_CRC_CE("SimulationManagerEditorService")); + } + + void SimulationManagerEditor::GetRequiredServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& required) + { + BaseSystemComponent::GetRequiredServices(required); + } + + void SimulationManagerEditor::GetDependentServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& dependent) + { + BaseSystemComponent::GetDependentServices(dependent); + } + void SimulationManagerEditor::Init() + { + BaseSystemComponent::Init(); + } + + void SimulationManagerEditor::Activate() + { + BaseSystemComponent::Activate(); + AzToolsFramework::EditorEvents::Bus::Handler::BusConnect(); + } + + void SimulationManagerEditor::Deactivate() + { + AzToolsFramework::EditorEvents::Bus::Handler::BusDisconnect(); + BaseSystemComponent::Deactivate(); + } + +} // namespace SimulationInterfaces diff --git a/Gems/SimulationInterfaces/Code/Source/Tools/SimulationManagerEditor.h b/Gems/SimulationInterfaces/Code/Source/Tools/SimulationManagerEditor.h new file mode 100644 index 0000000000..cfec99885a --- /dev/null +++ b/Gems/SimulationInterfaces/Code/Source/Tools/SimulationManagerEditor.h @@ -0,0 +1,43 @@ +/* +* Copyright (c) Contributors to the Open 3D Engine Project. +* For complete copyright and license terms please see the LICENSE at the root of this distribution. +* +* SPDX-License-Identifier: Apache-2.0 OR MIT +* +*/ + +#pragma once + +#include + +#include + +namespace SimulationInterfaces +{ + /// System component for SimulationInterfaces editor + class SimulationManagerEditor + : public SimulationManager + , protected AzToolsFramework::EditorEvents::Bus::Handler + { + using BaseSystemComponent = SimulationManager; + + public: + AZ_COMPONENT_DECL(SimulationManagerEditor); + + static void Reflect(AZ::ReflectContext* context); + + SimulationManagerEditor(); + ~SimulationManagerEditor(); + + private: + static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided); + static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible); + static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required); + static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent); + + // AZ::Component + void Init() override; + void Activate() override; + void Deactivate() override; + }; +} // namespace SimulationInterfaces diff --git a/Gems/SimulationInterfaces/Code/Tests/Clients/SimulationInterfacesTest.cpp b/Gems/SimulationInterfaces/Code/Tests/Clients/SimulationInterfacesTest.cpp new file mode 100644 index 0000000000..40217ff9bc --- /dev/null +++ b/Gems/SimulationInterfaces/Code/Tests/Clients/SimulationInterfacesTest.cpp @@ -0,0 +1,11 @@ +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include + +AZ_UNIT_TEST_HOOK(DEFAULT_UNIT_TEST_ENV); diff --git a/Gems/SimulationInterfaces/Code/Tests/Tools/SimulationInterfaceTests.cpp b/Gems/SimulationInterfaces/Code/Tests/Tools/SimulationInterfaceTests.cpp new file mode 100644 index 0000000000..17cb23c95c --- /dev/null +++ b/Gems/SimulationInterfaces/Code/Tests/Tools/SimulationInterfaceTests.cpp @@ -0,0 +1,170 @@ + +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include "TestFixture.h" +#include +namespace UnitTest +{ + + TEST_F(SimulationInterfaceTestFixture, EmptyScene) + { + using namespace SimulationInterfaces; + AZStd::vector entities; + SimulationEntityManagerRequestBus::BroadcastResult(entities, &SimulationEntityManagerRequestBus::Events::GetEntities, EntityFilters()); + EXPECT_EQ(entities.size(), 0); + } + + TEST_F(SimulationInterfaceTestFixture, AddSimulatedEntityThenRemove) + { + using namespace SimulationInterfaces; + const AZ::EntityId entityId1 = CreateEntityWithStaticBodyComponent("Foo", AZ::Transform::CreateIdentity()); + const AZ::EntityId entityId2 = CreateEntityWithStaticBodyComponent("Bar", AZ::Transform::CreateIdentity()); + + AZStd::vector entities; + SimulationEntityManagerRequestBus::BroadcastResult(entities, &SimulationEntityManagerRequestBus::Events::GetEntities, EntityFilters()); + ASSERT_EQ(entities.size(), 2); + DeleteEntity(entityId1); + + AZStd::vector entities2; + SimulationEntityManagerRequestBus::BroadcastResult(entities2, &SimulationEntityManagerRequestBus::Events::GetEntities, EntityFilters()); + EXPECT_EQ(entities2.size(), 1); + + DeleteEntity(entityId2); + AZStd::vector entities3; + SimulationEntityManagerRequestBus::BroadcastResult(entities3, &SimulationEntityManagerRequestBus::Events::GetEntities, EntityFilters()); + EXPECT_EQ(entities3.size(), 0); + } + + TEST_F(SimulationInterfaceTestFixture, AddEntitiesWithDupName) + { + using namespace SimulationInterfaces; + + const AZ::EntityId entityId1 = CreateEntityWithStaticBodyComponent("Bar1", AZ::Transform::CreateIdentity()); + const AZ::EntityId entityId2 = CreateEntityWithStaticBodyComponent("Bar1", AZ::Transform::CreateIdentity()); + AZStd::vector entities; + + SimulationEntityManagerRequestBus::BroadcastResult(entities, &SimulationEntityManagerRequestBus::Events::GetEntities, EntityFilters()); + EXPECT_EQ(entities.size(), 2); + EXPECT_NE(entities[0], entities[1]); + DeleteEntity(entityId1); + DeleteEntity(entityId2); + } + + TEST_F(SimulationInterfaceTestFixture, TestShapeFilter) + { + // This test is disabled since due to some issue outside to this gem, the rigid body is created without the collider shape + // and the filter is not applied. This test will be enabled once the issue is resolved. + GTEST_SKIP()<<"Need to fix the issue with the collider shape creation."; + using namespace SimulationInterfaces; + const AZ::EntityId entityId1 = + CreateEntityWithStaticBodyComponent("Inside", AZ::Transform::CreateTranslation(AZ::Vector3(0.0f, 0.0f, 0.0f))); + const AZ::EntityId entityId2 = + CreateEntityWithStaticBodyComponent("Outside", AZ::Transform::CreateTranslation(AZ::Vector3(10.0f, 0.0f, 0.0f))); + + EntityFilters filter; + filter.m_bounds_shape = AZStd::make_shared(2.0f); + + AZStd::vector entities; + SimulationEntityManagerRequestBus::BroadcastResult(entities, &SimulationEntityManagerRequestBus::Events::GetEntities, filter); + auto* physicsSystem = AZ::Interface::Get(); + physicsSystem->Simulate(1.0f / 60.0f); + + ASSERT_EQ(entities.size(), 1); + EXPECT_EQ(entities.front(), "Inside"); + + DeleteEntity(entityId1); + DeleteEntity(entityId2); + } + + TEST_F(SimulationInterfaceTestFixture, TestRegexFilter) + { + using namespace SimulationInterfaces; + const AZ::EntityId entityId1 = + CreateEntityWithStaticBodyComponent("WillMatch", AZ::Transform::CreateTranslation(AZ::Vector3(0.0f, 0.0f, 0.0f))); + const AZ::EntityId entityId2 = + CreateEntityWithStaticBodyComponent("WontMatch", AZ::Transform::CreateTranslation(AZ::Vector3(10.0f, 0.0f, 0.0f))); + + EntityFilters filter; + filter.m_filter = "Will.*"; + + AZStd::vector entities; + SimulationEntityManagerRequestBus::BroadcastResult(entities, &SimulationEntityManagerRequestBus::Events::GetEntities, filter); + + ASSERT_EQ(entities.size(), 1); + EXPECT_EQ(entities.front(), "WillMatch"); + + DeleteEntity(entityId1); + DeleteEntity(entityId2); + } + + TEST_F(SimulationInterfaceTestFixture, TestRegexFilterInvalid) + { + // Invalid regex should not match any entity + using namespace SimulationInterfaces; + const AZ::EntityId entityId1 = + CreateEntityWithStaticBodyComponent("WillMatch", AZ::Transform::CreateTranslation(AZ::Vector3(0.0f, 0.0f, 0.0f))); + const AZ::EntityId entityId2 = + CreateEntityWithStaticBodyComponent("WontMatch", AZ::Transform::CreateTranslation(AZ::Vector3(10.0f, 0.0f, 0.0f))); + + EntityFilters filter; + filter.m_filter = "[a-z"; + + AZStd::vector entities; + SimulationEntityManagerRequestBus::BroadcastResult(entities, &SimulationEntityManagerRequestBus::Events::GetEntities, filter); + + EXPECT_EQ(entities.size(), 0); + DeleteEntity(entityId1); + DeleteEntity(entityId2); + } + + TEST_F(SimulationInterfaceTestFixture, SmokeTestGetEntityState) + { + // Invalid regex should not match any entity + using namespace SimulationInterfaces; + const AZStd::string entityName = "DroppedBall"; + const AZ::EntityId entityId1 = + CreateEntityWithStaticBodyComponent(entityName, AZ::Transform::CreateTranslation(AZ::Vector3(2.0f, 0.0f, 10.0f))); + + EntityFilters filter; + EntityState stateBefore; + SimulationEntityManagerRequestBus::BroadcastResult(stateBefore, &SimulationEntityManagerRequestBus::Events::GetEntityState, entityName); + EXPECT_EQ(stateBefore.m_pose.GetTranslation(), AZ::Vector3(2.0f, 0.0f, 10.0f)); + for (int i = 0; i < 10; i++) + { + auto* physicsSystem = AZ::Interface::Get(); + physicsSystem->Simulate(1.0f / 60.0f); + } + EntityState stateAfter; + SimulationEntityManagerRequestBus::BroadcastResult(stateAfter, &SimulationEntityManagerRequestBus::Events::GetEntityState, entityName); + AZ::Vector3 deltaPos = stateAfter.m_pose.GetTranslation() - stateBefore.m_pose.GetTranslation(); + + // check if the entity moved + EXPECT_GT(deltaPos.GetLength(), 0.0f); + + // check if entity has velocity + EXPECT_GT(stateAfter.m_twist_linear.GetLength(), 0.0f); + + DeleteEntity(entityId1); + } + +} // namespace UnitTest + +// required to support running integration tests with Qt and PhysX +AZTEST_EXPORT int AZ_UNIT_TEST_HOOK_NAME(int argc, char** argv) +{ + ::testing::InitGoogleMock(&argc, argv); + AzQtComponents::PrepareQtPaths(); + QApplication app(argc, argv); + AZ::Test::printUnusedParametersWarning(argc, argv); + AZ::Test::addTestEnvironments({ new UnitTest::SimulationInterfaceTestEnvironment() }); + int result = RUN_ALL_TESTS(); + return result; +} + +IMPLEMENT_TEST_EXECUTABLE_MAIN(); diff --git a/Gems/SimulationInterfaces/Code/Tests/Tools/SimulationIterfaceAppTest.cpp b/Gems/SimulationInterfaces/Code/Tests/Tools/SimulationIterfaceAppTest.cpp new file mode 100644 index 0000000000..d389b3ba6f --- /dev/null +++ b/Gems/SimulationInterfaces/Code/Tests/Tools/SimulationIterfaceAppTest.cpp @@ -0,0 +1,139 @@ + +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include "TestFixture.h" +#include + +namespace UnitTest +{ + class SimulationInterfaceTestEnvironmentWithAssets : public SimulationInterfaceTestEnvironment + { + protected: + void PostSystemEntityActivate(); + }; + + void SimulationInterfaceTestEnvironmentWithAssets::PostSystemEntityActivate() + { + // Prepare the asset catalog and ensure that our test asset (testsimulationentity.spawnable) is loaded and + // ready to be used in test scenarios. + AZ::UserSettingsComponentRequestBus::Broadcast(&AZ::UserSettingsComponentRequests::DisableSaveOnFinalize); + + AZ::ComponentApplication* app = nullptr; + AZ::ComponentApplicationBus::BroadcastResult(app, &AZ::ComponentApplicationBus::Events::GetApplication); + AZ_Assert(app, "Failed to get application"); + auto products = AZ::Utils::GetProjectProductPathForPlatform().c_str(); + AZ::IO::Path assetCatalogPath = AZ::IO::Path(products) / "assetcatalog.xml"; + bool catalogExists = AZ::IO::FileIOBase::GetInstance()->Exists(assetCatalogPath.c_str()); + AZ_Assert(catalogExists, "Asset Catalog in %s does not exist", assetCatalogPath.c_str()); + + AZ::Data::AssetCatalogRequestBus::Broadcast(&AZ::Data::AssetCatalogRequestBus::Events::LoadCatalog, assetCatalogPath.c_str()); + + const AZ::IO::Path TestSpawnable = "sampleasset/testsimulationentity.spawnable"; + const AZ::IO::Path TestSpawnableGlobalPath = AZ::IO::Path(products) / TestSpawnable; + bool spawnableExists = AZ::IO::FileIOBase::GetInstance()->Exists(assetCatalogPath.c_str()); + AZ_Assert(spawnableExists, "%s does not exist", TestSpawnableGlobalPath.c_str()); + + AZ::Data::AssetId assetId; + AZ::Data::AssetCatalogRequestBus::BroadcastResult( + assetId, + &AZ::Data::AssetCatalogRequestBus::Events::GetAssetIdByPath, + TestSpawnable.c_str(), + AZ::Data::s_invalidAssetType, + false); + AZ_Assert(assetId.IsValid(), "Failed to get asset id for %s", TestSpawnable.c_str()); + } + + + TEST_F(SimulationInterfaceTestFixture, SpawnAppTest) + { + // This is an integration test that runs the test application with the SimulationInterfaces gem enabled. + // It has prepared asset catalog, and we are able to spawn entities with the test asset. + + using namespace SimulationInterfaces; + constexpr AZStd::string_view entityName = "MySuperDuperEntity"; + const AZ::Transform initialPose = AZ::Transform::CreateTranslation(AZ::Vector3(0.0f, 0.0f, 0.0f)); + constexpr AZStd::string_view uri = "product_asset:///sampleasset/testsimulationentity.spawnable"; + constexpr AZStd::string_view entityNamespace = ""; + AZStd::atomic_bool completed = false; + SimulationEntityManagerRequests::SpawnCompletedCb completedCb = [&](const AZ::Outcome& result) + { + EXPECT_TRUE(result.IsSuccess()); + completed = true; + }; + + SimulationEntityManagerRequestBus::Broadcast( + &SimulationEntityManagerRequestBus::Events::SpawnEntity, entityName, uri, entityNamespace, initialPose, completedCb); + + // entities are spawned asynchronously, so we need to tick the app to let the entity be spawned + TickApp(100); + EXPECT_TRUE(completed); + + // list simulation entities + AZStd::vector entities; + SimulationEntityManagerRequestBus::BroadcastResult(entities, &SimulationEntityManagerRequestBus::Events::GetEntities, EntityFilters()); + EXPECT_EQ(entities.size(), 1); + + ASSERT_FALSE(entities.empty())<< "Simulated Entities Empty"; + const AZStd::string spawnedEntityName = entities.front(); + printf("Spawned entity name %s\n", spawnedEntityName.c_str()); + + // run physics simulation + StepPhysics(100); + + // Get entity state, + AZStd::unordered_map entityStates; + SimulationEntityManagerRequestBus::BroadcastResult( + entityStates, &SimulationEntityManagerRequestBus::Events::GetEntitiesStates, EntityFilters()); + auto entityState = entityStates.find(spawnedEntityName); + ASSERT_NE(entityState, entityStates.end()); + EXPECT_EQ(entityState->first, spawnedEntityName); + + // check if the entity moved + EXPECT_GE(entityState->second.m_pose.GetTranslation().GetDistance(initialPose.GetTranslation()), 1.0f); + + // set new entity state - move the entity to X=1000 meters + const AZ::Vector3 newPosition = AZ::Vector3(1000.0f, 0.0f, 0.0f); + const EntityState newState = { AZ::Transform::CreateTranslation(newPosition), AZ::Vector3::CreateZero(), AZ::Vector3::CreateZero() }; + SimulationEntityManagerRequestBus::Broadcast(&SimulationEntityManagerRequestBus::Events::SetEntityState, spawnedEntityName, newState); + + StepPhysics(); + + // Check if entity was teleported by setting the new state, we use a filter to check if the entity is at the new position + EntityFilters filter; + filter.m_bounds_shape = AZStd::make_shared(2.0f); + filter.m_bounds_pose = AZ::Transform::CreateTranslation(AZ::Vector3(1000.0f, 0.0f, 0.0f)); + AZStd::vector entitiesFiltered; + SimulationEntityManagerRequestBus::BroadcastResult(entitiesFiltered, &SimulationEntityManagerRequestBus::Events::GetEntities, filter); + EXPECT_EQ(entitiesFiltered.size(), 1); + + // delete entity using its name + SimulationEntityManagerRequestBus::Broadcast(&SimulationEntityManagerRequestBus::Events::DeleteEntity, entityName); + TickApp(100); + + // list simulation entities after deletion, expect no simulation entities + AZStd::vector entities2; + SimulationEntityManagerRequestBus::BroadcastResult(entities2, &SimulationEntityManagerRequestBus::Events::GetEntities, EntityFilters()); + EXPECT_EQ(entities2.size(), 0); + } + +} // namespace UnitTest + +// required to support running integration tests with Qt and PhysX +AZTEST_EXPORT int AZ_UNIT_TEST_HOOK_NAME(int argc, char** argv) +{ + ::testing::InitGoogleMock(&argc, argv); + AzQtComponents::PrepareQtPaths(); + QApplication app(argc, argv); + AZ::Test::printUnusedParametersWarning(argc, argv); + AZ::Test::addTestEnvironments({ new UnitTest::SimulationInterfaceTestEnvironmentWithAssets() }); + int result = RUN_ALL_TESTS(); + return result; +} + +IMPLEMENT_TEST_EXECUTABLE_MAIN(); diff --git a/Gems/SimulationInterfaces/Code/Tests/Tools/TestFixture.cpp b/Gems/SimulationInterfaces/Code/Tests/Tools/TestFixture.cpp new file mode 100644 index 0000000000..9eec9d8e55 --- /dev/null +++ b/Gems/SimulationInterfaces/Code/Tests/Tools/TestFixture.cpp @@ -0,0 +1,151 @@ + +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include "TestFixture.h" +#include "Clients/SimulationEntitiesManager.h" +#include +namespace UnitTest +{ + void SimulationInterfaceTestEnvironment::AddGemsAndComponents() + { + constexpr AZStd::array requiredGems = { "PhysX5", // required for PhysX Dynamic + "LmbrCentral", // for shapes + "SimulationInterfaces" }; + AddActiveGems(requiredGems); + AddDynamicModulePaths({ "PhysX5.Gem" }); + AddDynamicModulePaths({ "LmbrCentral" }); + AddComponentDescriptors({ + SimulationInterfaces::SimulationEntitiesManager::CreateDescriptor(), + }); + AddRequiredComponents({ SimulationInterfaces::SimulationEntitiesManager::TYPEINFO_Uuid() }); + } + + void SimulationInterfaceTestEnvironment::PostSystemEntityActivate() + { + AZ::UserSettingsComponentRequestBus::Broadcast(&AZ::UserSettingsComponentRequests::DisableSaveOnFinalize); + + // load asset catalog + } + + AZ::ComponentApplication* SimulationInterfaceTestEnvironment::CreateApplicationInstance() + { + // Using ToolsTestApplication to have AzFramework and AzToolsFramework components. + return aznew UnitTest::ToolsTestApplication("SimulationInterfaceTestEnvironment"); + } + + void SimulationInterfaceTestFixture::AddAsset(const AZStd::string& assetPath) + { + AZ::Data::AssetInfo info; + info.m_relativePath = assetPath; + info.m_sizeBytes = 1; + AZ::Data::AssetId id = AZ::Data::AssetId(AZ::Uuid::CreateRandom()); + AZ::Data::AssetCatalogRequestBus::Broadcast(&AZ::Data::AssetCatalogRequestBus::Events::RegisterAsset, id, info); + m_registeredAssets.insert(id); + } + + AzPhysics::SceneHandle SimulationInterfaceTestFixture::GetDefaultSceneHandle() const + { + return m_testSceneHandle; + } + + AZ::EntityId SimulationInterfaceTestFixture::CreateEntityWithStaticBodyComponent( + const AZStd::string& entityName, const AZ::Transform& transform) + { + AZStd::unique_ptr entity = AZStd::make_unique(entityName.c_str()); + auto* transformComponent = entity->CreateComponent(AZ::TransformComponentTypeId); + AZ_Assert(transformComponent, "Failed to create TransformComponent"); + auto* transformInterface = azrtti_cast(transformComponent); + AZ_Assert(transformInterface, "Failed to get TransformInterface"); + transformInterface->SetWorldTM(transform); + entity->CreateComponent(AZ::Uuid(PhysXRigidBodyComponentTypeId)); + entity->CreateComponent(AZ::Uuid(PhysXShapeColliderComponentTypeId)); + entity->CreateComponent(AZ::Uuid(SphereShapeComponentTypeId)); + entity->Init(); + entity->Activate(); + AZ_Assert(entity->GetState() == AZ::Entity::State::Active, "Entity is not active"); + + auto id = entity->GetId(); + m_entities.emplace(AZStd::make_pair(id, AZStd::move(entity))); + + return id; + } + + void SimulationInterfaceTestFixture::ClearEntities() + { + for (auto& entity : m_entities) + { + entity.second->Deactivate(); + } + m_entities.clear(); + } + void SimulationInterfaceTestFixture::DeleteEntity(const AZ::EntityId& entityId) + { + auto findIt = m_entities.find(entityId); + if (findIt != m_entities.end()) + { + findIt->second->Deactivate(); + m_entities.erase(findIt); + } + } + + void SimulationInterfaceTestFixture::SetUp() + { + if (auto* physicsSystem = AZ::Interface::Get()) + { + AzPhysics::SceneConfiguration sceneConfiguration = physicsSystem->GetDefaultSceneConfiguration(); + sceneConfiguration.m_sceneName = AzPhysics::DefaultPhysicsSceneName; + m_testSceneHandle = physicsSystem->AddScene(sceneConfiguration); + m_defaultScene = physicsSystem->GetScene(m_testSceneHandle); + } + + Physics::DefaultWorldBus::Handler::BusConnect(); + } + + void SimulationInterfaceTestFixture::TearDown() + { + ClearEntities(); + Physics::DefaultWorldBus::Handler::BusDisconnect(); + m_defaultScene = nullptr; + + // Clean up the Test scene + if (auto* physicsSystem = AZ::Interface::Get()) + { + physicsSystem->RemoveScene(m_testSceneHandle); + } + m_testSceneHandle = AzPhysics::InvalidSceneHandle; + + for (const auto& id : m_registeredAssets) + { + AZ::Data::AssetCatalogRequestBus::Broadcast(&AZ::Data::AssetCatalogRequestBus::Events::UnregisterAsset, id); + } + m_registeredAssets.clear(); + } + + void SimulationInterfaceTestFixture::StepPhysics(int numSteps) + { + for (int i = 0; i < numSteps; i++) + { + auto* physicsSystem = AZ::Interface::Get(); + physicsSystem->Simulate(1.0f / 60.0f); + } + + } + + void SimulationInterfaceTestFixture::TickApp(int numTicks) + { + AZ::ComponentApplication* app = nullptr; + AZ::ComponentApplicationBus::BroadcastResult(app, &AZ::ComponentApplicationBus::Events::GetApplication); + AZ_Assert(app, "Failed to get application"); + for (int i = 0; i < numTicks; i++) + { + app->Tick(); + } + } + +} // namespace UnitTest \ No newline at end of file diff --git a/Gems/SimulationInterfaces/Code/Tests/Tools/TestFixture.h b/Gems/SimulationInterfaces/Code/Tests/Tools/TestFixture.h new file mode 100644 index 0000000000..5e2f1d4e8d --- /dev/null +++ b/Gems/SimulationInterfaces/Code/Tests/Tools/TestFixture.h @@ -0,0 +1,94 @@ + +/* + * Copyright (c) Contributors to the Open 3D Engine Project. + * For complete copyright and license terms please see the LICENSE at the root of this distribution. + * + * SPDX-License-Identifier: Apache-2.0 OR MIT + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace UnitTest +{ + class SimulationInterfaceTestEnvironment : public AZ::Test::GemTestEnvironment + { + // AZ::Test::GemTestEnvironment overrides ... + void AddGemsAndComponents() override; + AZ::ComponentApplication* CreateApplicationInstance() override; + + protected: + void PostSystemEntityActivate() override; + + public: + SimulationInterfaceTestEnvironment() = default; + ~SimulationInterfaceTestEnvironment() override = default; + }; + + class SimulationInterfaceTestFixture + : public ::testing::Test + , protected Physics::DefaultWorldBus::Handler + { + protected: + constexpr static auto PhysXRigidBodyComponentTypeId = "{D4E52A70-BDE1-4819-BD3C-93AB3F4F3BE3}"; // From PhysX + constexpr static auto PhysXStaticBodyComponentTypeId = "{A2CCCD3D-FB31-4D65-8DCD-2CD7E1D09538}"; // From PhysX + constexpr static auto PhysXShapeColliderComponentTypeId = "{30CC9E77-378C-49DF-9617-6BF191901FE0}"; // From PhysX + constexpr static auto PhysXSphereColliderComponentTypeId = "{108CD341-E5C3-4AE1-B712-21E81ED6C277}"; // From PhysX + constexpr static auto SphereShapeComponentTypeId = "{E24CBFF0-2531-4F8D-A8AB-47AF4D54BCD2}"; // From LmbrCentral + + void SetUp() override; + void TearDown() override; + + AZ::EntityId CreateEntityWithStaticBodyComponent(const AZStd::string& entityName, const AZ::Transform& transform); + + void DeleteEntity(const AZ::EntityId& entityId); + void ClearEntities(); + + AZStd::unordered_map> m_entities; + + void AddAsset(const AZStd::string& assetPath); + + //! Ask the physics system to step forward in time + void StepPhysics(int numSteps = 1); + + //! Ask the application to tick forward in time + void TickApp(int numTicks = 1); + + private: + AzPhysics::SceneHandle GetDefaultSceneHandle() const override; + AzPhysics::Scene* m_defaultScene = nullptr; + AzPhysics::SceneHandle m_testSceneHandle = AzPhysics::InvalidSceneHandle; + AZStd::unordered_set m_registeredAssets; + }; +} // namespace UnitTest \ No newline at end of file diff --git a/Gems/SimulationInterfaces/Code/simulationinterfaces_api_files.cmake b/Gems/SimulationInterfaces/Code/simulationinterfaces_api_files.cmake new file mode 100644 index 0000000000..329f4a5b87 --- /dev/null +++ b/Gems/SimulationInterfaces/Code/simulationinterfaces_api_files.cmake @@ -0,0 +1,11 @@ +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# + +set(FILES + Include/SimulationInterfaces/SimulationEntityManagerRequestBus.h + Include/SimulationInterfaces/SimulationMangerRequestBus.h + Include/SimulationInterfaces/SimulationInterfacesTypeIds.h +) diff --git a/Gems/SimulationInterfaces/Code/simulationinterfaces_editor_api_files.cmake b/Gems/SimulationInterfaces/Code/simulationinterfaces_editor_api_files.cmake new file mode 100644 index 0000000000..263b8b77c4 --- /dev/null +++ b/Gems/SimulationInterfaces/Code/simulationinterfaces_editor_api_files.cmake @@ -0,0 +1,9 @@ +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# + + +set(FILES +) diff --git a/Gems/SimulationInterfaces/Code/simulationinterfaces_editor_app_test.cmake b/Gems/SimulationInterfaces/Code/simulationinterfaces_editor_app_test.cmake new file mode 100644 index 0000000000..a2152427e2 --- /dev/null +++ b/Gems/SimulationInterfaces/Code/simulationinterfaces_editor_app_test.cmake @@ -0,0 +1,11 @@ +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# + +set(FILES + Tests/Tools/TestFixture.cpp + Tests/Tools/TestFixture.h + Tests/Tools/SimulationIterfaceAppTest.cpp +) diff --git a/Gems/SimulationInterfaces/Code/simulationinterfaces_editor_private_files.cmake b/Gems/SimulationInterfaces/Code/simulationinterfaces_editor_private_files.cmake new file mode 100644 index 0000000000..2b0af4483c --- /dev/null +++ b/Gems/SimulationInterfaces/Code/simulationinterfaces_editor_private_files.cmake @@ -0,0 +1,12 @@ +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# + +set(FILES + Source/Tools/SimulationManagerEditor.cpp + Source/Tools/SimulationManagerEditor.h + Source/Tools/SimulationEntitiesManagerEditor.cpp + Source/Tools/SimulationEntitiesManagerEditor.h +) diff --git a/Gems/SimulationInterfaces/Code/simulationinterfaces_editor_shared_files.cmake b/Gems/SimulationInterfaces/Code/simulationinterfaces_editor_shared_files.cmake new file mode 100644 index 0000000000..817cc1b292 --- /dev/null +++ b/Gems/SimulationInterfaces/Code/simulationinterfaces_editor_shared_files.cmake @@ -0,0 +1,9 @@ +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# + +set(FILES + Source/Tools/SimulationInterfacesEditorModule.cpp +) diff --git a/Gems/SimulationInterfaces/Code/simulationinterfaces_editor_tests_files.cmake b/Gems/SimulationInterfaces/Code/simulationinterfaces_editor_tests_files.cmake new file mode 100644 index 0000000000..c5ed58dbdc --- /dev/null +++ b/Gems/SimulationInterfaces/Code/simulationinterfaces_editor_tests_files.cmake @@ -0,0 +1,11 @@ +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# + +set(FILES + Tests/Tools/TestFixture.cpp + Tests/Tools/TestFixture.h + Tests/Tools/SimulationInterfaceTests.cpp +) diff --git a/Gems/SimulationInterfaces/Code/simulationinterfaces_private_files.cmake b/Gems/SimulationInterfaces/Code/simulationinterfaces_private_files.cmake new file mode 100644 index 0000000000..8947b47d86 --- /dev/null +++ b/Gems/SimulationInterfaces/Code/simulationinterfaces_private_files.cmake @@ -0,0 +1,16 @@ +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# + +set(FILES + Source/SimulationInterfacesModuleInterface.cpp + Source/SimulationInterfacesModuleInterface.h + Source/Clients/SimulationManager.cpp + Source/Clients/SimulationManager.h + Source/Clients/SimulationEntitiesManager.cpp + Source/Clients/SimulationEntitiesManager.h + Source/Clients/CommonUtilities.cpp + Source/Clients/CommonUtilities.h +) diff --git a/Gems/SimulationInterfaces/Code/simulationinterfaces_shared_files.cmake b/Gems/SimulationInterfaces/Code/simulationinterfaces_shared_files.cmake new file mode 100644 index 0000000000..a5659e6feb --- /dev/null +++ b/Gems/SimulationInterfaces/Code/simulationinterfaces_shared_files.cmake @@ -0,0 +1,9 @@ +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# + +set(FILES + Source/Clients/SimulationInterfacesModule.cpp +) diff --git a/Gems/SimulationInterfaces/Code/simulationinterfaces_tests_files.cmake b/Gems/SimulationInterfaces/Code/simulationinterfaces_tests_files.cmake new file mode 100644 index 0000000000..e12a6decea --- /dev/null +++ b/Gems/SimulationInterfaces/Code/simulationinterfaces_tests_files.cmake @@ -0,0 +1,9 @@ +# Copyright (c) Contributors to the Open 3D Engine Project. +# For complete copyright and license terms please see the LICENSE at the root of this distribution. +# +# SPDX-License-Identifier: Apache-2.0 OR MIT +# + +set(FILES + Tests/Clients/SimulationInterfacesTest.cpp +) diff --git a/Gems/SimulationInterfaces/Registry/assetprocessor_settings.setreg b/Gems/SimulationInterfaces/Registry/assetprocessor_settings.setreg new file mode 100644 index 0000000000..d7893eafaa --- /dev/null +++ b/Gems/SimulationInterfaces/Registry/assetprocessor_settings.setreg @@ -0,0 +1,18 @@ +{ + "Amazon": { + "AssetProcessor": { + "Settings": { + "ScanFolder SimulationInterfaces/Assets": { + "watch": "@GEMROOT:SimulationInterfaces@/Assets", + "recursive": 1, + "order": 101 + }, + "ScanFolder SimulationInterfaces/Registry": { + "watch": "@GEMROOT:SimulationInterfaces@/Registry", + "recursive": 1, + "order": 102 + } + } + } + } +} diff --git a/Gems/SimulationInterfaces/gem.json b/Gems/SimulationInterfaces/gem.json new file mode 100644 index 0000000000..6f1244c140 --- /dev/null +++ b/Gems/SimulationInterfaces/gem.json @@ -0,0 +1,28 @@ +{ + "gem_name": "SimulationInterfaces", + "version": "1.0.0", + "display_name": "SimulationInterfaces", + "license": "Apache-2.0 ", + "license_url": "https://opensource.org/licenses/Apache-2.0", + "origin": "RobotecAI", + "origin_url": "https://robotec.ai", + "type": "Code", + "summary": "This gem provides C++ API for simulation interfaces.", + "canonical_tags": [ + "Gem" + ], + "user_tags": [ + "SimulationInterfaces", "ROS2", "ROS 2" + ], + "platforms": [ + "" + ], + "icon_path": "preview.png", + "requirements": "", + "documentation_url": "", + "dependencies": [], + "repo_uri": "", + "compatible_engines": [], + "engine_api_dependencies": [], + "restricted": "SimulationInterfaces" +} diff --git a/Gems/SimulationInterfaces/preview.png b/Gems/SimulationInterfaces/preview.png new file mode 100644 index 0000000000..a33f8d2cf9 --- /dev/null +++ b/Gems/SimulationInterfaces/preview.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c2484ef49dcaa7474517f608e551ff2619ed5b1206febdd9c1d885b90b41c502 +size 4987