Parcourir la source

Merge pull request #70 from RobotecAI/merge_ros2_gem

Merge ROS2 Gem
Steve Pham il y a 2 ans
Parent
commit
c5e9d5ad6a
100 fichiers modifiés avec 6148 ajouts et 0 suppressions
  1. 1 0
      .github/CODEOWNERS
  2. 61 0
      Gems/ROS2/.clang-format
  3. 35 0
      Gems/ROS2/.gitignore
  4. 10 0
      Gems/ROS2/.licenserc.yaml
  5. 181 0
      Gems/ROS2/Assets/Editor/Images/Icons/ROS_import_icon.svg
  6. 5 0
      Gems/ROS2/Assets/Editor/Images/Icons/Resources.qrc
  7. 17 0
      Gems/ROS2/Assets/Materials/wheel_material.physxmaterial
  8. 3 0
      Gems/ROS2/Assets/Models/Sensors/Camera/AO.png
  9. 3 0
      Gems/ROS2/Assets/Models/Sensors/Camera/Body_Diffuse.png
  10. 3 0
      Gems/ROS2/Assets/Models/Sensors/Camera/CameraOrbbeck.fbx
  11. 47 0
      Gems/ROS2/Assets/Models/Sensors/Camera/CameraOrbbeck.fbx.assetinfo
  12. 3 0
      Gems/ROS2/Assets/Models/Sensors/IMU/AO.png
  13. 3 0
      Gems/ROS2/Assets/Models/Sensors/IMU/Diffuse.png
  14. 3 0
      Gems/ROS2/Assets/Models/Sensors/IMU/IMU_MTi10.fbx
  15. 47 0
      Gems/ROS2/Assets/Models/Sensors/IMU/IMU_MTi10.fbx.assetinfo
  16. 3 0
      Gems/ROS2/Assets/Models/Sensors/IMU/Normal.png
  17. 3 0
      Gems/ROS2/Assets/Models/Sensors/LidarOS2/LidarOS2.fbx
  18. 47 0
      Gems/ROS2/Assets/Models/Sensors/LidarOS2/LidarOS2.fbx.assetinfo
  19. 23 0
      Gems/ROS2/Assets/Models/Sensors/LidarOS2/LidarOS2_OS2Material.material
  20. 3 0
      Gems/ROS2/Assets/Models/Sensors/LidarOS2/textures/AO_green.png
  21. 3 0
      Gems/ROS2/Assets/Models/Sensors/LidarOS2/textures/AO_grey.png
  22. 3 0
      Gems/ROS2/Assets/Models/Sensors/LidarOS2/textures/Diffuse.png
  23. 3 0
      Gems/ROS2/Assets/Models/Sensors/LidarOS2/textures/Metallic.png
  24. 3 0
      Gems/ROS2/Assets/Models/Sensors/LidarOS2/textures/Roughness.png
  25. 3 0
      Gems/ROS2/Assets/Models/Sensors/LidarOS2/textures/Smoothness.png
  26. 392 0
      Gems/ROS2/Assets/Passes/PipelineROSColor.pass
  27. 89 0
      Gems/ROS2/Assets/Passes/PipelineROSDepth.pass
  28. 32 0
      Gems/ROS2/Assets/Passes/PipelineRenderToTextureROSColor.pass
  29. 32 0
      Gems/ROS2/Assets/Passes/PipelineRenderToTextureROSDepth.pass
  30. 25 0
      Gems/ROS2/Assets/Passes/ROSPassTemplates.azasset
  31. 165 0
      Gems/ROS2/Assets/Prefabs/Sensors/CameraOrbbeck.prefab
  32. 249 0
      Gems/ROS2/Assets/Prefabs/Sensors/Imu.prefab
  33. 274 0
      Gems/ROS2/Assets/Prefabs/Sensors/LidarOS2.prefab
  34. 26 0
      Gems/ROS2/CMakeLists.txt
  35. 18 0
      Gems/ROS2/CONTRIBUTING.md
  36. 193 0
      Gems/ROS2/Code/CMakeLists.txt
  37. 13 0
      Gems/ROS2/Code/FindROS2.cmake
  38. 37 0
      Gems/ROS2/Code/Include/ROS2/Communication/QoS.h
  39. 37 0
      Gems/ROS2/Code/Include/ROS2/Communication/TopicConfiguration.h
  40. 58 0
      Gems/ROS2/Code/Include/ROS2/Frame/NamespaceConfiguration.h
  41. 91 0
      Gems/ROS2/Code/Include/ROS2/Frame/ROS2FrameComponent.h
  42. 40 0
      Gems/ROS2/Code/Include/ROS2/Frame/ROS2Transform.h
  43. 57 0
      Gems/ROS2/Code/Include/ROS2/Manipulator/MotorizedJointBus.h
  44. 93 0
      Gems/ROS2/Code/Include/ROS2/Manipulator/MotorizedJointComponent.h
  45. 67 0
      Gems/ROS2/Code/Include/ROS2/ROS2Bus.h
  46. 47 0
      Gems/ROS2/Code/Include/ROS2/ROS2GemUtilities.h
  47. 30 0
      Gems/ROS2/Code/Include/ROS2/RobotControl/Ackermann/AckermannBus.h
  48. 24 0
      Gems/ROS2/Code/Include/ROS2/RobotControl/Ackermann/AckermannCommandStruct.h
  49. 36 0
      Gems/ROS2/Code/Include/ROS2/RobotControl/ControlConfiguration.h
  50. 89 0
      Gems/ROS2/Code/Include/ROS2/RobotControl/ControlSubscriptionHandler.h
  51. 31 0
      Gems/ROS2/Code/Include/ROS2/RobotControl/Twist/TwistBus.h
  52. 58 0
      Gems/ROS2/Code/Include/ROS2/Sensor/ROS2SensorComponent.h
  53. 39 0
      Gems/ROS2/Code/Include/ROS2/Sensor/SensorConfiguration.h
  54. 35 0
      Gems/ROS2/Code/Include/ROS2/Spawner/SpawnerBus.h
  55. 31 0
      Gems/ROS2/Code/Include/ROS2/Utilities/ROS2Conversions.h
  56. 38 0
      Gems/ROS2/Code/Include/ROS2/Utilities/ROS2Names.h
  57. 43 0
      Gems/ROS2/Code/Include/ROS2/VehicleDynamics/DriveModels/PidConfiguration.h
  58. 56 0
      Gems/ROS2/Code/Include/ROS2/VehicleDynamics/VehicleInputControlBus.h
  59. 9 0
      Gems/ROS2/Code/Platform/Android/PAL_android.cmake
  60. 11 0
      Gems/ROS2/Code/Platform/Common/Clang/ros2_static_editor_clang.cmake
  61. 11 0
      Gems/ROS2/Code/Platform/Common/GCC/ros2_static_editor_gcc.cmake
  62. 11 0
      Gems/ROS2/Code/Platform/Common/msvc/ros2_static_editor_msvc.cmake
  63. 9 0
      Gems/ROS2/Code/Platform/Linux/PAL_linux.cmake
  64. 9 0
      Gems/ROS2/Code/Platform/Mac/PAL_mac.cmake
  65. 9 0
      Gems/ROS2/Code/Platform/Windows/PAL_windows.cmake
  66. 9 0
      Gems/ROS2/Code/Platform/iOS/PAL_ios.cmake
  67. 243 0
      Gems/ROS2/Code/Source/Camera/CameraSensor.cpp
  68. 116 0
      Gems/ROS2/Code/Source/Camera/CameraSensor.h
  69. 166 0
      Gems/ROS2/Code/Source/Camera/ROS2CameraSensorComponent.cpp
  70. 76 0
      Gems/ROS2/Code/Source/Camera/ROS2CameraSensorComponent.h
  71. 53 0
      Gems/ROS2/Code/Source/Clock/SimulationClock.cpp
  72. 34 0
      Gems/ROS2/Code/Source/Clock/SimulationClock.h
  73. 67 0
      Gems/ROS2/Code/Source/Communication/QoS.cpp
  74. 36 0
      Gems/ROS2/Code/Source/Communication/TopicConfiguration.cpp
  75. 99 0
      Gems/ROS2/Code/Source/Frame/NamespaceConfiguration.cpp
  76. 216 0
      Gems/ROS2/Code/Source/Frame/ROS2FrameComponent.cpp
  77. 44 0
      Gems/ROS2/Code/Source/Frame/ROS2Transform.cpp
  78. 107 0
      Gems/ROS2/Code/Source/GNSS/GNSSFormatConversions.cpp
  79. 45 0
      Gems/ROS2/Code/Source/GNSS/GNSSFormatConversions.h
  80. 114 0
      Gems/ROS2/Code/Source/GNSS/ROS2GNSSSensorComponent.cpp
  81. 51 0
      Gems/ROS2/Code/Source/GNSS/ROS2GNSSSensorComponent.h
  82. 151 0
      Gems/ROS2/Code/Source/Imu/ROS2ImuSensorComponent.cpp
  83. 51 0
      Gems/ROS2/Code/Source/Imu/ROS2ImuSensorComponent.h
  84. 88 0
      Gems/ROS2/Code/Source/Lidar/LidarRaycaster.cpp
  85. 55 0
      Gems/ROS2/Code/Source/Lidar/LidarRaycaster.h
  86. 65 0
      Gems/ROS2/Code/Source/Lidar/LidarTemplate.cpp
  87. 51 0
      Gems/ROS2/Code/Source/Lidar/LidarTemplate.h
  88. 81 0
      Gems/ROS2/Code/Source/Lidar/LidarTemplateUtils.cpp
  89. 35 0
      Gems/ROS2/Code/Source/Lidar/LidarTemplateUtils.h
  90. 209 0
      Gems/ROS2/Code/Source/Lidar/ROS2LidarSensorComponent.cpp
  91. 63 0
      Gems/ROS2/Code/Source/Lidar/ROS2LidarSensorComponent.h
  92. 273 0
      Gems/ROS2/Code/Source/Manipulator/MotorizedJointComponent.cpp
  93. 99 0
      Gems/ROS2/Code/Source/Odometry/ROS2OdometrySensorComponent.cpp
  94. 45 0
      Gems/ROS2/Code/Source/Odometry/ROS2OdometrySensorComponent.h
  95. 45 0
      Gems/ROS2/Code/Source/ROS2EditorModule.cpp
  96. 57 0
      Gems/ROS2/Code/Source/ROS2EditorSystemComponent.cpp
  97. 40 0
      Gems/ROS2/Code/Source/ROS2EditorSystemComponent.h
  98. 41 0
      Gems/ROS2/Code/Source/ROS2GemUtilities.cpp
  99. 22 0
      Gems/ROS2/Code/Source/ROS2Module.cpp
  100. 71 0
      Gems/ROS2/Code/Source/ROS2ModuleInterface.h

+ 1 - 0
.github/CODEOWNERS

@@ -23,6 +23,7 @@ Projects/OpenXRTest/ @o3de/sig-platform-reviewers @o3de/sig-platform-maintainers
 # SIG-Security
 
 # SIG-Simulation (@o3de/sig-simulation-reviewers @o3de/sig-simulation-maintainers)
+Gems/ROS2/ @o3de/sig-simulation-reviewers @o3de/sig-simulation-maintainers
 
 # SIG-Testing (@o3de/sig-testing-reviewers)
 

+ 61 - 0
Gems/ROS2/.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

+ 35 - 0
Gems/ROS2/.gitignore

@@ -0,0 +1,35 @@
+# Prerequisites
+*.d
+
+# Compiled Object files
+*.slo
+*.lo
+*.o
+*.obj
+
+# Precompiled Headers
+*.gch
+*.pch
+
+# Compiled Dynamic libraries
+*.so
+*.dylib
+*.dll
+
+# Fortran module files
+*.mod
+*.smod
+
+# Compiled Static libraries
+*.lai
+*.la
+*.a
+*.lib
+
+# Executables
+*.exe
+*.out
+*.app
+
+# workspace
+.idea/

+ 10 - 0
Gems/ROS2/.licenserc.yaml

@@ -0,0 +1,10 @@
+header:
+  license:
+    content: |
+      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
+  paths:
+    - 'Code'
+    - 'CMakeLists.txt'

+ 181 - 0
Gems/ROS2/Assets/Editor/Images/Icons/ROS_import_icon.svg

@@ -0,0 +1,181 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Generator: Adobe Illustrator 24.1.3, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   version="1.1"
+   x="0px"
+   y="0px"
+   viewBox="0 0 112.5 117.5"
+   enable-background="new 0 0 112.5 117.5"
+   xml:space="preserve"
+   id="svg13"
+   sodipodi:docname="robot2.svg"
+   inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"><metadata
+   id="metadata19"><rdf:RDF><cc:Work
+       rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+         rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title /></cc:Work></rdf:RDF></metadata><defs
+   id="defs17" /><sodipodi:namedview
+   pagecolor="#000000"
+   bordercolor="#666666"
+   borderopacity="1"
+   objecttolerance="10"
+   gridtolerance="10"
+   guidetolerance="10"
+   inkscape:pageopacity="1"
+   inkscape:pageshadow="2"
+   inkscape:window-width="1853"
+   inkscape:window-height="1016"
+   id="namedview15"
+   showgrid="false"
+   inkscape:zoom="4.1039793"
+   inkscape:cx="24.6666"
+   inkscape:cy="55.450352"
+   inkscape:window-x="0"
+   inkscape:window-y="27"
+   inkscape:window-maximized="1"
+   inkscape:current-layer="layer3" />
+<!--<g id="BG">-->
+<!--	<rect fill="#231F20" width="112.5" height="117.5"/>-->
+<!--</g>-->
+
+<g
+   inkscape:groupmode="layer"
+   id="layer3"
+   inkscape:label="Layer 3"><path
+     style="fill:#ffffff;fill-opacity:1;stroke-width:0.9127773"
+     d="m 14.957655,71.717418 v 23.805641 h 3.844972 a 15.412426,14.479465 0 0 1 14.45729,-9.511191 15.412426,14.479465 0 0 1 14.462199,9.511191 h 17.610315 a 15.412426,14.479465 0 0 1 14.457292,-9.511191 15.412426,14.479465 0 0 1 14.462198,9.511191 h 4.311178 V 71.717418 Z"
+     id="rect3778"
+     inkscape:connector-curvature="0" /><g
+     id="g8534"
+     transform="matrix(0.83410812,0,0,0.8740509,13.56952,-4.015205)"><circle
+       r="7.8998156"
+       cy="79.225113"
+       cx="58.094997"
+       id="path3780"
+       style="fill:#ffffff;stroke-width:0.77192098" /><path
+       transform="matrix(0.40662987,0,0,0.44158069,44.528152,43.187563)"
+       inkscape:transform-center-y="-2.3597552"
+       inkscape:transform-center-x="0.019603266"
+       d="m 52.292398,101.6486 -18.656376,-0.0835 -18.656377,-0.0835 9.400502,-16.115141 9.400502,-16.115146 9.255875,16.198646 z"
+       inkscape:randomized="0"
+       inkscape:rounded="0"
+       inkscape:flatsided="false"
+       sodipodi:arg2="1.575272"
+       sodipodi:arg1="0.52807445"
+       sodipodi:r2="10.771372"
+       sodipodi:r1="21.542744"
+       sodipodi:cy="90.793831"
+       sodipodi:cx="33.684231"
+       sodipodi:sides="3"
+       id="path8469"
+       style="fill:#ffffff;fill-opacity:1"
+       sodipodi:type="star" /><circle
+       style="fill:#ffffff;stroke-width:0.85525429"
+       id="circle8473"
+       cx="26.783924"
+       cy="40.969563"
+       r="8.7526464" /><rect
+       transform="matrix(0.61142845,0.79129972,-0.82773647,0.56111705,0,0)"
+       id="rect8475"
+       width="46.437813"
+       height="11.56524"
+       x="51.345745"
+       y="-2.8817394"
+       style="fill:#ffffff;fill-opacity:1;stroke-width:0.38991228"
+       ry="0" /><rect
+       transform="rotate(-11.376123)"
+       id="rect8477"
+       width="39.719555"
+       height="9.3269396"
+       x="17.463406"
+       y="40.288296"
+       style="fill:#ffffff;fill-opacity:1;stroke-width:0.33133295"
+       ry="0" /><rect
+       y="69.49781"
+       x="47.514858"
+       height="1.8274946"
+       width="3.2894902"
+       id="rect8479"
+       style="fill:#ffffff;fill-opacity:1" /><rect
+       ry="0"
+       style="fill:#ffffff;fill-opacity:1;stroke-width:0.33133295"
+       y="38.932949"
+       x="43.483589"
+       height="12.308515"
+       width="17.414444"
+       id="rect8483"
+       transform="rotate(-11.376123)" /><rect
+       inkscape:transform-center-y="-0.51689357"
+       inkscape:transform-center-x="-0.51689393"
+       transform="rotate(-36.591608)"
+       id="rect8485"
+       width="17.649437"
+       height="5.1593475"
+       x="32.329655"
+       y="57.468563"
+       style="fill:#ffffff;fill-opacity:1;stroke-width:0.33133295"
+       ry="0" /><rect
+       ry="0"
+       style="fill:#ffffff;fill-opacity:1;stroke-width:0.33133295"
+       y="-4.2776647"
+       x="76.041084"
+       height="5.1593475"
+       width="17.649437"
+       id="rect8487"
+       transform="rotate(15.615416)"
+       inkscape:transform-center-x="-0.72522446"
+       inkscape:transform-center-y="0.091706908" /><rect
+       ry="0"
+       style="fill:#ffffff;fill-opacity:1;stroke-width:0.33133295"
+       y="-78.794434"
+       x="-62.685215"
+       height="5.1593475"
+       width="17.649437"
+       id="rect8489"
+       transform="rotate(149.64563)"
+       inkscape:transform-center-x="0.56998762"
+       inkscape:transform-center-y="0.45766766" /><rect
+       inkscape:transform-center-y="-0.1699534"
+       inkscape:transform-center-x="0.71096735"
+       transform="rotate(-158.14734)"
+       id="rect8491"
+       width="17.649437"
+       height="5.1593475"
+       x="-89.864113"
+       y="-12.697401"
+       style="fill:#ffffff;fill-opacity:1;stroke-width:0.33133295"
+       ry="0" /><circle
+       style="fill:#ffffff;stroke-width:0.77192098"
+       id="circle8493"
+       cx="58.094997"
+       cy="79.225113"
+       r="7.8998156" /><circle
+       r="3.463146"
+       cy="27.664984"
+       cx="63.134712"
+       id="circle8497"
+       style="fill:#ffffff;stroke-width:0.33839715" /><circle
+       style="fill:#ffffff;stroke-width:0.33839715"
+       id="circle8499"
+       cx="65.029991"
+       cy="37.399811"
+       r="3.463146" /></g><ellipse
+     cy="100.99573"
+     cx="79.701149"
+     id="circle8538"
+     style="fill:#ffffff;fill-opacity:1;stroke-width:1.21768486"
+     rx="12.121657"
+     ry="11.387897" /><ellipse
+     style="fill:#ffffff;fill-opacity:1;stroke-width:1.21768486"
+     id="circle29"
+     cx="33.324261"
+     cy="100.99573"
+     rx="12.121657"
+     ry="11.387897" /></g></svg>

+ 5 - 0
Gems/ROS2/Assets/Editor/Images/Icons/Resources.qrc

@@ -0,0 +1,5 @@
+<RCC>
+    <qresource prefix="/ROS2">
+     <file>ROS_import_icon.svg</file>
+    </qresource>
+</RCC>

+ 17 - 0
Gems/ROS2/Assets/Materials/wheel_material.physxmaterial

@@ -0,0 +1,17 @@
+<ObjectStream version="3">
+    <Class name="PhysX::EditorMaterialAsset" version="2" type="{BC7B88B9-EE31-4FBF-A01E-2A93624C49D3}">
+        <Class name="AssetData" field="BaseClass1" version="1" type="{AF3F7D32-1536-422A-89F3-A11E1F5B5A9C}"/>
+        <Class name="PhysX::MaterialConfiguration" field="MaterialConfiguration" version="1" type="{66213D20-9862-465D-AF4F-2D94317161F6}">
+            <Class name="float" field="DynamicFriction" value="1.0000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
+            <Class name="float" field="StaticFriction" value="1.0000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
+            <Class name="float" field="Restitution" value="0.0000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
+            <Class name="unsigned char" field="FrictionCombine" value="2" type="{72B9409A-7D1A-4831-9CFE-FCB3FADD3426}"/>
+            <Class name="unsigned char" field="RestitutionCombine" value="1" type="{72B9409A-7D1A-4831-9CFE-FCB3FADD3426}"/>
+            <Class name="float" field="Density" value="1200.0000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
+            <Class name="Color" field="DebugColor" value="0.1088426 0.1088426 0.1088426 1.0000000" type="{7894072A-9050-4F0F-901B-34B1A0D29417}"/>
+        </Class>
+        <Class name="PhysicsLegacy::MaterialId" field="LegacyPhysicsMaterialId" version="1" type="{744CCE6C-9F69-4E2F-B950-DAB8514F870B}">
+            <Class name="AZ::Uuid" field="MaterialId" value="{8C7A6011-61C2-46B7-9BF4-8D4DD2A624F1}" type="{E152C105-A133-4D03-BBF8-3D4B2FBA3E2A}"/>
+        </Class>
+    </Class>
+</ObjectStream>

+ 3 - 0
Gems/ROS2/Assets/Models/Sensors/Camera/AO.png

@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a02e1525050efa303a1e2f4f0873a27565a8e9c86601f6bd6dba59ae05110645
+size 1488731

+ 3 - 0
Gems/ROS2/Assets/Models/Sensors/Camera/Body_Diffuse.png

@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:9988892afcb8f4a2fc35b2059ce9364cdb8eb1f795be1da753ffad36bf7b7392
+size 62867

+ 3 - 0
Gems/ROS2/Assets/Models/Sensors/Camera/CameraOrbbeck.fbx

@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a409d15dff6905764f833d9c1776d91d47aa106cea0f36def24d3997e3f670b7
+size 86204

+ 47 - 0
Gems/ROS2/Assets/Models/Sensors/Camera/CameraOrbbeck.fbx.assetinfo

@@ -0,0 +1,47 @@
+{
+    "values": [
+        {
+            "$type": "{5B03C8E6-8CEE-4DA0-A7FA-CD88689DD45B} MeshGroup",
+            "id": "{4EC8CA5C-ED77-539D-A0D5-FDE774D3AE48}",
+            "name": "CameraOrbbeck",
+            "NodeSelectionList": {
+                "selectedNodes": [
+                    "RootNode.Collider"
+                ],
+                "unselectedNodes": [
+                    {},
+                    "RootNode",
+                    "RootNode.Orbbeck"
+                ]
+            },
+            "MaterialSlots": [
+                "DefaultMaterial"
+            ],
+            "PhysicsMaterials": [
+                "<Default Physics Material>"
+            ]
+        },
+        {
+            "$type": "{07B356B7-3635-40B5-878A-FAC4EFD5AD86} MeshGroup",
+            "name": "CameraOrbbeck",
+            "nodeSelectionList": {
+                "selectedNodes": [
+                    {},
+                    "RootNode",
+                    "RootNode.Orbbeck"
+                ],
+                "unselectedNodes": [
+                    "RootNode.Collider"
+                ]
+            },
+            "rules": {
+                "rules": [
+                    {
+                        "$type": "MaterialRule"
+                    }
+                ]
+            },
+            "id": "{DD50FDF8-5780-543A-AA6E-E5D30DFE7E72}"
+        }
+    ]
+}

+ 3 - 0
Gems/ROS2/Assets/Models/Sensors/IMU/AO.png

@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:fcb1733629c66bb240c91019ac4f7c00f34ed2ceb82c4caedf8b240b7110b252
+size 995734

+ 3 - 0
Gems/ROS2/Assets/Models/Sensors/IMU/Diffuse.png

@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:491fc1288d475c70c8bef5634a1a22b55688770a7e727f424897ca42df01c939
+size 108517

+ 3 - 0
Gems/ROS2/Assets/Models/Sensors/IMU/IMU_MTi10.fbx

@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:7d8c226347fad6b41cdcbc2fa38acfccb0fb44d2c33dbc806be254dfefcbe7b7
+size 91260

+ 47 - 0
Gems/ROS2/Assets/Models/Sensors/IMU/IMU_MTi10.fbx.assetinfo

@@ -0,0 +1,47 @@
+{
+    "values": [
+        {
+            "$type": "{5B03C8E6-8CEE-4DA0-A7FA-CD88689DD45B} MeshGroup",
+            "id": "{F1C07FE3-CCB1-5FEB-8CDD-8101E20A52D1}",
+            "name": "IMU_MTi10",
+            "NodeSelectionList": {
+                "selectedNodes": [
+                    "RootNode.Collider"
+                ],
+                "unselectedNodes": [
+                    {},
+                    "RootNode",
+                    "RootNode.MTi"
+                ]
+            },
+            "MaterialSlots": [
+                "DefaultMaterial"
+            ],
+            "PhysicsMaterials": [
+                "<Default Physics Material>"
+            ]
+        },
+        {
+            "$type": "{07B356B7-3635-40B5-878A-FAC4EFD5AD86} MeshGroup",
+            "name": "IMU_MTi10",
+            "nodeSelectionList": {
+                "selectedNodes": [
+                    {},
+                    "RootNode",
+                    "RootNode.MTi"
+                ],
+                "unselectedNodes": [
+                    "RootNode.Collider"
+                ]
+            },
+            "rules": {
+                "rules": [
+                    {
+                        "$type": "MaterialRule"
+                    }
+                ]
+            },
+            "id": "{8347CDE5-E6B6-50DC-B7FC-21B26C248E51}"
+        }
+    ]
+}

+ 3 - 0
Gems/ROS2/Assets/Models/Sensors/IMU/Normal.png

@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:0e2639cda66493e1c85a301de71aeae1eafcf5f59c8528e4918ac1923cc36f3b
+size 33162

+ 3 - 0
Gems/ROS2/Assets/Models/Sensors/LidarOS2/LidarOS2.fbx

@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:d1a630c97a3b192f56b42e87650edfb1f10a4967cb2c7211d7ac00eb487d1b0f
+size 237180

+ 47 - 0
Gems/ROS2/Assets/Models/Sensors/LidarOS2/LidarOS2.fbx.assetinfo

@@ -0,0 +1,47 @@
+{
+    "values": [
+        {
+            "$type": "{5B03C8E6-8CEE-4DA0-A7FA-CD88689DD45B} MeshGroup",
+            "id": "{8B671FF3-450D-5007-AC79-F2E406301A08}",
+            "name": "LidarOS2",
+            "NodeSelectionList": {
+                "selectedNodes": [
+                    "RootNode.Collider"
+                ],
+                "unselectedNodes": [
+                    {},
+                    "RootNode",
+                    "RootNode.OS2"
+                ]
+            },
+            "MaterialSlots": [
+                "DefaultMaterial"
+            ],
+            "PhysicsMaterials": [
+                "<Default Physics Material>"
+            ]
+        },
+        {
+            "$type": "{07B356B7-3635-40B5-878A-FAC4EFD5AD86} MeshGroup",
+            "name": "LidarOS2",
+            "nodeSelectionList": {
+                "selectedNodes": [
+                    {},
+                    "RootNode",
+                    "RootNode.OS2"
+                ],
+                "unselectedNodes": [
+                    "RootNode.Collider"
+                ]
+            },
+            "rules": {
+                "rules": [
+                    {
+                        "$type": "MaterialRule"
+                    }
+                ]
+            },
+            "id": "{830ADBFF-66E2-505D-BEE1-EFBD789FDF62}"
+        }
+    ]
+}

+ 23 - 0
Gems/ROS2/Assets/Models/Sensors/LidarOS2/LidarOS2_OS2Material.material

@@ -0,0 +1,23 @@
+{
+    "materialType": "@gemroot:Atom_Feature_Common@/Assets/Materials/Types/StandardPBR.materialtype",
+    "materialTypeVersion": 5,
+    "propertyValues": {
+        "baseColor.color": [
+            0.800000011920929,
+            0.800000011920929,
+            0.800000011920929,
+            1.0
+        ],
+        "baseColor.textureMap": "textures/Diffuse.png",
+        "emissive.color": [
+            0.0,
+            0.0,
+            0.0,
+            1.0
+        ],
+        "metallic.textureMap": "textures/Metallic.png",
+        "occlusion.diffuseTextureMap": "textures/AO_grey.png",
+        "opacity.factor": 1.0,
+        "roughness.textureMap": "textures/Roughness.png"
+    }
+}

+ 3 - 0
Gems/ROS2/Assets/Models/Sensors/LidarOS2/textures/AO_green.png

@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:f312eb2eac4a1d5cf7f3ebbdccc5f9e7095b810a912f715926c39b7d18864e91
+size 901336

+ 3 - 0
Gems/ROS2/Assets/Models/Sensors/LidarOS2/textures/AO_grey.png

@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:da65cd46f8e182ce09bde559243ebe05f000f99c2ef3088266ac27cc2ed56f81
+size 915623

+ 3 - 0
Gems/ROS2/Assets/Models/Sensors/LidarOS2/textures/Diffuse.png

@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:a111f317cd4903a72c6f9b899b53737c008d68c9fff0b2389f40f40b880a43e0
+size 133579

+ 3 - 0
Gems/ROS2/Assets/Models/Sensors/LidarOS2/textures/Metallic.png

@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:aac819c415821e7d8a96bbfeda3d8ea05ce379159ba97b941589808afefa7bde
+size 123338

+ 3 - 0
Gems/ROS2/Assets/Models/Sensors/LidarOS2/textures/Roughness.png

@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:76c6ff5762bd2a50feab6cfc170bc9245264335d5894319d07b50a31c088a785
+size 123950

+ 3 - 0
Gems/ROS2/Assets/Models/Sensors/LidarOS2/textures/Smoothness.png

@@ -0,0 +1,3 @@
+version https://git-lfs.github.com/spec/v1
+oid sha256:702ab6984e68abcfc434e90f3475badc9bbeeb8ef7edadd986ec84db985951a1
+size 123513

+ 392 - 0
Gems/ROS2/Assets/Passes/PipelineROSColor.pass

@@ -0,0 +1,392 @@
+{
+    "Type": "JsonSerialization",
+    "Version": 1,
+    "ClassName": "PassAsset",
+    "ClassData": {
+        "PassTemplate": {
+            "Name": "PipelineROSColor",
+            "PassClass": "ParentPass",
+            "Slots": [
+                {
+                    "Name": "PipelineOutput",
+                    "SlotType": "InputOutput"
+                }
+            ],
+            "PassData": {
+                "$type": "PassData",
+                "PipelineGlobalConnections": [
+                    {
+                        "GlobalName": "PipelineOutput",
+                        "Slot": "PipelineOutput"
+                    }
+                ]
+            },
+            "PassRequests": [
+                {
+                    "Name": "MorphTargetPass",
+                    "TemplateName": "MorphTargetPassTemplate"
+                },
+                {
+                    "Name": "SkinningPass",
+                    "TemplateName": "SkinningPassTemplate",
+                    "Connections": [
+                        {
+                            "LocalSlot": "SkinnedMeshOutputStream",
+                            "AttachmentRef": {
+                                "Pass": "MorphTargetPass",
+                                "Attachment": "MorphTargetDeltaOutput"
+                            }
+                        }
+                    ]
+                },
+                {
+                    "Name": "RayTracingAccelerationStructurePass",
+                    "TemplateName": "RayTracingAccelerationStructurePassTemplate"
+                },
+                {
+                    "Name": "DepthPrePass",
+                    "TemplateName": "DepthParentTemplate",
+                    "Connections": [
+                        {
+                            "LocalSlot": "SkinnedMeshes",
+                            "AttachmentRef": {
+                                "Pass": "SkinningPass",
+                                "Attachment": "SkinnedMeshOutputStream"
+                            }
+                        },
+                        {
+                            "LocalSlot": "PipelineOutput",
+                            "AttachmentRef": {
+                                "Pass": "PipelineGlobal",
+                                "Attachment": "PipelineOutput"
+                            }
+                        }
+                    ]
+                },
+                {
+                    "Name": "MotionVectorPass",
+                    "TemplateName": "MotionVectorParentTemplate",
+                    "Connections": [
+                        {
+                            "LocalSlot": "SkinnedMeshes",
+                            "AttachmentRef": {
+                                "Pass": "SkinningPass",
+                                "Attachment": "SkinnedMeshOutputStream"
+                            }
+                        },
+                        {
+                            "LocalSlot": "Depth",
+                            "AttachmentRef": {
+                                "Pass": "DepthPrePass",
+                                "Attachment": "Depth"
+                            }
+                        },
+                        {
+                            "LocalSlot": "PipelineOutput",
+                            "AttachmentRef": {
+                                "Pass": "PipelineGlobal",
+                                "Attachment": "PipelineOutput"
+                            }
+                        }
+                    ]
+                },
+                {
+                    "Name": "LightCullingPass",
+                    "TemplateName": "LightCullingParentTemplate",
+                    "Connections": [
+                        {
+                            "LocalSlot": "SkinnedMeshes",
+                            "AttachmentRef": {
+                                "Pass": "SkinningPass",
+                                "Attachment": "SkinnedMeshOutputStream"
+                            }
+                        },
+                        {
+                            "LocalSlot": "DepthMSAA",
+                            "AttachmentRef": {
+                                "Pass": "DepthPrePass",
+                                "Attachment": "DepthMSAA"
+                            }
+                        },
+                        {
+                            "LocalSlot": "PipelineOutput",
+                            "AttachmentRef": {
+                                "Pass": "PipelineGlobal",
+                                "Attachment": "PipelineOutput"
+                            }
+                        }
+                    ]
+                },
+                {
+                    "Name": "Shadows",
+                    "TemplateName": "ShadowParentTemplate",
+                    "Connections": [
+                        {
+                            "LocalSlot": "SkinnedMeshes",
+                            "AttachmentRef": {
+                                "Pass": "SkinningPass",
+                                "Attachment": "SkinnedMeshOutputStream"
+                            }
+                        },
+                        {
+                            "LocalSlot": "PipelineOutput",
+                            "AttachmentRef": {
+                                "Pass": "PipelineGlobal",
+                                "Attachment": "PipelineOutput"
+                            }
+                        },
+                        {
+                            "LocalSlot": "Depth",
+                            "AttachmentRef": {
+                                "Pass": "DepthPrePass",
+                                "Attachment": "DepthMSAA"
+                            }
+                        }
+                    ]
+                },
+                {
+                    "Name": "OpaquePass",
+                    "TemplateName": "OpaqueParentTemplate",
+                    "Connections": [
+                        {
+                            "LocalSlot": "DirectionalShadowmap",
+                            "AttachmentRef": {
+                                "Pass": "Shadows",
+                                "Attachment": "DirectionalShadowmap"
+                            }
+                        },
+                        {
+                            "LocalSlot": "DirectionalESM",
+                            "AttachmentRef": {
+                                "Pass": "Shadows",
+                                "Attachment": "DirectionalESM"
+                            }
+                        },
+                        {
+                            "LocalSlot": "ProjectedShadowmap",
+                            "AttachmentRef": {
+                                "Pass": "Shadows",
+                                "Attachment": "ProjectedShadowmap"
+                            }
+                        },
+                        {
+                            "LocalSlot": "ProjectedESM",
+                            "AttachmentRef": {
+                                "Pass": "Shadows",
+                                "Attachment": "ProjectedESM"
+                            }
+                        },
+                        {
+                            "LocalSlot": "TileLightData",
+                            "AttachmentRef": {
+                                "Pass": "LightCullingPass",
+                                "Attachment": "TileLightData"
+                            }
+                        },
+                        {
+                            "LocalSlot": "LightListRemapped",
+                            "AttachmentRef": {
+                                "Pass": "LightCullingPass",
+                                "Attachment": "LightListRemapped"
+                            }
+                        },
+                        {
+                            "LocalSlot": "DepthLinear",
+                            "AttachmentRef": {
+                                "Pass": "DepthPrePass",
+                                "Attachment": "DepthLinear"
+                            }
+                        },
+                        {
+                            "LocalSlot": "DepthStencil",
+                            "AttachmentRef": {
+                                "Pass": "DepthPrePass",
+                                "Attachment": "DepthMSAA"
+                            }
+                        },
+                        {
+                            "LocalSlot": "PipelineOutput",
+                            "AttachmentRef": {
+                                "Pass": "PipelineGlobal",
+                                "Attachment": "PipelineOutput"
+                            }
+                        }
+                    ]
+                },
+                {
+                    "Name": "TransparentPass",
+                    "TemplateName": "TransparentParentTemplate",
+                    "Connections": [
+                        {
+                            "LocalSlot": "DirectionalShadowmap",
+                            "AttachmentRef": {
+                                "Pass": "Shadows",
+                                "Attachment": "DirectionalShadowmap"
+                            }
+                        },
+                        {
+                            "LocalSlot": "DirectionalESM",
+                            "AttachmentRef": {
+                                "Pass": "Shadows",
+                                "Attachment": "DirectionalESM"
+                            }
+                        },
+                        {
+                            "LocalSlot": "ProjectedShadowmap",
+                            "AttachmentRef": {
+                                "Pass": "Shadows",
+                                "Attachment": "ProjectedShadowmap"
+                            }
+                        },
+                        {
+                            "LocalSlot": "ProjectedESM",
+                            "AttachmentRef": {
+                                "Pass": "Shadows",
+                                "Attachment": "ProjectedESM"
+                            }
+                        },
+                        {
+                            "LocalSlot": "TileLightData",
+                            "AttachmentRef": {
+                                "Pass": "LightCullingPass",
+                                "Attachment": "TileLightData"
+                            }
+                        },
+                        {
+                            "LocalSlot": "LightListRemapped",
+                            "AttachmentRef": {
+                                "Pass": "LightCullingPass",
+                                "Attachment": "LightListRemapped"
+                            }
+                        },
+                        {
+                            "LocalSlot": "InputLinearDepth",
+                            "AttachmentRef": {
+                                "Pass": "DepthPrePass",
+                                "Attachment": "DepthLinear"
+                            }
+                        },
+                        {
+                            "LocalSlot": "DepthStencil",
+                            "AttachmentRef": {
+                                "Pass": "DepthPrePass",
+                                "Attachment": "Depth"
+                            }
+                        },
+                        {
+                            "LocalSlot": "InputOutput",
+                            "AttachmentRef": {
+                                "Pass": "OpaquePass",
+                                "Attachment": "Output"
+                            }
+                        }
+                    ]
+                },
+                {
+                    "Name": "DeferredFogPass",
+                    "TemplateName": "DeferredFogPassTemplate",
+                    "Enabled": false,
+                    "Connections": [
+                        {
+                            "LocalSlot": "InputLinearDepth",
+                            "AttachmentRef": {
+                                "Pass": "DepthPrePass",
+                                "Attachment": "DepthLinear"
+                            }
+                        },
+                        {
+                            "LocalSlot": "InputDepthStencil",
+                            "AttachmentRef": {
+                                "Pass": "DepthPrePass",
+                                "Attachment": "Depth"
+                            }
+                        },
+                        {
+                            "LocalSlot": "RenderTargetInputOutput",
+                            "AttachmentRef": {
+                                "Pass": "TransparentPass",
+                                "Attachment": "InputOutput"
+                            }
+                        }
+                    ],
+                    "PassData": {
+                        "$type": "FullscreenTrianglePassData",
+                        "ShaderAsset": {
+                            "FilePath": "Shaders/ScreenSpace/DeferredFog.shader"
+                        },
+                        "PipelineViewTag": "MainCamera"
+                    }
+                },
+                {
+                    "Name": "ReflectionCopyFrameBufferPass",
+                    "TemplateName": "ReflectionCopyFrameBufferPassTemplate",
+                    "Enabled": false,
+                    "Connections": [
+                        {
+                            "LocalSlot": "Input",
+                            "AttachmentRef": {
+                                "Pass": "DeferredFogPass",
+                                "Attachment": "RenderTargetInputOutput"
+                            }
+                        }
+                    ]
+                },
+                {
+                    "Name": "PostProcessPass",
+                    "TemplateName": "PostProcessParentTemplate",
+                    "Connections": [
+                        {
+                            "LocalSlot": "LightingInput",
+                            "AttachmentRef": {
+                                "Pass": "DeferredFogPass",
+                                "Attachment": "RenderTargetInputOutput"
+                            }
+                        },
+                        {
+                            "LocalSlot": "Depth",
+                            "AttachmentRef": {
+                                "Pass": "DepthPrePass",
+                                "Attachment": "Depth"
+                            }
+                        },
+                        {
+                            "LocalSlot": "MotionVectors",
+                            "AttachmentRef": {
+                                "Pass": "MotionVectorPass",
+                                "Attachment": "MotionVectorOutput"
+                            }
+                        },
+                        {
+                            "LocalSlot": "PipelineOutput",
+                            "AttachmentRef": {
+                                "Pass": "PipelineGlobal",
+                                "Attachment": "PipelineOutput"
+                            }
+                        }
+                    ]
+                },
+
+                {
+                    "Name": "CopyToSwapChain",
+                    "TemplateName": "FullscreenCopyTemplate",
+                    "Connections": [
+                        {
+                            "LocalSlot": "Input",
+                            "AttachmentRef": {
+                                "Pass": "PostProcessPass",
+                                "Attachment": "Output"
+                            }
+                        },
+                        {
+                            "LocalSlot": "Output",
+                            "AttachmentRef": {
+                                "Pass": "PipelineGlobal",
+                                "Attachment": "PipelineOutput"
+                            }
+                        }
+                    ]
+                }
+            ]
+        }
+    }
+}

+ 89 - 0
Gems/ROS2/Assets/Passes/PipelineROSDepth.pass

@@ -0,0 +1,89 @@
+{
+    "Type": "JsonSerialization",
+    "Version": 1,
+    "ClassName": "PassAsset",
+    "ClassData": {
+        "PassTemplate": {
+            "Name": "PipelineROSDepth",
+            "PassClass": "ParentPass",
+            "Slots": [
+                {
+                    "Name": "PipelineOutput",
+                    "SlotType": "InputOutput"
+                }
+            ],
+            "PassData": {
+                "$type": "PassData",
+                "PipelineGlobalConnections": [
+                    {
+                        "GlobalName": "PipelineOutput",
+                        "Slot": "PipelineOutput"
+                    }
+                ]
+            },
+            "PassRequests": [
+                {
+                    "Name": "MorphTargetPass",
+                    "TemplateName": "MorphTargetPassTemplate"
+                },
+                {
+                    "Name": "SkinningPass",
+                    "TemplateName": "SkinningPassTemplate",
+                    "Connections": [
+                        {
+                            "LocalSlot": "SkinnedMeshOutputStream",
+                            "AttachmentRef": {
+                                "Pass": "MorphTargetPass",
+                                "Attachment": "MorphTargetDeltaOutput"
+                            }
+                        }
+                    ]
+                },
+                {
+                    "Name": "RayTracingAccelerationStructurePass",
+                    "TemplateName": "RayTracingAccelerationStructurePassTemplate"
+                },
+                {
+                    "Name": "DepthPrePass",
+                    "TemplateName": "DepthParentTemplate",
+                    "Connections": [
+                        {
+                            "LocalSlot": "SkinnedMeshes",
+                            "AttachmentRef": {
+                                "Pass": "SkinningPass",
+                                "Attachment": "SkinnedMeshOutputStream"
+                            }
+                        },
+                        {
+                            "LocalSlot": "PipelineOutput",
+                            "AttachmentRef": {
+                                "Pass": "PipelineGlobal",
+                                "Attachment": "PipelineOutput"
+                            }
+                        }
+                    ]
+                },
+                {
+                    "Name": "CopyToSwapChain",
+                    "TemplateName": "FullscreenCopyTemplate",
+                    "Connections": [
+                        {
+                            "LocalSlot": "Input",
+                            "AttachmentRef": {
+                                "Pass": "DepthPrePass",
+                                "Attachment": "DepthLinear"
+                            }
+                        },
+                        {
+                            "LocalSlot": "Output",
+                            "AttachmentRef": {
+                                "Pass": "PipelineGlobal",
+                                "Attachment": "PipelineOutput"
+                            }
+                        }
+                    ]
+                }
+            ]
+        }
+    }
+}

+ 32 - 0
Gems/ROS2/Assets/Passes/PipelineRenderToTextureROSColor.pass

@@ -0,0 +1,32 @@
+{
+    "Type": "JsonSerialization",
+    "Version": 1,
+    "ClassName": "PassAsset",
+    "ClassData": {
+        "PassTemplate": {
+            "Name": "PipelineRenderToTextureROSDepth",
+            "PassClass": "RenderToTexturePass",
+            "PassData": {
+                "$type": "RenderToTexturePassData",
+                "OutputWidth": 512,
+                "OutputHeight": 512,
+                "OutputFormat": "R8G8B8A8_UNORM"
+            },
+            "PassRequests": [
+                {
+                    "Name": "Pipeline",
+                    "TemplateName": "PipelineROSColor",
+                    "Connections": [
+                        {
+                            "LocalSlot": "PipelineOutput",
+                            "AttachmentRef": {
+                                "Pass": "Parent",
+                                "Attachment": "Output"
+                            }
+                        }
+                    ]
+                }
+            ]
+        }
+    }
+}

+ 32 - 0
Gems/ROS2/Assets/Passes/PipelineRenderToTextureROSDepth.pass

@@ -0,0 +1,32 @@
+{
+    "Type": "JsonSerialization",
+    "Version": 1,
+    "ClassName": "PassAsset",
+    "ClassData": {
+        "PassTemplate": {
+            "Name": "PipelineRenderToTextureROSDepth",
+            "PassClass": "RenderToTexturePass",
+            "PassData": {
+                "$type": "RenderToTexturePassData",
+                "OutputWidth": 512,
+                "OutputHeight": 512,
+                "OutputFormat": "R32_FLOAT"
+            },
+            "PassRequests": [
+                {
+                    "Name": "Pipeline",
+                    "TemplateName": "PipelineROSDepth",
+                    "Connections": [
+                        {
+                            "LocalSlot": "PipelineOutput",
+                            "AttachmentRef": {
+                                "Pass": "Parent",
+                                "Attachment": "Output"
+                            }
+                        }
+                    ]
+                }
+            ]
+        }
+    }
+}

+ 25 - 0
Gems/ROS2/Assets/Passes/ROSPassTemplates.azasset

@@ -0,0 +1,25 @@
+{
+    "Type": "JsonSerialization",
+    "Version": 1,
+    "ClassName": "AssetAliasesSourceData",
+    "ClassData": {
+        "AssetPaths": [
+            {
+                "Name": "PipelineROSColor",
+                "Path": "Passes/PipelineROSColor.pass"
+            },
+            {
+                "Name": "PipelineROSDepth",
+                "Path": "Passes/PipelineROSDepth.pass"
+            },
+            {
+                "Name": "PipelineRenderToTextureROSColor",
+                "Path": "Passes/PipelineRenderToTextureROSColor.pass"
+            },
+            {
+                "Name": "PipelineRenderToTextureROSDepth",
+                "Path": "Passes/PipelineRenderToTextureROSDepth.pass"
+            }
+        ]
+    }
+}

+ 165 - 0
Gems/ROS2/Assets/Prefabs/Sensors/CameraOrbbeck.prefab

@@ -0,0 +1,165 @@
+{
+    "ContainerEntity": {
+        "Id": "ContainerEntity",
+        "Name": "CameraOrbbeck",
+        "Components": {
+            "Component_[11102275644839424768]": {
+                "$type": "EditorDisabledCompositionComponent",
+                "Id": 11102275644839424768
+            },
+            "Component_[11542908075789116644]": {
+                "$type": "SelectionComponent",
+                "Id": 11542908075789116644
+            },
+            "Component_[13196476423961983989]": {
+                "$type": "EditorVisibilityComponent",
+                "Id": 13196476423961983989
+            },
+            "Component_[13762686544738860494]": {
+                "$type": "EditorPendingCompositionComponent",
+                "Id": 13762686544738860494
+            },
+            "Component_[14647808383678281689]": {
+                "$type": "EditorPrefabComponent",
+                "Id": 14647808383678281689
+            },
+            "Component_[16541569782343084053]": {
+                "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent",
+                "Id": 16541569782343084053,
+                "Parent Entity": ""
+            },
+            "Component_[362243497987326595]": {
+                "$type": "EditorEntitySortComponent",
+                "Id": 362243497987326595,
+                "Child Entity Order": [
+                    "Entity_[505721939488]"
+                ]
+            },
+            "Component_[6195463370225568393]": {
+                "$type": "EditorLockComponent",
+                "Id": 6195463370225568393
+            },
+            "Component_[7051867183047048361]": {
+                "$type": "EditorEntityIconComponent",
+                "Id": 7051867183047048361
+            },
+            "Component_[8693333803190442857]": {
+                "$type": "EditorOnlyEntityComponent",
+                "Id": 8693333803190442857
+            },
+            "Component_[921489768285039708]": {
+                "$type": "EditorInspectorComponent",
+                "Id": 921489768285039708
+            }
+        }
+    },
+    "Entities": {
+        "Entity_[505721939488]": {
+            "Id": "Entity_[505721939488]",
+            "Name": "shape",
+            "Components": {
+                "Component_[10894288830090806011]": {
+                    "$type": "SelectionComponent",
+                    "Id": 10894288830090806011
+                },
+                "Component_[12232650884412772636]": {
+                    "$type": "EditorDisabledCompositionComponent",
+                    "Id": 12232650884412772636
+                },
+                "Component_[13423704711553959863]": {
+                    "$type": "EditorLockComponent",
+                    "Id": 13423704711553959863
+                },
+                "Component_[14616296837285217185]": {
+                    "$type": "EditorVisibilityComponent",
+                    "Id": 14616296837285217185
+                },
+                "Component_[14985514809153430690]": {
+                    "$type": "EditorColliderComponent",
+                    "Id": 14985514809153430690,
+                    "ColliderConfiguration": {
+                        "MaterialSelection": {
+                            "MaterialIds": [
+                                {}
+                            ]
+                        }
+                    },
+                    "ShapeConfiguration": {
+                        "PhysicsAsset": {
+                            "Asset": {
+                                "assetId": {
+                                    "guid": "{0E27CF27-5752-5557-B77A-4B29A5870E0E}",
+                                    "subId": 3865077323
+                                },
+                                "assetHint": "models/sensors/camera/cameraorbbeck.pxmesh"
+                            },
+                            "Configuration": {
+                                "PhysicsAsset": {
+                                    "assetId": {
+                                        "guid": "{0E27CF27-5752-5557-B77A-4B29A5870E0E}",
+                                        "subId": 3865077323
+                                    },
+                                    "loadBehavior": "QueueLoad",
+                                    "assetHint": "models/sensors/camera/cameraorbbeck.pxmesh"
+                                }
+                            }
+                        }
+                    }
+                },
+                "Component_[15329441649387498950]": {
+                    "$type": "AZ::Render::EditorMeshComponent",
+                    "Id": 15329441649387498950,
+                    "Controller": {
+                        "Configuration": {
+                            "ModelAsset": {
+                                "assetId": {
+                                    "guid": "{0E27CF27-5752-5557-B77A-4B29A5870E0E}",
+                                    "subId": 272993556
+                                },
+                                "assetHint": "models/sensors/camera/cameraorbbeck.azmodel"
+                            }
+                        }
+                    }
+                },
+                "Component_[16525421850445080131]": {
+                    "$type": "EditorPendingCompositionComponent",
+                    "Id": 16525421850445080131
+                },
+                "Component_[1664914396196781949]": {
+                    "$type": "EditorEntityIconComponent",
+                    "Id": 1664914396196781949
+                },
+                "Component_[18099860883597356778]": {
+                    "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent",
+                    "Id": 18099860883597356778,
+                    "Parent Entity": "ContainerEntity"
+                },
+                "Component_[18322469032445804849]": {
+                    "$type": "EditorOnlyEntityComponent",
+                    "Id": 18322469032445804849
+                },
+                "Component_[2491808840491479985]": {
+                    "$type": "EditorInspectorComponent",
+                    "Id": 2491808840491479985,
+                    "ComponentOrderEntryArray": [
+                        {
+                            "ComponentId": 18099860883597356778
+                        },
+                        {
+                            "ComponentId": 15329441649387498950,
+                            "SortIndex": 1
+                        },
+                        {
+                            "ComponentId": 14985514809153430690,
+                            "SortIndex": 2
+                        }
+                    ]
+                },
+                "Component_[6578496273892635118]": {
+                    "$type": "EditorEntitySortComponent",
+                    "Id": 6578496273892635118
+                }
+            }
+        }
+    }
+}

+ 249 - 0
Gems/ROS2/Assets/Prefabs/Sensors/Imu.prefab

@@ -0,0 +1,249 @@
+{
+    "ContainerEntity": {
+        "Id": "ContainerEntity",
+        "Name": "Imu",
+        "Components": {
+            "Component_[10933327020638125059]": {
+                "$type": "EditorLockComponent",
+                "Id": 10933327020638125059
+            },
+            "Component_[13139737976503828664]": {
+                "$type": "SelectionComponent",
+                "Id": 13139737976503828664
+            },
+            "Component_[15418568890677341368]": {
+                "$type": "EditorOnlyEntityComponent",
+                "Id": 15418568890677341368
+            },
+            "Component_[1567675407994048561]": {
+                "$type": "EditorEntityIconComponent",
+                "Id": 1567675407994048561
+            },
+            "Component_[2143987639398891621]": {
+                "$type": "EditorDisabledCompositionComponent",
+                "Id": 2143987639398891621
+            },
+            "Component_[4252676114928404176]": {
+                "$type": "EditorPrefabComponent",
+                "Id": 4252676114928404176
+            },
+            "Component_[6700600896017100313]": {
+                "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent",
+                "Id": 6700600896017100313,
+                "Parent Entity": ""
+            },
+            "Component_[8103406177016615226]": {
+                "$type": "EditorVisibilityComponent",
+                "Id": 8103406177016615226
+            },
+            "Component_[8182586409240141630]": {
+                "$type": "EditorPendingCompositionComponent",
+                "Id": 8182586409240141630
+            },
+            "Component_[901516877242196147]": {
+                "$type": "EditorEntitySortComponent",
+                "Id": 901516877242196147,
+                "Child Entity Order": [
+                    "Entity_[799444499120]"
+                ]
+            },
+            "Component_[9830322531478794982]": {
+                "$type": "EditorInspectorComponent",
+                "Id": 9830322531478794982
+            }
+        }
+    },
+    "Entities": {
+        "Entity_[690707340010]": {
+            "Id": "Entity_[690707340010]",
+            "Name": "Imu",
+            "Components": {
+                "Component_[10340392978555572105]": {
+                    "$type": "EditorVisibilityComponent",
+                    "Id": 10340392978555572105
+                },
+                "Component_[11958193181460909696]": {
+                    "$type": "EditorOnlyEntityComponent",
+                    "Id": 11958193181460909696
+                },
+                "Component_[12008599340723129520]": {
+                    "$type": "SelectionComponent",
+                    "Id": 12008599340723129520
+                },
+                "Component_[12053638524449760314]": {
+                    "$type": "EditorColliderComponent",
+                    "Id": 12053638524449760314,
+                    "ColliderConfiguration": {
+                        "MaterialSelection": {
+                            "MaterialIds": [
+                                {}
+                            ]
+                        }
+                    },
+                    "ShapeConfiguration": {
+                        "PhysicsAsset": {
+                            "Asset": {
+                                "assetId": {
+                                    "guid": "{50286487-658B-5B99-99D6-3AB4002EFF73}",
+                                    "subId": 3726733934
+                                },
+                                "assetHint": "models/sensors/imu/imu_mti10.pxmesh"
+                            },
+                            "Configuration": {
+                                "PhysicsAsset": {
+                                    "assetId": {
+                                        "guid": "{50286487-658B-5B99-99D6-3AB4002EFF73}",
+                                        "subId": 3726733934
+                                    },
+                                    "loadBehavior": "QueueLoad",
+                                    "assetHint": "models/sensors/imu/imu_mti10.pxmesh"
+                                }
+                            }
+                        }
+                    }
+                },
+                "Component_[12417918822595867875]": {
+                    "$type": "EditorEntitySortComponent",
+                    "Id": 12417918822595867875,
+                    "Child Entity Order": [
+                        "Entity_[799444499120]"
+                    ]
+                },
+                "Component_[13004836012029176840]": {
+                    "$type": "EditorInspectorComponent",
+                    "Id": 13004836012029176840,
+                    "ComponentOrderEntryArray": [
+                        {
+                            "ComponentId": 16208993720168044799
+                        },
+                        {
+                            "ComponentId": 13855119924746268003,
+                            "SortIndex": 1
+                        },
+                        {
+                            "ComponentId": 1876289897715338766,
+                            "SortIndex": 2
+                        },
+                        {
+                            "ComponentId": 8463513152996653994,
+                            "SortIndex": 3
+                        },
+                        {
+                            "ComponentId": 12053638524449760314,
+                            "SortIndex": 4
+                        }
+                    ]
+                },
+                "Component_[13717336165918392]": {
+                    "$type": "EditorDisabledCompositionComponent",
+                    "Id": 13717336165918392
+                },
+                "Component_[13855119924746268003]": {
+                    "$type": "AZ::Render::EditorMeshComponent",
+                    "Id": 13855119924746268003,
+                    "Controller": {
+                        "Configuration": {
+                            "ModelAsset": {
+                                "assetId": {
+                                    "guid": "{50286487-658B-5B99-99D6-3AB4002EFF73}",
+                                    "subId": 282234899
+                                },
+                                "assetHint": "models/sensors/imu/imu_mti10.azmodel"
+                            }
+                        }
+                    }
+                },
+                "Component_[14241613391642309632]": {
+                    "$type": "EditorEntityIconComponent",
+                    "Id": 14241613391642309632
+                },
+                "Component_[1528101389635990529]": {
+                    "$type": "EditorLockComponent",
+                    "Id": 1528101389635990529
+                },
+                "Component_[15355821739017208405]": {
+                    "$type": "EditorPendingCompositionComponent",
+                    "Id": 15355821739017208405
+                },
+                "Component_[16208993720168044799]": {
+                    "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent",
+                    "Id": 16208993720168044799,
+                    "Parent Entity": "ContainerEntity"
+                }
+            }
+        },
+        "Entity_[799444499120]": {
+            "Id": "Entity_[799444499120]",
+            "Name": "sensor",
+            "Components": {
+                "Component_[11165492144511882665]": {
+                    "$type": "EditorEntityIconComponent",
+                    "Id": 11165492144511882665
+                },
+                "Component_[12018107220451562856]": {
+                    "$type": "EditorInspectorComponent",
+                    "Id": 12018107220451562856
+                },
+                "Component_[13764353402899190262]": {
+                    "$type": "SelectionComponent",
+                    "Id": 13764353402899190262
+                },
+                "Component_[14000889612025468259]": {
+                    "$type": "EditorLockComponent",
+                    "Id": 14000889612025468259
+                },
+                "Component_[14513347572674200286]": {
+                    "$type": "EditorDisabledCompositionComponent",
+                    "Id": 14513347572674200286
+                },
+                "Component_[15502515964653740915]": {
+                    "$type": "GenericComponentWrapper",
+                    "Id": 15502515964653740915,
+                    "m_template": {
+                        "$type": "ROS2ImuSensorComponent",
+                        "SensorConfiguration": {
+                            "Publishers": {
+                                "sensor_msgs::msg::Imu": {
+                                    "Type": "sensor_msgs::msg::Imu",
+                                    "Topic": "imu"
+                                }
+                            }
+                        }
+                    }
+                },
+                "Component_[16218438073533307497]": {
+                    "$type": "EditorPendingCompositionComponent",
+                    "Id": 16218438073533307497
+                },
+                "Component_[16632774364418825439]": {
+                    "$type": "EditorOnlyEntityComponent",
+                    "Id": 16632774364418825439
+                },
+                "Component_[3132451334418324807]": {
+                    "$type": "EditorEntitySortComponent",
+                    "Id": 3132451334418324807
+                },
+                "Component_[6224354809090239739]": {
+                    "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent",
+                    "Id": 6224354809090239739,
+                    "Parent Entity": "Entity_[690707340010]"
+                },
+                "Component_[8034005069780921515]": {
+                    "$type": "GenericComponentWrapper",
+                    "Id": 8034005069780921515,
+                    "m_template": {
+                        "$type": "ROS2FrameComponent",
+                        "Namespace Configuration": {
+                            "Namespace Strategy": 1
+                        },
+                        "Frame Name": "imu"
+                    }
+                },
+                "Component_[993718121342624828]": {
+                    "$type": "EditorVisibilityComponent",
+                    "Id": 993718121342624828
+                }
+            }
+        }
+    }
+}

+ 274 - 0
Gems/ROS2/Assets/Prefabs/Sensors/LidarOS2.prefab

@@ -0,0 +1,274 @@
+{
+    "ContainerEntity": {
+        "Id": "ContainerEntity",
+        "Name": "LidarOS2",
+        "Components": {
+            "Component_[11054240545439632695]": {
+                "$type": "EditorPendingCompositionComponent",
+                "Id": 11054240545439632695
+            },
+            "Component_[12267693294305582384]": {
+                "$type": "EditorPrefabComponent",
+                "Id": 12267693294305582384
+            },
+            "Component_[13753717631378233523]": {
+                "$type": "EditorInspectorComponent",
+                "Id": 13753717631378233523
+            },
+            "Component_[14137377640240728084]": {
+                "$type": "EditorOnlyEntityComponent",
+                "Id": 14137377640240728084
+            },
+            "Component_[17530435867448426523]": {
+                "$type": "EditorLockComponent",
+                "Id": 17530435867448426523
+            },
+            "Component_[18045801361193694910]": {
+                "$type": "EditorEntityIconComponent",
+                "Id": 18045801361193694910
+            },
+            "Component_[3246013088748543125]": {
+                "$type": "EditorEntitySortComponent",
+                "Id": 3246013088748543125,
+                "Child Entity Order": [
+                    "Entity_[417184227077]"
+                ]
+            },
+            "Component_[5389828146587111030]": {
+                "$type": "EditorDisabledCompositionComponent",
+                "Id": 5389828146587111030
+            },
+            "Component_[5989395931141545750]": {
+                "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent",
+                "Id": 5989395931141545750,
+                "Parent Entity": ""
+            },
+            "Component_[6982904761689756877]": {
+                "$type": "EditorVisibilityComponent",
+                "Id": 6982904761689756877
+            }
+        }
+    },
+    "Entities": {
+        "Entity_[1273969985848]": {
+            "Id": "Entity_[1273969985848]",
+            "Name": "Sensor",
+            "Components": {
+                "Component_[1058300359310703890]": {
+                    "$type": "GenericComponentWrapper",
+                    "Id": 1058300359310703890,
+                    "m_template": {
+                        "$type": "ROS2FrameComponent",
+                        "Namespace Configuration": {
+                            "Namespace Strategy": 1
+                        },
+                        "Frame Name": "lidar"
+                    }
+                },
+                "Component_[1067011392942644324]": {
+                    "$type": "EditorEntityIconComponent",
+                    "Id": 1067011392942644324
+                },
+                "Component_[10842630456390194262]": {
+                    "$type": "GenericComponentWrapper",
+                    "Id": 10842630456390194262,
+                    "m_template": {
+                        "$type": "ROS2LidarSensorComponent",
+                        "SensorConfiguration": {
+                            "Publishers": {
+                                "sensor_msgs::msg::PointCloud2": {
+                                    "Type": "sensor_msgs::msg::PointCloud2",
+                                    "Topic": "pc"
+                                }
+                            }
+                        },
+                        "LidarTransparentEntityId": ""
+                    }
+                },
+                "Component_[13013301136498148727]": {
+                    "$type": "EditorPendingCompositionComponent",
+                    "Id": 13013301136498148727
+                },
+                "Component_[13444160964623421011]": {
+                    "$type": "EditorLockComponent",
+                    "Id": 13444160964623421011
+                },
+                "Component_[14110476541514862518]": {
+                    "$type": "EditorOnlyEntityComponent",
+                    "Id": 14110476541514862518
+                },
+                "Component_[17124975368240161289]": {
+                    "$type": "EditorVisibilityComponent",
+                    "Id": 17124975368240161289
+                },
+                "Component_[1863593986687460136]": {
+                    "$type": "EditorDisabledCompositionComponent",
+                    "Id": 1863593986687460136
+                },
+                "Component_[3070834537283917104]": {
+                    "$type": "EditorInspectorComponent",
+                    "Id": 3070834537283917104
+                },
+                "Component_[7054215981025466544]": {
+                    "$type": "EditorEntitySortComponent",
+                    "Id": 7054215981025466544
+                },
+                "Component_[8854312938977545065]": {
+                    "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent",
+                    "Id": 8854312938977545065,
+                    "Parent Entity": "Entity_[417184227077]",
+                    "Transform Data": {
+                        "Translate": [
+                            0.0,
+                            0.0,
+                            0.06961321830749512
+                        ]
+                    }
+                }
+            }
+        },
+        "Entity_[417184227077]": {
+            "Id": "Entity_[417184227077]",
+            "Name": "LidarOS2",
+            "Components": {
+                "Component_[10387713135725007135]": {
+                    "$type": "AZ::Render::EditorMeshComponent",
+                    "Id": 10387713135725007135,
+                    "Controller": {
+                        "Configuration": {
+                            "ModelAsset": {
+                                "assetId": {
+                                    "guid": "{99D850A3-8E59-5E78-93FE-AE8DB228684D}",
+                                    "subId": 268955557
+                                },
+                                "assetHint": "models/sensors/lidaros2/lidaros2.azmodel"
+                            }
+                        }
+                    }
+                },
+                "Component_[11659553095730431803]": {
+                    "$type": "EditorPendingCompositionComponent",
+                    "Id": 11659553095730431803
+                },
+                "Component_[13177781661023720894]": {
+                    "$type": "EditorOnlyEntityComponent",
+                    "Id": 13177781661023720894
+                },
+                "Component_[14535670656735835043]": {
+                    "$type": "EditorColliderComponent",
+                    "Id": 14535670656735835043,
+                    "ColliderConfiguration": {
+                        "CollisionGroupId": {
+                            "GroupId": "{DED3A46E-3588-4BB8-BADF-E3114DBA95A6}"
+                        },
+                        "MaterialSlots": {
+                            "Slots": [
+                                {
+                                    "Name": "DefaultMaterial"
+                                }
+                            ]
+                        },
+                        "MaterialSelection": {
+                            "MaterialIds": [
+                                {}
+                            ]
+                        }
+                    },
+                    "ShapeConfiguration": {
+                        "PhysicsAsset": {
+                            "Asset": {
+                                "assetId": {
+                                    "guid": "{99D850A3-8E59-5E78-93FE-AE8DB228684D}",
+                                    "subId": 2574200790
+                                },
+                                "assetHint": "models/sensors/lidaros2/lidaros2.pxmesh"
+                            },
+                            "Configuration": {
+                                "PhysicsAsset": {
+                                    "assetId": {
+                                        "guid": "{99D850A3-8E59-5E78-93FE-AE8DB228684D}",
+                                        "subId": 2574200790
+                                    },
+                                    "loadBehavior": "QueueLoad",
+                                    "assetHint": "models/sensors/lidaros2/lidaros2.pxmesh"
+                                }
+                            }
+                        }
+                    }
+                },
+                "Component_[17075071070477116796]": {
+                    "$type": "EditorDisabledCompositionComponent",
+                    "Id": 17075071070477116796
+                },
+                "Component_[3201525403568523459]": {
+                    "$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent",
+                    "Id": 3201525403568523459,
+                    "Parent Entity": "ContainerEntity"
+                },
+                "Component_[5004394864880282263]": {
+                    "$type": "EditorEntityIconComponent",
+                    "Id": 5004394864880282263
+                },
+                "Component_[5142969012241927438]": {
+                    "$type": "EditorInspectorComponent",
+                    "Id": 5142969012241927438,
+                    "ComponentOrderEntryArray": [
+                        {
+                            "ComponentId": 3201525403568523459
+                        },
+                        {
+                            "ComponentId": 10387713135725007135,
+                            "SortIndex": 1
+                        },
+                        {
+                            "ComponentId": 18284489930066132216,
+                            "SortIndex": 2
+                        },
+                        {
+                            "ComponentId": 98811077871648366,
+                            "SortIndex": 3
+                        }
+                    ]
+                },
+                "Component_[6216877561479815861]": {
+                    "$type": "EditorMaterialComponent",
+                    "Id": 6216877561479815861,
+                    "Controller": {
+                        "Configuration": {
+                            "materials": [
+                                {
+                                    "Key": {
+                                        "materialSlotStableId": 3498918361
+                                    },
+                                    "Value": {
+                                        "MaterialAsset": {
+                                            "assetId": {
+                                                "guid": "{305BAE93-C067-5CA7-8C6F-6D9737E15C1B}"
+                                            },
+                                            "assetHint": "models/sensors/lidaros2/lidaros2_os2material.azmaterial"
+                                        }
+                                    }
+                                }
+                            ]
+                        }
+                    }
+                },
+                "Component_[6868930103042649999]": {
+                    "$type": "EditorLockComponent",
+                    "Id": 6868930103042649999
+                },
+                "Component_[8621809176728013021]": {
+                    "$type": "EditorVisibilityComponent",
+                    "Id": 8621809176728013021
+                },
+                "Component_[8808389010192194694]": {
+                    "$type": "EditorEntitySortComponent",
+                    "Id": 8808389010192194694,
+                    "Child Entity Order": [
+                        "Entity_[1273969985848]"
+                    ]
+                }
+            }
+        }
+    }
+}

+ 26 - 0
Gems/ROS2/CMakeLists.txt

@@ -0,0 +1,26 @@
+# 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
+
+# Query the gem name from the gem.json file if possible
+# otherwise fallback to using ${Name}
+o3de_find_ancestor_gem_root(gempath gem_name "${CMAKE_CURRENT_SOURCE_DIR}")
+if (NOT gem_name)
+    set(gem_name "ROS2")
+endif()
+
+list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/Code")
+set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} PARENT_SCOPE)
+
+set(gem_path ${CMAKE_CURRENT_LIST_DIR})
+set(gem_json ${gem_path}/gem.json)
+o3de_restricted_path(${gem_json} gem_restricted_path gem_parent_relative_path)
+
+ly_add_external_target_path(${CMAKE_CURRENT_LIST_DIR}/3rdParty)
+
+# Include ROS2 target function here so that every Project with this Gem can make use of it
+# Currently only affects the Gem itself but this likely will change in the future
+include(${CMAKE_CURRENT_LIST_DIR}/Code/ros2_target_depends.cmake)
+
+add_subdirectory(Code)

+ 18 - 0
Gems/ROS2/CONTRIBUTING.md

@@ -0,0 +1,18 @@
+
+### Code of conduct
+
+Please refer to and respect the [O3DE Code of Conduct](https://www.o3de.org/docs/contributing/code-of-conduct/).
+
+### Authorship and Developer Certificate of Origin (DCO)
+
+When creating a pull request to this repository, make sure that:
+- Authorship of the code is properly attributed. You need to agree with the [Developer Certificate of Origin](https://developercertificate.org/) to contribute. Some important points:
+  - Make sure your username and user email are [set up](https://docs.github.com/en/get-started/getting-started-with-git/setting-your-username-in-git).
+  - If you commit parts of code which are not yours, make sure you are allowed to do so (in terms of license) and respect all the requirements of the license, such as Attribution.
+  - If you are not the author of the commit, indicate authorship with `--author` flag of `git commit` command.
+  - Try to divide your work into commits done by a single author. If you decide to use a single commit for a multi-author work, use the [Co-authored-by syntax](https://docs.github.com/en/pull-requests/committing-changes-to-your-project/creating-and-editing-commits/creating-a-commit-with-multiple-authors) in the commit message.
+- Your commits are signed, indicating complience with the DCO: use `git commit -s` to ensure that.
+
+### Additional information
+
+We recommend you to read [O3DE contribution guide](https://github.com/o3de/community/blob/main/CONTRIBUTING.md) and [ROS contribution guide](https://docs.ros.org/en/humble/Contributing.html) as well.

+ 193 - 0
Gems/ROS2/Code/CMakeLists.txt

@@ -0,0 +1,193 @@
+# 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
+
+
+# Only allow this gem to be configured on platforms that are currently supported
+include(${CMAKE_CURRENT_SOURCE_DIR}/Platform/${PAL_PLATFORM_NAME}/PAL_${PAL_PLATFORM_NAME_LOWERCASE}.cmake)
+if(NOT PAL_TRAIT_BUILD_ROS2_GEM_SUPPORTED)
+    message(FATAL_ERROR "The ROS2 Gem is not currently supported on ${PAL_PLATFORM_NAME}")
+    return()
+endif()
+
+# If no ROS2 is found, no targets are valid for this Gem
+find_package(ROS2 MODULE)
+if (NOT ROS2_FOUND)
+    message(FATAL_ERROR "Unable to detect a the ROS2 distribution on this system. Make sure it is installed and enabled.")
+    return()
+endif()
+
+# Add the ROS2.Static target
+# Note: We include the common files and the platform specific files which are set in ros2_common_files.cmake
+# and in ${pal_dir}/ros2_${PAL_PLATFORM_NAME_LOWERCASE}_files.cmake
+ly_add_target(
+    NAME ${gem_name}.Static STATIC
+    NAMESPACE Gem
+    FILES_CMAKE
+        ros2_header_files.cmake
+        ros2_files.cmake
+    INCLUDE_DIRECTORIES
+        PUBLIC
+            Include
+        PRIVATE
+            Source
+    BUILD_DEPENDENCIES
+        PUBLIC
+            AZ::AzCore
+            AZ::AzFramework
+            Gem::Atom_RPI.Public
+            Gem::Atom_Feature_Common.Static
+            Gem::Atom_Component_DebugCamera.Static
+            Gem::StartingPointInput
+)
+
+target_depends_on_ros2_packages(${gem_name}.Static rclcpp builtin_interfaces std_msgs sensor_msgs nav_msgs urdfdom tf2_ros ackermann_msgs gazebo_msgs control_toolbox)
+
+ly_add_target(
+    NAME ${gem_name}.API HEADERONLY
+    NAMESPACE Gem
+    FILES_CMAKE
+        ros2_header_files.cmake
+    INCLUDE_DIRECTORIES
+        INTERFACE
+            Include
+)
+
+# Here add ROS2 target, it depends on the ROS2.Static
+ly_add_target(
+    NAME ${gem_name} ${PAL_TRAIT_MONOLITHIC_DRIVEN_MODULE_TYPE}
+    NAMESPACE Gem
+    FILES_CMAKE
+        ros2_shared_files.cmake
+    INCLUDE_DIRECTORIES
+        PUBLIC
+            Include
+        PRIVATE
+            Source
+    BUILD_DEPENDENCIES
+        PRIVATE
+            Gem::${gem_name}.Static
+            Gem::Atom_Feature_Common.Static
+)
+
+# By default, we will specify that the above target ROS2 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})
+
+# If we are on a host platform, we want to add the host tools targets like the ${gem_name}.Editor target which
+# will also depend on ${gem_name}.Static
+if(PAL_TRAIT_BUILD_HOST_TOOLS)
+    ly_add_target(
+        NAME ${gem_name}.Editor.Static STATIC
+        NAMESPACE Gem
+        AUTOMOC
+        AUTORCC
+        FILES_CMAKE
+            ros2_editor_files.cmake
+        PLATFORM_INCLUDE_FILES
+            ${CMAKE_CURRENT_LIST_DIR}/Platform/Common/${PAL_TRAIT_COMPILER_ID}/ros2_static_editor_${PAL_TRAIT_COMPILER_ID_LOWERCASE}.cmake
+        INCLUDE_DIRECTORIES
+            PRIVATE
+                Source
+            PUBLIC
+                Include
+        COMPILE_DEFINITIONS
+            PRIVATE
+                ROS2_EDITOR
+        BUILD_DEPENDENCIES
+            PUBLIC
+                AZ::AzToolsFramework
+                Gem::AtomLyIntegration_CommonFeatures.Editor.Static
+                Gem::LmbrCentral.API
+                Gem::PhysX.Editor.Static
+                Gem::${gem_name}.Static
+    )
+
+    ly_add_target(
+        NAME ${gem_name}.Editor GEM_MODULE
+        NAMESPACE Gem
+        FILES_CMAKE
+            ros2_editor_files.cmake
+            ros2_editor_shared_files.cmake
+        PLATFORM_INCLUDE_FILES
+            ${CMAKE_CURRENT_LIST_DIR}/Platform/Common/${PAL_TRAIT_COMPILER_ID}/ros2_static_editor_${PAL_TRAIT_COMPILER_ID_LOWERCASE}.cmake
+        COMPILE_DEFINITIONS
+            PRIVATE
+                ROS2_EDITOR
+        INCLUDE_DIRECTORIES
+            PRIVATE
+                Source
+            PUBLIC
+                Include
+        BUILD_DEPENDENCIES
+            PUBLIC
+                Gem::${gem_name}.Editor.Static
+                Gem::Atom_Feature_Common.Static
+    )
+
+    # By default, we will specify that the above target ROS2 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)
+endif()
+
+################################################################################
+# Tests
+################################################################################
+# See if globally, tests are supported
+if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
+    if(PAL_TRAIT_TEST_GOOGLE_TEST_SUPPORTED)
+        # We support ROS2.Tests on this platform, add ROS2.Tests target which depends on ROS2.Static
+        ly_add_target(
+            NAME ${gem_name}.Tests ${PAL_TRAIT_TEST_TARGET_TYPE}
+            NAMESPACE Gem
+            FILES_CMAKE
+                ros2_tests_files.cmake
+            INCLUDE_DIRECTORIES
+                PRIVATE
+                    Tests
+                    Source
+            BUILD_DEPENDENCIES
+                PRIVATE
+                    AZ::AzTest
+                    Gem::${gem_name}.Static
+        )
+
+        # Add ROS2.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)
+        if(PAL_TRAIT_TEST_GOOGLE_TEST_SUPPORTED)
+            # We support ROS2.Editor.Tests on this platform, add ROS2.Editor.Tests target which depends on ROS2.Editor
+            ly_add_target(
+                NAME ${gem_name}.Editor.Tests ${PAL_TRAIT_TEST_TARGET_TYPE}
+                NAMESPACE Gem
+                FILES_CMAKE
+                    ros2_editor_tests_files.cmake
+                PLATFORM_INCLUDE_FILES
+                    ${CMAKE_CURRENT_LIST_DIR}/Platform/Common/${PAL_TRAIT_COMPILER_ID}/ros2_static_editor_${PAL_TRAIT_COMPILER_ID_LOWERCASE}.cmake
+                INCLUDE_DIRECTORIES
+                    PRIVATE
+                        Tests
+                        Source
+                BUILD_DEPENDENCIES
+                    PRIVATE
+                        AZ::AzTest
+                        Gem::${gem_name}.Editor
+            )
+
+            # Add ROS2.Editor.Tests to googletest
+            ly_add_googletest(
+                NAME Gem::${gem_name}.Editor.Tests
+            )
+        endif()
+    endif()
+endif()

+ 13 - 0
Gems/ROS2/Code/FindROS2.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
+
+# Note that this does not find any ros2 package in particular, but determines whether a distro is sourced properly
+# Can be extended to handle supported / unsupported distros
+if (NOT DEFINED ENV{ROS_DISTRO} OR NOT DEFINED ENV{AMENT_PREFIX_PATH})
+    message(WARNING, "To build ROS2 Gem a ROS distribution needs to be sourced, but none detected")
+    set(ROS2_FOUND FALSE)
+    return()
+endif()
+set(ROS2_FOUND TRUE)

+ 37 - 0
Gems/ROS2/Code/Include/ROS2/Communication/QoS.h

@@ -0,0 +1,37 @@
+/*
+ * 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 <AzCore/RTTI/RTTI.h>
+#include <AzCore/Serialization/SerializeContext.h>
+#include <rclcpp/qos.hpp>
+
+namespace ROS2
+{
+    //! A wrapper for rclcpp::QoS (Quality of Service for DDS) with reflection.
+    //! @see <a href="https://design.ros2.org/articles/qos.html">Quality of Service policies</a>.
+    struct QoS
+    {
+    public:
+        AZ_TYPE_INFO(QoS, "{46692EA4-EA4C-495E-AD3C-426EAB8954D3}");
+        QoS(const rclcpp::QoS& qos = rclcpp::QoS(rmw_qos_profile_default.depth));
+        static void Reflect(AZ::ReflectContext* context);
+
+        //! Convert and return a rclcpp::QoS instance based on member values (Editor combos selection).
+        //! @return a <a href="https://docs.ros2.org/latest/api/rclcpp/classrclcpp_1_1QoS.html">ROS2 QoS struct</a>.
+        rclcpp::QoS GetQoS() const;
+
+    private:
+        AZ::Crc32 OnQoSSelected() const;
+
+        rclcpp::ReliabilityPolicy m_reliabilityPolicy;
+        rclcpp::DurabilityPolicy m_durabilityPolicy;
+        uint32_t m_depth;
+    };
+} // namespace ROS2

+ 37 - 0
Gems/ROS2/Code/Include/ROS2/Communication/TopicConfiguration.h

@@ -0,0 +1,37 @@
+/*
+ * 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 "QoS.h"
+#include <AzCore/RTTI/RTTI.h>
+#include <AzCore/Serialization/SerializeContext.h>
+#include <AzCore/std/string/string.h>
+
+namespace ROS2
+{
+    //! A structure for a single ROS2 topic, a part of publisher or subscriber configuration.
+    struct TopicConfiguration
+    {
+    public:
+        AZ_TYPE_INFO(TopicConfiguration, "{7535D58F-5284-4657-A799-1F69D3F5AA42}");
+        static void Reflect(AZ::ReflectContext* context);
+
+        AZStd::string m_type = "std_msgs::msg::Empty"; //!< descriptive topic type for identification.
+        AZStd::string m_topic = "default_topic"; //!< Topic to publish. Final topic will have a namespace added.
+
+        //! Get topic QoS (Quality of Service) settings.
+        //! @see ROS2::QoS.
+        rclcpp::QoS GetQoS() const
+        {
+            return m_qos.GetQoS();
+        }
+
+    private:
+        QoS m_qos = rclcpp::SensorDataQoS();
+    };
+} // namespace ROS2

+ 58 - 0
Gems/ROS2/Code/Include/ROS2/Frame/NamespaceConfiguration.h

@@ -0,0 +1,58 @@
+/*
+ * 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 <AzCore/RTTI/RTTI.h>
+#include <AzCore/Serialization/SerializeContext.h>
+#include <AzCore/std/string/string.h>
+
+namespace ROS2
+{
+    //! Configuration for handling of namespaces.
+    //! Namespaces are useful for various ROS2 components. This structure encapsulates the namespace itself,
+    //! composing namespaces and context-dependent default values.
+    //! @note This structure is handled through ROS2FrameComponent.
+    struct NamespaceConfiguration
+    {
+    public:
+        AZ_TYPE_INFO(NamespaceConfiguration, "{5E5BC6EA-DD01-480E-A4D1-6857CF70FDC8}");
+        static void Reflect(AZ::ReflectContext* context);
+
+        //! A choice of methods to handle namespaces.
+        //! @note Top level ROS2FrameComponent will likely be associated with an interesting object (robot). For multi-robot
+        //! simulations, namespaces are often derived from the robot name itself. For this reason, the default behavior
+        //! for top level ROS2FrameComponent is to generate the namespace from entity name.
+        enum class NamespaceStrategy
+        {
+            Default, //!< FromEntityName for top-level frames, Empty otherwise.
+            Empty, //!< An empty, blank namespace
+            FromEntityName, //!< Generate from Entity name, but substitute disallowed characters through RosifyName.
+            Custom //!< Non-empty and based on user-provided value.
+        };
+
+        //! Set namespace based on context.
+        //! @param isRoot Whether or not the namespace belongs to top-level entity in the entity hierarchy.
+        //! @param entityName Raw (not ros-ified) name of the entity to which the namespace belongs.
+        void PopulateNamespace(bool isRoot, const AZStd::string& entityName);
+        AZStd::string GetNamespace(const AZStd::string& parentNamespace) const;
+
+    private:
+        AZStd::string m_namespace;
+        NamespaceStrategy m_namespaceStrategy = NamespaceStrategy::Default;
+        bool m_isRoot;
+        AZStd::string m_entityName;
+
+        //! Determine if namespace is using the Custom namespace strategy
+        bool IsNamespaceCustom() const;
+
+        //! Update the namespace based on the current attributes
+        void UpdateNamespace();
+
+        AZ::Crc32 OnNamespaceStrategySelected();
+    };
+} // namespace ROS2

+ 91 - 0
Gems/ROS2/Code/Include/ROS2/Frame/ROS2FrameComponent.h

@@ -0,0 +1,91 @@
+/*
+ * 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 <AzCore/Component/Component.h>
+#include <AzCore/std/smart_ptr/unique_ptr.h>
+#include <AzFramework/Components/TransformComponent.h>
+#include <ROS2/Frame/NamespaceConfiguration.h>
+#include <ROS2/Frame/ROS2Transform.h>
+#include <ROS2/ROS2GemUtilities.h>
+
+namespace ROS2
+{
+    //! This component marks an interesting reference frame for ROS2 ecosystem.
+    //! It serves as sensor data frame of reference and is responsible, through ROS2Transform, for publishing
+    //! ros2 static and dynamic transforms (/tf_static, /tf). It also facilitates namespace handling.
+    //! An entity can only have a single ROS2Frame on each level. Many ROS2 Components require this component.
+    //! @note A robot should have this component on every level of entity hierarchy (for each joint, fixed or dynamic)
+    class ROS2FrameComponent
+        : public AZ::Component
+        , public AZ::TickBus::Handler
+    {
+    public:
+        AZ_COMPONENT(ROS2FrameComponent, "{EE743472-3E25-41EA-961B-14096AC1D66F}");
+
+        ROS2FrameComponent();
+        //! Initialize to a specific frame id
+        ROS2FrameComponent(const AZStd::string& frameId);
+
+        //////////////////////////////////////////////////////////////////////////
+        // Component overrides
+        void Activate() override;
+        void Deactivate() override;
+        //////////////////////////////////////////////////////////////////////////
+
+        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);
+
+        //! Get a frame id, which is needed for any ROS2 message with a Header
+        //! @return Frame id which includes the namespace, ready to send in a ROS2 message
+        AZStd::string GetFrameID() const;
+
+        //! Set a above-mentioned frame id
+        void SetFrameID(const AZStd::string& frameId);
+
+        //! Get a namespace, which should be used for any publisher or subscriber in the same entity.
+        //! @return A complete namespace (including parent namespaces)
+        AZStd::string GetNamespace() const;
+
+        //! Get AZ Transform for this frame.
+        //! @return If parent ROS2Frame is found, return its Transform.
+        //! Otherwise, return a global Transform.
+        const AZ::Transform& GetFrameTransform() const;
+
+        //! Global frame name in ros2 ecosystem.
+        //! @return The name of the global frame with namespace attached. It is typically "odom", "map", "world".
+        AZStd::string GetGlobalFrameName() const;
+
+    private:
+        //////////////////////////////////////////////////////////////////////////
+        // AZ::TickBus::Handler overrides
+        void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
+        //////////////////////////////////////////////////////////////////////////
+
+        bool IsTopLevel() const; //!< True if this entity does not have a parent entity with ROS2.
+
+        //! Whether transformation to parent frame can change during the simulation, or is fixed.
+        bool IsDynamic() const;
+
+        const ROS2FrameComponent* GetParentROS2FrameComponent() const;
+
+        //! Return the frame id of this frame's parent. It can be useful to determine ROS 2 transformations.
+        //! @return Parent frame ID.
+        //! @note This also works with top-level frames, returning a global frame name.
+        //! @see GetGlobalFrameName().
+        AZStd::string GetParentFrameID() const;
+
+        NamespaceConfiguration m_namespaceConfiguration;
+        AZStd::string m_frameName = "sensor_frame";
+
+        bool m_publishTransform = true;
+        AZStd::unique_ptr<ROS2Transform> m_ros2Transform;
+    };
+} // namespace ROS2

+ 40 - 0
Gems/ROS2/Code/Include/ROS2/Frame/ROS2Transform.h

@@ -0,0 +1,40 @@
+/*
+ * 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 <AzCore/Math/Transform.h>
+#include <AzCore/std/string/string.h>
+#include <geometry_msgs/msg/transform_stamped.hpp>
+
+namespace ROS2
+{
+    //! Publishes transforms as standard ROS2 tf2 messages. Static transforms are published once.
+    //! @note This class is already used through ROS2FrameComponent.
+    class ROS2Transform
+    {
+    public:
+        //! Create a transform between given frames.
+        //! @param parentFrame id of parent frame of transformation.
+        //! @param childFrame id of child frame of transformation.
+        //! @param isDynamic whether the transformation is dynamic (should be computed every frame) or static (only once).
+        ROS2Transform(AZStd::string parentFrame, AZStd::string childFrame, bool isDynamic);
+
+        //! Construct and delegate publishing of a transform according to members' values.
+        //! @param transform AZ::Transform with current transformation between m_parentFrame and m_childFrame.
+        //! @note The actual publishing is done by singleton tf broadcasters.
+        void Publish(const AZ::Transform& transform);
+
+    private:
+        geometry_msgs::msg::TransformStamped CreateTransformMessage(const AZ::Transform& transform);
+
+        const AZStd::string m_parentFrame;
+        const AZStd::string m_childFrame;
+        bool m_isPublished = false;
+        bool m_isDynamic;
+    };
+} // namespace ROS2

+ 57 - 0
Gems/ROS2/Code/Include/ROS2/Manipulator/MotorizedJointBus.h

@@ -0,0 +1,57 @@
+/*
+ * 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 <AzCore/Component/EntityId.h>
+#include <AzCore/EBus/EBus.h>
+#include <AzCore/Interface/Interface.h>
+
+namespace ROS2
+{
+    //! Interface to communicate with motorized joints.
+    //! It allows to apply setpoint and tracking performance of the PID controller.
+    class MotorizedJointRequest : public AZ::EBusTraits
+    {
+    public:
+        static constexpr AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
+        using BusIdType = AZ::EntityId;
+
+        virtual ~MotorizedJointRequest() = default;
+
+        //! Set current setpoint value for position controller.
+        //! The setpoint is the desired position for a simulated actuator.
+        //! For linear actuators, the setpoint is given in meters.
+        //! For the rotational actuators, the setpoint is given in radians.
+        //! @param setpoint value in meters or radians
+        virtual void SetSetpoint(float setpoint) = 0;
+
+        //! Get current setpoint value for position controller.
+        //! If `SetSetpoint` has not been called, it returns initial position of joint.
+        //! For linear actuators, the setpoint is given in meters.
+        //! For the rotational actuators, the setpoint is given in radians.
+        //! @returns setpoint value in meters or radians
+        virtual float GetSetpoint() = 0;
+
+        //! Retrieve current measurement.
+        //! Measurement is the value of the movement - eg protrusion of an actuator.
+        //! For linear actuators, measurement is given in meters.
+        //! For the rotational actuators, measurement is given in radians.
+        //! When the setpoint is reached this should be close to setpoint.
+        //! @returns measurement value in meters or radians.
+        virtual float GetCurrentMeasurement() = 0;
+
+        //! Retrieve current control error (difference between setpoint and measurement)
+        //! When the setpoint is reached this should be close to zero.
+        //! For linear actuators, the error is given in meters.
+        //! For the rotational actuators, the error is given in radians.
+        //! @returns controller error's value in meters or radians
+        virtual float GetError() = 0;
+    };
+
+    using MotorizedJointRequestBus = AZ::EBus<MotorizedJointRequest>;
+} // namespace ROS2

+ 93 - 0
Gems/ROS2/Code/Include/ROS2/Manipulator/MotorizedJointComponent.h

@@ -0,0 +1,93 @@
+/*
+ * 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 "MotorizedJointBus.h"
+#include <AzCore/Component/Component.h>
+#include <AzCore/Component/TickBus.h>
+#include <AzCore/Math/Transform.h>
+#include <AzCore/Math/Vector2.h>
+#include <ROS2/VehicleDynamics/DriveModels/PidConfiguration.h>
+
+namespace ROS2
+{
+    //! A prototype component for simulated joint with a motor.
+    //! It works with either TransformBus or RigidBodyBus.
+    //! TransformBus mode, called `AnimationMode` changes local transform. In this mode, you cannot have a rigid body
+    //! controller enabled. With RigidBodyBus it applies forces and torque according to PID control.
+    //! @note This class is already used through ROS2FrameComponent.
+    class MotorizedJointComponent
+        : public AZ::Component
+        , public AZ::TickBus::Handler
+        , public MotorizedJointRequestBus::Handler
+    {
+    public:
+        AZ_COMPONENT(MotorizedJointComponent, "{AE9207DB-5B7E-4F70-A7DD-C4EAD8DD9403}", AZ::Component);
+
+        MotorizedJointComponent() = default;
+        ~MotorizedJointComponent() = default;
+        //////////////////////////////////////////////////////////////////////////
+        // Component overrides
+        void Activate() override;
+        void Deactivate() override;
+        static void Reflect(AZ::ReflectContext* context);
+        //////////////////////////////////////////////////////////////////////////
+
+        ////////////////////////////////////////////////////////////////////////
+        // MotorizedJointRequestBus::Handler overrides
+        void SetSetpoint(float setpoint) override;
+        float GetSetpoint() override;
+        float GetError() override;
+        float GetCurrentMeasurement() override;
+        ////////////////////////////////////////////////////////////////////////
+
+        //! Get a degree of freedom direction.
+        //! @returns direction of joint movement in global coordinates.
+        AZ::Vector3 GetDir() const
+        {
+            return m_jointDir;
+        };
+
+    private:
+        float ComputeMeasurement(AZ::ScriptTimePoint time);
+        void SetVelocity(float velocity /* m/s */, float deltaTime /* seconds */);
+        void ApplyLinVelAnimation(float velocity /* m/s */, float deltaTime /* seconds */);
+        void ApplyLinVelRigidBodyImpulse(float velocity /* m/s */, float deltaTime /* seconds */);
+        void ApplyLinVelRigidBody(float velocity /* m/s */, float deltaTime /* seconds */);
+        //////////////////////////////////////////////////////////////////////////
+        // AZ::TickBus::Handler overrides
+        void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
+        //////////////////////////////////////////////////////////////////////////
+
+        AZ::Vector3 m_jointDir{ 0.f, 0.f, 1.f }; //!< Direction of joint movement in parent frame of reference, used to compute measurement.
+        AZ::Vector3 m_effortAxis{ 0.f, 0.f, 1.f }; //!< Direction of force or torque application in owning entity frame of reference.
+        AZStd::pair<float, float> m_limits{ -0.5f, 0.5f }; //!< limits of joint, the force is applied only when joint is within limits.
+        VehicleDynamics::PidConfiguration m_pidPos; //!< PID controller for position.
+
+        bool m_linear{ true }; //!< Linear mode. The force is applied through RigidBodyBus.
+        bool m_animationMode{ true }; //!< Use TransformBus (animation mode, no physics) instead of RigidBodyBus.
+
+        bool m_testSinusoidal{ true }; //!< Enable sinusoidal signal generator to setpoint (for tuning).
+        float m_sinAmplitude{ 0.5 }; //!< Amplitude of test signal generator.
+        float m_sinDC{ 0.25 }; //!< DC of test signal generator.
+        float m_sinFreq{ 0.1 }; //!< Frequency of test signal generator.
+
+        float m_zeroOffset{ 0.f }; //!< offset added to setpoint.
+        float m_setpoint{ 0 }; //!< Desired local position.
+        float m_error{ 0 }; //!< Current error (difference between control value and measurement).
+        float m_currentPosition{ 0 }; //!< Last measured position.
+        float m_currentVelocity{ 0 }; //!< Last measured velocity.
+        double m_lastMeasurementTime{ 0 }; //!< Last measurement time in seconds.
+
+        AZ::EntityId m_debugDrawEntity; //!< Optional Entity that allows to visualize desired setpoint value.
+        AZ::Transform m_debugDrawEntityInitialTransform; //!< Initial transform of m_debugDrawEntity.
+        bool m_debugPrint{ false }; //!< Print debug info to the console.
+
+        AZ::EntityId m_measurementReferenceEntity; //!< Entity used for reference for measurements. Defaults to parent entity.
+    };
+} // namespace ROS2

+ 67 - 0
Gems/ROS2/Code/Include/ROS2/ROS2Bus.h

@@ -0,0 +1,67 @@
+/*
+ * 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 <AzCore/EBus/EBus.h>
+#include <AzCore/Interface/Interface.h>
+#include <builtin_interfaces/msg/time.hpp>
+#include <geometry_msgs/msg/transform_stamped.hpp>
+#include <rclcpp/node.hpp>
+
+namespace ROS2
+{
+    //! Interface to the central ROS2SystemComponent.
+    //! Use this API through ROS2Interface, for example:
+    //! @code
+    //! auto node = ROS2Interface::Get()->GetNode();
+    //! @endcode
+    class ROS2Requests
+    {
+    public:
+        AZ_RTTI(ROS2Requests, "{a9bdbff6-e644-430d-8096-cdb53c88e8fc}");
+        virtual ~ROS2Requests() = default;
+
+        //! Get a central ROS2 node of the Gem.
+        //! You can use this node to create publishers and subscribers.
+        //! @return The central ROS2 node which holds default publishers for core topics such as /clock and /tf.
+        //! @note Alternatively, you can use your own node along with an executor.
+        virtual std::shared_ptr<rclcpp::Node> GetNode() const = 0;
+
+        //! Acquire current time as ROS2 timestamp.
+        //! Timestamps provide temporal context for messages such as sensor data.
+        //! @code
+        //! auto message = sensor_msgs::msg::PointCloud2();
+        //! message.header.stamp = ROS2Interface::Get()->GetROSTimestamp();
+        //! @endcode
+        //! @return Simulation time in ROS2 format. Time source is also valid with non-real time settings.
+        //! @note Make sure to set the use_sim_time parameter for ROS2 nodes which will use the simulation data.
+        virtual builtin_interfaces::msg::Time GetROSTimestamp() const = 0;
+
+        //! Send transformation between ROS2 frames.
+        //! @param t is a <a href="https://docs.ros2.org/latest/api/geometry_msgs/msg/TransformStamped.html">ROS2 TransformStamped
+        //! message</a>.
+        //! @param isDynamic controls whether a static or dynamic transform is sent. Static transforms are published
+        //! only once and are to be used when the spatial relationship between two frames does not change.
+        //! @note Transforms are already published by each ROS2FrameComponent.
+        //! Use this function directly only when default behavior of ROS2FrameComponent is not sufficient.
+        virtual void BroadcastTransform(const geometry_msgs::msg::TransformStamped& t, bool isDynamic) const = 0;
+    };
+
+    class ROS2BusTraits : public AZ::EBusTraits
+    {
+    public:
+        //////////////////////////////////////////////////////////////////////////
+        // EBusTraits overrides
+        static constexpr AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
+        static constexpr AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
+        //////////////////////////////////////////////////////////////////////////
+    };
+
+    using ROS2RequestBus = AZ::EBus<ROS2Requests, ROS2BusTraits>;
+    using ROS2Interface = AZ::Interface<ROS2Requests>;
+} // namespace ROS2

+ 47 - 0
Gems/ROS2/Code/Include/ROS2/ROS2GemUtilities.h

@@ -0,0 +1,47 @@
+/*
+ * 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 <AzCore/Component/ComponentBus.h>
+#include <AzCore/Component/Entity.h>
+#ifdef ROS2_EDITOR
+#include <AzToolsFramework/ToolsComponents/GenericComponentWrapper.h>
+#endif
+namespace ROS2
+{
+    namespace Utils
+    {
+        /// Create component for a given entity in safe way.
+        /// @param entityId entity that will own component
+        /// @param componentType Uuid of component to create
+        /// @return The created componentId if successful, otherwise returns an invalid id
+        AZ::ComponentId CreateComponent(const AZ::EntityId entityId, const AZ::Uuid componentType);
+
+        /// Retrieve component from entity given by a pointer. It is a way to get game components and wrapped components.
+        /// We should use that that we are not sure if we access eg ROS2FrameComponent in game mode or from Editor
+        /// @param entity pointer to entity eg with GetEntity()
+        /// @return pointer to component with type T
+
+        template<class ComponentType>
+        ComponentType* GetGameOrEditorComponent(const AZ::Entity* entity)
+        {
+            AZ_Assert(entity, "Called with empty entity");
+            ComponentType* component = entity->FindComponent<ComponentType>();
+            if (component)
+            {
+                return component;
+            }
+#ifdef ROS2_EDITOR
+            // failed to get game object, let us retry as editor
+            component = AzToolsFramework::FindWrappedComponentForEntity<ComponentType>(entity);
+#endif
+            return component;
+        }
+
+    } // namespace Utils
+} // namespace ROS2

+ 30 - 0
Gems/ROS2/Code/Include/ROS2/RobotControl/Ackermann/AckermannBus.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
+ *
+ */
+#pragma once
+
+#include "AckermannCommandStruct.h"
+#include <AzCore/EBus/EBus.h>
+#include <AzCore/RTTI/BehaviorContext.h>
+
+namespace ROS2
+{
+    //! Interface class for handling Ackermann kinematics steering commands through EBus notifications.
+    //! The interface serves to enable control through AckermannDrive (and AckermannDriveStamped) messages.
+    class AckermannNotifications : public AZ::EBusTraits
+    {
+    public:
+        static constexpr AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
+        using BusIdType = AZ::EntityId;
+
+        //! Handle Ackermann command
+        //! @param ackermannCommand A structure with AckermannDrive message fields
+        virtual void AckermannReceived(const AckermannCommandStruct& ackermannCommand) = 0;
+    };
+
+    using AckermannNotificationBus = AZ::EBus<AckermannNotifications>;
+} // namespace ROS2

+ 24 - 0
Gems/ROS2/Code/Include/ROS2/RobotControl/Ackermann/AckermannCommandStruct.h

@@ -0,0 +1,24 @@
+/*
+ * 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 <AzCore/RTTI/TypeInfo.h>
+
+namespace ROS2
+{
+    //! Abstracted from ROS message: http://docs.ros.org/en/api/ackermann_msgs/html/msg/AckermannDrive.html
+    struct AckermannCommandStruct
+    {
+        AZ_TYPE_INFO(AckermannCommandStruct, "{6D03C30F-F06B-4CEE-8AD1-DDCCCB57C4B5}");
+        float m_steeringAngle = 0; //!< desired virtual angle (radians)
+        float m_steeringAngleVelocity = 0; //!< desired rate of change (radians/s)
+        float m_speed = 0; //!< desired forward speed (m/s)
+        float m_acceleration = 0; //!< desired acceleration (m/s^2)
+        float m_jerk = 0; //!< desired jerk (m/s^3)
+    };
+} // namespace ROS2

+ 36 - 0
Gems/ROS2/Code/Include/ROS2/RobotControl/ControlConfiguration.h

@@ -0,0 +1,36 @@
+/*
+ * 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 <AzCore/Component/Entity.h>
+#include <AzCore/RTTI/RTTI.h>
+#include <AzCore/Serialization/SerializeContext.h>
+
+namespace ROS2
+{
+    //! Configuration for handling of robot control buses.
+    //! Used through ROS2RobotControlComponent.
+    struct ControlConfiguration
+    {
+    public:
+        AZ_TYPE_INFO(ControlConfiguration, "{3D3E69EE-0F28-46D5-95F1-956550BA97B9}");
+
+        //! Type of control for the robot.
+        //! Different types of steering can fit different platforms,
+        //! depending on the type their mobile base (four wheels, omniwheels, ..).
+        enum class Steering
+        {
+            Twist, //!< @see <a href="https://docs.ros2.org/latest/api/geometry_msgs/msg/Twist.html">Twist</a>.
+            Ackermann //!< @see <a href="https://github.com/ros-drivers/ackermann_msgs/blob/ros2/msg/AckermannDrive.msg">AckermannDrive</a>.
+        };
+
+        static void Reflect(AZ::ReflectContext* context);
+
+        Steering m_steering = Steering::Twist;
+    };
+} // namespace ROS2

+ 89 - 0
Gems/ROS2/Code/Include/ROS2/RobotControl/ControlSubscriptionHandler.h

@@ -0,0 +1,89 @@
+/*
+ * 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 <ROS2/Communication/TopicConfiguration.h>
+#include <ROS2/Frame/ROS2FrameComponent.h>
+#include <ROS2/ROS2Bus.h>
+#include <ROS2/Utilities/ROS2Names.h>
+#include <rclcpp/rclcpp.hpp>
+
+namespace ROS2
+{
+    //! Component extension enabling polymorphic use of generics.
+    class IControlSubscriptionHandler
+    {
+    public:
+        //! Interface handling component activation
+        //! Only activated IComponentActivationHandler will receive and process control messages.
+        //! @param entity Activation context for the owning Component - the entity it belongs to.
+        //! @param subscriberConfiguration configuration with topic and qos
+        virtual void Activate(const AZ::Entity* entity, const TopicConfiguration& subscriberConfiguration) = 0;
+        //! Interface handling component deactivation
+        virtual void Deactivate() = 0;
+        virtual ~IControlSubscriptionHandler() = default;
+    };
+
+    //! The generic class for handling subscriptions to ROS2 control messages of different types.
+    //! @see ControlConfiguration::Steering.
+    template<typename T>
+    class ControlSubscriptionHandler : public IControlSubscriptionHandler
+    {
+    public:
+        void Activate(const AZ::Entity* entity, const TopicConfiguration& subscriberConfiguration) override final
+        {
+            m_active = true;
+            m_entityId = entity->GetId();
+            if (!m_controlSubscription)
+            {
+                auto ros2Frame = entity->FindComponent<ROS2FrameComponent>();
+                AZStd::string namespacedTopic = ROS2Names::GetNamespacedName(ros2Frame->GetNamespace(), subscriberConfiguration.m_topic);
+
+                auto ros2Node = ROS2Interface::Get()->GetNode();
+                m_controlSubscription = ros2Node->create_subscription<T>(
+                    namespacedTopic.data(),
+                    subscriberConfiguration.GetQoS(),
+                    [this](const T& message)
+                    {
+                        OnControlMessage(message);
+                    });
+            }
+        };
+
+        void Deactivate() override final
+        {
+            m_active = false;
+            m_controlSubscription.reset(); // Note: topic and qos can change, need to re-subscribe
+        };
+
+        virtual ~ControlSubscriptionHandler() = default;
+
+    protected:
+        AZ::EntityId GetEntityId() const
+        {
+            return m_entityId;
+        }
+
+    private:
+        void OnControlMessage(const T& message)
+        {
+            if (!m_active)
+            {
+                return;
+            }
+
+            SendToBus(message);
+        };
+
+        virtual void SendToBus(const T& message) = 0;
+
+        AZ::EntityId m_entityId;
+        bool m_active = false;
+        typename rclcpp::Subscription<T>::SharedPtr m_controlSubscription;
+    };
+} // namespace ROS2

+ 31 - 0
Gems/ROS2/Code/Include/ROS2/RobotControl/Twist/TwistBus.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
+
+#include <AzCore/EBus/EBus.h>
+#include <AzCore/Math/Vector3.h>
+#include <AzCore/RTTI/BehaviorContext.h>
+
+namespace ROS2
+{
+    //! Interface class for handling Twist commands through EBus notifications.
+    //! The interface serves to enable control through Twist (and TwistStamped) messages.
+    class TwistNotifications : public AZ::EBusTraits
+    {
+    public:
+        static constexpr AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
+        using BusIdType = AZ::EntityId;
+
+        //! Handle control command
+        //! @param linear Linear speed in each axis, in robot reference frame, in m/s.
+        //! @param angular Angular speed in each axis, in robot reference frame, in m/s.
+        virtual void TwistReceived(const AZ::Vector3& linear, const AZ::Vector3& angular) = 0;
+    };
+
+    using TwistNotificationBus = AZ::EBus<TwistNotifications>;
+} // namespace ROS2

+ 58 - 0
Gems/ROS2/Code/Include/ROS2/Sensor/ROS2SensorComponent.h

@@ -0,0 +1,58 @@
+/*
+ * 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 "SensorConfiguration.h"
+#include <AzCore/Component/Component.h>
+#include <AzCore/Component/TickBus.h>
+#include <AzCore/std/smart_ptr/unique_ptr.h>
+#include <ROS2/ROS2GemUtilities.h>
+
+namespace ROS2
+{
+    //! Captures common behavior of ROS2 sensor Components.
+    //! Sensors acquire data from the simulation engine and publish it to ROS2 ecosystem.
+    //! Derive this Component to implement a new ROS2 sensor. Each sensor Component requires ROS2FrameComponent.
+    class ROS2SensorComponent
+        : public AZ::Component
+        , public AZ::TickBus::Handler
+    {
+    public:
+        ROS2SensorComponent() = default;
+        virtual ~ROS2SensorComponent() = default;
+        AZ_COMPONENT(ROS2SensorComponent, "{91BCC1E9-6D93-4466-9CDB-E73D497C6B5E}");
+
+        //////////////////////////////////////////////////////////////////////////
+        // Component overrides
+        void Activate() override;
+        void Deactivate() override;
+        //////////////////////////////////////////////////////////////////////////
+
+        static void Reflect(AZ::ReflectContext* context);
+        void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
+        static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required);
+
+    protected:
+        AZStd::string GetNamespace() const; //!< Get a complete namespace for this sensor topics and frame ids.
+        AZStd::string GetFrameID() const; //!< Returns this sensor frame ID. The ID contains namespace.
+
+        SensorConfiguration m_sensorConfiguration;
+
+    private:
+        //! Executes the sensor action (acquire data -> publish) according to frequency.
+        //! Override to implement a specific sensor behavior.
+        virtual void FrequencyTick(){};
+
+        //! Visualise sensor operation.
+        //! For example, draw points or rays for a lidar, viewport for a camera, etc.
+        //! Visualisation can be turned on or off in SensorConfiguration.
+        virtual void Visualise(){};
+
+        float m_timeElapsedSinceLastTick = 0.0f;
+    };
+} // namespace ROS2

+ 39 - 0
Gems/ROS2/Code/Include/ROS2/Sensor/SensorConfiguration.h

@@ -0,0 +1,39 @@
+/*
+ * 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 <AzCore/RTTI/RTTI.h>
+#include <AzCore/Serialization/SerializeContext.h>
+#include <AzCore/std/containers/map.h>
+#include <AzCore/std/string/string.h>
+#include <ROS2/Communication/TopicConfiguration.h>
+
+namespace ROS2
+{
+    //! General configuration for sensors.
+    //! All sensors can be set to a certain frequency, have their data published or not,
+    //! and visualised or not.
+    struct SensorConfiguration
+    {
+    public:
+        AZ_TYPE_INFO(SensorConfiguration, "{4755363D-0B5A-42D7-BBEF-152D87BA10D7}");
+        static void Reflect(AZ::ReflectContext* context);
+
+        //! ROS2 Publishers of this sensor.
+        //! Some sensors can have more than one publisher (example: Camera).
+        //! @note This map will typically hold 1-3 elements.
+        AZStd::map<AZStd::string, TopicConfiguration> m_publishersConfigurations;
+
+        //! Frequency in Hz (1/s).
+        //! Applies both to data acquisition and publishing.
+        float m_frequency = 10;
+
+        bool m_publishingEnabled = true; //!< Determines whether the sensor is publishing (sending data to ROS 2 ecosystem).
+        bool m_visualise = true; //!< Determines whether the sensor is visualised in O3DE (for example, point cloud is drawn for LIDAR).
+    };
+} // namespace ROS2

+ 35 - 0
Gems/ROS2/Code/Include/ROS2/Spawner/SpawnerBus.h

@@ -0,0 +1,35 @@
+/*
+ * 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 <AzCore/Component/ComponentBus.h>
+#include <AzCore/EBus/EBus.h>
+#include <AzCore/Math/Transform.h>
+#include <AzCore/RTTI/BehaviorContext.h>
+
+namespace ROS2
+{
+    //!  Interface class allowing requesting Spawner interface from other components.
+    class SpawnerRequests : public AZ::ComponentBus
+    {
+    public:
+        AZ_RTTI(SpawnerRequests, "{3C42A3A1-1B8E-4800-9473-E4441315D7C8}");
+
+        static constexpr AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
+
+        virtual ~SpawnerRequests() = default;
+
+        //! Default spawn pose getter
+        //! @return default spawn point coordinates set by user in Editor (by default: translation: {0, 0, 0}, rotation: {0, 0, 0, 1},
+        //! scale: 1.0)
+        virtual const AZ::Transform& GetDefaultSpawnPose() const = 0;
+    };
+
+    using SpawnerRequestsBus = AZ::EBus<SpawnerRequests>;
+    using SpawnerInterface = AZ::Interface<SpawnerRequests>;
+} // namespace ROS2

+ 31 - 0
Gems/ROS2/Code/Include/ROS2/Utilities/ROS2Conversions.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
+
+#include <AzCore/Math/Quaternion.h>
+#include <AzCore/Math/Transform.h>
+#include <AzCore/Math/Vector3.h>
+#include <geometry_msgs/msg/point.hpp>
+#include <geometry_msgs/msg/pose.hpp>
+#include <geometry_msgs/msg/vector3.hpp>
+
+namespace ROS2
+{
+    //! Utility class for conversions between ROS2 types and O3DE (AZ::) types.
+    namespace ROS2Conversions
+    {
+        AZ::Vector3 FromROS2Vector3(const geometry_msgs::msg::Vector3& ros2vector);
+        geometry_msgs::msg::Vector3 ToROS2Vector3(const AZ::Vector3& azvector);
+        geometry_msgs::msg::Point ToROS2Point(const AZ::Vector3& azvector);
+        geometry_msgs::msg::Quaternion ToROS2Quaternion(const AZ::Quaternion& azquaternion);
+        geometry_msgs::msg::Pose ToROS2Pose(const AZ::Transform& aztransform);
+        AZ::Transform FromROS2Pose(const geometry_msgs::msg::Pose& ros2pose);
+        AZ::Vector3 FromROS2Point(const geometry_msgs::msg::Point& ros2point);
+        AZ::Quaternion FromROS2Quaternion(const geometry_msgs::msg::Quaternion& ros2quaternion);
+    }; // namespace ROS2Conversions
+} // namespace ROS2

+ 38 - 0
Gems/ROS2/Code/Include/ROS2/Utilities/ROS2Names.h

@@ -0,0 +1,38 @@
+/*
+ * 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 <AzCore/Outcome/Outcome.h>
+#include <AzCore/RTTI/TypeInfo.h>
+#include <AzCore/std/string/string.h>
+
+namespace ROS2
+{
+    //! Utility class for handling ROS2 naming rules.
+    namespace ROS2Names
+    {
+        //! Joins namespace and the given name.
+        AZStd::string GetNamespacedName(const AZStd::string& ns, const AZStd::string& name);
+
+        //! Converts input to a ROS2-acceptable name for topics and namespaces.
+        //! Any characters not fitting ROS2 naming specification are replaced with underscores.
+        AZStd::string RosifyName(const AZStd::string& input);
+
+        //! Validate namespace adherence to ROS2 specification. Delegates validation to ROS2 layers.
+        AZ::Outcome<void, AZStd::string> ValidateNamespace(const AZStd::string& ros2Namespace);
+
+        //! Validate namespace field. Fits ChangeValidate for Editor fields.
+        AZ::Outcome<void, AZStd::string> ValidateNamespaceField(void* newValue, const AZ::Uuid& valueType);
+
+        //! Validate topic adherence to ROS2 specification.
+        AZ::Outcome<void, AZStd::string> ValidateTopic(const AZStd::string& topic);
+
+        //! Validate topic field. Fits ChangeValidate for Editor fields.
+        AZ::Outcome<void, AZStd::string> ValidateTopicField(void* newValue, const AZ::Uuid& valueType);
+    }; // namespace ROS2Names
+} // namespace ROS2

+ 43 - 0
Gems/ROS2/Code/Include/ROS2/VehicleDynamics/DriveModels/PidConfiguration.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 <AzCore/Serialization/SerializeContext.h>
+#include <control_toolbox/pid.hpp>
+
+namespace ROS2::VehicleDynamics
+{
+    //! A wrapper for ROS 2 control_toolbox pid controller.
+    //! @see <a href="https://github.com/ros-controls/control_toolbox">control_toolbox</a>.
+    class PidConfiguration
+    {
+    public:
+        AZ_TYPE_INFO(PidConfiguration, "{814E0D1E-2C33-44A5-868E-C914640E2F7E}");
+        static void Reflect(AZ::ReflectContext* context);
+
+        //! Initialize PID using member fields as set by the user.
+        void InitializePid();
+
+        //! Compute the value of PID command.
+        //! @param error Value of difference between target and state since last call.
+        //! @param deltaTimeNanoseconds change in time since last call (nanoseconds).
+        //! @returns Value of computed command.
+        double ComputeCommand(double error, uint64_t deltaTimeNanoseconds);
+
+    private:
+        double m_p = 1.0; //!< proportional gain.
+        double m_i = 0.0; //!< integral gain.
+        double m_d = 0.0; //!< derivative gain.
+        double m_iMax = 10.0; //!< maximal allowable integral term.
+        double m_iMin = -10.0; //!< minimal allowable integral term.
+        bool m_antiWindup = false; //!< prevents condition of integrator overflow in integral action.
+        double m_outputLimit = 0.0; //!< limit PID output; set to 0.0 to disable.
+
+        control_toolbox::Pid m_pid; //!< PID implementation object from control_toolbox (of ros2_control).
+    };
+} // namespace ROS2::VehicleDynamics

+ 56 - 0
Gems/ROS2/Code/Include/ROS2/VehicleDynamics/VehicleInputControlBus.h

@@ -0,0 +1,56 @@
+/*
+ * 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 <AzCore/Component/EntityId.h>
+#include <AzCore/EBus/EBus.h>
+#include <AzCore/Interface/Interface.h>
+
+namespace ROS2::VehicleDynamics
+{
+    //! Inputs (speed, steering, acceleration etc.) for vehicle dynamics system
+    //! Inputs are valid for a short time (configurable) and need to be repeated if continuous movement is needed.
+    //! (Use cruise system to set cruise speed).
+    class VehicleInputControlRequests : public AZ::EBusTraits
+    {
+    public:
+        static constexpr AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
+        using BusIdType = AZ::EntityId;
+
+        virtual ~VehicleInputControlRequests() = default;
+
+        //! Set target for the vehicle linear speed. It should be realized over time according to drive model.
+        //! @param speedMps is a linear speed in meters per second with the plus sign in the forward direction.
+        virtual void SetTargetLinearSpeed(float speedMps) = 0;
+
+        //! Steer in a direction given in relative coordinate system (current direction is 0).
+        //! @param steering is angle in radians, positive to the right and negative to the left.
+        //! @note The actual angle applied is subject to limits and implementation (eg smoothing).
+        virtual void SetTargetSteering(float steering) = 0;
+
+        //! Accelerate without target speed, relative to the limits.
+        //! @param accelerationFraction is relative to limits of possible acceleration.
+        //! 1 - accelerate as much as possible, -1 - brake as much as possible.
+        virtual void SetTargetAccelerationFraction(float accelerationFraction) = 0;
+
+        //! Steer input version which is relative to limits.
+        //! @param steeringFraction is -1 to 1, which applies as a fraction of vehicle model steering limits.
+        //! @note The actual angle applied is subject to limits and implementation (eg smoothing).
+        virtual void SetTargetSteeringFraction(float steeringFraction) = 0;
+
+        //! Speed input version which is relative to limits.
+        //! @param speedFraction is -1 to 1, which applies as a fraction of vehicle model speed limits.
+        virtual void SetTargetLinearSpeedFraction(float speedFraction) = 0;
+
+        //! Disables (or enables) the vehicle dynamics
+        //! @param disable if set true no torque will be applied
+        virtual void SetDisableVehicleDynamics(bool disable) = 0;
+    };
+
+    using VehicleInputControlRequestBus = AZ::EBus<VehicleInputControlRequests>;
+} // namespace ROS2::VehicleDynamics

+ 9 - 0
Gems/ROS2/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_BUILD_ROS2_GEM_SUPPORTED FALSE)

+ 11 - 0
Gems/ROS2/Code/Platform/Common/Clang/ros2_static_editor_clang.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(LY_COMPILE_OPTIONS
+        PRIVATE
+            -fexceptions
+)

+ 11 - 0
Gems/ROS2/Code/Platform/Common/GCC/ros2_static_editor_gcc.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(LY_COMPILE_OPTIONS
+        PRIVATE
+            -fexceptions
+)

+ 11 - 0
Gems/ROS2/Code/Platform/Common/msvc/ros2_static_editor_msvc.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(LY_COMPILE_OPTIONS
+        PRIVATE
+            /EHsc
+)

+ 9 - 0
Gems/ROS2/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_BUILD_ROS2_GEM_SUPPORTED TRUE)

+ 9 - 0
Gems/ROS2/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_BUILD_ROS2_GEM_SUPPORTED FALSE)

+ 9 - 0
Gems/ROS2/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_BUILD_ROS2_GEM_SUPPORTED FALSE)

+ 9 - 0
Gems/ROS2/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_BUILD_ROS2_GEM_SUPPORTED FALSE)

+ 243 - 0
Gems/ROS2/Code/Source/Camera/CameraSensor.cpp

@@ -0,0 +1,243 @@
+/*
+ * 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 "CameraSensor.h"
+
+#include <AzCore/Math/MatrixUtils.h>
+
+#include <Atom/RPI.Public/Base.h>
+#include <Atom/RPI.Public/Pass/Specific/RenderToTexturePass.h>
+#include <Atom/RPI.Public/RPISystemInterface.h>
+#include <Atom/RPI.Public/RenderPipeline.h>
+#include <Atom/RPI.Public/Scene.h>
+#include <AzFramework/Components/TransformComponent.h>
+#include <AzFramework/Scene/SceneSystemInterface.h>
+
+#include <Atom/RPI.Public/FeatureProcessorFactory.h>
+#include <Atom/RPI.Public/Pass/PassSystemInterface.h>
+#include <PostProcess/PostProcessFeatureProcessor.h>
+
+#include <Atom/RPI.Public/Pass/PassFactory.h>
+
+namespace ROS2
+{
+    namespace Internal
+    {
+
+        /// @FormatMappings - contains the mapping from RHI to ROS image encodings. List of supported
+        /// ROS image encodings lives in `sensor_msgs/image_encodings.hpp`
+        /// We are not including `image_encodings.hpp` since it uses exceptions.
+        AZStd::unordered_map<AZ::RHI::Format, const char*> FormatMappings{
+            { AZ::RHI::Format::R8G8B8A8_UNORM, "rgba8" },     
+            { AZ::RHI::Format::R16G16B16A16_UNORM, "rgba16" },
+            { AZ::RHI::Format::R32G32B32A32_FLOAT, "32FC4" }, // Unsuported by RVIZ2
+            { AZ::RHI::Format::R8_UNORM, "mono8" },           
+            { AZ::RHI::Format::R16_UNORM, "mono16" },
+            { AZ::RHI::Format::R32_FLOAT, "32FC1" },
+        };
+
+        /// @BitDepth - contains the mapping from RHI to size used in `step` size computation.
+        /// It is some equivalent to `bitDepth()` function from `sensor_msgs/image_encodings.hpp`
+        AZStd::unordered_map<AZ::RHI::Format, int> BitDepth{
+            { AZ::RHI::Format::R8G8B8A8_UNORM, 4 * sizeof(uint8_t) },
+            { AZ::RHI::Format::R16G16B16A16_UNORM, 4 * sizeof(uint16_t) },
+            { AZ::RHI::Format::R32G32B32A32_FLOAT, 4 * sizeof(float) }, // Unsuported by RVIZ2
+            { AZ::RHI::Format::R8_UNORM, sizeof(uint8_t) },
+            { AZ::RHI::Format::R16_UNORM, sizeof(uint16_t) },
+            { AZ::RHI::Format::R32_FLOAT, sizeof(float) },
+        };
+
+    } // namespace Internal
+    CameraSensorDescription::CameraSensorDescription(const AZStd::string& cameraName, float verticalFov, int width, int height)
+        : m_verticalFieldOfViewDeg(verticalFov)
+        , m_width(width)
+        , m_height(height)
+        , m_cameraName(cameraName)
+        , m_aspectRatio(static_cast<float>(width) / static_cast<float>(height))
+        , m_viewToClipMatrix(MakeViewToClipMatrix())
+        , m_cameraIntrinsics(MakeCameraIntrinsics())
+    {
+        ValidateParameters();
+    }
+
+    AZ::Matrix4x4 CameraSensorDescription::MakeViewToClipMatrix() const
+    {
+        const float nearDist = 0.1f, farDist = 100.0f;
+        AZ::Matrix4x4 localViewToClipMatrix;
+        AZ::MakePerspectiveFovMatrixRH(
+            localViewToClipMatrix, AZ::DegToRad(m_verticalFieldOfViewDeg), m_aspectRatio, nearDist, farDist, true);
+        return localViewToClipMatrix;
+    }
+
+    void CameraSensorDescription::ValidateParameters() const
+    {
+        AZ_Assert(
+            m_verticalFieldOfViewDeg > 0.0f && m_verticalFieldOfViewDeg < 180.0f,
+            "Vertical fov should be in range 0.0 < FoV < 180.0 degrees");
+        AZ_Assert(!m_cameraName.empty(), "Camera name cannot be empty");
+    }
+
+    AZStd::array<double, 9> CameraSensorDescription::MakeCameraIntrinsics() const
+    {
+        /* Intrinsic camera matrix of the camera image is being created here
+           It is based on other parameters available in the structure - they must be initialized before this function is called
+            Matrix is row-major and has the following form:
+            [fx  0 cx]
+            [ 0 fy cy]
+            [ 0  0  1]
+           Projects 3D points in the camera coordinate frame to 2D pixel
+           coordinates using the focal lengths (fx, fy) and principal point
+           (cx, cy).  
+       */
+        const auto w = static_cast<double>(m_width);
+        const auto h = static_cast<double>(m_height);
+        const double verticalFieldOfView = AZ::DegToRad(m_verticalFieldOfViewDeg);
+        const double horizontalFoV = 2.0 * AZStd::atan(AZStd::tan(verticalFieldOfView / 2.0) * m_aspectRatio);
+        const double focalLengthX = w / (2.0 * AZStd::tan(horizontalFoV / 2.0));
+        const double focalLengthY = h / (2.0 * AZStd::tan(verticalFieldOfView / 2.0));
+        return { focalLengthX, 0.0, w / 2.0, 0.0, focalLengthY, h / 2.0, 0.0, 0.0, 1.0 };
+    }
+
+    CameraSensor::CameraSensor(const CameraSensorDescription& cameraSensorDescription)
+        : m_cameraSensorDescription(cameraSensorDescription)
+    {
+    }
+
+    void CameraSensor::SetupPasses()
+    {
+        AZ_TracePrintf("CameraSensor", "Initializing pipeline for %s\n", m_cameraSensorDescription.m_cameraName.c_str());
+
+        const AZ::Name viewName = AZ::Name("MainCamera");
+        m_view = AZ::RPI::View::CreateView(viewName, AZ::RPI::View::UsageCamera);
+        m_view->SetViewToClipMatrix(m_cameraSensorDescription.m_viewToClipMatrix);
+        m_scene = AZ::RPI::RPISystemInterface::Get()->GetSceneByName(AZ::Name("Main"));
+
+        const AZStd::string pipelineName = m_cameraSensorDescription.m_cameraName + "Pipeline" + GetPipelineTypeName();
+
+        AZ::RPI::RenderPipelineDescriptor pipelineDesc;
+        pipelineDesc.m_mainViewTagName = "MainCamera";
+        pipelineDesc.m_name = pipelineName;
+
+        pipelineDesc.m_rootPassTemplate = GetPipelineTemplateName();
+
+        pipelineDesc.m_renderSettings.m_multisampleState = AZ::RPI::RPISystemInterface::Get()->GetApplicationMultisampleState();
+        m_pipeline = AZ::RPI::RenderPipeline::CreateRenderPipeline(pipelineDesc);
+        m_pipeline->RemoveFromRenderTick();
+
+        if (auto renderToTexturePass = azrtti_cast<AZ::RPI::RenderToTexturePass*>(m_pipeline->GetRootPass().get()))
+        {
+            renderToTexturePass->ResizeOutput(m_cameraSensorDescription.m_width, m_cameraSensorDescription.m_height);
+        }
+
+        m_scene->AddRenderPipeline(m_pipeline);
+
+        m_passHierarchy.push_back(pipelineName);
+        m_passHierarchy.push_back("CopyToSwapChain");
+
+        m_pipeline->SetDefaultView(m_view);
+        const AZ::RPI::ViewPtr targetView = m_scene->GetDefaultRenderPipeline()->GetDefaultView();
+        if (auto* fp = m_scene->GetFeatureProcessor<AZ::Render::PostProcessFeatureProcessor>())
+        {
+            fp->SetViewAlias(m_view, targetView);
+        }
+    }
+
+    CameraSensor::~CameraSensor()
+    {
+        if (m_scene)
+        {
+            if (auto* fp = m_scene->GetFeatureProcessor<AZ::Render::PostProcessFeatureProcessor>())
+            {
+                fp->RemoveViewAlias(m_view);
+            }
+            m_scene->RemoveRenderPipeline(m_pipeline->GetId());
+            m_scene = nullptr;
+        }
+        m_passHierarchy.clear();
+        m_pipeline.reset();
+        m_view.reset();
+    }
+
+    void CameraSensor::RequestFrame(
+        const AZ::Transform& cameraPose, AZStd::function<void(const AZ::RPI::AttachmentReadback::ReadbackResult& result)> callback)
+    {
+        const AZ::Transform inverse = (cameraPose * AtomToRos).GetInverse();
+        m_view->SetWorldToViewMatrix(AZ::Matrix4x4::CreateFromQuaternionAndTranslation(inverse.GetRotation(), inverse.GetTranslation()));
+
+        AZ::Render::FrameCaptureId captureId = AZ::Render::InvalidFrameCaptureId;
+
+        m_pipeline->AddToRenderTickOnce();
+        AZ::Render::FrameCaptureRequestBus::BroadcastResult(
+            captureId,
+            &AZ::Render::FrameCaptureRequestBus::Events::CapturePassAttachmentWithCallback,
+            m_passHierarchy,
+            AZStd::string("Output"),
+            callback,
+            AZ::RPI::PassAttachmentReadbackOption::Output);
+    }
+
+    const CameraSensorDescription& CameraSensor::GetCameraSensorDescription() const
+    {
+        return m_cameraSensorDescription;
+    }
+
+    void CameraSensor::RequestMessagePublication(
+        std::shared_ptr<rclcpp::Publisher<sensor_msgs::msg::Image>> publisher,
+        const AZ::Transform& cameraPose,
+        const std_msgs::msg::Header& header)
+    {
+        RequestFrame(
+            cameraPose,
+            [header, publisher](const AZ::RPI::AttachmentReadback::ReadbackResult& result)
+            {
+                const AZ::RHI::ImageDescriptor& descriptor = result.m_imageDescriptor;
+                const auto format = descriptor.m_format;
+                AZ_Assert(Internal::FormatMappings.contains(format), "Unknown format in result %u", static_cast<uint32_t>(format));
+                sensor_msgs::msg::Image message;
+                message.encoding = Internal::FormatMappings.at(format);
+                message.width = descriptor.m_size.m_width;
+                message.height = descriptor.m_size.m_height;
+                message.step = message.width * Internal::BitDepth.at(format);
+                message.data = std::vector<uint8_t>(result.m_dataBuffer->data(), result.m_dataBuffer->data() + result.m_dataBuffer->size());
+                message.header = header;
+                publisher->publish(message);
+            });
+    }
+
+    CameraDepthSensor::CameraDepthSensor(const CameraSensorDescription& cameraSensorDescription)
+        : CameraSensor(cameraSensorDescription)
+    {
+        SetupPasses();
+    }
+
+    AZStd::string CameraDepthSensor::GetPipelineTemplateName() const
+    {
+        return "PipelineRenderToTextureROSDepth";
+    };
+
+    AZStd::string CameraDepthSensor::GetPipelineTypeName() const
+    {
+        return "Depth";
+    };
+
+    CameraColorSensor::CameraColorSensor(const CameraSensorDescription& cameraSensorDescription)
+        : CameraSensor(cameraSensorDescription)
+    {
+        SetupPasses();
+    }
+
+    AZStd::string CameraColorSensor::GetPipelineTemplateName() const
+    {
+        return "PipelineRenderToTextureROSColor";
+    };
+
+    AZStd::string CameraColorSensor::GetPipelineTypeName() const
+    {
+        return "Color";
+    };
+
+} // namespace ROS2

+ 116 - 0
Gems/ROS2/Code/Source/Camera/CameraSensor.h

@@ -0,0 +1,116 @@
+/*
+ * 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 <Atom/Feature/Utils/FrameCaptureBus.h>
+#include <ROS2/ROS2GemUtilities.h>
+#include <chrono>
+#include <rclcpp/publisher.hpp>
+#include <sensor_msgs/msg/image.hpp>
+#include <std_msgs/msg/header.hpp>
+
+namespace ROS2
+{
+
+    //! Structure containing all information required to create the camera sensor
+    struct CameraSensorDescription
+    {
+        //! Constructor to create the description
+        //! @param cameraName - name of the camera; used to differentiate cameras in a multi-camera setup
+        //! @param verticalFov - vertical field of view of camera sensor
+        //! @param width - camera image width in pixels
+        //! @param height - camera image height in pixels
+        CameraSensorDescription(const AZStd::string& cameraName, float verticalFov, int width, int height);
+
+        const float m_verticalFieldOfViewDeg; //!< camera vertical field of view
+        const int m_width; //!< camera image width in pixels
+        const int m_height; //!< camera image height in pixels
+        const AZStd::string m_cameraName; //!< camera name to differentiate cameras in a multi-camera setup
+
+        const float m_aspectRatio; //!< camera image aspect ratio; equal to (width / height)
+        const AZ::Matrix4x4 m_viewToClipMatrix; //!< camera view to clip space transform matrix; derived from other parameters
+        const AZStd::array<double, 9> m_cameraIntrinsics; //!< camera intrinsics; derived from other parameters
+
+    private:
+        AZ::Matrix4x4 MakeViewToClipMatrix() const;
+
+        AZStd::array<double, 9> MakeCameraIntrinsics() const;
+
+        void ValidateParameters() const;
+    };
+
+    //! Class to create camera sensor using Atom renderer
+    //! It creates dedicated rendering pipeline for each camera
+    class CameraSensor
+    {
+    public:
+        //! Initializes rendering pipeline for the camera sensor
+        //! @param cameraSensorDescription - camera sensor description used to create camera pipeline
+        CameraSensor(const CameraSensorDescription& cameraSensorDescription);
+
+        //! Deinitializes rendering pipeline for the camera sensor
+        virtual ~CameraSensor();
+
+        //! Publish Image Message frame from rendering pipeline
+        //! @param publisher - ROS2 publisher to publish image in future
+        //! @param header - header with filled message information (frame, timestamp, seq)
+        //! @param cameraPose - current camera pose from which the rendering should take place
+        void RequestMessagePublication(
+            std::shared_ptr<rclcpp::Publisher<sensor_msgs::msg::Image>> publisher,
+            const AZ::Transform& cameraPose,
+            const std_msgs::msg::Header& header);
+
+        //! Get the camera sensor description
+        [[nodiscard]] const CameraSensorDescription& GetCameraSensorDescription() const;
+
+    private:
+        //! Request a frame from the rendering pipeline
+        //! @param cameraPose - current camera pose from which the rendering should take place
+        //! @param callback - callback function object that will be called when capture is ready
+        //!                   it's argument is readback structure containing, among other thins, captured image
+        void RequestFrame(
+            const AZ::Transform& cameraPose, AZStd::function<void(const AZ::RPI::AttachmentReadback::ReadbackResult& result)> callback);
+
+        CameraSensorDescription m_cameraSensorDescription;
+        AZStd::vector<AZStd::string> m_passHierarchy;
+        AZ::RPI::RenderPipelinePtr m_pipeline;
+        AZ::RPI::ViewPtr m_view;
+        AZ::RPI::Scene* m_scene = nullptr;
+        const AZ::Transform AtomToRos{ AZ::Transform::CreateFromQuaternion(
+            AZ::Quaternion::CreateFromMatrix3x3(AZ::Matrix3x3::CreateFromRows({ 1, 0, 0 }, { 0, -1, 0 }, { 0, 0, -1 }))) };
+        virtual AZStd::string GetPipelineTemplateName() const = 0; //! Returns name of pass template to use in pipeline
+        virtual AZStd::string GetPipelineTypeName() const = 0; //! Type of returned data eg Color, Depth, Optical flow
+
+    protected:
+        //! Read and setup Atom Passes
+        void SetupPasses();
+    };
+
+    //! Implementation of camera sensors that runs pipeline which produces depth image
+    class CameraDepthSensor : public CameraSensor
+    {
+    public:
+        CameraDepthSensor(const CameraSensorDescription& cameraSensorDescription);
+
+    private:
+        AZStd::string GetPipelineTemplateName() const override;
+        AZStd::string GetPipelineTypeName() const override;
+    };
+
+    //! Implementation of camera sensors that runs pipeline which produces color image
+    class CameraColorSensor : public CameraSensor
+    {
+    public:
+        CameraColorSensor(const CameraSensorDescription& cameraSensorDescription);
+
+    private:
+        AZStd::string GetPipelineTemplateName() const override;
+        AZStd::string GetPipelineTypeName() const override;
+    };
+
+} // namespace ROS2

+ 166 - 0
Gems/ROS2/Code/Source/Camera/ROS2CameraSensorComponent.cpp

@@ -0,0 +1,166 @@
+/*
+ * 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 "ROS2CameraSensorComponent.h"
+#include <ROS2/Communication/TopicConfiguration.h>
+#include <ROS2/Frame/ROS2FrameComponent.h>
+#include <ROS2/ROS2Bus.h>
+#include <ROS2/Utilities/ROS2Names.h>
+
+#include <AzCore/Component/Entity.h>
+#include <AzCore/Component/TransformBus.h>
+#include <AzCore/Serialization/EditContext.h>
+#include <AzCore/Serialization/SerializeContext.h>
+
+#include <sensor_msgs/distortion_models.hpp>
+
+namespace ROS2
+{
+    namespace Internal
+    {
+        const char* kImageMessageType = "sensor_msgs::msg::Image";
+        const char* kDepthImageConfig = "Depth Image";
+        const char* kColorImageConfig = "Color Image";
+        const char* kInfoConfig = "Camera Info";
+        const char* kCameraInfoMessageType = "sensor_msgs::msg::CameraInfo";
+
+        AZStd::pair<AZStd::string, TopicConfiguration> MakeTopicConfigurationPair(
+            const AZStd::string& topic, const AZStd::string& messageType, const AZStd::string& configName)
+        {
+            TopicConfiguration config;
+            config.m_topic = topic;
+            config.m_type = messageType;
+            return AZStd::make_pair(configName, config);
+        }
+
+        AZStd::string GetCameraNameFromFrame(const AZ::Entity* entity)
+        {
+            const auto* component = Utils::GetGameOrEditorComponent<ROS2FrameComponent>(entity);
+            AZStd::string cameraName = component->GetFrameID();
+            AZStd::replace(cameraName.begin(), cameraName.end(), '/', '_');
+            return cameraName;
+        }
+
+    } // namespace Internal
+
+    ROS2CameraSensorComponent::ROS2CameraSensorComponent()
+    {
+        m_sensorConfiguration.m_frequency = 10;
+        m_sensorConfiguration.m_publishersConfigurations.insert(
+            Internal::MakeTopicConfigurationPair("camera_image_color", Internal::kImageMessageType, Internal::kColorImageConfig));
+        m_sensorConfiguration.m_publishersConfigurations.insert(
+            Internal::MakeTopicConfigurationPair("camera_image_depth", Internal::kImageMessageType, Internal::kDepthImageConfig));
+        m_sensorConfiguration.m_publishersConfigurations.insert(
+            Internal::MakeTopicConfigurationPair("camera_info", Internal::kCameraInfoMessageType, Internal::kInfoConfig));
+    }
+
+    void ROS2CameraSensorComponent::Reflect(AZ::ReflectContext* context)
+    {
+        auto* serialize = azrtti_cast<AZ::SerializeContext*>(context);
+        if (serialize)
+        {
+            serialize->Class<ROS2CameraSensorComponent, ROS2SensorComponent>()
+                ->Version(3)
+                ->Field("VerticalFieldOfViewDeg", &ROS2CameraSensorComponent::m_VerticalFieldOfViewDeg)
+                ->Field("Width", &ROS2CameraSensorComponent::m_width)
+                ->Field("Height", &ROS2CameraSensorComponent::m_height)
+                ->Field("Depth", &ROS2CameraSensorComponent::m_depthCamera)
+                ->Field("Color", &ROS2CameraSensorComponent::m_colorCamera);
+
+            AZ::EditContext* ec = serialize->GetEditContext();
+            if (ec)
+            {
+                ec->Class<ROS2CameraSensorComponent>("ROS2 Camera Sensor", "[Camera component]")
+                    ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
+                    ->Attribute(AZ::Edit::Attributes::Category, "ROS2")
+                    ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game"))
+                    ->DataElement(
+                        AZ::Edit::UIHandlers::Default,
+                        &ROS2CameraSensorComponent::m_VerticalFieldOfViewDeg,
+                        "Vertical field of view",
+                        "Camera's vertical (y axis) field of view in degrees.")
+                    ->DataElement(AZ::Edit::UIHandlers::Default, &ROS2CameraSensorComponent::m_width, "Image width", "Image width")
+                    ->DataElement(AZ::Edit::UIHandlers::Default, &ROS2CameraSensorComponent::m_height, "Image height", "Image height")
+                    ->DataElement(AZ::Edit::UIHandlers::Default, &ROS2CameraSensorComponent::m_colorCamera, "Color Camera", "Color Camera")
+                    ->DataElement(AZ::Edit::UIHandlers::Default, &ROS2CameraSensorComponent::m_depthCamera, "Depth Camera", "Depth Camera");
+            }
+        }
+    }
+
+    void ROS2CameraSensorComponent::Activate()
+    {
+        ROS2SensorComponent::Activate();
+
+        auto ros2Node = ROS2Interface::Get()->GetNode();
+
+        const auto cameraInfoPublisherConfig = m_sensorConfiguration.m_publishersConfigurations[Internal::kInfoConfig];
+        AZStd::string cameraInfoFullTopic = ROS2Names::GetNamespacedName(GetNamespace(), cameraInfoPublisherConfig.m_topic);
+        AZ_TracePrintf("ROS2", "Creating publisher for camera info on topic %s\n", cameraInfoFullTopic.data());
+
+        m_cameraInfoPublisher =
+            ros2Node->create_publisher<sensor_msgs::msg::CameraInfo>(cameraInfoFullTopic.data(), cameraInfoPublisherConfig.GetQoS());
+
+        const CameraSensorDescription description{
+            Internal::GetCameraNameFromFrame(GetEntity()), m_VerticalFieldOfViewDeg, m_width, m_height
+        };
+        if (m_colorCamera)
+        {
+            const auto cameraImagePublisherConfig = m_sensorConfiguration.m_publishersConfigurations[Internal::kColorImageConfig];
+            AZStd::string cameraImageFullTopic = ROS2Names::GetNamespacedName(GetNamespace(), cameraImagePublisherConfig.m_topic);
+            auto publisher =
+                ros2Node->create_publisher<sensor_msgs::msg::Image>(cameraImageFullTopic.data(), cameraImagePublisherConfig.GetQoS());
+            m_cameraSensorsWithPublihsers.emplace_back(CreatePair<CameraColorSensor>(publisher, description));
+        }
+        if (m_depthCamera)
+        {
+            const auto cameraImagePublisherConfig = m_sensorConfiguration.m_publishersConfigurations[Internal::kDepthImageConfig];
+            AZStd::string cameraImageFullTopic = ROS2Names::GetNamespacedName(GetNamespace(), cameraImagePublisherConfig.m_topic);
+            auto publisher =
+                ros2Node->create_publisher<sensor_msgs::msg::Image>(cameraImageFullTopic.data(), cameraImagePublisherConfig.GetQoS());
+            m_cameraSensorsWithPublihsers.emplace_back(CreatePair<CameraDepthSensor>(publisher, description));
+        }
+        const auto* component = Utils::GetGameOrEditorComponent<ROS2FrameComponent>(GetEntity());
+        AZ_Assert(component, "Entity has no ROS2FrameComponent");
+        m_frameName = component->GetFrameID();
+    }
+
+    void ROS2CameraSensorComponent::Deactivate()
+    {
+        m_cameraSensorsWithPublihsers.clear();
+        ROS2SensorComponent::Deactivate();
+    }
+
+    void ROS2CameraSensorComponent::FrequencyTick()
+    {
+        const AZ::Transform transform = GetEntity()->GetTransform()->GetWorldTM();
+        const auto timestamp = ROS2Interface::Get()->GetROSTimestamp();
+        std_msgs::msg::Header ros_header;
+        if (!m_cameraSensorsWithPublihsers.empty())
+        {
+            const auto& camera_descritpion = m_cameraSensorsWithPublihsers.front().second->GetCameraSensorDescription();
+            const auto& cameraIntrinsics = camera_descritpion.m_cameraIntrinsics;
+            sensor_msgs::msg::CameraInfo cameraInfo;
+            ros_header.stamp = timestamp;
+            ros_header.frame_id = m_frameName.c_str();
+            cameraInfo.header = ros_header;
+            cameraInfo.width = m_width;
+            cameraInfo.height = m_height;
+            cameraInfo.distortion_model = sensor_msgs::distortion_models::PLUMB_BOB;
+            AZ_Assert(cameraIntrinsics.size() == 9, "camera matrix should have 9 elements");
+            AZ_Assert(cameraInfo.k.size() == 9, "camera matrix should have 9 elements");
+            AZStd::copy(cameraIntrinsics.begin(), cameraIntrinsics.end(), cameraInfo.k.begin());
+            cameraInfo.p = { cameraInfo.k[0], cameraInfo.k[1], cameraInfo.k[2], 0, cameraInfo.k[3], cameraInfo.k[4], cameraInfo.k[5], 0,
+                             cameraInfo.k[6], cameraInfo.k[7], cameraInfo.k[8], 0 };
+            m_cameraInfoPublisher->publish(cameraInfo);
+        }
+        for (auto& [publisher, sensor] : m_cameraSensorsWithPublihsers)
+        {
+            sensor->RequestMessagePublication(publisher, transform, ros_header);
+        }
+    }
+} // namespace ROS2

+ 76 - 0
Gems/ROS2/Code/Source/Camera/ROS2CameraSensorComponent.h

@@ -0,0 +1,76 @@
+/*
+ * 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 <rclcpp/publisher.hpp>
+#include <sensor_msgs/msg/camera_info.hpp>
+#include <sensor_msgs/msg/image.hpp>
+
+#include <ROS2/Sensor/ROS2SensorComponent.h>
+
+#include <AzCore/Component/Component.h>
+
+#include <ROS2/Frame/NamespaceConfiguration.h>
+#include <ROS2/Frame/ROS2Transform.h>
+
+#include "CameraSensor.h"
+
+namespace ROS2
+{
+    //! ROS2 Camera sensor component class
+    //! Allows turning an entity into a camera sensor
+    //! Can be parametrized with following values:
+    //!   - camera name
+    //!   - camera image width and height in pixels
+    //!   - camera vertical field of view in degrees
+    //! Camera frustum is facing negative Z axis; image plane is parallel to X,Y plane: X - right, Y - up
+    class ROS2CameraSensorComponent : public ROS2SensorComponent
+    {
+    public:
+        ROS2CameraSensorComponent();
+        ~ROS2CameraSensorComponent() override = default;
+        AZ_COMPONENT(ROS2CameraSensorComponent, "{3C6B8AE6-9721-4639-B8F9-D8D28FD7A071}", ROS2SensorComponent);
+        static void Reflect(AZ::ReflectContext* context);
+
+        void Activate() override;
+        void Deactivate() override;
+
+    private:
+        //! Pointer to ROS2 image publisher type
+        using ImagePublisherPtrType = std::shared_ptr<rclcpp::Publisher<sensor_msgs::msg::Image>>;
+
+        //! Pointer to ROS2 camera sensor publisher type
+        using CameraInfoPublisherPtrType = std::shared_ptr<rclcpp::Publisher<sensor_msgs::msg::CameraInfo>>;
+
+        //! Type that combines pointer to ROS2 publisher and CameraSensor
+        using PublisherSensorPtrPair = AZStd::pair<ImagePublisherPtrType, AZStd::shared_ptr<CameraSensor>>;
+
+        //! Helper to construct a PublisherSensorPtrPair with a pointer to ROS2 publisher and intrinsic calibration
+        //! @tparam CameraType type of camera sensor (eg 'CameraColorSensor')
+        //! @param publisher pointer to ROS2 image publisher
+        //! @param description CameraSensorDescription with intrinsic calibration
+        //! @return PublisherSensorPtrPair with all provided parameters
+        template<typename CameraType>
+        PublisherSensorPtrPair CreatePair(ImagePublisherPtrType publisher, const CameraSensorDescription& description) const
+        {
+            return { publisher, AZStd::make_shared<CameraType>(description) };
+        }
+
+        float m_VerticalFieldOfViewDeg = 90.0f;
+        int m_width = 640;
+        int m_height = 480;
+        bool m_colorCamera = true;
+        bool m_depthCamera = true;
+
+        void FrequencyTick() override;
+        AZStd::vector<PublisherSensorPtrPair> m_cameraSensorsWithPublihsers;
+        CameraInfoPublisherPtrType m_cameraInfoPublisher;
+
+        AZStd::string m_frameName;
+    };
+} // namespace ROS2

+ 53 - 0
Gems/ROS2/Code/Source/Clock/SimulationClock.cpp

@@ -0,0 +1,53 @@
+/*
+ * 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 "SimulationClock.h"
+#include <AzCore/Time/ITime.h>
+#include <ROS2/ROS2Bus.h>
+#include <rclcpp/qos.hpp>
+
+namespace ROS2
+{
+    builtin_interfaces::msg::Time SimulationClock::GetROSTimestamp() const
+    {
+        const auto elapsedTime = GetElapsedTimeMicroseconds();
+
+        builtin_interfaces::msg::Time timeStamp;
+        timeStamp.sec = static_cast<int32_t>(elapsedTime / 1000000);
+        timeStamp.nanosec = static_cast<uint32_t>((elapsedTime % 1000000) * 1000);
+        return timeStamp;
+    }
+
+    int64_t SimulationClock::GetElapsedTimeMicroseconds() const
+    {
+        if (auto* timeSystem = AZ::Interface<AZ::ITime>::Get())
+        {
+            return static_cast<int64_t>(timeSystem->GetElapsedTimeUs());
+        }
+        else
+        {
+            AZ_Error("SimulationClock", false, "No ITime interface available for ROS2 Gem simulation clock");
+            return 0;
+        }
+    }
+
+    void SimulationClock::Tick()
+    {
+        if (!m_clockPublisher)
+        { // Lazy construct
+            auto ros2Node = ROS2Interface::Get()->GetNode();
+
+            rclcpp::ClockQoS qos;
+            m_clockPublisher = ros2Node->create_publisher<rosgraph_msgs::msg::Clock>("/clock", qos);
+        }
+
+        rosgraph_msgs::msg::Clock msg;
+        msg.clock = GetROSTimestamp();
+        m_clockPublisher->publish(msg);
+    }
+} // namespace ROS2

+ 34 - 0
Gems/ROS2/Code/Source/Clock/SimulationClock.h

@@ -0,0 +1,34 @@
+/*
+ * 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 <builtin_interfaces/msg/time.hpp>
+#include <rclcpp/publisher.hpp>
+#include <rosgraph_msgs/msg/clock.hpp>
+
+namespace ROS2
+{
+    //! Simulation clock which can tick and serve time stamps.
+    class SimulationClock
+    {
+    public:
+        //! Get simulation time as ROS2 message.
+        //! @see ROS2Requests::GetROSTimestamp() for more details.
+        builtin_interfaces::msg::Time GetROSTimestamp() const;
+
+        //! Update time in the ROS 2 ecosystem.
+        //! This will publish current time to the ROS 2 `/clock` topic.
+        void Tick();
+
+    private:
+        //! Get the time since start of sim, scaled with t_simulationTickScale
+        int64_t GetElapsedTimeMicroseconds() const;
+
+        rclcpp::Publisher<rosgraph_msgs::msg::Clock>::SharedPtr m_clockPublisher;
+    };
+} // namespace ROS2

+ 67 - 0
Gems/ROS2/Code/Source/Communication/QoS.cpp

@@ -0,0 +1,67 @@
+/*
+ * 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 <AzCore/Serialization/EditContext.h>
+#include <AzCore/Serialization/SerializeContext.h>
+#include <ROS2/Communication/QoS.h>
+
+namespace ROS2
+{
+    QoS::QoS(const rclcpp::QoS& qos)
+        : m_reliabilityPolicy(qos.reliability())
+        , m_durabilityPolicy(qos.durability())
+        , m_depth(qos.depth())
+    {
+    }
+
+    AZ::Crc32 QoS::OnQoSSelected() const
+    {
+        return AZ::Edit::PropertyRefreshLevels::AttributesAndValues;
+    }
+
+    rclcpp::QoS QoS::GetQoS() const
+    {
+        rclcpp::QoS qos(m_depth);
+        return qos.reliability(m_reliabilityPolicy).durability(m_durabilityPolicy);
+    }
+
+    void QoS::Reflect(AZ::ReflectContext* context)
+    {
+        if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
+        {
+            serializeContext->Class<QoS>()
+                ->Version(1)
+                ->Field("Reliability", &QoS::m_reliabilityPolicy)
+                ->Field("Durability", &QoS::m_durabilityPolicy)
+                ->Field("Depth", &QoS::m_depth);
+
+            if (AZ::EditContext* ec = serializeContext->GetEditContext())
+            {
+                ec->Class<QoS>("Quality of Service configuration", "")
+                    ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
+                    ->DataElement(
+                        AZ::Edit::UIHandlers::ComboBox,
+                        &QoS::m_reliabilityPolicy,
+                        "Reliability policy",
+                        "Determines DDS reliability policy for the publisher")
+                    ->Attribute(AZ::Edit::Attributes::ChangeNotify, &QoS::OnQoSSelected)
+                    ->EnumAttribute(rclcpp::ReliabilityPolicy::Reliable, "Reliable")
+                    ->EnumAttribute(rclcpp::ReliabilityPolicy::BestEffort, "Best Effort")
+                    ->DataElement(
+                        AZ::Edit::UIHandlers::ComboBox,
+                        &QoS::m_durabilityPolicy,
+                        "Durability policy",
+                        "Determines DDS durability policy for the publisher")
+                    ->Attribute(AZ::Edit::Attributes::ChangeNotify, &QoS::OnQoSSelected)
+                    ->EnumAttribute(rclcpp::DurabilityPolicy::Volatile, "Volatile")
+                    ->EnumAttribute(rclcpp::DurabilityPolicy::TransientLocal, "Transient Local")
+                    ->DataElement(AZ::Edit::UIHandlers::Default, &QoS::m_depth, "History depth", "Determines DDS publisher queue size");
+            }
+        }
+    };
+} // namespace ROS2

+ 36 - 0
Gems/ROS2/Code/Source/Communication/TopicConfiguration.cpp

@@ -0,0 +1,36 @@
+/*
+ * 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 <AzCore/Serialization/EditContext.h>
+#include <ROS2/Communication/TopicConfiguration.h>
+#include <ROS2/Utilities/ROS2Names.h>
+
+namespace ROS2
+{
+    void TopicConfiguration::Reflect(AZ::ReflectContext* context)
+    {
+        if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
+        {
+            serializeContext->Class<TopicConfiguration>()
+                ->Version(1)
+                ->Field("Type", &TopicConfiguration::m_type)
+                ->Field("Topic", &TopicConfiguration::m_topic)
+                ->Field("QoS", &TopicConfiguration::m_qos);
+
+            if (AZ::EditContext* ec = serializeContext->GetEditContext())
+            {
+                ec->Class<TopicConfiguration>("Publisher configuration", "")
+                    ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
+                    ->DataElement(AZ::Edit::UIHandlers::Default, &TopicConfiguration::m_type, "Type", "Type of topic messages")
+                    ->DataElement(AZ::Edit::UIHandlers::Default, &TopicConfiguration::m_topic, "Topic", "Topic with no namespace")
+                    ->Attribute(AZ::Edit::Attributes::ChangeValidate, &ROS2Names::ValidateTopicField)
+                    ->DataElement(AZ::Edit::UIHandlers::Default, &TopicConfiguration::m_qos, "QoS", "Quality of Service");
+            }
+        }
+    };
+} // namespace ROS2

+ 99 - 0
Gems/ROS2/Code/Source/Frame/NamespaceConfiguration.cpp

@@ -0,0 +1,99 @@
+/*
+ * 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 <AzCore/Serialization/EditContext.h>
+#include <AzCore/Serialization/EditContextConstants.inl>
+#include <ROS2/Frame/NamespaceConfiguration.h>
+#include <ROS2/Utilities/ROS2Names.h>
+
+namespace ROS2
+{
+    void NamespaceConfiguration::PopulateNamespace(bool isRoot, const AZStd::string& entityName)
+    {
+        m_isRoot = isRoot;
+        m_entityName = entityName;
+        OnNamespaceStrategySelected();
+    }
+
+    void NamespaceConfiguration::UpdateNamespace()
+    {
+        switch (m_namespaceStrategy)
+        {
+        case NamespaceStrategy::Empty:
+            m_namespace = "";
+            break;
+        case NamespaceStrategy::Default:
+            m_namespace = m_isRoot ? ROS2Names::RosifyName(m_entityName) : "";
+            break;
+        case NamespaceStrategy::FromEntityName:
+            m_namespace = ROS2Names::RosifyName(m_entityName);
+            break;
+        case NamespaceStrategy::Custom:
+            // Leave the namespace as is
+            break;
+        default:
+            AZ_Assert(false, "Unhandled namespace strategy");
+            break;
+        }
+    }
+
+    AZ::Crc32 NamespaceConfiguration::OnNamespaceStrategySelected()
+    {
+        UpdateNamespace();
+        return AZ::Edit::PropertyRefreshLevels::EntireTree;
+    }
+
+    AZStd::string NamespaceConfiguration::GetNamespace(const AZStd::string& parentNamespace) const
+    {
+        if (parentNamespace.empty())
+        {
+            return m_namespace;
+        }
+
+        if (m_namespace.empty())
+        {
+            return parentNamespace;
+        }
+
+        return ROS2Names::GetNamespacedName(parentNamespace, m_namespace);
+    }
+
+    bool NamespaceConfiguration::IsNamespaceCustom() const
+    {
+        return m_namespaceStrategy == NamespaceConfiguration::NamespaceStrategy::Custom;
+    }
+
+    void NamespaceConfiguration::Reflect(AZ::ReflectContext* context)
+    {
+        if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
+        {
+            serializeContext->Class<NamespaceConfiguration>()
+                ->Version(1)
+                ->Field("Namespace Strategy", &NamespaceConfiguration::m_namespaceStrategy)
+                ->Field("Namespace", &NamespaceConfiguration::m_namespace);
+
+            if (AZ::EditContext* ec = serializeContext->GetEditContext())
+            {
+                ec->Class<NamespaceConfiguration>("Namespace Configuration", "Handles ROS2 namespaces")
+                    ->DataElement(
+                        AZ::Edit::UIHandlers::ComboBox,
+                        &NamespaceConfiguration::m_namespaceStrategy,
+                        "Namespace strategy",
+                        "Determines how namespace for frames and topics is created from the hierarchy")
+                    ->Attribute(AZ::Edit::Attributes::ChangeNotify, &NamespaceConfiguration::OnNamespaceStrategySelected)
+                    ->EnumAttribute(NamespaceConfiguration::NamespaceStrategy::Default, "Default")
+                    ->EnumAttribute(NamespaceConfiguration::NamespaceStrategy::Empty, "Empty")
+                    ->EnumAttribute(NamespaceConfiguration::NamespaceStrategy::FromEntityName, "Generate from entity name")
+                    ->EnumAttribute(NamespaceConfiguration::NamespaceStrategy::Custom, "Custom")
+                    ->DataElement(AZ::Edit::UIHandlers::Default, &NamespaceConfiguration::m_namespace, "Namespace", "Namespace")
+                    ->Attribute(AZ::Edit::Attributes::Visibility, &NamespaceConfiguration::IsNamespaceCustom)
+                    ->Attribute(AZ::Edit::Attributes::ChangeValidate, &ROS2Names::ValidateNamespaceField);
+            }
+        }
+    }
+} // namespace ROS2

+ 216 - 0
Gems/ROS2/Code/Source/Frame/ROS2FrameComponent.cpp

@@ -0,0 +1,216 @@
+/*
+ * 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 <AzCore/Component/Entity.h>
+#include <AzCore/Serialization/EditContext.h>
+#include <AzCore/Serialization/EditContextConstants.inl>
+#include <AzCore/Serialization/SerializeContext.h>
+#include <ROS2/Frame/ROS2FrameComponent.h>
+#include <ROS2/ROS2Bus.h>
+#include <ROS2/ROS2GemUtilities.h>
+#include <ROS2/Utilities/ROS2Names.h>
+namespace ROS2
+{
+    namespace Internal
+    {
+        AZ::TransformInterface* GetEntityTransformInterface(const AZ::Entity* entity)
+        {
+            if (!entity)
+            {
+                AZ_Error("GetEntityTransformInterface", false, "Invalid entity!");
+                return nullptr;
+            }
+
+            auto* interface = Utils::GetGameOrEditorComponent<AzFramework::TransformComponent>(entity);
+
+            return interface;
+        }
+
+        const ROS2FrameComponent* GetFirstROS2FrameAncestor(const AZ::Entity* entity)
+        {
+            auto* entityTransformInterface = GetEntityTransformInterface(entity);
+            if (!entityTransformInterface)
+            {
+                AZ_Error("GetFirstROS2FrameAncestor", false, "Invalid transform interface!");
+                return nullptr;
+            }
+
+            AZ::EntityId parentEntityId = entityTransformInterface->GetParentId();
+            if (!parentEntityId.IsValid())
+            { // We have reached the top level, there is no parent entity so there can be no parent ROS2Frame
+                return nullptr;
+            }
+            AZ::Entity* parentEntity = nullptr;
+            AZ::ComponentApplicationBus::BroadcastResult(parentEntity, &AZ::ComponentApplicationRequests::FindEntity, parentEntityId);
+            AZ_Assert(parentEntity, "No parent entity id : %s", parentEntityId.ToString().c_str());
+            auto* component = Utils::GetGameOrEditorComponent<ROS2FrameComponent>(parentEntity);
+            if (component == nullptr)
+            { // Parent entity has no ROS2Frame, but there can still be a ROS2Frame in its ancestors
+                return GetFirstROS2FrameAncestor(parentEntity);
+            }
+
+            // Found the component!
+            return component;
+        }
+    } // namespace Internal
+
+    void ROS2FrameComponent::Activate()
+    {
+        m_namespaceConfiguration.PopulateNamespace(IsTopLevel(), GetEntity()->GetName());
+
+        if (m_publishTransform)
+        {
+            AZ_TracePrintf(
+                "ROS2FrameComponent",
+                "Setting up %s transfrom between parent %s and child %s to be published %s\n",
+                IsDynamic() ? "dynamic" : "static",
+                GetParentFrameID().data(),
+                GetFrameID().data(),
+                IsDynamic() ? "continuously to /tf" : "once to /tf_static");
+
+            m_ros2Transform = AZStd::make_unique<ROS2Transform>(GetParentFrameID(), GetFrameID(), IsDynamic());
+            if (IsDynamic())
+            {
+                AZ::TickBus::Handler::BusConnect();
+            }
+            else
+            {
+                m_ros2Transform->Publish(GetFrameTransform());
+            }
+        }
+    }
+
+    void ROS2FrameComponent::Deactivate()
+    {
+        if (m_publishTransform)
+        {
+            if (IsDynamic())
+            {
+                AZ::TickBus::Handler::BusDisconnect();
+            }
+            m_ros2Transform.reset();
+        }
+    }
+
+    void ROS2FrameComponent::OnTick([[maybe_unused]] float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time)
+    {
+        m_ros2Transform->Publish(GetFrameTransform());
+    }
+
+    AZStd::string ROS2FrameComponent::GetGlobalFrameName() const
+    {
+        return ROS2Names::GetNamespacedName(GetNamespace(), AZStd::string("odom"));
+    }
+
+    bool ROS2FrameComponent::IsTopLevel() const
+    {
+        return GetGlobalFrameName() == GetParentFrameID();
+    }
+
+    bool ROS2FrameComponent::IsDynamic() const
+    {
+        return IsTopLevel();
+    }
+
+    const ROS2FrameComponent* ROS2FrameComponent::GetParentROS2FrameComponent() const
+    {
+        return Internal::GetFirstROS2FrameAncestor(GetEntity());
+    }
+
+    const AZ::Transform& ROS2FrameComponent::GetFrameTransform() const
+    {
+        auto* transformInterface = Internal::GetEntityTransformInterface(GetEntity());
+        if (GetParentROS2FrameComponent() != nullptr)
+        {
+            return transformInterface->GetLocalTM();
+        }
+        return transformInterface->GetWorldTM();
+    }
+
+    AZStd::string ROS2FrameComponent::GetParentFrameID() const
+    {
+        if (auto parentFrame = GetParentROS2FrameComponent(); parentFrame != nullptr)
+        {
+            return parentFrame->GetFrameID();
+        }
+        // If parent entity does not exist or does not have a ROS2FrameComponent, return ROS2 default global frame.
+        return GetGlobalFrameName();
+    }
+
+    AZStd::string ROS2FrameComponent::GetFrameID() const
+    {
+        return ROS2Names::GetNamespacedName(GetNamespace(), m_frameName);
+    }
+
+    void ROS2FrameComponent::SetFrameID(const AZStd::string& frameId)
+    {
+        m_frameName = frameId;
+    }
+
+    AZStd::string ROS2FrameComponent::GetNamespace() const
+    {
+        auto parentFrame = GetParentROS2FrameComponent();
+        AZStd::string parentNamespace;
+        if (parentFrame != nullptr)
+        {
+            parentNamespace = parentFrame->GetNamespace();
+        }
+        return m_namespaceConfiguration.GetNamespace(parentNamespace);
+    }
+
+    void ROS2FrameComponent::Reflect(AZ::ReflectContext* context)
+    {
+        NamespaceConfiguration::Reflect(context);
+        if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context))
+        {
+            serialize->Class<ROS2FrameComponent, AZ::Component>()
+                ->Version(1)
+                ->Field("Namespace Configuration", &ROS2FrameComponent::m_namespaceConfiguration)
+                ->Field("Frame Name", &ROS2FrameComponent::m_frameName)
+                ->Field("Publish Transform", &ROS2FrameComponent::m_publishTransform);
+
+            if (AZ::EditContext* ec = serialize->GetEditContext())
+            {
+                ec->Class<ROS2FrameComponent>("ROS2 Frame", "[ROS2 Frame component]")
+                    ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
+                    ->Attribute(AZ::Edit::Attributes::Category, "ROS2")
+                    ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game"))
+                    ->DataElement(
+                        AZ::Edit::UIHandlers::Default,
+                        &ROS2FrameComponent::m_namespaceConfiguration,
+                        "Namespace Configuration",
+                        "Namespace Configuration")
+                    ->DataElement(AZ::Edit::UIHandlers::Default, &ROS2FrameComponent::m_frameName, "Frame Name", "Frame Name")
+                    ->DataElement(
+                        AZ::Edit::UIHandlers::Default, &ROS2FrameComponent::m_publishTransform, "Publish Transform", "Publish Transform");
+            }
+        }
+    }
+
+    void ROS2FrameComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
+    {
+        provided.push_back(AZ_CRC_CE("ROS2Frame"));
+    }
+
+    void ROS2FrameComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
+    {
+        incompatible.push_back(AZ_CRC_CE("ROS2Frame"));
+    }
+
+    void ROS2FrameComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
+    {
+        required.push_back(AZ_CRC_CE("TransformService"));
+    }
+
+    ROS2FrameComponent::ROS2FrameComponent() = default;
+
+    ROS2FrameComponent::ROS2FrameComponent(const AZStd::string& frameId)
+        : m_frameName(frameId)
+    {
+    }
+} // namespace ROS2

+ 44 - 0
Gems/ROS2/Code/Source/Frame/ROS2Transform.cpp

@@ -0,0 +1,44 @@
+/*
+ * 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 <ROS2/Frame/ROS2Transform.h>
+#include <ROS2/ROS2Bus.h>
+#include <ROS2/Utilities/ROS2Conversions.h>
+#include <tf2_ros/qos.hpp>
+#include <tf2_ros/transform_broadcaster.h>
+
+namespace ROS2
+{
+    ROS2Transform::ROS2Transform(AZStd::string parentFrame, AZStd::string childFrame, bool isDynamic)
+        : m_parentFrame(AZStd::move(parentFrame))
+        , m_childFrame(AZStd::move(childFrame))
+        , m_isDynamic(isDynamic)
+    {
+    }
+
+    geometry_msgs::msg::TransformStamped ROS2Transform::CreateTransformMessage(const AZ::Transform& o3deTransform)
+    {
+        geometry_msgs::msg::TransformStamped t;
+        t.header.stamp = ROS2Interface::Get()->GetROSTimestamp();
+        t.header.frame_id = m_parentFrame.data();
+        t.child_frame_id = m_childFrame.data();
+        t.transform.translation = ROS2Conversions::ToROS2Vector3(o3deTransform.GetTranslation());
+        t.transform.rotation = ROS2Conversions::ToROS2Quaternion(o3deTransform.GetRotation());
+        return t;
+    }
+
+    void ROS2Transform::Publish(const AZ::Transform& transform)
+    {
+        if (m_isPublished && !m_isDynamic)
+        { // Only publish static transforms once
+            return;
+        }
+        ROS2Interface::Get()->BroadcastTransform(CreateTransformMessage(transform), m_isDynamic);
+        m_isPublished = true;
+    }
+} // namespace ROS2

+ 107 - 0
Gems/ROS2/Code/Source/GNSS/GNSSFormatConversions.cpp

@@ -0,0 +1,107 @@
+/*
+ * 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 "GNSSFormatConversions.h"
+
+constexpr double earthSemimajorAxis = 6378137.0f;
+constexpr double reciprocalFlattening = 1 / 298.257223563f;
+constexpr double earthSemiminorAxis = earthSemimajorAxis * (1.0f - reciprocalFlattening);
+constexpr double firstEccentricitySquared = 2 * reciprocalFlattening - reciprocalFlattening * reciprocalFlattening;
+constexpr double secondEccentrictySquared =
+    reciprocalFlattening * (2.0f - reciprocalFlattening) / ((1.0f - reciprocalFlattening) * (1.0f - reciprocalFlattening));
+
+// Based on http://wiki.gis.com/wiki/index.php/Geodetic_system
+namespace ROS2::GNSS
+{
+
+    AZ::Vector3 WGS84ToECEF(const AZ::Vector3& latitudeLongitudeAltitude)
+    {
+        const double latitudeRad = AZ::DegToRad(latitudeLongitudeAltitude.GetX());
+        const double longitudeRad = AZ::DegToRad(latitudeLongitudeAltitude.GetY());
+        const double altitude = latitudeLongitudeAltitude.GetZ();
+
+        const double helper = AZStd::sqrt(1.0f - firstEccentricitySquared * AZStd::sin(latitudeRad) * AZStd::sin(latitudeRad));
+
+        const double X = (earthSemimajorAxis / helper + altitude) * AZStd::cos(latitudeRad) * AZStd::cos(longitudeRad);
+        const double Y = (earthSemimajorAxis / helper + altitude) * AZStd::cos(latitudeRad) * AZStd::sin(longitudeRad);
+        const double Z = (earthSemimajorAxis * (1.0f - firstEccentricitySquared) / helper + altitude) * AZStd::sin(latitudeRad);
+
+        return { static_cast<float>(X), static_cast<float>(Y), static_cast<float>(Z) };
+    }
+
+    AZ::Vector3 ECEFToENU(const AZ::Vector3& referenceLatitudeLongitudeAltitude, const AZ::Vector3& ECEFPoint)
+    {
+        const AZ::Vector3 referencePointInECEF = WGS84ToECEF(referenceLatitudeLongitudeAltitude);
+        const AZ::Vector3 pointToReferencePointECEF = ECEFPoint - referencePointInECEF;
+
+        const float referenceLatitudeRad = AZ::DegToRad(referenceLatitudeLongitudeAltitude.GetX());
+        const float referenceLongitudeRad = AZ::DegToRad(referenceLatitudeLongitudeAltitude.GetY());
+
+        return {
+            -AZStd::sin(referenceLongitudeRad) * pointToReferencePointECEF.GetX() +
+                AZStd::cos(referenceLongitudeRad) * pointToReferencePointECEF.GetY(),
+            -AZStd::sin(referenceLatitudeRad) * AZStd::cos(referenceLongitudeRad) * pointToReferencePointECEF.GetX() -
+                AZStd::sin(referenceLatitudeRad) * AZStd::sin(referenceLongitudeRad) * pointToReferencePointECEF.GetY() +
+                AZStd::cos(referenceLatitudeRad) * pointToReferencePointECEF.GetZ(),
+            AZStd::cos(referenceLatitudeRad) * AZStd::cos(referenceLongitudeRad) * pointToReferencePointECEF.GetX() +
+                AZStd::cos(referenceLatitudeRad) * AZStd::sin(referenceLongitudeRad) * pointToReferencePointECEF.GetY() +
+                AZStd::sin(referenceLatitudeRad) * pointToReferencePointECEF.GetZ(),
+        };
+    }
+
+    AZ::Vector3 ENUToECEF(const AZ::Vector3& referenceLatitudeLongitudeAltitude, const AZ::Vector3& ENUPoint)
+    {
+        AZ::Vector3 referenceECEF = WGS84ToECEF(referenceLatitudeLongitudeAltitude);
+
+        const float referenceLatitudeRad = AZ::DegToRad(referenceLatitudeLongitudeAltitude.GetX());
+        const float referenceLongitudeRad = AZ::DegToRad(referenceLatitudeLongitudeAltitude.GetY());
+        const float e = ENUPoint.GetX();
+        const float n = ENUPoint.GetY();
+        const float u = ENUPoint.GetZ();
+
+        return { -AZStd::sin(referenceLongitudeRad) * e - AZStd::cos(referenceLongitudeRad) * AZStd::sin(referenceLatitudeRad) * n +
+                     AZStd::cos(referenceLongitudeRad) * AZStd::cos(referenceLatitudeRad) * u + referenceECEF.GetX(),
+                 AZStd::cos(referenceLongitudeRad) * e - AZStd::sin(referenceLongitudeRad) * AZStd::sin(referenceLatitudeRad) * n +
+                     AZStd::cos(referenceLatitudeRad) * AZStd::sin(referenceLongitudeRad) * u + referenceECEF.GetY(),
+                 AZStd::cos(referenceLatitudeRad) * n + AZStd::sin(referenceLatitudeRad) * u + referenceECEF.GetZ() };
+    }
+
+    AZ::Vector3 ECEFToWGS84(const AZ::Vector3& ECFEPoint)
+    {
+        const double x = ECFEPoint.GetX();
+        const double y = ECFEPoint.GetY();
+        const double z = ECFEPoint.GetZ();
+
+        const double radiusSquared = x * x + y * y;
+        const double radius = AZStd::sqrt(radiusSquared);
+
+        const double E2 = earthSemimajorAxis * earthSemimajorAxis - earthSemiminorAxis * earthSemiminorAxis;
+        const double F = 54.0f * earthSemiminorAxis * earthSemiminorAxis * z * z;
+        const double G = radiusSquared + (1.0f - firstEccentricitySquared) * z * z - firstEccentricitySquared * E2;
+        const double c = (firstEccentricitySquared * firstEccentricitySquared * F * radiusSquared) / (G * G * G);
+        const double s = AZStd::pow(1. + c + AZStd::sqrt(c * c + 2. * c), 1. / 3);
+        const double P = F / (3.0f * (s + 1.0f / s + 1.0f) * (s + 1.0f / s + 1.0f) * G * G);
+        const double Q = AZStd::sqrt(1.0f + 2.0f * firstEccentricitySquared * firstEccentricitySquared * P);
+
+        const double ro = -(firstEccentricitySquared * P * radius) / (1.0f + Q) +
+            AZStd::sqrt(
+                (earthSemimajorAxis * earthSemimajorAxis / 2.0f) * (1.0f + 1.0f / Q) -
+                ((1.0f - firstEccentricitySquared) * P * z * z) / (Q * (1.0f + Q)) - P * radiusSquared / 2.0f);
+        const double tmp = (radius - firstEccentricitySquared * ro) * (radius - firstEccentricitySquared * ro);
+        const double U = AZStd::sqrt(tmp + z * z);
+        const double V = AZStd::sqrt(tmp + (1.0f - firstEccentricitySquared) * z * z);
+        const double zo = (earthSemiminorAxis * earthSemiminorAxis * z) / (earthSemimajorAxis * V);
+
+        const double latitude = AZStd::atan((z + secondEccentrictySquared * zo) / radius);
+        const double longitude = AZStd::atan2(y, x);
+        const double altitude = U * (1.0f - earthSemiminorAxis * earthSemiminorAxis / (earthSemimajorAxis * V));
+
+        return { AZ::RadToDeg(static_cast<float>(latitude)), AZ::RadToDeg(static_cast<float>(longitude)), static_cast<float>(altitude) };
+    }
+
+} // namespace ROS2::GNSS

+ 45 - 0
Gems/ROS2/Code/Source/GNSS/GNSSFormatConversions.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 <AzCore/Math/Matrix4x4.h>
+
+namespace ROS2::GNSS
+{
+
+    //! Converts point in 1984 World Geodetic System (GS84) to Earth Centred Earth Fixed (ECEF)
+    //! @param latitudeLongitudeAltitude - point's latitude, longitude and altitude as 3d vector.
+    //!     latitude and longitude are in decimal degrees
+    //!     altitude is in meters
+    //! @return 3d vector of ECEF coordinates.
+    AZ::Vector3 WGS84ToECEF(const AZ::Vector3& latitudeLongitudeAltitude);
+
+    //! Converts Earth Centred Earth Fixed (ECEF) coordinates to local east, north, up (ENU)
+    //! @param referenceLatitudeLongitudeAltitude - reference point's latitude, longitude and altitude as 3d vector.
+    //!     latitude and longitude are in decimal degrees
+    //!     altitude is in meters
+    //! @param ECEFPoint - ECEF point to bo converted.
+    //! @return 3d vector of local east, north, up (ENU) coordinates.
+    AZ::Vector3 ECEFToENU(const AZ::Vector3& referenceLatitudeLongitudeAltitude, const AZ::Vector3& ECEFPoint);
+
+    //! Converts local east, north, up (ENU) coordinates to Earth Centred Earth Fixed (ECEF)
+    //! @param referenceLatitudeLongitudeAltitude - reference point's latitude, longitude and altitude as 3d vector.
+    //!     latitude and longitude are in decimal degrees
+    //!     altitude is in meters
+    //! @param ENUPoint - ENU point to bo converted.
+    //! @return 3d vector of ECEF coordinates.
+    AZ::Vector3 ENUToECEF(const AZ::Vector3& referenceLatitudeLongitudeAltitude, const AZ::Vector3& ENUPoint);
+
+    //! Converts point in Earth Centred Earth Fixed (ECEF) to  984 World Geodetic System (GS84)
+    //! @param ECEFPoint - ECEF point to bo converted.
+    //! @return point's latitude, longitude and altitude as 3d vector.
+    //!     latitude and longitude are in decimal degrees
+    //!     altitude is in meters
+    AZ::Vector3 ECEFToWGS84(const AZ::Vector3& ECFEPoint);
+} // namespace ROS2::GNSS

+ 114 - 0
Gems/ROS2/Code/Source/GNSS/ROS2GNSSSensorComponent.cpp

@@ -0,0 +1,114 @@
+/*
+ * 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 "ROS2GNSSSensorComponent.h"
+#include <ROS2/Frame/ROS2FrameComponent.h>
+#include <ROS2/ROS2Bus.h>
+#include <ROS2/ROS2GemUtilities.h>
+#include <ROS2/Utilities/ROS2Conversions.h>
+#include <ROS2/Utilities/ROS2Names.h>
+
+#include <AzCore/Math/Matrix4x4.h>
+#include <AzCore/Serialization/EditContext.h>
+#include <AzCore/Serialization/EditContextConstants.inl>
+
+#include "GNSSFormatConversions.h"
+
+namespace ROS2
+{
+    namespace Internal
+    {
+        const char* kGNSSMsgType = "sensor_msgs::msg::NavSatFix";
+    }
+
+    void ROS2GNSSSensorComponent::Reflect(AZ::ReflectContext* context)
+    {
+        if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context))
+        {
+            serialize->Class<ROS2GNSSSensorComponent, ROS2SensorComponent>()
+                ->Version(1)
+                ->Field("gnssOriginLatitude", &ROS2GNSSSensorComponent::m_gnssOriginLatitudeDeg)
+                ->Field("gnssOriginLongitude", &ROS2GNSSSensorComponent::m_gnssOriginLongitudeDeg)
+                ->Field("gnssOriginAltitude", &ROS2GNSSSensorComponent::m_gnssOriginAltitude);
+
+            if (AZ::EditContext* ec = serialize->GetEditContext())
+            {
+                ec->Class<ROS2GNSSSensorComponent>("ROS2 GNSS Sensor", "GNSS sensor component")
+                    ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
+                    ->Attribute(AZ::Edit::Attributes::Category, "ROS2")
+                    ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game"))
+                    ->DataElement(
+                        AZ::Edit::UIHandlers::Default,
+                        &ROS2GNSSSensorComponent::m_gnssOriginLatitudeDeg,
+                        "Latitude offset",
+                        "GNSS latitude position offset in degrees")
+                    ->DataElement(
+                        AZ::Edit::UIHandlers::Default,
+                        &ROS2GNSSSensorComponent::m_gnssOriginLongitudeDeg,
+                        "Longitude offset",
+                        "GNSS longitude position offset in degrees")
+                    ->DataElement(
+                        AZ::Edit::UIHandlers::Default,
+                        &ROS2GNSSSensorComponent::m_gnssOriginAltitude,
+                        "Altitude offset",
+                        "GNSS altitude position offset in meters");
+            }
+        }
+    }
+
+    ROS2GNSSSensorComponent::ROS2GNSSSensorComponent()
+    {
+        TopicConfiguration pc;
+        pc.m_type = Internal::kGNSSMsgType;
+        pc.m_topic = "gnss";
+        m_sensorConfiguration.m_frequency = 10;
+        m_sensorConfiguration.m_publishersConfigurations.insert(AZStd::make_pair(Internal::kGNSSMsgType, pc));
+    }
+
+    void ROS2GNSSSensorComponent::Activate()
+    {
+        ROS2SensorComponent::Activate();
+        auto ros2Node = ROS2Interface::Get()->GetNode();
+        AZ_Assert(m_sensorConfiguration.m_publishersConfigurations.size() == 1, "Invalid configuration of publishers for GNSS sensor");
+
+        const auto publisherConfig = m_sensorConfiguration.m_publishersConfigurations[Internal::kGNSSMsgType];
+        const auto fullTopic = ROS2Names::GetNamespacedName(GetNamespace(), publisherConfig.m_topic);
+        m_gnssPublisher = ros2Node->create_publisher<sensor_msgs::msg::NavSatFix>(fullTopic.data(), publisherConfig.GetQoS());
+
+        m_gnssMsg.header.frame_id = "gnss_frame_id";
+    }
+
+    void ROS2GNSSSensorComponent::Deactivate()
+    {
+        ROS2SensorComponent::Deactivate();
+        m_gnssPublisher.reset();
+    }
+
+    void ROS2GNSSSensorComponent::FrequencyTick()
+    {
+        const AZ::Vector3 currentPosition = GetCurrentPose().GetTranslation();
+        const AZ::Vector3 currentPositionECEF =
+            GNSS::ENUToECEF({ m_gnssOriginLatitudeDeg, m_gnssOriginLongitudeDeg, m_gnssOriginAltitude }, currentPosition);
+        const AZ::Vector3 currentPositionWGS84 = GNSS::ECEFToWGS84(currentPositionECEF);
+
+        m_gnssMsg.latitude = currentPositionWGS84.GetX();
+        m_gnssMsg.longitude = currentPositionWGS84.GetY();
+        m_gnssMsg.altitude = currentPositionWGS84.GetZ();
+
+        m_gnssMsg.status.status = sensor_msgs::msg::NavSatStatus::STATUS_SBAS_FIX;
+        m_gnssMsg.status.service = sensor_msgs::msg::NavSatStatus::SERVICE_GALILEO;
+
+        m_gnssPublisher->publish(m_gnssMsg);
+    }
+
+    AZ::Transform ROS2GNSSSensorComponent::GetCurrentPose() const
+    {
+        auto* ros2Frame = Utils::GetGameOrEditorComponent<ROS2FrameComponent>(GetEntity());
+        return ros2Frame->GetFrameTransform();
+    }
+} // namespace ROS2

+ 51 - 0
Gems/ROS2/Code/Source/GNSS/ROS2GNSSSensorComponent.h

@@ -0,0 +1,51 @@
+/*
+ * 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 <AzCore/Math/Transform.h>
+#include <AzCore/Serialization/SerializeContext.h>
+#include <ROS2/Sensor/ROS2SensorComponent.h>
+#include <rclcpp/publisher.hpp>
+#include <sensor_msgs/msg/nav_sat_fix.hpp>
+
+namespace ROS2
+{
+    //! Global Navigation Satellite Systems (GNSS) sensor component class
+    //! It provides NavSatFix data of sensor's position in GNSS frame which is defined by GNSS origin offset
+    //! Offset is provided as latitude [deg], longitude [deg], altitude [m] of o3de global frame
+    //! It is assumed that o3de global frame overlaps with ENU coordinate system
+    class ROS2GNSSSensorComponent : public ROS2SensorComponent
+    {
+    public:
+        AZ_COMPONENT(ROS2GNSSSensorComponent, "{55B4A299-7FA3-496A-88F0-764C75B0E9A7}", ROS2SensorComponent);
+        ROS2GNSSSensorComponent();
+        ~ROS2GNSSSensorComponent() = default;
+        static void Reflect(AZ::ReflectContext* context);
+        //////////////////////////////////////////////////////////////////////////
+        // Component overrides
+        void Activate() override;
+        void Deactivate() override;
+        //////////////////////////////////////////////////////////////////////////
+
+    private:
+        float m_gnssOriginLatitudeDeg = 0.0f;
+        float m_gnssOriginLongitudeDeg = 0.0f;
+        float m_gnssOriginAltitude = 0.0f;
+
+        //////////////////////////////////////////////////////////////////////////
+        // ROS2SensorComponent overrides
+        void FrequencyTick() override;
+        //////////////////////////////////////////////////////////////////////////
+
+        AZ::Transform GetCurrentPose() const;
+
+        std::shared_ptr<rclcpp::Publisher<sensor_msgs::msg::NavSatFix>> m_gnssPublisher;
+        sensor_msgs::msg::NavSatFix m_gnssMsg;
+    };
+
+} // namespace ROS2

+ 151 - 0
Gems/ROS2/Code/Source/Imu/ROS2ImuSensorComponent.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 "ROS2ImuSensorComponent.h"
+#include <ROS2/Frame/ROS2FrameComponent.h>
+#include <ROS2/ROS2Bus.h>
+#include <ROS2/Utilities/ROS2Conversions.h>
+#include <ROS2/Utilities/ROS2Names.h>
+
+#include <AzCore/Component/Entity.h>
+#include <AzCore/Script/ScriptTimePoint.h>
+#include <AzCore/Serialization/EditContext.h>
+#include <AzCore/Serialization/EditContextConstants.inl>
+#include <AzCore/std/smart_ptr/make_shared.h>
+
+namespace ROS2
+{
+    namespace Internal
+    {
+        const char* kImuMsgType = "sensor_msgs::msg::Imu";
+    }
+
+    void ROS2ImuSensorComponent::Reflect(AZ::ReflectContext* context)
+    {
+        if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context))
+        {
+            serialize->Class<ROS2ImuSensorComponent, ROS2SensorComponent>()->Version(1);
+
+            if (AZ::EditContext* ec = serialize->GetEditContext())
+            {
+                ec->Class<ROS2ImuSensorComponent>("ROS2 Imu Sensor", "Imu sensor component")
+                    ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
+                    ->Attribute(AZ::Edit::Attributes::Category, "ROS2")
+                    ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game"));
+            }
+        }
+    }
+
+    ROS2ImuSensorComponent::ROS2ImuSensorComponent()
+    {
+        TopicConfiguration pc;
+        const AZStd::string type = Internal::kImuMsgType;
+        pc.m_type = type;
+        pc.m_topic = "imu";
+        m_sensorConfiguration.m_frequency = 10;
+        m_sensorConfiguration.m_publishersConfigurations.insert(AZStd::make_pair(type, pc));
+    }
+
+    void ROS2ImuSensorComponent::Activate()
+    {
+        ROS2SensorComponent::Activate();
+        auto ros2Node = ROS2Interface::Get()->GetNode();
+        AZ_Assert(m_sensorConfiguration.m_publishersConfigurations.size() == 1, "Invalid configuration of publishers for IMU sensor");
+
+        const auto publisherConfig = m_sensorConfiguration.m_publishersConfigurations[Internal::kImuMsgType];
+        const auto fullTopic = ROS2Names::GetNamespacedName(GetNamespace(), publisherConfig.m_topic);
+        m_imuPublisher = ros2Node->create_publisher<sensor_msgs::msg::Imu>(fullTopic.data(), publisherConfig.GetQoS());
+
+        InitializeImuMessage();
+
+        m_previousPose = GetCurrentPose();
+        m_previousTime = GetCurrentTimeInSec();
+    }
+
+    void ROS2ImuSensorComponent::Deactivate()
+    {
+        ROS2SensorComponent::Deactivate();
+        m_imuPublisher.reset();
+    }
+
+    void ROS2ImuSensorComponent::FrequencyTick()
+    {
+        const double currentTime = GetCurrentTimeInSec();
+        const auto timeDiff = currentTime - m_previousTime;
+
+        const auto currentPose = GetCurrentPose();
+        const auto frequency = 1.0 / timeDiff;
+
+        // Calculate angular velocity
+        const auto& currentRotation = currentPose.GetRotation();
+        const auto& previousRotation = m_previousPose.GetRotation();
+        const auto deltaRotation = currentRotation * previousRotation.GetInverseFull();
+        AZ::Vector3 axis;
+        float angle;
+        deltaRotation.ConvertToAxisAngle(axis, angle);
+        const auto angularVelocity = frequency * angle * axis;
+
+        // Calculate linear acceleration
+        const auto& currentPosition = currentPose.GetTranslation();
+        const auto deltaPositions = currentPosition - m_previousPose.GetTranslation();
+        const auto linearVelocity = currentPose.GetInverse().TransformVector(deltaPositions) * frequency;
+        const auto linearAcceleration = (linearVelocity - m_previousLinearVelocity) * frequency;
+
+        // Store current values
+        m_previousTime = currentTime;
+        m_previousPose = currentPose;
+        m_previousLinearVelocity = linearVelocity;
+
+        // Fill message fields
+        m_imuMsg.header.frame_id = GetFrameID().data();
+        m_imuMsg.header.stamp = ROS2Interface::Get()->GetROSTimestamp();
+
+        m_imuMsg.angular_velocity = ROS2Conversions::ToROS2Vector3(angularVelocity);
+        m_imuMsg.linear_acceleration = ROS2Conversions::ToROS2Vector3(linearAcceleration);
+
+        m_imuPublisher->publish(m_imuMsg);
+    }
+
+    void ROS2ImuSensorComponent::InitializeImuMessage()
+    {
+        // Set identity orientation
+        m_imuMsg.orientation.x = 0.0;
+        m_imuMsg.orientation.y = 0.0;
+        m_imuMsg.orientation.z = 0.0;
+        m_imuMsg.orientation.w = 1.0;
+
+        // Set all the covariances to 0
+        for (auto& e : m_imuMsg.orientation_covariance)
+        {
+            e = 0.0;
+        }
+
+        for (auto& e : m_imuMsg.angular_velocity_covariance)
+        {
+            e = 0.0;
+        }
+
+        for (auto& e : m_imuMsg.linear_acceleration_covariance)
+        {
+            e = 0.0;
+        }
+    }
+
+    double ROS2ImuSensorComponent::GetCurrentTimeInSec() const
+    {
+        AZ::ScriptTimePoint timePoint;
+        return timePoint.GetSeconds();
+    }
+
+    AZ::Transform ROS2ImuSensorComponent::GetCurrentPose() const
+    {
+        auto* ros2Frame = Utils::GetGameOrEditorComponent<ROS2FrameComponent>(GetEntity());
+        return ros2Frame->GetFrameTransform();
+    }
+
+} // namespace ROS2

+ 51 - 0
Gems/ROS2/Code/Source/Imu/ROS2ImuSensorComponent.h

@@ -0,0 +1,51 @@
+/*
+ * 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 <AzCore/Math/Transform.h>
+#include <AzCore/Serialization/SerializeContext.h>
+#include <ROS2/Sensor/ROS2SensorComponent.h>
+#include <rclcpp/publisher.hpp>
+#include <sensor_msgs/msg/imu.hpp>
+
+namespace ROS2
+{
+    //! An IMU (Inertial Measurement Unit) sensor Component.
+    //! IMUs typically include gyroscopes, accelerometers and magnetometers. This component encapsulates data
+    //! acquisition and its publishing to ROS2 ecosystem. IMU Component requires ROS2FrameComponent.
+    class ROS2ImuSensorComponent : public ROS2SensorComponent
+    {
+    public:
+        AZ_COMPONENT(ROS2ImuSensorComponent, "{502A955E-7742-4E23-AD77-5E4063739DCA}", ROS2SensorComponent);
+        ROS2ImuSensorComponent();
+        ~ROS2ImuSensorComponent() = default;
+        static void Reflect(AZ::ReflectContext* context);
+        //////////////////////////////////////////////////////////////////////////
+        // Component overrides
+        void Activate() override;
+        void Deactivate() override;
+        //////////////////////////////////////////////////////////////////////////
+
+    private:
+        //////////////////////////////////////////////////////////////////////////
+        // ROS2SensorComponent overrides
+        void FrequencyTick() override;
+        //////////////////////////////////////////////////////////////////////////
+
+        void InitializeImuMessage();
+        double GetCurrentTimeInSec() const;
+        AZ::Transform GetCurrentPose() const;
+
+        std::shared_ptr<rclcpp::Publisher<sensor_msgs::msg::Imu>> m_imuPublisher;
+
+        sensor_msgs::msg::Imu m_imuMsg;
+        double m_previousTime = 0.0;
+        AZ::Transform m_previousPose = AZ::Transform::CreateIdentity();
+        AZ::Vector3 m_previousLinearVelocity = AZ::Vector3::CreateZero();
+    };
+} // namespace ROS2

+ 88 - 0
Gems/ROS2/Code/Source/Lidar/LidarRaycaster.cpp

@@ -0,0 +1,88 @@
+/*
+ * 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 "LidarRaycaster.h"
+#include <AzCore/Interface/Interface.h>
+#include <AzCore/std/smart_ptr/make_shared.h>
+#include <AzCore/std/smart_ptr/shared_ptr.h>
+#include <AzFramework/Physics/Common/PhysicsSceneQueries.h>
+#include <AzFramework/Physics/PhysicsScene.h>
+#include <AzFramework/Physics/PhysicsSystem.h>
+#include <AzFramework/Physics/Shape.h>
+
+namespace ROS2
+{
+    void LidarRaycaster::SetRaycasterScene(const AzPhysics::SceneHandle& handle)
+    {
+        m_sceneHandle = handle;
+    }
+
+    AZStd::vector<AZ::Vector3> LidarRaycaster::PerformRaycast(
+        const AZ::Vector3& start,
+        const AZStd::vector<AZ::Vector3>& directions,
+        const AZ::Transform& globalToLidarTM,
+        float distance,
+        bool ignoreLayer,
+        unsigned int ignoredLayerIndex) const
+    {
+        AZStd::vector<AZ::Vector3> results;
+        if (m_sceneHandle == AzPhysics::InvalidSceneHandle)
+        {
+            AZ_Warning("LidarRaycaster", false, "No valid scene handle");
+            return results;
+        }
+
+        AzPhysics::SceneQueryRequests requests;
+        requests.reserve(directions.size());
+        results.reserve(directions.size());
+        for (const AZ::Vector3& direction : directions)
+        {
+            AZStd::shared_ptr<AzPhysics::RayCastRequest> request = AZStd::make_shared<AzPhysics::RayCastRequest>();
+            request->m_start = start;
+            request->m_direction = direction;
+            request->m_distance = distance;
+            request->m_reportMultipleHits = false;
+            request->m_filterCallback =
+                [ignoreLayer, ignoredLayerIndex](const AzPhysics::SimulatedBody* simBody, const Physics::Shape* shape)
+            {
+                if (ignoreLayer && (shape->GetCollisionLayer().GetIndex() == ignoredLayerIndex))
+                {
+                    return AzPhysics::SceneQuery::QueryHitType::None;
+                }
+                else
+                {
+                    return AzPhysics::SceneQuery::QueryHitType::Block;
+                }
+            };
+            requests.emplace_back(AZStd::move(request));
+        }
+
+        auto* sceneInterface = AZ::Interface<AzPhysics::SceneInterface>::Get();
+        auto requestResults = sceneInterface->QuerySceneBatch(m_sceneHandle, requests);
+        AZ_Assert(requestResults.size() == directions.size(), "request size should be equal to directions size");
+        for (int i = 0; i < requestResults.size(); i++)
+        {
+            const auto& requestResult = requestResults[i];
+            if (!requestResult.m_hits.empty())
+            {
+                auto globalHitPoint = requestResult.m_hits[0].m_position;
+                results.push_back(globalToLidarTM.TransformPoint(globalHitPoint)); // Transform back to local frame
+            }
+            else if (m_addPointsMaxRange)
+            {
+                results.push_back(globalToLidarTM.TransformPoint(start + directions[i] * distance));
+            }
+        }
+        return results;
+    }
+
+    void LidarRaycaster::SetAddPointsMaxRange(bool addPointsMaxRange)
+    {
+        m_addPointsMaxRange = addPointsMaxRange;
+    }
+
+} // namespace ROS2

+ 55 - 0
Gems/ROS2/Code/Source/Lidar/LidarRaycaster.h

@@ -0,0 +1,55 @@
+/*
+ * 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 <AzCore/Component/EntityId.h>
+#include <AzCore/Math/Transform.h>
+#include <AzCore/Math/Vector3.h>
+#include <AzCore/std/containers/vector.h>
+#include <AzFramework/Physics/PhysicsScene.h>
+
+namespace ROS2
+{
+    //! A simple implementation of Lidar operation in terms of raycasting.
+    class LidarRaycaster
+    {
+    public:
+        //! Set the Scene for the ray-casting.
+        //! This should be the scene with the Entity that holds the sensor.
+        //! @code
+        //! auto sceneHandle = AZ::RPI::Scene::GetSceneForEntityId(GetEntityId());
+        //! @endcode
+        //! @param handle Scene that will be subject to ray-casting.
+        void SetRaycasterScene(const AzPhysics::SceneHandle& handle);
+
+        //! Perform raycast against the current scene.
+        //! @param start Starting point of rays. This is a simplification since there can be multiple starting points
+        //! in real sensors.
+        //! @param directions Directions in which to shoot rays. These should be generated from Lidar configuration.
+        //! @param globalToLidarTM Transform from global to lidar reference frame.
+        //! @param distance Maximum distance for ray-casting.
+        //! @param ignoreLayer Should a specified collision layer be ignored
+        //! @param ignoredLayerIndex Index of collision layer to be ignored
+        //! @return Hits of raycast. The returned vector size can be anything between zero and size of directions.
+        //! No hits further than distance will be reported.
+        AZStd::vector<AZ::Vector3> PerformRaycast(
+            const AZ::Vector3& start,
+            const AZStd::vector<AZ::Vector3>& directions,
+            const AZ::Transform& globalToLidarTM,
+            float distance,
+            bool ignoreLayer,
+            unsigned int ignoredLayerIndex) const;
+
+        //! If true the raycaster will also include points at maximum range when nothing was hit
+        void SetAddPointsMaxRange(bool addPointsMaxRange);
+
+    private:
+        AzPhysics::SceneHandle m_sceneHandle;
+        bool m_addPointsMaxRange{ false };
+    };
+} // namespace ROS2

+ 65 - 0
Gems/ROS2/Code/Source/Lidar/LidarTemplate.cpp

@@ -0,0 +1,65 @@
+/*
+ * 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 "LidarTemplate.h"
+#include <AzCore/Serialization/EditContext.h>
+#include <AzCore/Serialization/EditContextConstants.inl>
+
+namespace ROS2
+{
+    void LidarTemplate::Reflect(AZ::ReflectContext* context)
+    {
+        if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
+        {
+            serializeContext->Class<LidarTemplate>()
+                ->Version(1)
+                ->Field("Name", &LidarTemplate::m_name)
+                ->Field("Layers", &LidarTemplate::m_layers)
+                ->Field("Points per layer", &LidarTemplate::m_numberOfIncrements)
+                ->Field("Min horizontal angle", &LidarTemplate::m_minHAngle)
+                ->Field("Max horizontal angle", &LidarTemplate::m_maxHAngle)
+                ->Field("Min vertical angle", &LidarTemplate::m_minVAngle)
+                ->Field("Max vertical angle", &LidarTemplate::m_maxVAngle)
+                ->Field("Max range", &LidarTemplate::m_maxRange)
+                ->Field("Max range add points", &LidarTemplate::m_addPointsAtMax);
+
+            if (AZ::EditContext* ec = serializeContext->GetEditContext())
+            {
+                ec->Class<LidarTemplate>("Lidar Template", "Lidar Template")
+                    ->DataElement(AZ::Edit::UIHandlers::Default, &LidarTemplate::m_name, "Name", "Custom lidar name")
+                    ->DataElement(AZ::Edit::UIHandlers::Default, &LidarTemplate::m_layers, "Layers", "Vertical dimension")
+                    ->DataElement(
+                        AZ::Edit::UIHandlers::Default, &LidarTemplate::m_numberOfIncrements, "Points per layer", "Horizontal dimension")
+                    ->DataElement(
+                        AZ::Edit::UIHandlers::Default, &LidarTemplate::m_minHAngle, "Min horizontal angle [Deg]", "Left-most reach of fov")
+                    ->Attribute(AZ::Edit::Attributes::Min, -180.0f)
+                    ->Attribute(AZ::Edit::Attributes::Max, 180.0f)
+                    ->DataElement(
+                        AZ::Edit::UIHandlers::Default, &LidarTemplate::m_maxHAngle, "Max horizontal angle [Deg]", "Right-most reach of fov")
+                    ->Attribute(AZ::Edit::Attributes::Min, -180.0f)
+                    ->Attribute(AZ::Edit::Attributes::Max, 180.0f)
+                    ->DataElement(
+                        AZ::Edit::UIHandlers::Default, &LidarTemplate::m_minVAngle, "Min vertical angle [Deg]", "Downwards reach of fov")
+                    ->Attribute(AZ::Edit::Attributes::Min, -180.0f)
+                    ->Attribute(AZ::Edit::Attributes::Max, 180.0f)
+                    ->DataElement(
+                        AZ::Edit::UIHandlers::Default, &LidarTemplate::m_maxVAngle, "Max vertical angle [Deg]", "Upwards reach of fov")
+                    ->Attribute(AZ::Edit::Attributes::Min, -180.0f)
+                    ->Attribute(AZ::Edit::Attributes::Max, 180.0f)
+                    ->DataElement(AZ::Edit::UIHandlers::Default, &LidarTemplate::m_maxRange, "Max range", "Maximum beam range [m]")
+                    ->Attribute(AZ::Edit::Attributes::Min, 0.0f)
+                    ->Attribute(AZ::Edit::Attributes::Max, 1000.0f)
+                    ->DataElement(
+                        AZ::Edit::UIHandlers::Default,
+                        &LidarTemplate::m_addPointsAtMax,
+                        "Points at Max",
+                        "If set true LiDAR will produce points at max range for free space");
+            }
+        }
+    }
+} // namespace ROS2

+ 51 - 0
Gems/ROS2/Code/Source/Lidar/LidarTemplate.h

@@ -0,0 +1,51 @@
+/*
+ * 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 <AzCore/RTTI/RTTI.h>
+#include <AzCore/Serialization/SerializeContext.h>
+#include <AzCore/std/string/string.h>
+
+namespace ROS2
+{
+    //! Configuration reflecting a specific Lidar model.
+    //! This is meant to capture differences between different Lidars available on the market.
+    //! @note Current implementation is simplified. Rays in real lidars are often not uniformly
+    //! distributed among angular range, there is noise etc.
+    struct LidarTemplate
+    {
+    public:
+        AZ_TYPE_INFO(LidarTemplate, "{9E9EF583-733D-4450-BBA0-ADD4D1BEFBF2}");
+        static void Reflect(AZ::ReflectContext* context);
+
+        enum class LidarModel
+        {
+            Generic3DLidar
+        };
+
+        LidarModel m_model;
+        //! Name of lidar template
+        AZStd::string m_name;
+        //! Minimum horizontal angle (altitude of the ray), in degrees
+        float m_minHAngle = 0.0f;
+        //! Maximum horizontal angle (altitude of the ray), in degrees
+        float m_maxHAngle = 0.0f;
+        //! Minimum vertical angle (azimuth of the ray), in degrees
+        float m_minVAngle = 0.0f;
+        //! Maximum vertical angle (azimuth of the ray), in degrees
+        float m_maxVAngle = 0.0f;
+        //! Number of lasers layers (resolution in horizontal direction)
+        unsigned int m_layers = 0;
+        //! Resolution in vertical direction
+        unsigned int m_numberOfIncrements = 0;
+        //! Maximum range of simulated LiDAR
+        float m_maxRange = 0.0f;
+        //! Adds point with maximum range when ray does not hit obstacle
+        bool m_addPointsAtMax = false;
+    };
+} // namespace ROS2

+ 81 - 0
Gems/ROS2/Code/Source/Lidar/LidarTemplateUtils.cpp

@@ -0,0 +1,81 @@
+/*
+ * 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 "LidarTemplateUtils.h"
+#include <AzCore/Math/MathUtils.h>
+#include <AzCore/Math/Matrix4x4.h>
+#include <AzCore/Math/Quaternion.h>
+#include <AzCore/Math/Vector3.h>
+#include <AzCore/Utils/Utils.h>
+#include <AzCore/std/containers/unordered_map.h>
+
+namespace ROS2
+{
+    LidarTemplate LidarTemplateUtils::GetTemplate(LidarTemplate::LidarModel model)
+    {
+        static AZStd::unordered_map<LidarTemplate::LidarModel, LidarTemplate> templates;
+
+        if (templates.empty())
+        {
+            LidarTemplate generic3DLidar = { /*.m_model = */ LidarTemplate::LidarModel::Generic3DLidar,
+                                             /*.m_name = */ "GenericLidar",
+                                             /*.m_minHAngle = */ -180.0f,
+                                             /*.m_maxHAngle = */ 180.0f,
+                                             /*.m_minVAngle = */ 35.0f,
+                                             /*.m_maxVAngle = */ -35.0f,
+                                             /*.m_layers = */ 24,
+                                             /*.m_numberOfIncrements = */ 924,
+                                             /*.m_maxRange = */ 100.0f };
+            templates[LidarTemplate::LidarModel::Generic3DLidar] = generic3DLidar;
+        }
+
+        auto it = templates.find(model);
+        if (it == templates.end())
+        {
+            return LidarTemplate();
+        }
+
+        return it->second;
+    }
+
+    size_t LidarTemplateUtils::TotalPointCount(const LidarTemplate& t)
+    {
+        return t.m_layers * t.m_numberOfIncrements;
+    }
+
+    AZStd::vector<AZ::Vector3> LidarTemplateUtils::PopulateRayDirections(
+        const LidarTemplate& lidarTemplate, const AZ::Vector3& rootRotation)
+    {
+        const float minVertAngle = AZ::DegToRad(lidarTemplate.m_minVAngle);
+        const float maxVertAngle = AZ::DegToRad(lidarTemplate.m_maxVAngle);
+        const float minHorAngle = AZ::DegToRad(lidarTemplate.m_minHAngle);
+        const float maxHorAngle = AZ::DegToRad(lidarTemplate.m_maxHAngle);
+
+        const float verticalStep = (maxVertAngle - minVertAngle) / static_cast<float>(lidarTemplate.m_layers);
+        const float horizontalStep = (maxHorAngle - minHorAngle) / static_cast<float>(lidarTemplate.m_numberOfIncrements);
+
+        AZStd::vector<AZ::Vector3> directions;
+
+        for (int incr = 0; incr < lidarTemplate.m_numberOfIncrements; incr++)
+        {
+            for (int layer = 0; layer < lidarTemplate.m_layers; layer++)
+            {
+                const float pitch = minVertAngle + layer * verticalStep + rootRotation.GetY();
+                const float yaw = minHorAngle + incr * horizontalStep + rootRotation.GetZ();
+
+                const float x = AZ::Cos(yaw) * AZ::Cos(pitch);
+                const float y = AZ::Sin(yaw) * AZ::Cos(pitch);
+                const float z = AZ::Sin(pitch);
+
+                directions.push_back(AZ::Vector3(x, y, z));
+            }
+        }
+
+        return directions;
+    }
+} // namespace ROS2

+ 35 - 0
Gems/ROS2/Code/Source/Lidar/LidarTemplateUtils.h

@@ -0,0 +1,35 @@
+/*
+ * 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 "LidarTemplate.h"
+#include <AzCore/Math/Vector3.h>
+#include <AzCore/std/containers/vector.h>
+
+namespace ROS2
+{
+    //! Utility class for Lidar model computations.
+    namespace LidarTemplateUtils
+    {
+        //! Get the lidar template for a model.
+        //! @param model lidar model.
+        //! @return the matching template which describes parameters for the model.
+        LidarTemplate GetTemplate(LidarTemplate::LidarModel model);
+
+        //! Get total point count for a given template.
+        //! @param t lidar template.
+        //! @return total count of points that the lidar specified by the template would produce on each scan.
+        size_t TotalPointCount(const LidarTemplate& t);
+
+        //! Compute ray directions based on lidar model and rotation.
+        //! @param model Lidar model to use. Note that different models will produce different number of rays.
+        //! @param rootRotation Root rotation as Euler angles in radians.
+        //! @return All ray directions which can be used to perform ray-casting simulation of lidar operation.
+        AZStd::vector<AZ::Vector3> PopulateRayDirections(const LidarTemplate& lidarTemplate, const AZ::Vector3& rootRotation);
+    }; // namespace LidarTemplateUtils
+} // namespace ROS2

+ 209 - 0
Gems/ROS2/Code/Source/Lidar/ROS2LidarSensorComponent.cpp

@@ -0,0 +1,209 @@
+/*
+ * 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 <Atom/RPI.Public/AuxGeom/AuxGeomFeatureProcessorInterface.h>
+#include <Atom/RPI.Public/RPISystemInterface.h>
+#include <Atom/RPI.Public/Scene.h>
+#include <AzCore/Component/Entity.h>
+#include <AzCore/Serialization/EditContext.h>
+#include <AzCore/Serialization/EditContextConstants.inl>
+#include <AzFramework/Physics/PhysicsSystem.h>
+#include <Lidar/LidarTemplateUtils.h>
+#include <Lidar/ROS2LidarSensorComponent.h>
+#include <ROS2/Frame/ROS2FrameComponent.h>
+#include <ROS2/ROS2Bus.h>
+#include <ROS2/Utilities/ROS2Names.h>
+
+namespace ROS2
+{
+    namespace Internal
+    {
+        const char* kPointCloudType = "sensor_msgs::msg::PointCloud2";
+    }
+
+    void ROS2LidarSensorComponent::Reflect(AZ::ReflectContext* context)
+    {
+        LidarTemplate::Reflect(context);
+        if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context))
+        {
+            serialize->Class<ROS2LidarSensorComponent, ROS2SensorComponent>()
+                ->Version(1)
+                ->Field("lidarModel", &ROS2LidarSensorComponent::m_lidarModel)
+                ->Field("LidarParameters", &ROS2LidarSensorComponent::m_lidarParameters)
+                ->Field("IgnoreLayer", &ROS2LidarSensorComponent::m_ignoreLayer)
+                ->Field("IgnoredLayerIndex", &ROS2LidarSensorComponent::m_ignoredLayerIndex);
+
+            if (AZ::EditContext* ec = serialize->GetEditContext())
+            {
+                ec->Class<ROS2LidarSensorComponent>("ROS2 Lidar Sensor", "Lidar sensor component")
+                    ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
+                    ->Attribute(AZ::Edit::Attributes::Category, "ROS2")
+                    ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game"))
+                    ->DataElement(AZ::Edit::UIHandlers::ComboBox, &ROS2LidarSensorComponent::m_lidarModel, "Lidar Model", "Lidar model")
+                    ->Attribute(AZ::Edit::Attributes::ChangeNotify, &ROS2LidarSensorComponent::OnLidarModelSelected)
+                    ->EnumAttribute(LidarTemplate::LidarModel::Generic3DLidar, "Generic Lidar")
+                    ->DataElement(
+                        AZ::Edit::UIHandlers::EntityId,
+                        &ROS2LidarSensorComponent::m_lidarParameters,
+                        "Lidar parameters",
+                        "Configuration of Generic lidar")
+                    ->Attribute(AZ::Edit::Attributes::Visibility, &ROS2LidarSensorComponent::IsConfigurationVisible)
+                    ->DataElement(
+                        AZ::Edit::UIHandlers::ComboBox,
+                        &ROS2LidarSensorComponent::m_ignoreLayer,
+                        "Ignore layer",
+                        "Should we ignore selected layer index")
+                    ->DataElement(
+                        AZ::Edit::UIHandlers::Default,
+                        &ROS2LidarSensorComponent::m_ignoredLayerIndex,
+                        "Ignored layer index",
+                        "Layer index to ignore");
+            }
+        }
+    }
+
+    bool ROS2LidarSensorComponent::IsConfigurationVisible() const
+    {
+        return m_lidarModel == LidarTemplate::LidarModel::Generic3DLidar;
+    }
+
+    AZ::Crc32 ROS2LidarSensorComponent::OnLidarModelSelected()
+    {
+        m_lidarParameters = LidarTemplateUtils::GetTemplate(m_lidarModel);
+        return AZ::Edit::PropertyRefreshLevels::EntireTree;
+    }
+
+    ROS2LidarSensorComponent::ROS2LidarSensorComponent()
+    {
+        TopicConfiguration pc;
+        AZStd::string type = Internal::kPointCloudType;
+        pc.m_type = type;
+        pc.m_topic = "pc";
+        m_sensorConfiguration.m_frequency = 10;
+        m_sensorConfiguration.m_publishersConfigurations.insert(AZStd::make_pair(type, pc));
+    }
+
+    void ROS2LidarSensorComponent::Visualise()
+    {
+        if (m_visualisationPoints.empty())
+        {
+            return;
+        }
+
+        if (m_drawQueue)
+        {
+            const uint8_t pixelSize = 2;
+            AZ::RPI::AuxGeomDraw::AuxGeomDynamicDrawArguments drawArgs;
+            drawArgs.m_verts = m_visualisationPoints.data();
+            drawArgs.m_vertCount = m_visualisationPoints.size();
+            drawArgs.m_colors = &AZ::Colors::Red;
+            drawArgs.m_colorCount = 1;
+            drawArgs.m_opacityType = AZ::RPI::AuxGeomDraw::OpacityType::Opaque;
+            drawArgs.m_size = pixelSize;
+            m_drawQueue->DrawPoints(drawArgs);
+        }
+    }
+
+    void ROS2LidarSensorComponent::SetPhysicsScene()
+    {
+        auto* physicsSystem = AZ::Interface<AzPhysics::SystemInterface>::Get();
+        auto foundBody = physicsSystem->FindAttachedBodyHandleFromEntityId(GetEntityId());
+        auto lidarPhysicsSceneHandle = foundBody.first;
+        if (foundBody.first == AzPhysics::InvalidSceneHandle)
+        {
+            auto* sceneInterface = AZ::Interface<AzPhysics::SceneInterface>::Get();
+            lidarPhysicsSceneHandle = sceneInterface->GetSceneHandle(AzPhysics::DefaultPhysicsSceneName);
+        }
+
+        AZ_Assert(lidarPhysicsSceneHandle != AzPhysics::InvalidSceneHandle, "Invalid physics scene handle for entity");
+        m_lidarRaycaster.SetRaycasterScene(lidarPhysicsSceneHandle);
+    }
+
+    void ROS2LidarSensorComponent::Activate()
+    {
+        auto ros2Node = ROS2Interface::Get()->GetNode();
+        AZ_Assert(m_sensorConfiguration.m_publishersConfigurations.size() == 1, "Invalid configuration of publishers for lidar sensor");
+
+        const TopicConfiguration& publisherConfig = m_sensorConfiguration.m_publishersConfigurations[Internal::kPointCloudType];
+        AZStd::string fullTopic = ROS2Names::GetNamespacedName(GetNamespace(), publisherConfig.m_topic);
+        m_pointCloudPublisher = ros2Node->create_publisher<sensor_msgs::msg::PointCloud2>(fullTopic.data(), publisherConfig.GetQoS());
+
+        SetPhysicsScene();
+        if (m_sensorConfiguration.m_visualise)
+        {
+            auto* entityScene = AZ::RPI::Scene::GetSceneForEntityId(GetEntityId());
+            m_drawQueue = AZ::RPI::AuxGeomFeatureProcessorInterface::GetDrawQueueForScene(entityScene);
+        }
+        m_lidarRaycaster.SetAddPointsMaxRange(m_lidarParameters.m_addPointsAtMax);
+        ROS2SensorComponent::Activate();
+    }
+
+    void ROS2LidarSensorComponent::Deactivate()
+    {
+        ROS2SensorComponent::Deactivate();
+        m_pointCloudPublisher.reset();
+    }
+
+    void ROS2LidarSensorComponent::FrequencyTick()
+    {
+        float distance = m_lidarParameters.m_maxRange;
+        auto entityTransform = GetEntity()->FindComponent<AzFramework::TransformComponent>();
+        const auto directions =
+            LidarTemplateUtils::PopulateRayDirections(m_lidarParameters, entityTransform->GetWorldTM().GetEulerRadians());
+        AZ::Vector3 start = entityTransform->GetWorldTM().GetTranslation();
+        start.SetZ(start.GetZ());
+
+        auto worldToLidarTransform = entityTransform->GetWorldTM();
+        worldToLidarTransform.Invert();
+
+        m_lastScanResults =
+            m_lidarRaycaster.PerformRaycast(start, directions, worldToLidarTransform, distance, m_ignoreLayer, m_ignoredLayerIndex);
+        if (m_lastScanResults.empty())
+        {
+            AZ_TracePrintf("Lidar Sensor Component", "No results from raycast\n");
+            return;
+        }
+
+        if (m_sensorConfiguration.m_visualise)
+        { // Store points for visualisation purposes, in global frame
+            auto localToWorldTM = entityTransform->GetWorldTM();
+
+            m_visualisationPoints = m_lastScanResults;
+            for (AZ::Vector3& point : m_visualisationPoints)
+            {
+                point = localToWorldTM.TransformPoint(point);
+            }
+        }
+
+        auto* ros2Frame = Utils::GetGameOrEditorComponent<ROS2FrameComponent>(GetEntity());
+        auto message = sensor_msgs::msg::PointCloud2();
+        message.header.frame_id = ros2Frame->GetFrameID().data();
+        message.header.stamp = ROS2Interface::Get()->GetROSTimestamp();
+        message.height = 1;
+        message.width = m_lastScanResults.size();
+        message.point_step = sizeof(AZ::Vector3);
+        message.row_step = message.width * message.point_step;
+
+        AZStd::array<const char*, 3> point_field_names = { "x", "y", "z" };
+        for (int i = 0; i < point_field_names.size(); i++)
+        {
+            sensor_msgs::msg::PointField pf;
+            pf.name = point_field_names[i];
+            pf.offset = i * 4;
+            pf.datatype = sensor_msgs::msg::PointField::FLOAT32;
+            pf.count = 1;
+            message.fields.push_back(pf);
+        }
+
+        size_t sizeInBytes = m_lastScanResults.size() * sizeof(AZ::Vector3);
+        message.data.resize(sizeInBytes);
+        AZ_Assert(message.row_step * message.height == sizeInBytes, "Inconsistency in the size of point cloud data");
+        memcpy(message.data.data(), m_lastScanResults.data(), sizeInBytes);
+        m_pointCloudPublisher->publish(message);
+    }
+} // namespace ROS2

+ 63 - 0
Gems/ROS2/Code/Source/Lidar/ROS2LidarSensorComponent.h

@@ -0,0 +1,63 @@
+/*
+ * 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 "LidarRaycaster.h"
+#include "LidarTemplate.h"
+#include "LidarTemplateUtils.h"
+#include <Atom/RPI.Public/AuxGeom/AuxGeomDraw.h>
+#include <AzCore/Serialization/SerializeContext.h>
+#include <ROS2/Sensor/ROS2SensorComponent.h>
+#include <rclcpp/publisher.hpp>
+#include <sensor_msgs/msg/point_cloud2.hpp>
+
+namespace ROS2
+{
+    //! Lidar sensor Component.
+    //! Lidars (Light Detection and Ranging) emit laser light and measure it after reflection.
+    //! Lidar Component allows customization of lidar type and behavior and encapsulates both simulation
+    //! and data publishing. It requires ROS2FrameComponent.
+    class ROS2LidarSensorComponent : public ROS2SensorComponent
+    {
+    public:
+        AZ_COMPONENT(ROS2LidarSensorComponent, "{502A955F-7742-4E23-AD77-5E4063739DCA}", ROS2SensorComponent);
+        ROS2LidarSensorComponent();
+        ~ROS2LidarSensorComponent() = default;
+        static void Reflect(AZ::ReflectContext* context);
+        //////////////////////////////////////////////////////////////////////////
+        // Component overrides
+        void Activate() override;
+        void Deactivate() override;
+        //////////////////////////////////////////////////////////////////////////
+
+    private:
+        //////////////////////////////////////////////////////////////////////////
+        // ROS2SensorComponent overrides
+        void FrequencyTick() override;
+        void Visualise() override;
+        //////////////////////////////////////////////////////////////////////////
+        void SetPhysicsScene();
+
+        AZ::Crc32 OnLidarModelSelected();
+        bool IsConfigurationVisible() const;
+
+        LidarTemplate::LidarModel m_lidarModel = LidarTemplate::LidarModel::Generic3DLidar;
+        LidarTemplate m_lidarParameters = LidarTemplateUtils::GetTemplate(LidarTemplate::LidarModel::Generic3DLidar);
+        LidarRaycaster m_lidarRaycaster;
+        std::shared_ptr<rclcpp::Publisher<sensor_msgs::msg::PointCloud2>> m_pointCloudPublisher;
+
+        // Used only when visualisation is on - points differ since they are in global transform as opposed to local
+        AZStd::vector<AZ::Vector3> m_visualisationPoints;
+        AZ::RPI::AuxGeomDrawPtr m_drawQueue;
+
+        AZStd::vector<AZ::Vector3> m_lastScanResults;
+
+        unsigned int m_ignoredLayerIndex = 0;
+        bool m_ignoreLayer = false;
+    };
+} // namespace ROS2

+ 273 - 0
Gems/ROS2/Code/Source/Manipulator/MotorizedJointComponent.cpp

@@ -0,0 +1,273 @@
+/*
+ * 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 <AzCore/Component/Entity.h>
+#include <AzCore/Component/TransformBus.h>
+#include <AzCore/Serialization/EditContext.h>
+#include <AzFramework/Physics/Components/SimulatedBodyComponentBus.h>
+#include <AzFramework/Physics/RigidBodyBus.h>
+#include <ROS2/Manipulator/MotorizedJointComponent.h>
+
+namespace ROS2
+{
+    void MotorizedJointComponent::Activate()
+    {
+        AZ::TickBus::Handler::BusConnect();
+        m_pidPos.InitializePid();
+        if (m_debugDrawEntity.IsValid())
+        {
+            AZ::TransformBus::EventResult(m_debugDrawEntityInitialTransform, this->GetEntityId(), &AZ::TransformBus::Events::GetLocalTM);
+        }
+        MotorizedJointRequestBus::Handler::BusConnect(m_entity->GetId());
+    }
+
+    void MotorizedJointComponent::Deactivate()
+    {
+        AZ::TickBus::Handler::BusDisconnect();
+        MotorizedJointRequestBus::Handler::BusDisconnect();
+    }
+
+    void MotorizedJointComponent::Reflect(AZ::ReflectContext* context)
+    {
+        if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context))
+        {
+            serialize->Class<MotorizedJointComponent, AZ::Component>()
+                ->Version(2)
+                ->Field("JointAxis", &MotorizedJointComponent::m_jointDir)
+                ->Field("EffortAxis", &MotorizedJointComponent::m_effortAxis)
+                ->Field("Limit", &MotorizedJointComponent::m_limits)
+                ->Field("Linear", &MotorizedJointComponent::m_linear)
+                ->Field("AnimationMode", &MotorizedJointComponent::m_animationMode)
+                ->Field("ZeroOffset", &MotorizedJointComponent::m_zeroOffset)
+                ->Field("PidPosition", &MotorizedJointComponent::m_pidPos)
+                ->Field("DebugDrawEntity", &MotorizedJointComponent::m_debugDrawEntity)
+                ->Field("TestSinActive", &MotorizedJointComponent::m_testSinusoidal)
+                ->Field("TestSinAmplitude", &MotorizedJointComponent::m_sinAmplitude)
+                ->Field("TestSinFreq", &MotorizedJointComponent::m_sinFreq)
+                ->Field("TestSinDC", &MotorizedJointComponent::m_sinDC)
+                ->Field("DebugPrint", &MotorizedJointComponent::m_debugPrint)
+                ->Field("OverrideParent", &MotorizedJointComponent::m_measurementReferenceEntity);
+
+            if (AZ::EditContext* ec = serialize->GetEditContext())
+            {
+                ec->Class<MotorizedJointComponent>("MotorizedJointComponent", "MotorizedJointComponent")
+                    ->ClassElement(AZ::Edit::ClassElements::EditorData, "MotorizedJointComponent")
+                    ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game"))
+                    ->Attribute(AZ::Edit::Attributes::Category, "MotorizedJointComponent")
+                    ->DataElement(
+                        AZ::Edit::UIHandlers::Default,
+                        &MotorizedJointComponent::m_jointDir,
+                        "Joint Dir.",
+                        "Direction of joint in parent's reference frame.")
+                    ->DataElement(
+                        AZ::Edit::UIHandlers::Default,
+                        &MotorizedJointComponent::m_effortAxis,
+                        "Effort Dir.",
+                        "Desired direction of force/torque vector that is applied to rigid body.")
+
+                    ->DataElement(
+                        AZ::Edit::UIHandlers::Default,
+                        &MotorizedJointComponent::m_limits,
+                        "ControllerLimits",
+                        "When measurement is outside the limits, ")
+                    ->DataElement(
+                        AZ::Edit::UIHandlers::Default,
+                        &MotorizedJointComponent::m_debugDrawEntity,
+                        "Setpoint",
+                        "Allows to apply debug setpoint visualizer")
+                    ->DataElement(
+                        AZ::Edit::UIHandlers::Default,
+                        &MotorizedJointComponent::m_zeroOffset,
+                        "Zero Off.",
+                        "Allows to change offset of zero to set point")
+                    ->DataElement(
+                        AZ::Edit::UIHandlers::Default,
+                        &MotorizedJointComponent::m_linear,
+                        "Linear joint",
+                        "Applies linear force instead of torque")
+                    ->DataElement(
+                        AZ::Edit::UIHandlers::Default,
+                        &MotorizedJointComponent::m_animationMode,
+                        "Animation mode",
+                        "In animation mode, the transform API is used instead of Rigid Body. "
+                        "If this property is set to true the Rigid Body Component should be disabled.")
+                    ->DataElement(AZ::Edit::UIHandlers::Default, &MotorizedJointComponent::m_pidPos, "PidPosition", "PidPosition")
+                    ->DataElement(
+                        AZ::Edit::UIHandlers::Default,
+                        &MotorizedJointComponent::m_testSinusoidal,
+                        "SinusoidalTest",
+                        "Allows to apply sinusoidal test signal")
+                    ->DataElement(
+                        AZ::Edit::UIHandlers::Default,
+                        &MotorizedJointComponent::m_sinAmplitude,
+                        "Amplitude",
+                        "Amplitude of sinusoidal test signal.")
+                    ->DataElement(
+                        AZ::Edit::UIHandlers::Default,
+                        &MotorizedJointComponent::m_sinFreq,
+                        "Frequency",
+                        "Frequency of sinusoidal test signal.")
+                    ->DataElement(AZ::Edit::UIHandlers::Default, &MotorizedJointComponent::m_sinDC, "DC", "DC of sinusoidal test signal.")
+                    ->DataElement(AZ::Edit::UIHandlers::Default, &MotorizedJointComponent::m_debugPrint, "Debug", "Print debug to console")
+                    ->DataElement(
+                        AZ::Edit::UIHandlers::Default,
+                        &MotorizedJointComponent::m_measurementReferenceEntity,
+                        "Step Parent",
+                        "Allows to override a parent to get correct measurement");
+            }
+        }
+    }
+    void MotorizedJointComponent::OnTick([[maybe_unused]] float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time)
+    {
+        const float measurement = ComputeMeasurement(time);
+        if (m_testSinusoidal)
+        {
+            m_setpoint = m_sinDC + m_sinAmplitude * AZ::Sin(m_sinFreq * time.GetSeconds());
+        }
+        const float control_position_error = (m_setpoint + m_zeroOffset) - measurement;
+        m_error = control_position_error;
+
+        if (m_debugDrawEntity.IsValid())
+        {
+            if (m_linear)
+            {
+                AZ::Transform transform = AZ::Transform::Identity();
+                transform.SetTranslation(m_jointDir * (m_setpoint + m_zeroOffset));
+                AZ::TransformBus::Event(
+                    m_debugDrawEntity, &AZ::TransformBus::Events::SetLocalTM, transform * m_debugDrawEntityInitialTransform);
+            }
+            else
+            {
+                AZ_Assert(false, "Not implemented");
+            }
+        }
+
+        const uint64_t deltaTimeNs = deltaTime * 1'000'000'000;
+        float speed_control = m_pidPos.ComputeCommand(control_position_error, deltaTimeNs);
+
+        if (measurement <= m_limits.first)
+        {
+            // allow only positive control
+            speed_control = AZStd::max(0.f, speed_control);
+        }
+        else if (measurement >= m_limits.second)
+        {
+            // allow only negative control
+            speed_control = AZStd::min(0.f, speed_control);
+        }
+
+        if (m_debugPrint)
+        {
+            AZ_Printf(
+                "MotorizedJointComponent",
+                " %s | pos: %f | err: %f | cntrl : %f | set : %f |\n",
+                GetEntity()->GetName().c_str(),
+                measurement,
+                control_position_error,
+                speed_control,
+                m_setpoint);
+        }
+        SetVelocity(speed_control, deltaTime);
+    }
+
+    float MotorizedJointComponent::ComputeMeasurement(AZ::ScriptTimePoint time)
+    {
+        AZ::Transform transform;
+        if (!m_measurementReferenceEntity.IsValid())
+        {
+            AZ::TransformBus::EventResult(transform, this->GetEntityId(), &AZ::TransformBus::Events::GetLocalTM);
+        }
+        else
+        {
+            AZ::Transform transformStepParent;
+            AZ::TransformBus::EventResult(transformStepParent, m_measurementReferenceEntity, &AZ::TransformBus::Events::GetWorldTM);
+            AZ::Transform transformStepChild;
+            AZ::TransformBus::EventResult(transformStepChild, GetEntityId(), &AZ::TransformBus::Events::GetWorldTM);
+            transform = transformStepParent.GetInverse() * transformStepChild;
+        }
+        if (m_linear)
+        {
+            const float last_position = m_currentPosition;
+            m_currentPosition = transform.GetTranslation().Dot(this->m_jointDir);
+            if (m_lastMeasurementTime > 0)
+            {
+                double delta_time = time.GetSeconds() - m_lastMeasurementTime;
+                m_currentVelocity = (m_currentPosition - last_position) / delta_time;
+            }
+            m_lastMeasurementTime = time.GetSeconds();
+            return m_currentPosition;
+        }
+        AZ_Assert(false, "Measurement computation for rotation is not implemented");
+        return 0;
+    }
+
+    void MotorizedJointComponent::SetVelocity(float velocity, float deltaTime)
+    {
+        if (m_animationMode)
+        {
+            ApplyLinVelAnimation(velocity, deltaTime);
+        }
+        else
+        {
+            deltaTime = AZStd::min(deltaTime, 0.1f); // this affects applied force. Need to prevent value that is too large.
+            if (m_linear)
+            {
+                ApplyLinVelRigidBodyImpulse(velocity, deltaTime);
+            }
+        }
+    }
+
+    void MotorizedJointComponent::ApplyLinVelAnimation(float velocity, float deltaTime)
+    {
+        AZ::Transform transform;
+        AZ::TransformBus::EventResult(transform, this->GetEntityId(), &AZ::TransformBus::Events::GetLocalTM);
+        transform.SetTranslation(transform.GetTranslation() + velocity * m_jointDir * deltaTime);
+        AZ::TransformBus::Event(this->GetEntityId(), &AZ::TransformBus::Events::SetLocalTM, transform);
+    }
+
+    void MotorizedJointComponent::ApplyLinVelRigidBodyImpulse(float velocity, float deltaTime)
+    {
+        AZ::Quaternion transform;
+        AZ::TransformBus::EventResult(transform, this->GetEntityId(), &AZ::TransformBus::Events::GetWorldRotationQuaternion);
+        auto force_impulse = transform.TransformVector(m_effortAxis * velocity);
+        Physics::RigidBodyRequestBus::Event(
+            this->GetEntityId(), &Physics::RigidBodyRequests::ApplyLinearImpulse, force_impulse * deltaTime);
+    }
+
+    void MotorizedJointComponent::ApplyLinVelRigidBody(float velocity, float deltaTime)
+    {
+        AZ::Quaternion transform;
+        AZ::TransformBus::EventResult(transform, this->GetEntityId(), &AZ::TransformBus::Events::GetWorldRotationQuaternion);
+        AZ::Vector3 currentVelocity;
+        auto transformed_velocity_increment = transform.TransformVector(m_effortAxis * velocity);
+        Physics::RigidBodyRequestBus::EventResult(currentVelocity, this->GetEntityId(), &Physics::RigidBodyRequests::GetLinearVelocity);
+        AZ::Vector3 new_velocity = currentVelocity + transformed_velocity_increment;
+        Physics::RigidBodyRequestBus::Event(this->GetEntityId(), &Physics::RigidBodyRequests::SetLinearVelocity, new_velocity);
+    }
+
+    void MotorizedJointComponent::SetSetpoint(float setpoint)
+    {
+        m_setpoint = setpoint;
+    }
+
+    float MotorizedJointComponent::GetSetpoint()
+    {
+        return m_setpoint;
+    }
+
+    float MotorizedJointComponent::GetError()
+    {
+        return m_error;
+    }
+
+    float MotorizedJointComponent::GetCurrentMeasurement()
+    {
+        return m_currentPosition - m_zeroOffset;
+    }
+
+} // namespace ROS2

+ 99 - 0
Gems/ROS2/Code/Source/Odometry/ROS2OdometrySensorComponent.cpp

@@ -0,0 +1,99 @@
+/*
+ * 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 "ROS2OdometrySensorComponent.h"
+#include <AzCore/Serialization/EditContext.h>
+#include <AzCore/Serialization/EditContextConstants.inl>
+#include <AzFramework/Physics/RigidBodyBus.h>
+#include <ROS2/Frame/ROS2FrameComponent.h>
+#include <ROS2/ROS2Bus.h>
+#include <ROS2/Utilities/ROS2Conversions.h>
+#include <ROS2/Utilities/ROS2Names.h>
+
+namespace ROS2
+{
+    namespace Internal
+    {
+        const char* kOdometryMsgType = "nav_msgs::msg::Odometry";
+    }
+
+    void ROS2OdometrySensorComponent::Reflect(AZ::ReflectContext* context)
+    {
+        if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context))
+        {
+            serialize->Class<ROS2OdometrySensorComponent, ROS2SensorComponent>()->Version(1);
+
+            if (AZ::EditContext* ec = serialize->GetEditContext())
+            {
+                ec->Class<ROS2OdometrySensorComponent>("ROS2 Odometry Sensor", "Odometry sensor component")
+                    ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
+                    ->Attribute(AZ::Edit::Attributes::Category, "ROS2")
+                    ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game"));
+            }
+        }
+    }
+
+    ROS2OdometrySensorComponent::ROS2OdometrySensorComponent()
+    {
+        TopicConfiguration tc;
+        const AZStd::string type = Internal::kOdometryMsgType;
+        tc.m_type = type;
+        tc.m_topic = "odom";
+        m_sensorConfiguration.m_frequency = 10;
+        m_sensorConfiguration.m_publishersConfigurations.insert(AZStd::make_pair(type, tc));
+    }
+
+    void ROS2OdometrySensorComponent::FrequencyTick()
+    {
+        auto* ros2Frame = Utils::GetGameOrEditorComponent<ROS2FrameComponent>(GetEntity());
+        auto transform = ros2Frame->GetFrameTransform();
+
+        AZ::Vector3 linearVelocity;
+        Physics::RigidBodyRequestBus::EventResult(linearVelocity, m_entity->GetId(), &Physics::RigidBodyRequests::GetLinearVelocity);
+
+        linearVelocity = transform.GetInverse().TransformVector(linearVelocity);
+        AZ::Vector3 angularVelocity;
+        Physics::RigidBodyRequestBus::EventResult(angularVelocity, m_entity->GetId(), &Physics::RigidBodyRequests::GetAngularVelocity);
+        angularVelocity = transform.GetInverse().TransformVector(angularVelocity);
+        m_odometryMsg.twist.twist.linear = ROS2Conversions::ToROS2Vector3(linearVelocity);
+        m_odometryMsg.twist.twist.angular = ROS2Conversions::ToROS2Vector3(angularVelocity);
+
+        auto translation = transform.GetTranslation();
+        m_odometryMsg.pose.pose.position.x = translation.GetX();
+        m_odometryMsg.pose.pose.position.y = translation.GetY();
+        m_odometryMsg.pose.pose.position.z = translation.GetZ();
+
+        m_odometryPublisher->publish(m_odometryMsg);
+    }
+
+    void ROS2OdometrySensorComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
+    {
+        required.push_back(AZ_CRC_CE("PhysicsRigidBodyService"));
+    }
+
+    void ROS2OdometrySensorComponent::Activate()
+    {
+        // "odom" is globally fixed frame for all robots, no matter the namespace
+        m_odometryMsg.header.frame_id = ROS2Names::GetNamespacedName(GetNamespace(), "odom").c_str();
+        m_odometryMsg.child_frame_id = GetFrameID().c_str();
+
+        ROS2SensorComponent::Activate();
+        auto ros2Node = ROS2Interface::Get()->GetNode();
+        AZ_Assert(m_sensorConfiguration.m_publishersConfigurations.size() == 1, "Invalid configuration of publishers for Odometry sensor");
+
+        const auto publisherConfig = m_sensorConfiguration.m_publishersConfigurations[Internal::kOdometryMsgType];
+        const auto fullTopic = ROS2Names::GetNamespacedName(GetNamespace(), publisherConfig.m_topic);
+        m_odometryPublisher = ros2Node->create_publisher<nav_msgs::msg::Odometry>(fullTopic.data(), publisherConfig.GetQoS());
+    }
+
+    void ROS2OdometrySensorComponent::Deactivate()
+    {
+        ROS2SensorComponent::Deactivate();
+        m_odometryPublisher.reset();
+    }
+} // namespace ROS2

+ 45 - 0
Gems/ROS2/Code/Source/Odometry/ROS2OdometrySensorComponent.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 <AzCore/Math/Transform.h>
+#include <AzCore/Serialization/SerializeContext.h>
+#include <ROS2/Sensor/ROS2SensorComponent.h>
+#include <nav_msgs/msg/odometry.hpp>
+#include <rclcpp/publisher.hpp>
+
+namespace ROS2
+{
+    //! Odometry sensor Component.
+    //! It constructs and publishes an odometry message, which contains information about vehicle velocity and position in space.
+    //! This is a ground truth "sensor", which can be helpful for development and machine learning.
+    //! @see <a href="https://index.ros.org/p/nav_msgs/"> nav_msgs package. </a>
+    class ROS2OdometrySensorComponent : public ROS2SensorComponent
+    {
+    public:
+        AZ_COMPONENT(ROS2OdometrySensorComponent, "{61387448-63AA-4563-AF87-60C72B05B863}", ROS2SensorComponent);
+        ROS2OdometrySensorComponent();
+        ~ROS2OdometrySensorComponent() = default;
+        static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required);
+        static void Reflect(AZ::ReflectContext* context);
+        //////////////////////////////////////////////////////////////////////////
+        // Component overrides
+        void Activate() override;
+        void Deactivate() override;
+        //////////////////////////////////////////////////////////////////////////
+
+    private:
+        //////////////////////////////////////////////////////////////////////////
+        // ROS2SensorComponent overrides
+        void FrequencyTick() override;
+        //////////////////////////////////////////////////////////////////////////
+
+        std::shared_ptr<rclcpp::Publisher<nav_msgs::msg::Odometry>> m_odometryPublisher;
+        nav_msgs::msg::Odometry m_odometryMsg;
+    };
+} // namespace ROS2

+ 45 - 0
Gems/ROS2/Code/Source/ROS2EditorModule.cpp

@@ -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
+ *
+ */
+#include <ROS2EditorSystemComponent.h>
+#include <ROS2ModuleInterface.h>
+#include <RobotImporter/ROS2RobotImporterEditorSystemComponent.h>
+
+namespace ROS2
+{
+    class ROS2EditorModule : public ROS2ModuleInterface
+    {
+    public:
+        AZ_RTTI(ROS2EditorModule, "{3DDFC98F-D1CC-4658-BAF8-2CC34A9D39F3}", ROS2ModuleInterface);
+        AZ_CLASS_ALLOCATOR(ROS2EditorModule, AZ::SystemAllocator, 0);
+
+        ROS2EditorModule()
+        {
+            // Push results of [MyComponent]::CreateDescriptor() into m_descriptors here.
+            // Add ALL components descriptors associated with this gem to m_descriptors.
+            // This will associate the AzTypeInfo information for the components with the SerializeContext, BehaviorContext and
+            // EditContext. This happens through the [MyComponent]::Reflect() function.
+            m_descriptors.insert(
+                m_descriptors.end(),
+                { ROS2EditorSystemComponent::CreateDescriptor(), ROS2RobotImporterEditorSystemComponent::CreateDescriptor() });
+        }
+
+        /**
+         * Add required SystemComponents to the SystemEntity.
+         * Non-SystemComponents should not be added here
+         */
+        AZ::ComponentTypeList GetRequiredSystemComponents() const override
+        {
+            return AZ::ComponentTypeList{
+                azrtti_typeid<ROS2EditorSystemComponent>(),
+                azrtti_typeid<ROS2RobotImporterEditorSystemComponent>(),
+            };
+        }
+    };
+} // namespace ROS2
+
+AZ_DECLARE_MODULE_CLASS(Gem_ROS2, ROS2::ROS2EditorModule)

+ 57 - 0
Gems/ROS2/Code/Source/ROS2EditorSystemComponent.cpp

@@ -0,0 +1,57 @@
+/*
+ * 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 <AzCore/Serialization/SerializeContext.h>
+#include <ROS2EditorSystemComponent.h>
+
+namespace ROS2
+{
+    void ROS2EditorSystemComponent::Reflect(AZ::ReflectContext* context)
+    {
+        if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
+        {
+            serializeContext->Class<ROS2EditorSystemComponent, ROS2SystemComponent>()->Version(0);
+        }
+    }
+
+    ROS2EditorSystemComponent::ROS2EditorSystemComponent() = default;
+
+    ROS2EditorSystemComponent::~ROS2EditorSystemComponent() = default;
+
+    void ROS2EditorSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
+    {
+        BaseSystemComponent::GetProvidedServices(provided);
+        provided.push_back(AZ_CRC_CE("ROS2EditorService"));
+    }
+
+    void ROS2EditorSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
+    {
+        BaseSystemComponent::GetIncompatibleServices(incompatible);
+        incompatible.push_back(AZ_CRC_CE("ROS2EditorService"));
+    }
+
+    void ROS2EditorSystemComponent::GetRequiredServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& required)
+    {
+        BaseSystemComponent::GetRequiredServices(required);
+    }
+
+    void ROS2EditorSystemComponent::GetDependentServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& dependent)
+    {
+        BaseSystemComponent::GetDependentServices(dependent);
+    }
+
+    void ROS2EditorSystemComponent::Activate()
+    {
+        ROS2SystemComponent::Activate();
+    }
+
+    void ROS2EditorSystemComponent::Deactivate()
+    {
+        ROS2SystemComponent::Deactivate();
+    }
+
+} // namespace ROS2

+ 40 - 0
Gems/ROS2/Code/Source/ROS2EditorSystemComponent.h

@@ -0,0 +1,40 @@
+/*
+ * 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 <ROS2SystemComponent.h>
+
+#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
+
+namespace ROS2
+{
+    /// System component for ROS2 editor
+    class ROS2EditorSystemComponent : public ROS2SystemComponent
+    {
+        using BaseSystemComponent = ROS2SystemComponent;
+
+    public:
+        AZ_COMPONENT(ROS2EditorSystemComponent, "{34fa5d9a-956b-4655-a6bc-1d57dce8e7a2}", BaseSystemComponent);
+        static void Reflect(AZ::ReflectContext* context);
+
+        ROS2EditorSystemComponent();
+        ~ROS2EditorSystemComponent();
+
+    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);
+
+        //////////////////////////////////////////////////////////////////////////
+        // Component overrides
+        void Activate() override;
+        void Deactivate() override;
+        //////////////////////////////////////////////////////////////////////////
+    };
+} // namespace ROS2

+ 41 - 0
Gems/ROS2/Code/Source/ROS2GemUtilities.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 <AzCore/std/string/regex.h>
+#include <AzToolsFramework/API/EntityCompositionRequestBus.h>
+#include <ROS2/ROS2GemUtilities.h>
+
+namespace ROS2
+{
+
+    AZ::ComponentId Utils::CreateComponent(const AZ::EntityId entityId, const AZ::Uuid componentType)
+    {
+        const AZ::ComponentTypeList componentsToAdd{ componentType };
+        const AZStd::vector<AZ::EntityId> entityIds{ entityId };
+        AzToolsFramework::EntityCompositionRequests::AddComponentsOutcome addComponentsOutcome = AZ::Failure(AZStd::string());
+        AzToolsFramework::EntityCompositionRequestBus::BroadcastResult(
+            addComponentsOutcome, &AzToolsFramework::EntityCompositionRequests::AddComponentsToEntities, entityIds, componentsToAdd);
+        if (!addComponentsOutcome.IsSuccess())
+        {
+            AZ_Warning(
+                "URDF importer",
+                false,
+                "Failed to create component %s, entity %s : %s",
+                componentType.ToString<AZStd::string>().c_str(),
+                entityId.ToString().c_str(),
+                addComponentsOutcome.GetError().c_str());
+        }
+        const auto& added = addComponentsOutcome.GetValue().at(entityId).m_componentsAdded;
+        if (!added.empty())
+        {
+            return added.front()->GetId();
+        }
+        return AZ::InvalidComponentId;
+    }
+
+} // namespace ROS2

+ 22 - 0
Gems/ROS2/Code/Source/ROS2Module.cpp

@@ -0,0 +1,22 @@
+/*
+ * 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 <ROS2ModuleInterface.h>
+#include <ROS2SystemComponent.h>
+
+namespace ROS2
+{
+    class ROS2Module : public ROS2ModuleInterface
+    {
+    public:
+        AZ_RTTI(ROS2Module, "{e23a1379-787c-481e-ad83-c0e04a3d06fe}", ROS2ModuleInterface);
+        AZ_CLASS_ALLOCATOR(ROS2Module, AZ::SystemAllocator, 0);
+    };
+} // namespace ROS2
+
+AZ_DECLARE_MODULE_CLASS(Gem_ROS2, ROS2::ROS2Module)

+ 71 - 0
Gems/ROS2/Code/Source/ROS2ModuleInterface.h

@@ -0,0 +1,71 @@
+/*
+ * 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 "ROS2SystemComponent.h"
+#include <AzCore/Memory/SystemAllocator.h>
+#include <AzCore/Module/Module.h>
+#include <Camera/ROS2CameraSensorComponent.h>
+#include <GNSS/ROS2GNSSSensorComponent.h>
+#include <Imu/ROS2ImuSensorComponent.h>
+#include <Lidar/ROS2LidarSensorComponent.h>
+#include <Odometry/ROS2OdometrySensorComponent.h>
+#include <ROS2/Frame/ROS2FrameComponent.h>
+#include <ROS2/Manipulator/MotorizedJointComponent.h>
+#include <RobotControl/Controllers/AckermannController/AckermannControlComponent.h>
+#include <RobotControl/Controllers/RigidBodyController/RigidBodyTwistControlComponent.h>
+#include <RobotControl/ROS2RobotControlComponent.h>
+#include <RobotImporter/ROS2RobotImporterSystemComponent.h>
+#include <Spawner/ROS2SpawnPointComponent.h>
+#include <Spawner/ROS2SpawnerComponent.h>
+#include <VehicleDynamics/VehicleModelComponent.h>
+#include <VehicleDynamics/WheelControllerComponent.h>
+
+namespace ROS2
+{
+    class ROS2ModuleInterface : public AZ::Module
+    {
+    public:
+        AZ_RTTI(ROS2ModuleInterface, "{8b5567cb-1de9-49af-9cd4-9750d4abcd6b}", AZ::Module);
+        AZ_CLASS_ALLOCATOR(ROS2ModuleInterface, AZ::SystemAllocator, 0);
+
+        ROS2ModuleInterface()
+        {
+            // Push results of [MyComponent]::CreateDescriptor() into m_descriptors here.
+            // Add ALL components descriptors associated with this gem to m_descriptors.
+            // This will associate the AzTypeInfo information for the components with the SerializeContext, BehaviorContext and EditContext.
+            // This happens through the [MyComponent]::Reflect() function.
+            m_descriptors.insert(
+                m_descriptors.end(),
+                { ROS2SystemComponent::CreateDescriptor(),
+                  ROS2RobotImporterSystemComponent::CreateDescriptor(),
+                  ROS2SensorComponent::CreateDescriptor(),
+                  ROS2ImuSensorComponent::CreateDescriptor(),
+                  ROS2GNSSSensorComponent::CreateDescriptor(),
+                  ROS2LidarSensorComponent::CreateDescriptor(),
+                  ROS2OdometrySensorComponent::CreateDescriptor(),
+                  ROS2FrameComponent::CreateDescriptor(),
+                  ROS2RobotControlComponent::CreateDescriptor(),
+                  ROS2CameraSensorComponent::CreateDescriptor(),
+                  AckermannControlComponent::CreateDescriptor(),
+                  RigidBodyTwistControlComponent::CreateDescriptor(),
+                  ROS2CameraSensorComponent::CreateDescriptor(),
+                  ROS2SpawnerComponent::CreateDescriptor(),
+                  ROS2SpawnPointComponent::CreateDescriptor(),
+                  VehicleDynamics::VehicleModelComponent::CreateDescriptor(),
+                  VehicleDynamics::WheelControllerComponent::CreateDescriptor(),
+                  MotorizedJointComponent::CreateDescriptor() });
+        }
+
+        //! Add required SystemComponents to the SystemEntity.
+        AZ::ComponentTypeList GetRequiredSystemComponents() const override
+        {
+            return AZ::ComponentTypeList{ azrtti_typeid<ROS2SystemComponent>(), azrtti_typeid<ROS2RobotImporterSystemComponent>() };
+        }
+    };
+} // namespace ROS2

Certains fichiers n'ont pas été affichés car il y a eu trop de fichiers modifiés dans ce diff