Pārlūkot izejas kodu

merge development

Signed-off-by: jiaweig <[email protected]>
jiaweig 2 gadi atpakaļ
vecāks
revīzija
4a1821ba95
100 mainītis faili ar 2859 papildinājumiem un 176 dzēšanām
  1. 20 0
      .github/ISSUE_TEMPLATE/ar_bug_report.md.txt
  2. 50 0
      .github/ISSUE_TEMPLATE/bug_template.md.txt
  3. 6 0
      .github/ISSUE_TEMPLATE/config.yml.txt
  4. 23 0
      .github/ISSUE_TEMPLATE/deprecation_template.md.txt
  5. 20 0
      .github/ISSUE_TEMPLATE/feature_request.md.txt
  6. 13 0
      Gems/ProteusRobot/Assets/Materials/Proteus_chassis_Front_Double_Lights.material
  7. 13 0
      Gems/ProteusRobot/Assets/Materials/Proteus_chassis_Front_Light.material
  8. 13 0
      Gems/ProteusRobot/Assets/Materials/Proteus_chassis_Indicator.material
  9. 18 0
      Gems/ProteusRobot/Assets/Materials/Proteus_chassis_Proteus.material
  10. 13 0
      Gems/ProteusRobot/Assets/Materials/Proteus_chassis_Rear_Lights.material
  11. 13 0
      Gems/ProteusRobot/Assets/Materials/Proteus_chassis_Side_Lights.material
  12. 19 0
      Gems/ProteusRobot/Assets/Materials/Proteus_chassis_wheel.material
  13. 14 0
      Gems/ProteusRobot/Assets/Materials/Proteus_headLamp.material
  14. 4 0
      Gems/ProteusRobot/Assets/Materials/untitled.material
  15. 257 0
      Gems/ProteusRobot/Assets/Proteus.prefab
  16. 3 0
      Gems/ProteusRobot/Assets/Proteus2_chassis.fbx
  17. 3 0
      Gems/ProteusRobot/Assets/Proteus2_lift.fbx
  18. 3 0
      Gems/ProteusRobot/Assets/Proteus_chassis.fbx
  19. 151 0
      Gems/ProteusRobot/Assets/Proteus_chassis.fbx.assetinfo
  20. 3 0
      Gems/ProteusRobot/Assets/Proteus_wheel.fbx
  21. 88 0
      Gems/ProteusRobot/Assets/Proteus_wheel.fbx.assetinfo
  22. 45 0
      Gems/ProteusRobot/Assets/Scripts/ProteusRobot_inputs.inputbindings
  23. 3 0
      Gems/ProteusRobot/Assets/Textures/Lights_Emissive.png
  24. 3 0
      Gems/ProteusRobot/Assets/Textures/Proteus_BaseMap.png
  25. 3 0
      Gems/ProteusRobot/Assets/Textures/Proteus_MaskMap.png
  26. 3 0
      Gems/ProteusRobot/Assets/Textures/Proteus_MaskMap_A.png
  27. 3 0
      Gems/ProteusRobot/Assets/Textures/Proteus_MaskMap_R.png
  28. 3 0
      Gems/ProteusRobot/Assets/Textures/Proteus_Normal.png
  29. 18 0
      Gems/ProteusRobot/Assets/no_friction.physxmaterial
  30. 18 0
      Gems/ProteusRobot/Assets/robot_tire.physxmaterial
  31. 22 0
      Gems/ProteusRobot/CMakeLists.txt
  32. 210 0
      Gems/ProteusRobot/Code/CMakeLists.txt
  33. 31 0
      Gems/ProteusRobot/Code/Include/ProteusRobot/ProteusRobotBus.h
  34. 4 0
      Gems/ProteusRobot/Code/Platform/Android/PAL_android.cmake
  35. 3 0
      Gems/ProteusRobot/Code/Platform/Android/proteusrobot_api_files.cmake
  36. 8 0
      Gems/ProteusRobot/Code/Platform/Android/proteusrobot_private_files.cmake
  37. 8 0
      Gems/ProteusRobot/Code/Platform/Android/proteusrobot_shared_files.cmake
  38. 4 0
      Gems/ProteusRobot/Code/Platform/Linux/PAL_linux.cmake
  39. 3 0
      Gems/ProteusRobot/Code/Platform/Linux/proteusrobot_api_files.cmake
  40. 3 0
      Gems/ProteusRobot/Code/Platform/Linux/proteusrobot_editor_api_files.cmake
  41. 8 0
      Gems/ProteusRobot/Code/Platform/Linux/proteusrobot_private_files.cmake
  42. 8 0
      Gems/ProteusRobot/Code/Platform/Linux/proteusrobot_shared_files.cmake
  43. 4 0
      Gems/ProteusRobot/Code/Platform/Mac/PAL_mac.cmake
  44. 3 0
      Gems/ProteusRobot/Code/Platform/Mac/proteusrobot_api_files.cmake
  45. 3 0
      Gems/ProteusRobot/Code/Platform/Mac/proteusrobot_editor_api_files.cmake
  46. 8 0
      Gems/ProteusRobot/Code/Platform/Mac/proteusrobot_private_files.cmake
  47. 8 0
      Gems/ProteusRobot/Code/Platform/Mac/proteusrobot_shared_files.cmake
  48. 4 0
      Gems/ProteusRobot/Code/Platform/Windows/PAL_windows.cmake
  49. 3 0
      Gems/ProteusRobot/Code/Platform/Windows/proteusrobot_api_files.cmake
  50. 3 0
      Gems/ProteusRobot/Code/Platform/Windows/proteusrobot_editor_api_files.cmake
  51. 8 0
      Gems/ProteusRobot/Code/Platform/Windows/proteusrobot_private_files.cmake
  52. 8 0
      Gems/ProteusRobot/Code/Platform/Windows/proteusrobot_shared_files.cmake
  53. 4 0
      Gems/ProteusRobot/Code/Platform/iOS/PAL_ios.cmake
  54. 3 0
      Gems/ProteusRobot/Code/Platform/iOS/proteusrobot_api_files.cmake
  55. 8 0
      Gems/ProteusRobot/Code/Platform/iOS/proteusrobot_private_files.cmake
  56. 8 0
      Gems/ProteusRobot/Code/Platform/iOS/proteusrobot_shared_files.cmake
  57. 17 0
      Gems/ProteusRobot/Code/Source/Clients/ProteusRobotModule.cpp
  58. 83 0
      Gems/ProteusRobot/Code/Source/Clients/ProteusRobotSystemComponent.cpp
  59. 47 0
      Gems/ProteusRobot/Code/Source/Clients/ProteusRobotSystemComponent.h
  60. 36 0
      Gems/ProteusRobot/Code/Source/ProteusRobotModuleInterface.h
  61. 38 0
      Gems/ProteusRobot/Code/Source/Tools/ProteusRobotEditorModule.cpp
  62. 54 0
      Gems/ProteusRobot/Code/Source/Tools/ProteusRobotEditorSystemComponent.cpp
  63. 33 0
      Gems/ProteusRobot/Code/Source/Tools/ProteusRobotEditorSystemComponent.h
  64. 4 0
      Gems/ProteusRobot/Code/Tests/Clients/ProteusRobotTest.cpp
  65. 4 0
      Gems/ProteusRobot/Code/Tests/Tools/ProteusRobotEditorTest.cpp
  66. 4 0
      Gems/ProteusRobot/Code/proteusrobot_api_files.cmake
  67. 4 0
      Gems/ProteusRobot/Code/proteusrobot_editor_api_files.cmake
  68. 5 0
      Gems/ProteusRobot/Code/proteusrobot_editor_private_files.cmake
  69. 4 0
      Gems/ProteusRobot/Code/proteusrobot_editor_shared_files.cmake
  70. 4 0
      Gems/ProteusRobot/Code/proteusrobot_editor_tests_files.cmake
  71. 6 0
      Gems/ProteusRobot/Code/proteusrobot_private_files.cmake
  72. 4 0
      Gems/ProteusRobot/Code/proteusrobot_shared_files.cmake
  73. 4 0
      Gems/ProteusRobot/Code/proteusrobot_tests_files.cmake
  74. 18 0
      Gems/ProteusRobot/Registry/assetprocessor_settings.setreg
  75. 28 0
      Gems/ProteusRobot/gem.json
  76. 3 0
      Gems/ProteusRobot/preview.png
  77. 5 3
      Gems/ROS2/Code/Include/ROS2/Frame/ROS2FrameComponent.h
  78. 190 0
      Gems/ROS2/Code/Include/ROS2/Lidar/LidarRaycasterBus.h
  79. 76 0
      Gems/ROS2/Code/Include/ROS2/Lidar/LidarRegistrarBus.h
  80. 43 0
      Gems/ROS2/Code/Include/ROS2/Lidar/LidarSystemBus.h
  81. 2 2
      Gems/ROS2/Code/Include/ROS2/Manipulator/MotorizedJointComponent.h
  82. 2 2
      Gems/ROS2/Code/Include/ROS2/Utilities/Controllers/PidConfiguration.h
  83. 42 6
      Gems/ROS2/Code/Source/Frame/ROS2FrameComponent.cpp
  84. 86 30
      Gems/ROS2/Code/Source/Lidar/LidarRaycaster.cpp
  85. 24 31
      Gems/ROS2/Code/Source/Lidar/LidarRaycaster.h
  86. 53 0
      Gems/ROS2/Code/Source/Lidar/LidarRegistrarEditorSystemComponent.cpp
  87. 34 0
      Gems/ROS2/Code/Source/Lidar/LidarRegistrarEditorSystemComponent.h
  88. 112 0
      Gems/ROS2/Code/Source/Lidar/LidarRegistrarSystemComponent.cpp
  89. 48 0
      Gems/ROS2/Code/Source/Lidar/LidarRegistrarSystemComponent.h
  90. 56 0
      Gems/ROS2/Code/Source/Lidar/LidarSystem.cpp
  91. 38 0
      Gems/ROS2/Code/Source/Lidar/LidarSystem.h
  92. 44 7
      Gems/ROS2/Code/Source/Lidar/LidarTemplate.cpp
  93. 23 3
      Gems/ROS2/Code/Source/Lidar/LidarTemplate.h
  94. 144 32
      Gems/ROS2/Code/Source/Lidar/LidarTemplateUtils.cpp
  95. 10 5
      Gems/ROS2/Code/Source/Lidar/LidarTemplateUtils.h
  96. 150 43
      Gems/ROS2/Code/Source/Lidar/ROS2LidarSensorComponent.cpp
  97. 28 10
      Gems/ROS2/Code/Source/Lidar/ROS2LidarSensorComponent.h
  98. 7 1
      Gems/ROS2/Code/Source/ROS2EditorModule.cpp
  99. 7 1
      Gems/ROS2/Code/Source/ROS2ModuleInterface.h
  100. 2 0
      Gems/ROS2/Code/Source/ROS2SystemComponent.cpp

+ 20 - 0
.github/ISSUE_TEMPLATE/ar_bug_report.md.txt

@@ -0,0 +1,20 @@
+---
+name: Automated Review bug report
+about: Create an issue for a bug found in the Automated Review
+title: 'AR Bug Report'
+labels: 'needs-triage,kind/bug,kind/automation'
+assignees: ''
+
+---
+
+**Describe the bug**
+A clear and concise description of what the bug is.
+
+**Failed Jenkins Job Information:**
+The name of the job that failed, job build number, and code snippit of the failure.
+
+**Attachments**
+Attach the Jenkins job log as a .txt file and any other relevant information.
+
+**Additional context**
+Add any other context about the problem here.

+ 50 - 0
.github/ISSUE_TEMPLATE/bug_template.md.txt

@@ -0,0 +1,50 @@
+---
+name: Bug report
+about: Create a report to help us improve
+title: 'Bug Report'
+labels: 'needs-triage,needs-sig,kind/bug'
+
+---
+
+**Describe the bug**
+A clear and concise description of what the bug is. Try to isolate the issue to help the community to reproduce it easily and increase chances for a fast fix.
+
+**Assets required**
+[!Important!] Please provide sample assets needed to reproduce the issue, either as an [attachment](https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/attaching-files) or as a link to a public asset in Github.
+
+**Steps to reproduce**
+Steps to reproduce the behavior:
+1. Go to '...'
+2. Click on '...'
+3. Select attached asset '...'
+4. Scroll down to '...'
+5. See error
+
+**Expected behavior**
+A clear and concise description of what you expected to happen.
+
+**Actual behavior**
+A clear and concise description of what actually happened.
+
+**Screenshots/Video**
+If applicable, add screenshots and/or a video to help explain your problem.
+
+**Found in Branch**
+Name of or link to the branch where the issue occurs.
+
+**Commit ID from [o3de/o3de](https://github.com/o3de/o3de) and [o3de/o3de-extras](https://github.com/o3de/o3de-extras) repositories**
+Please provide the SHA/hash that identifies the latest commit from the o3de/o3de and o3de/o3de-extras repository if the issue is reproducible in the development or other official branches. 
+You can get the commit ID by running the `git rev-parse HEAD` command on your current branch.
+
+
+**Desktop/Device (please complete the following information):**
+ - Device: [e.g. PC, Mac, iPhone, Samsung] 
+ - OS: [e.g. Windows, macOS, iOS, Android, Linux, including distribution and version (e. g. Ubuntu 22.04 / Windows 11 22H2)]
+
+ - Version [e.g. 10, Monterey, Oreo]
+ - CPU [e.g. Intel I9-9900k , Ryzen 5900x, ]
+ - GPU [AMD 6800 XT, NVidia RTX 3090]
+ - Memory [e.g. 16GB]
+
+**Additional context**
+Add any other context about the problem here.

+ 6 - 0
.github/ISSUE_TEMPLATE/config.yml.txt

@@ -0,0 +1,6 @@
+blank_issues_enabled: false
+contact_links:
+  - name: Documentation Issues
+    about: Report issues with the O3DE.org/docs documentation
+    url: https://github.com/o3de/o3de.org/issues/new/choose 
+

+ 23 - 0
.github/ISSUE_TEMPLATE/deprecation_template.md.txt

@@ -0,0 +1,23 @@
+---
+name: Deprecation Notice
+about: A notice for one or more feature or API deprecations
+title: 'Deprecation Notice'
+labels: 'needs-triage,needs-sig,kind/deprecation'
+
+---
+
+## Deprecated APIs
+List the fully qualified APIs/types. For example, `AZ::Quaternion::SetFromEulerRadians(const Vector3&)`
+
+## Alternatives APIs
+List new or updated APIs to replace deprecated API. 
+For example, use 
+`Quaternion AZ::Quaternion::CreateFromEulerRadiansXYZ(const Vector3&)` 
+to repleace 
+`void AZ::Quaternion::SetFromEulerRadians(const Vector3&)` and `Quaternion AZ::Quaternion::ConvertEulerRadiansToQuaternion(const Vector3&)`
+
+## Last Release
+Describe the last release version before this deprecation. For example: `stabilization/2210`
+
+## Additional Context
+Add some information about the reasons for the deprecations

+ 20 - 0
.github/ISSUE_TEMPLATE/feature_request.md.txt

@@ -0,0 +1,20 @@
+---
+
+name: Feature request
+about: Suggest an idea for this project
+title: 'Feature Request'
+labels: 'needs-triage,needs-sig'
+
+---
+
+**Is your feature request related to a problem? Please describe.**
+A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
+
+**Describe the solution you'd like**
+A clear and concise description of what you want to happen.
+
+**Describe alternatives you've considered**
+A clear and concise description of any alternative solutions or features you've considered.
+
+**Additional context**
+Add any other context or screenshots about the feature request here.

+ 13 - 0
Gems/ProteusRobot/Assets/Materials/Proteus_chassis_Front_Double_Lights.material

@@ -0,0 +1,13 @@
+{
+    "materialType": "@gemroot:Atom_Feature_Common@/Assets/Materials/Types/StandardPBR.materialtype",
+    "materialTypeVersion": 5,
+    "propertyValues": {
+        "baseColor.textureMap": "../Textures/Lights_Emissive.png",
+        "emissive.enable": true,
+        "emissive.intensity": 7.0,
+        "emissive.textureMap": "../Textures/Lights_Emissive.png",
+        "normal.textureMap": "../Textures/Proteus_Normal.png",
+        "opacity.factor": 1.0,
+        "roughness.factor": 0.5
+    }
+}

+ 13 - 0
Gems/ProteusRobot/Assets/Materials/Proteus_chassis_Front_Light.material

@@ -0,0 +1,13 @@
+{
+    "materialType": "@gemroot:Atom_Feature_Common@/Assets/Materials/Types/StandardPBR.materialtype",
+    "materialTypeVersion": 5,
+    "propertyValues": {
+        "baseColor.textureMap": "../Textures/Lights_Emissive.png",
+        "emissive.enable": true,
+        "emissive.intensity": 7.0,
+        "emissive.textureMap": "../Textures/Lights_Emissive.png",
+        "normal.textureMap": "../Textures/Proteus_Normal.png",
+        "opacity.factor": 1.0,
+        "roughness.factor": 0.5
+    }
+}

+ 13 - 0
Gems/ProteusRobot/Assets/Materials/Proteus_chassis_Indicator.material

@@ -0,0 +1,13 @@
+{
+    "materialType": "@gemroot:Atom_Feature_Common@/Assets/Materials/Types/StandardPBR.materialtype",
+    "materialTypeVersion": 5,
+    "propertyValues": {
+        "baseColor.textureMap": "../Textures/Lights_Emissive.png",
+        "emissive.enable": true,
+        "emissive.intensity": 7.0,
+        "emissive.textureMap": "../Textures/Lights_Emissive.png",
+        "normal.textureMap": "../Textures/Proteus_Normal.png",
+        "opacity.factor": 1.0,
+        "roughness.factor": 0.5
+    }
+}

+ 18 - 0
Gems/ProteusRobot/Assets/Materials/Proteus_chassis_Proteus.material

@@ -0,0 +1,18 @@
+{
+    "materialType": "@gemroot:Atom_Feature_Common@/Assets/Materials/Types/StandardPBR.materialtype",
+    "materialTypeVersion": 5,
+    "propertyValues": {
+        "baseColor.textureMap": "../Textures/Proteus_BaseMap.png",
+        "emissive.enable": true,
+        "emissive.intensity": 7.420000076293945,
+        "emissive.textureMap": "../Textures/Lights_Emissive.png",
+        "metallic.textureMap": "../Textures/Proteus_MaskMap_R.png",
+        "normal.textureMap": "../Textures/Proteus_Normal.png",
+        "opacity.factor": 1.0,
+        "roughness.factor": 0.5,
+        "roughness.lowerBound": 0.20000000298023224,
+        "roughness.textureMap": "../Textures/Proteus_MaskMap_A.png",
+        "roughness.upperBound": 0.9399999976158142,
+        "specularF0.factor": 0.5099999904632568
+    }
+}

+ 13 - 0
Gems/ProteusRobot/Assets/Materials/Proteus_chassis_Rear_Lights.material

@@ -0,0 +1,13 @@
+{
+    "materialType": "@gemroot:Atom_Feature_Common@/Assets/Materials/Types/StandardPBR.materialtype",
+    "materialTypeVersion": 5,
+    "propertyValues": {
+        "baseColor.textureMap": "../Textures/Lights_Emissive.png",
+        "emissive.enable": true,
+        "emissive.intensity": 7.0,
+        "emissive.textureMap": "../Textures/Lights_Emissive.png",
+        "normal.textureMap": "../Textures/Proteus_Normal.png",
+        "opacity.factor": 1.0,
+        "roughness.factor": 0.5
+    }
+}

+ 13 - 0
Gems/ProteusRobot/Assets/Materials/Proteus_chassis_Side_Lights.material

@@ -0,0 +1,13 @@
+{
+    "materialType": "@gemroot:Atom_Feature_Common@/Assets/Materials/Types/StandardPBR.materialtype",
+    "materialTypeVersion": 5,
+    "propertyValues": {
+        "baseColor.textureMap": "../Textures/Lights_Emissive.png",
+        "emissive.enable": true,
+        "emissive.intensity": 7.0,
+        "emissive.textureMap": "../Textures/Lights_Emissive.png",
+        "normal.textureMap": "../Textures/Proteus_Normal.png",
+        "opacity.factor": 1.0,
+        "roughness.factor": 0.5
+    }
+}

+ 19 - 0
Gems/ProteusRobot/Assets/Materials/Proteus_chassis_wheel.material

@@ -0,0 +1,19 @@
+{
+    "materialType": "@gemroot:Atom_Feature_Common@/Assets/Materials/Types/StandardPBR.materialtype",
+    "materialTypeVersion": 5,
+    "propertyValues": {
+        "baseColor.textureBlendMode": "LinearLight",
+        "baseColor.textureMap": "../Textures/Proteus_BaseMap.png",
+        "emissive.enable": true,
+        "emissive.intensity": 7.420000076293945,
+        "emissive.textureMap": "../Textures/Lights_Emissive.png",
+        "metallic.textureMap": "../Textures/Proteus_MaskMap_R.png",
+        "normal.textureMap": "../Textures/Proteus_Normal.png",
+        "opacity.factor": 1.0,
+        "roughness.factor": 0.5,
+        "roughness.lowerBound": 0.20000000298023224,
+        "roughness.textureMap": "../Textures/Proteus_MaskMap_A.png",
+        "roughness.upperBound": 0.9399999976158142,
+        "specularF0.factor": 0.5099999904632568
+    }
+}

+ 14 - 0
Gems/ProteusRobot/Assets/Materials/Proteus_headLamp.material

@@ -0,0 +1,14 @@
+{
+    "materialType": "@gemroot:Atom_Feature_Common@/Assets/Materials/Types/StandardPBR.materialtype",
+    "materialTypeVersion": 5,
+    "propertyValues": {
+        "emissive.color": [
+            0.027573052793741226,
+            0.6679789423942566,
+            0.0,
+            1.0
+        ],
+        "emissive.enable": true,
+        "emissive.intensity": 7.860000133514404
+    }
+}

+ 4 - 0
Gems/ProteusRobot/Assets/Materials/untitled.material

@@ -0,0 +1,4 @@
+{
+    "materialType": "@gemroot:Atom_Feature_Common@/Assets/Materials/Types/StandardPBR.materialtype",
+    "materialTypeVersion": 5
+}

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 257 - 0
Gems/ProteusRobot/Assets/Proteus.prefab


+ 3 - 0
Gems/ProteusRobot/Assets/Proteus2_chassis.fbx

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

+ 3 - 0
Gems/ProteusRobot/Assets/Proteus2_lift.fbx

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

+ 3 - 0
Gems/ProteusRobot/Assets/Proteus_chassis.fbx

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

+ 151 - 0
Gems/ProteusRobot/Assets/Proteus_chassis.fbx.assetinfo

@@ -0,0 +1,151 @@
+{
+    "values": [
+        {
+            "$type": "{5B03C8E6-8CEE-4DA0-A7FA-CD88689DD45B} MeshGroup",
+            "id": "{C881D739-5AD7-5795-915C-E5E2A85CB9E0}",
+            "name": "Proteus_chassis",
+            "NodeSelectionList": {
+                "selectedNodes": [
+                    "RootNode.Proteus"
+                ],
+                "unselectedNodes": [
+                    {},
+                    "RootNode",
+                    "RootNode.WheelBack",
+                    "RootNode.WheelFront"
+                ]
+            },
+            "export method": 1,
+            "PhysicsMaterialSlots": {
+                "Slots": [
+                    {
+                        "Name": "Proteus"
+                    }
+                ]
+            }
+        },
+        {
+            "$type": "{07B356B7-3635-40B5-878A-FAC4EFD5AD86} MeshGroup",
+            "name": "Proteus_chassis",
+            "nodeSelectionList": {
+                "selectedNodes": [
+                    {},
+                    "RootNode",
+                    "RootNode.Proteus",
+                    "RootNode.WheelBack",
+                    "RootNode.WheelFront"
+                ]
+            },
+            "rules": {
+                "rules": [
+                    {
+                        "$type": "StaticMeshAdvancedRule",
+                        "vertexColorStreamName": "Col0"
+                    },
+                    {
+                        "$type": "MaterialRule"
+                    }
+                ]
+            },
+            "id": "{5B66A762-9687-5E87-AD1B-706B69996173}"
+        },
+        {
+            "$type": "{07B356B7-3635-40B5-878A-FAC4EFD5AD86} MeshGroup",
+            "name": "default_Proteus_chassis_DA9DBE87_1547_5F98_825B_BB5776FC2E64_",
+            "nodeSelectionList": {
+                "selectedNodes": [
+                    "RootNode.Proteus"
+                ],
+                "unselectedNodes": [
+                    "RootNode.WheelBack",
+                    "RootNode.WheelFront"
+                ]
+            },
+            "rules": {
+                "rules": [
+                    {
+                        "$type": "ProceduralMeshGroupRule"
+                    },
+                    {
+                        "$type": "UnmodifiableRule"
+                    },
+                    {
+                        "$type": "CoordinateSystemRule",
+                        "useAdvancedData": true
+                    },
+                    {
+                        "$type": "{6E796AC8-1484-4909-860A-6D3F22A7346F} LodRule"
+                    }
+                ]
+            },
+            "id": "{DA9DBE87-1547-5F98-825B-BB5776FC2E64}"
+        },
+        {
+            "$type": "{07B356B7-3635-40B5-878A-FAC4EFD5AD86} MeshGroup",
+            "name": "default_Proteus_chassis_F2316B03_A973_5DF0_9FAC_01F13A4EF1BF_",
+            "nodeSelectionList": {
+                "selectedNodes": [
+                    "RootNode.WheelBack"
+                ],
+                "unselectedNodes": [
+                    "RootNode.Proteus",
+                    "RootNode.WheelFront"
+                ]
+            },
+            "rules": {
+                "rules": [
+                    {
+                        "$type": "ProceduralMeshGroupRule"
+                    },
+                    {
+                        "$type": "UnmodifiableRule"
+                    },
+                    {
+                        "$type": "CoordinateSystemRule",
+                        "useAdvancedData": true
+                    },
+                    {
+                        "$type": "{6E796AC8-1484-4909-860A-6D3F22A7346F} LodRule"
+                    }
+                ]
+            },
+            "id": "{F2316B03-A973-5DF0-9FAC-01F13A4EF1BF}"
+        },
+        {
+            "$type": "{07B356B7-3635-40B5-878A-FAC4EFD5AD86} MeshGroup",
+            "name": "default_Proteus_chassis_07C13738_36F4_5638_A16E_33582EFA5AE2_",
+            "nodeSelectionList": {
+                "selectedNodes": [
+                    "RootNode.WheelFront"
+                ],
+                "unselectedNodes": [
+                    "RootNode.Proteus",
+                    "RootNode.WheelBack"
+                ]
+            },
+            "rules": {
+                "rules": [
+                    {
+                        "$type": "ProceduralMeshGroupRule"
+                    },
+                    {
+                        "$type": "UnmodifiableRule"
+                    },
+                    {
+                        "$type": "CoordinateSystemRule",
+                        "useAdvancedData": true
+                    },
+                    {
+                        "$type": "{6E796AC8-1484-4909-860A-6D3F22A7346F} LodRule"
+                    }
+                ]
+            },
+            "id": "{07C13738-36F4-5638-A16E-33582EFA5AE2}"
+        },
+        {
+            "$type": "PrefabGroup",
+            "name": "Assets/ProteusRobot/Proteus_chassis_fbx.procprefab",
+            "id": "{54641020-4DC4-5AEE-A44A-A6D9BD8C0CBA}"
+        }
+    ]
+}

+ 3 - 0
Gems/ProteusRobot/Assets/Proteus_wheel.fbx

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

+ 88 - 0
Gems/ProteusRobot/Assets/Proteus_wheel.fbx.assetinfo

@@ -0,0 +1,88 @@
+{
+    "values": [
+        {
+            "$type": "{5B03C8E6-8CEE-4DA0-A7FA-CD88689DD45B} MeshGroup",
+            "id": "{80311A34-6917-57C6-819B-8222047E8B5B}",
+            "name": "Proteus_wheel",
+            "NodeSelectionList": {
+                "selectedNodes": [
+                    "RootNode",
+                    "RootNode.WheelSide",
+                    "RootNode.WheelSide.transform",
+                    "RootNode.WheelSide.custom_properties",
+                    "RootNode.WheelSide.UVMap",
+                    "RootNode.WheelSide.Proteus"
+                ]
+            },
+            "export method": 1,
+            "PhysicsMaterialSlots": {
+                "Slots": [
+                    {
+                        "Name": "Proteus",
+                        "MaterialAsset": {
+                            "assetId": {
+                                "guid": "{74448F35-B8C8-5CF3-984C-58CF92889961}",
+                                "subId": 1
+                            },
+                            "assetHint": "assets/proteusrobot/robot_tire.physicsmaterial"
+                        }
+                    }
+                ]
+            }
+        },
+        {
+            "$type": "{07B356B7-3635-40B5-878A-FAC4EFD5AD86} MeshGroup",
+            "name": "Proteus_wheel",
+            "nodeSelectionList": {
+                "selectedNodes": [
+                    "RootNode",
+                    "RootNode.WheelSide",
+                    "RootNode.WheelSide.transform",
+                    "RootNode.WheelSide.custom_properties",
+                    "RootNode.WheelSide.UVMap",
+                    "RootNode.WheelSide.Proteus"
+                ]
+            },
+            "rules": {
+                "rules": [
+                    {
+                        "$type": "MaterialRule"
+                    }
+                ]
+            },
+            "id": "{B2B65532-24AA-559B-8C87-AA2E0EDD85E7}"
+        },
+        {
+            "$type": "{07B356B7-3635-40B5-878A-FAC4EFD5AD86} MeshGroup",
+            "name": "default_Proteus_wheel_2427710A_245B_51D6_BF38_B05455BE2C6F_",
+            "nodeSelectionList": {
+                "selectedNodes": [
+                    "RootNode.WheelSide"
+                ]
+            },
+            "rules": {
+                "rules": [
+                    {
+                        "$type": "ProceduralMeshGroupRule"
+                    },
+                    {
+                        "$type": "UnmodifiableRule"
+                    },
+                    {
+                        "$type": "CoordinateSystemRule",
+                        "useAdvancedData": true
+                    },
+                    {
+                        "$type": "{6E796AC8-1484-4909-860A-6D3F22A7346F} LodRule"
+                    }
+                ]
+            },
+            "id": "{2427710A-245B-51D6-BF38-B05455BE2C6F}"
+        },
+        {
+            "$type": "PrefabGroup",
+            "name": "Assets/ProteusRobot/Proteus_wheel_fbx.procprefab",
+            "id": "{1C55117B-259E-5770-8E5D-22594768E8AE}"
+        }
+    ]
+}

+ 45 - 0
Gems/ProteusRobot/Assets/Scripts/ProteusRobot_inputs.inputbindings

@@ -0,0 +1,45 @@
+<ObjectStream version="3">
+	<Class name="InputEventBindingsAsset" type="{25971C7A-26E2-4D08-A146-2EFCC1C36B0C}">
+		<Class name="InputEventBindings" field="Bindings" version="1" type="{14FFD4A8-AE46-4E23-B45B-6A7C4F787A91}">
+			<Class name="AZStd::vector&lt;InputEventGroup, allocator&gt;" field="Input Event Groups" type="{E7A38039-E414-5322-B384-210475DA7732}">
+				<Class name="InputEventGroup" field="element" version="1" type="{25143B7E-2FEC-4CC5-92FE-270B67E79734}">
+					<Class name="AZStd::string" field="Event Name" value="rotate" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
+					<Class name="AZStd::vector&lt;InputSubComponent*, allocator&gt;" field="Event Generators" type="{7B0B6F41-794A-5CFF-8275-91A3137E747D}">
+						<Class name="InputEventMap" field="element" version="2" type="{A14EA0A3-F053-469D-840E-A70002F51384}">
+							<Class name="AZStd::string" field="Input Device Type" value="keyboard" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
+							<Class name="AZStd::string" field="Input Name" value="keyboard_key_navigation_arrow_left" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
+							<Class name="float" field="Event Value Multiplier" value="1.0000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
+							<Class name="float" field="Dead Zone" value="0.0000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
+						</Class>
+						<Class name="InputEventMap" field="element" version="2" type="{A14EA0A3-F053-469D-840E-A70002F51384}">
+							<Class name="AZStd::string" field="Input Device Type" value="keyboard" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
+							<Class name="AZStd::string" field="Input Name" value="keyboard_key_navigation_arrow_right" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
+							<Class name="float" field="Event Value Multiplier" value="-1.0000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
+							<Class name="float" field="Dead Zone" value="0.0000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
+						</Class>
+					</Class>
+					<Class name="bool" field="Exclude From Release" value="false" type="{A0CA880C-AFE4-43CB-926C-59AC48496112}"/>
+				</Class>
+				<Class name="InputEventGroup" field="element" version="1" type="{25143B7E-2FEC-4CC5-92FE-270B67E79734}">
+					<Class name="AZStd::string" field="Event Name" value="accelerate" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
+					<Class name="AZStd::vector&lt;InputSubComponent*, allocator&gt;" field="Event Generators" type="{7B0B6F41-794A-5CFF-8275-91A3137E747D}">
+						<Class name="InputEventMap" field="element" version="2" type="{A14EA0A3-F053-469D-840E-A70002F51384}">
+							<Class name="AZStd::string" field="Input Device Type" value="keyboard" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
+							<Class name="AZStd::string" field="Input Name" value="keyboard_key_navigation_arrow_up" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
+							<Class name="float" field="Event Value Multiplier" value="1.0000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
+							<Class name="float" field="Dead Zone" value="0.0000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
+						</Class>
+						<Class name="InputEventMap" field="element" version="2" type="{A14EA0A3-F053-469D-840E-A70002F51384}">
+							<Class name="AZStd::string" field="Input Device Type" value="keyboard" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
+							<Class name="AZStd::string" field="Input Name" value="keyboard_key_navigation_arrow_down" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
+							<Class name="float" field="Event Value Multiplier" value="-1.0000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
+							<Class name="float" field="Dead Zone" value="0.0000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
+						</Class>
+					</Class>
+					<Class name="bool" field="Exclude From Release" value="false" type="{A0CA880C-AFE4-43CB-926C-59AC48496112}"/>
+				</Class>
+			</Class>
+		</Class>
+	</Class>
+</ObjectStream>
+

+ 3 - 0
Gems/ProteusRobot/Assets/Textures/Lights_Emissive.png

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

+ 3 - 0
Gems/ProteusRobot/Assets/Textures/Proteus_BaseMap.png

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

+ 3 - 0
Gems/ProteusRobot/Assets/Textures/Proteus_MaskMap.png

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

+ 3 - 0
Gems/ProteusRobot/Assets/Textures/Proteus_MaskMap_A.png

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

+ 3 - 0
Gems/ProteusRobot/Assets/Textures/Proteus_MaskMap_R.png

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

+ 3 - 0
Gems/ProteusRobot/Assets/Textures/Proteus_Normal.png

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

+ 18 - 0
Gems/ProteusRobot/Assets/no_friction.physxmaterial

@@ -0,0 +1,18 @@
+<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="0.0000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
+			<Class name="float" field="StaticFriction" value="0.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="1" 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="1000.0000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
+			<Class name="Color" field="DebugColor" value="1.0000000 1.0000000 1.0000000 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="{00000000-0000-0000-0000-000000000000}" type="{E152C105-A133-4D03-BBF8-3D4B2FBA3E2A}"/>
+		</Class>
+	</Class>
+</ObjectStream>
+

+ 18 - 0
Gems/ProteusRobot/Assets/robot_tire.physxmaterial

@@ -0,0 +1,18 @@
+<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="0.8000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
+			<Class name="float" field="StaticFriction" value="0.8000000" 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="0" 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="1000.0000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
+			<Class name="Color" field="DebugColor" value="1.0000000 1.0000000 1.0000000 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="{00000000-0000-0000-0000-000000000000}" type="{E152C105-A133-4D03-BBF8-3D4B2FBA3E2A}"/>
+		</Class>
+	</Class>
+</ObjectStream>
+

+ 22 - 0
Gems/ProteusRobot/CMakeLists.txt

@@ -0,0 +1,22 @@
+
+# Query the gem name from the gem.json file if possible
+# otherwise fallback to using ProteusRobot
+o3de_find_ancestor_gem_root(gem_path gem_name "${CMAKE_CURRENT_SOURCE_DIR}")
+if (NOT gem_name)
+    set(gem_name "ProteusRobot")
+endif()
+
+# Fallback to using the current source CMakeLists.txt directory as the gem root path
+if (NOT gem_path)
+    set(gem_path ${CMAKE_CURRENT_SOURCE_DIR})
+endif()
+
+set(gem_json ${gem_path}/gem.json)
+
+o3de_restricted_path(${gem_json} gem_restricted_path gem_parent_relative_path)
+
+o3de_pal_dir(pal_dir ${CMAKE_CURRENT_SOURCE_DIR}/Platform/${PAL_PLATFORM_NAME} "${gem_restricted_path}" "${gem_path}" "${gem_parent_relative_path}")
+
+ly_add_external_target_path(${CMAKE_CURRENT_SOURCE_DIR}/3rdParty)
+
+add_subdirectory(Code)

+ 210 - 0
Gems/ProteusRobot/Code/CMakeLists.txt

@@ -0,0 +1,210 @@
+
+# Currently we are in the Code folder: ${CMAKE_CURRENT_LIST_DIR}
+# Get the platform specific folder ${pal_dir} for the current folder: ${CMAKE_CURRENT_LIST_DIR}/Platform/${PAL_PLATFORM_NAME}
+# Note: o3de_pal_dir will take care of the details for us, as this may be a restricted platform
+#       in which case it will see if that platform is present here or in the restricted folder.
+#       i.e. It could here in our gem : Gems/ProteusRobot/Code/Platform/<platorm_name>  or
+#            <restricted_folder>/<platform_name>/Gems/ProteusRobot/Code
+o3de_pal_dir(pal_dir ${CMAKE_CURRENT_LIST_DIR}/Platform/${PAL_PLATFORM_NAME} "${gem_restricted_path}" "${gem_path}" "${gem_parent_relative_path}")
+
+# Now that we have the platform abstraction layer (PAL) folder for this folder, thats where we will find the
+# traits for this platform. Traits for a platform are defines for things like whether or not something in this gem
+# is supported by this platform.
+include(${pal_dir}/PAL_${PAL_PLATFORM_NAME_LOWERCASE}.cmake)
+
+# Check to see if building the Gem Modules are supported for the current platform
+if(NOT PAL_TRAIT_PROTEUSROBOT_SUPPORTED)
+    return()
+endif()
+
+# The ${gem_name}.API target declares the common interface that users of this gem should depend on in their targets
+ly_add_target(
+    NAME ${gem_name}.API INTERFACE
+    NAMESPACE Gem
+    FILES_CMAKE
+        proteusrobot_api_files.cmake
+        ${pal_dir}/proteusrobot_api_files.cmake
+    INCLUDE_DIRECTORIES
+        INTERFACE
+            Include
+    BUILD_DEPENDENCIES
+        INTERFACE
+           AZ::AzCore
+)
+
+# The ${gem_name}.Private.Object target is an internal target
+# It should not be used outside of this Gems CMakeLists.txt
+ly_add_target(
+    NAME ${gem_name}.Private.Object STATIC
+    NAMESPACE Gem
+    FILES_CMAKE
+        proteusrobot_private_files.cmake
+        ${pal_dir}/proteusrobot_private_files.cmake
+    TARGET_PROPERTIES
+        O3DE_PRIVATE_TARGET TRUE
+    INCLUDE_DIRECTORIES
+        PRIVATE
+            Include
+            Source
+    BUILD_DEPENDENCIES
+        PUBLIC
+            AZ::AzCore
+            AZ::AzFramework
+)
+
+# Here add ${gem_name} target, it depends on the Private Object library and Public API interface
+ly_add_target(
+    NAME ${gem_name} ${PAL_TRAIT_MONOLITHIC_DRIVEN_MODULE_TYPE}
+    NAMESPACE Gem
+    FILES_CMAKE
+        proteusrobot_shared_files.cmake
+        ${pal_dir}/proteusrobot_shared_files.cmake
+    INCLUDE_DIRECTORIES
+        PUBLIC
+            Include
+        PRIVATE
+            Source
+    BUILD_DEPENDENCIES
+        PUBLIC
+            Gem::${gem_name}.API
+        PRIVATE
+            Gem::${gem_name}.Private.Object
+)
+
+# By default, we will specify that the above target ${gem_name} would be used by
+# Client and Server type targets when this gem is enabled.  If you don't want it
+# active in Clients or Servers by default, delete one of both of the following lines:
+ly_create_alias(NAME ${gem_name}.Clients NAMESPACE Gem TARGETS Gem::${gem_name})
+ly_create_alias(NAME ${gem_name}.Servers NAMESPACE Gem TARGETS Gem::${gem_name})
+ly_create_alias(NAME ${gem_name}.Unified NAMESPACE Gem TARGETS Gem::${gem_name})
+
+# For the Client and Server variants of ${gem_name} Gem, an alias to the ${gem_name}.API target will be made
+ly_create_alias(NAME ${gem_name}.Clients.API NAMESPACE Gem TARGETS Gem::${gem_name}.API)
+ly_create_alias(NAME ${gem_name}.Servers.API NAMESPACE Gem TARGETS Gem::${gem_name}.API)
+ly_create_alias(NAME ${gem_name}.Unified.API NAMESPACE Gem TARGETS Gem::${gem_name}.API)
+
+# If we are on a host platform, we want to add the host tools targets like the ${gem_name}.Editor MODULE target
+if(PAL_TRAIT_BUILD_HOST_TOOLS)
+    # The ${gem_name}.Editor.API target can be used by other gems that want to interact with the ${gem_name}.Editor module
+    ly_add_target(
+        NAME ${gem_name}.Editor.API INTERFACE
+        NAMESPACE Gem
+        FILES_CMAKE
+            proteusrobot_editor_api_files.cmake
+            ${pal_dir}/proteusrobot_editor_api_files.cmake
+        INCLUDE_DIRECTORIES
+            INTERFACE
+                Include
+        BUILD_DEPENDENCIES
+            INTERFACE
+                AZ::AzToolsFramework
+    )
+
+    # The ${gem_name}.Editor.Private.Object target is an internal target
+    # which is only to be used by this gems CMakeLists.txt and any subdirectories
+    # Other gems should not use this target
+    ly_add_target(
+        NAME ${gem_name}.Editor.Private.Object STATIC
+        NAMESPACE Gem
+        FILES_CMAKE
+            proteusrobot_editor_private_files.cmake
+        TARGET_PROPERTIES
+            O3DE_PRIVATE_TARGET TRUE
+        INCLUDE_DIRECTORIES
+            PRIVATE
+                Include
+                Source
+        BUILD_DEPENDENCIES
+            PUBLIC
+                AZ::AzToolsFramework
+                $<TARGET_OBJECTS:Gem::${gem_name}.Private.Object>
+    )
+
+    ly_add_target(
+        NAME ${gem_name}.Editor GEM_MODULE
+        NAMESPACE Gem
+        AUTOMOC
+        FILES_CMAKE
+            proteusrobot_editor_shared_files.cmake
+        INCLUDE_DIRECTORIES
+            PRIVATE
+                Source
+            PUBLIC
+                Include
+        BUILD_DEPENDENCIES
+            PUBLIC
+                Gem::${gem_name}.Editor.API
+            PRIVATE
+                Gem::${gem_name}.Editor.Private.Object
+    )
+
+    # By default, we will specify that the above target ${gem_name} would be used by
+    # Tool and Builder type targets when this gem is enabled.  If you don't want it
+    # active in Tools or Builders by default, delete one of both of the following lines:
+    ly_create_alias(NAME ${gem_name}.Tools    NAMESPACE Gem TARGETS Gem::${gem_name}.Editor)
+    ly_create_alias(NAME ${gem_name}.Builders NAMESPACE Gem TARGETS Gem::${gem_name}.Editor)
+
+    # For the Tools and Builders variants of ${gem_name} Gem, an alias to the ${gem_name}.Editor API target will be made
+    ly_create_alias(NAME ${gem_name}.Tools.API NAMESPACE Gem TARGETS Gem::${gem_name}.Editor.API)
+    ly_create_alias(NAME ${gem_name}.Builders.API NAMESPACE Gem TARGETS Gem::${gem_name}.Editor.API)
+
+endif()
+
+################################################################################
+# Tests
+################################################################################
+# See if globally, tests are supported
+if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
+    # We globally support tests, see if we support tests on this platform for ${gem_name}.Tests
+    if(PAL_TRAIT_PROTEUSROBOT_TEST_SUPPORTED)
+        # We support ${gem_name}.Tests on this platform, add dependency on the Private Object target
+        ly_add_target(
+            NAME ${gem_name}.Tests ${PAL_TRAIT_TEST_TARGET_TYPE}
+            NAMESPACE Gem
+            FILES_CMAKE
+                proteusrobot_tests_files.cmake
+            INCLUDE_DIRECTORIES
+                PRIVATE
+                    Tests
+                    Source
+            BUILD_DEPENDENCIES
+                PRIVATE
+                    AZ::AzTest
+                    AZ::AzFramework
+                    Gem::${gem_name}.Private.Object
+        )
+
+        # Add ${gem_name}.Tests to googletest
+        ly_add_googletest(
+            NAME Gem::${gem_name}.Tests
+        )
+    endif()
+
+    # If we are a host platform we want to add tools test like editor tests here
+    if(PAL_TRAIT_BUILD_HOST_TOOLS)
+        # We are a host platform, see if Editor tests are supported on this platform
+        if(PAL_TRAIT_PROTEUSROBOT_EDITOR_TEST_SUPPORTED)
+            # We support ${gem_name}.Editor.Tests on this platform, add ${gem_name}.Editor.Tests target which depends on
+            # private ${gem_name}.Editor.Private.Object target
+            ly_add_target(
+                NAME ${gem_name}.Editor.Tests ${PAL_TRAIT_TEST_TARGET_TYPE}
+                NAMESPACE Gem
+                FILES_CMAKE
+                    proteusrobot_editor_tests_files.cmake
+                INCLUDE_DIRECTORIES
+                    PRIVATE
+                        Tests
+                        Source
+                BUILD_DEPENDENCIES
+                    PRIVATE
+                        AZ::AzTest
+                        Gem::${gem_name}.Private.Object
+            )
+
+            # Add ${gem_name}.Editor.Tests to googletest
+            ly_add_googletest(
+                NAME Gem::${gem_name}.Editor.Tests
+            )
+        endif()
+    endif()
+endif()

+ 31 - 0
Gems/ProteusRobot/Code/Include/ProteusRobot/ProteusRobotBus.h

@@ -0,0 +1,31 @@
+
+#pragma once
+
+#include <AzCore/EBus/EBus.h>
+#include <AzCore/Interface/Interface.h>
+
+namespace ProteusRobot
+{
+    class ProteusRobotRequests
+    {
+    public:
+        AZ_RTTI(ProteusRobotRequests, "{2480DA88-89C0-40D0-9C27-CA19C99FECC7}");
+        virtual ~ProteusRobotRequests() = default;
+        // Put your public methods here
+    };
+    
+    class ProteusRobotBusTraits
+        : public AZ::EBusTraits
+    {
+    public:
+        //////////////////////////////////////////////////////////////////////////
+        // EBusTraits overrides
+        static constexpr AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
+        static constexpr AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
+        //////////////////////////////////////////////////////////////////////////
+    };
+
+    using ProteusRobotRequestBus = AZ::EBus<ProteusRobotRequests, ProteusRobotBusTraits>;
+    using ProteusRobotInterface = AZ::Interface<ProteusRobotRequests>;
+
+} // namespace ProteusRobot

+ 4 - 0
Gems/ProteusRobot/Code/Platform/Android/PAL_android.cmake

@@ -0,0 +1,4 @@
+
+set(PAL_TRAIT_PROTEUSROBOT_SUPPORTED TRUE)
+set(PAL_TRAIT_PROTEUSROBOT_TEST_SUPPORTED FALSE)
+set(PAL_TRAIT_PROTEUSROBOT_EDITOR_TEST_SUPPORTED FALSE)

+ 3 - 0
Gems/ProteusRobot/Code/Platform/Android/proteusrobot_api_files.cmake

@@ -0,0 +1,3 @@
+
+set(FILES
+)

+ 8 - 0
Gems/ProteusRobot/Code/Platform/Android/proteusrobot_private_files.cmake

@@ -0,0 +1,8 @@
+
+# Platform specific files for Android
+# i.e. ../Source/Android/ProteusRobotAndroid.cpp
+#      ../Source/Android/ProteusRobotAndroid.h
+#      ../Include/Android/ProteusRobotAndroid.h
+
+set(FILES
+)

+ 8 - 0
Gems/ProteusRobot/Code/Platform/Android/proteusrobot_shared_files.cmake

@@ -0,0 +1,8 @@
+
+# Platform specific files for Android
+# i.e. ../Source/Android/ProteusRobotAndroid.cpp
+#      ../Source/Android/ProteusRobotAndroid.h
+#      ../Include/Android/ProteusRobotAndroid.h
+
+set(FILES
+)

+ 4 - 0
Gems/ProteusRobot/Code/Platform/Linux/PAL_linux.cmake

@@ -0,0 +1,4 @@
+
+set(PAL_TRAIT_PROTEUSROBOT_SUPPORTED TRUE)
+set(PAL_TRAIT_PROTEUSROBOT_TEST_SUPPORTED FALSE)
+set(PAL_TRAIT_PROTEUSROBOT_EDITOR_TEST_SUPPORTED FALSE)

+ 3 - 0
Gems/ProteusRobot/Code/Platform/Linux/proteusrobot_api_files.cmake

@@ -0,0 +1,3 @@
+
+set(FILES
+)

+ 3 - 0
Gems/ProteusRobot/Code/Platform/Linux/proteusrobot_editor_api_files.cmake

@@ -0,0 +1,3 @@
+
+set(FILES
+)

+ 8 - 0
Gems/ProteusRobot/Code/Platform/Linux/proteusrobot_private_files.cmake

@@ -0,0 +1,8 @@
+
+# Platform specific files for Linux
+# i.e. ../Source/Linux/ProteusRobotLinux.cpp
+#      ../Source/Linux/ProteusRobotLinux.h
+#      ../Include/Linux/ProteusRobotLinux.h
+
+set(FILES
+)

+ 8 - 0
Gems/ProteusRobot/Code/Platform/Linux/proteusrobot_shared_files.cmake

@@ -0,0 +1,8 @@
+
+# Platform specific files for Linux
+# i.e. ../Source/Linux/ProteusRobotLinux.cpp
+#      ../Source/Linux/ProteusRobotLinux.h
+#      ../Include/Linux/ProteusRobotLinux.h
+
+set(FILES
+)

+ 4 - 0
Gems/ProteusRobot/Code/Platform/Mac/PAL_mac.cmake

@@ -0,0 +1,4 @@
+
+set(PAL_TRAIT_PROTEUSROBOT_SUPPORTED TRUE)
+set(PAL_TRAIT_PROTEUSROBOT_TEST_SUPPORTED FALSE)
+set(PAL_TRAIT_PROTEUSROBOT_EDITOR_TEST_SUPPORTED FALSE)

+ 3 - 0
Gems/ProteusRobot/Code/Platform/Mac/proteusrobot_api_files.cmake

@@ -0,0 +1,3 @@
+
+set(FILES
+)

+ 3 - 0
Gems/ProteusRobot/Code/Platform/Mac/proteusrobot_editor_api_files.cmake

@@ -0,0 +1,3 @@
+
+set(FILES
+)

+ 8 - 0
Gems/ProteusRobot/Code/Platform/Mac/proteusrobot_private_files.cmake

@@ -0,0 +1,8 @@
+
+# Platform specific files for Mac
+# i.e. ../Source/Mac/ProteusRobotMac.cpp
+#      ../Source/Mac/ProteusRobotMac.h
+#      ../Include/Mac/ProteusRobotMac.h
+
+set(FILES
+)

+ 8 - 0
Gems/ProteusRobot/Code/Platform/Mac/proteusrobot_shared_files.cmake

@@ -0,0 +1,8 @@
+
+# Platform specific files for Mac
+# i.e. ../Source/Mac/ProteusRobotMac.cpp
+#      ../Source/Mac/ProteusRobotMac.h
+#      ../Include/Mac/ProteusRobotMac.h
+
+set(FILES
+)

+ 4 - 0
Gems/ProteusRobot/Code/Platform/Windows/PAL_windows.cmake

@@ -0,0 +1,4 @@
+
+set(PAL_TRAIT_PROTEUSROBOT_SUPPORTED TRUE)
+set(PAL_TRAIT_PROTEUSROBOT_TEST_SUPPORTED FALSE)
+set(PAL_TRAIT_PROTEUSROBOT_EDITOR_TEST_SUPPORTED FALSE)

+ 3 - 0
Gems/ProteusRobot/Code/Platform/Windows/proteusrobot_api_files.cmake

@@ -0,0 +1,3 @@
+
+set(FILES
+)

+ 3 - 0
Gems/ProteusRobot/Code/Platform/Windows/proteusrobot_editor_api_files.cmake

@@ -0,0 +1,3 @@
+
+set(FILES
+)

+ 8 - 0
Gems/ProteusRobot/Code/Platform/Windows/proteusrobot_private_files.cmake

@@ -0,0 +1,8 @@
+
+# Platform specific files for Windows
+# i.e. ../Source/Windows/ProteusRobotWindows.cpp
+#      ../Source/Windows/ProteusRobotWindows.h
+#      ../Include/Windows/ProteusRobotWindows.h
+
+set(FILES
+)

+ 8 - 0
Gems/ProteusRobot/Code/Platform/Windows/proteusrobot_shared_files.cmake

@@ -0,0 +1,8 @@
+
+# Platform specific files for Windows
+# i.e. ../Source/Windows/ProteusRobotWindows.cpp
+#      ../Source/Windows/ProteusRobotWindows.h
+#      ../Include/Windows/ProteusRobotWindows.h
+
+set(FILES
+)

+ 4 - 0
Gems/ProteusRobot/Code/Platform/iOS/PAL_ios.cmake

@@ -0,0 +1,4 @@
+
+set(PAL_TRAIT_PROTEUSROBOT_SUPPORTED TRUE)
+set(PAL_TRAIT_PROTEUSROBOT_TEST_SUPPORTED FALSE)
+set(PAL_TRAIT_PROTEUSROBOT_EDITOR_TEST_SUPPORTED FALSE)

+ 3 - 0
Gems/ProteusRobot/Code/Platform/iOS/proteusrobot_api_files.cmake

@@ -0,0 +1,3 @@
+
+set(FILES
+)

+ 8 - 0
Gems/ProteusRobot/Code/Platform/iOS/proteusrobot_private_files.cmake

@@ -0,0 +1,8 @@
+
+# Platform specific files for iOS
+# i.e. ../Source/iOS/ProteusRobotiOS.cpp
+#      ../Source/iOS/ProteusRobotiOS.h
+#      ../Include/iOS/ProteusRobotiOS.h
+
+set(FILES
+)

+ 8 - 0
Gems/ProteusRobot/Code/Platform/iOS/proteusrobot_shared_files.cmake

@@ -0,0 +1,8 @@
+
+# Platform specific files for iOS
+# i.e. ../Source/iOS/ProteusRobotiOS.cpp
+#      ../Source/iOS/ProteusRobotiOS.h
+#      ../Include/iOS/ProteusRobotiOS.h
+
+set(FILES
+)

+ 17 - 0
Gems/ProteusRobot/Code/Source/Clients/ProteusRobotModule.cpp

@@ -0,0 +1,17 @@
+
+
+#include <ProteusRobotModuleInterface.h>
+#include "ProteusRobotSystemComponent.h"
+
+namespace ProteusRobot
+{
+    class ProteusRobotModule
+        : public ProteusRobotModuleInterface
+    {
+    public:
+        AZ_RTTI(ProteusRobotModule, "{F9558D3E-566B-4824-8634-015F21864F5E}", ProteusRobotModuleInterface);
+        AZ_CLASS_ALLOCATOR(ProteusRobotModule, AZ::SystemAllocator, 0);
+    };
+}// namespace ProteusRobot
+
+AZ_DECLARE_MODULE_CLASS(Gem_ProteusRobot, ProteusRobot::ProteusRobotModule)

+ 83 - 0
Gems/ProteusRobot/Code/Source/Clients/ProteusRobotSystemComponent.cpp

@@ -0,0 +1,83 @@
+
+#include "ProteusRobotSystemComponent.h"
+
+#include <AzCore/Serialization/SerializeContext.h>
+#include <AzCore/Serialization/EditContext.h>
+#include <AzCore/Serialization/EditContextConstants.inl>
+
+namespace ProteusRobot
+{
+    void ProteusRobotSystemComponent::Reflect(AZ::ReflectContext* context)
+    {
+        if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context))
+        {
+            serialize->Class<ProteusRobotSystemComponent, AZ::Component>()
+                ->Version(0)
+                ;
+
+            if (AZ::EditContext* ec = serialize->GetEditContext())
+            {
+                ec->Class<ProteusRobotSystemComponent>("ProteusRobot", "[Description of functionality provided by this System Component]")
+                    ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
+                        ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("System"))
+                        ->Attribute(AZ::Edit::Attributes::AutoExpand, true)
+                    ;
+            }
+        }
+    }
+
+    void ProteusRobotSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
+    {
+        provided.push_back(AZ_CRC_CE("ProteusRobotService"));
+    }
+
+    void ProteusRobotSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
+    {
+        incompatible.push_back(AZ_CRC_CE("ProteusRobotService"));
+    }
+
+    void ProteusRobotSystemComponent::GetRequiredServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& required)
+    {
+    }
+
+    void ProteusRobotSystemComponent::GetDependentServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& dependent)
+    {
+    }
+
+    ProteusRobotSystemComponent::ProteusRobotSystemComponent()
+    {
+        if (ProteusRobotInterface::Get() == nullptr)
+        {
+            ProteusRobotInterface::Register(this);
+        }
+    }
+
+    ProteusRobotSystemComponent::~ProteusRobotSystemComponent()
+    {
+        if (ProteusRobotInterface::Get() == this)
+        {
+            ProteusRobotInterface::Unregister(this);
+        }
+    }
+
+    void ProteusRobotSystemComponent::Init()
+    {
+    }
+
+    void ProteusRobotSystemComponent::Activate()
+    {
+        ProteusRobotRequestBus::Handler::BusConnect();
+        AZ::TickBus::Handler::BusConnect();
+    }
+
+    void ProteusRobotSystemComponent::Deactivate()
+    {
+        AZ::TickBus::Handler::BusDisconnect();
+        ProteusRobotRequestBus::Handler::BusDisconnect();
+    }
+
+    void ProteusRobotSystemComponent::OnTick([[maybe_unused]] float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time)
+    {
+    }
+
+} // namespace ProteusRobot

+ 47 - 0
Gems/ProteusRobot/Code/Source/Clients/ProteusRobotSystemComponent.h

@@ -0,0 +1,47 @@
+
+#pragma once
+
+#include <AzCore/Component/Component.h>
+#include <AzCore/Component/TickBus.h>
+#include <ProteusRobot/ProteusRobotBus.h>
+
+namespace ProteusRobot
+{
+    class ProteusRobotSystemComponent
+        : public AZ::Component
+        , protected ProteusRobotRequestBus::Handler
+        , public AZ::TickBus::Handler
+    {
+    public:
+        AZ_COMPONENT(ProteusRobotSystemComponent, "{250B4B63-AF8F-4DC0-BF73-9880E8BEA444}");
+
+        static void Reflect(AZ::ReflectContext* context);
+
+        static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
+        static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
+        static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required);
+        static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent);
+
+        ProteusRobotSystemComponent();
+        ~ProteusRobotSystemComponent();
+
+    protected:
+        ////////////////////////////////////////////////////////////////////////
+        // ProteusRobotRequestBus interface implementation
+
+        ////////////////////////////////////////////////////////////////////////
+
+        ////////////////////////////////////////////////////////////////////////
+        // AZ::Component interface implementation
+        void Init() override;
+        void Activate() override;
+        void Deactivate() override;
+        ////////////////////////////////////////////////////////////////////////
+
+        ////////////////////////////////////////////////////////////////////////
+        // AZTickBus interface implementation
+        void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
+        ////////////////////////////////////////////////////////////////////////
+    };
+
+} // namespace ProteusRobot

+ 36 - 0
Gems/ProteusRobot/Code/Source/ProteusRobotModuleInterface.h

@@ -0,0 +1,36 @@
+
+#include <AzCore/Memory/SystemAllocator.h>
+#include <AzCore/Module/Module.h>
+#include <Clients/ProteusRobotSystemComponent.h>
+
+namespace ProteusRobot
+{
+    class ProteusRobotModuleInterface
+        : public AZ::Module
+    {
+    public:
+        AZ_RTTI(ProteusRobotModuleInterface, "{C2853683-8867-42E4-BAF8-6BAE29CB53E1}", AZ::Module);
+        AZ_CLASS_ALLOCATOR(ProteusRobotModuleInterface, AZ::SystemAllocator, 0);
+
+        ProteusRobotModuleInterface()
+        {
+            // 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 the SerializeContext, BehaviorContext and EditContext.
+            // This happens through the [MyComponent]::Reflect() function.
+            m_descriptors.insert(m_descriptors.end(), {
+                ProteusRobotSystemComponent::CreateDescriptor(),
+                });
+        }
+
+        /**
+         * Add required SystemComponents to the SystemEntity.
+         */
+        AZ::ComponentTypeList GetRequiredSystemComponents() const override
+        {
+            return AZ::ComponentTypeList{
+                azrtti_typeid<ProteusRobotSystemComponent>(),
+            };
+        }
+    };
+}// namespace ProteusRobot

+ 38 - 0
Gems/ProteusRobot/Code/Source/Tools/ProteusRobotEditorModule.cpp

@@ -0,0 +1,38 @@
+
+#include <ProteusRobotModuleInterface.h>
+#include "ProteusRobotEditorSystemComponent.h"
+
+namespace ProteusRobot
+{
+    class ProteusRobotEditorModule
+        : public ProteusRobotModuleInterface
+    {
+    public:
+        AZ_RTTI(ProteusRobotEditorModule, "{F9558D3E-566B-4824-8634-015F21864F5E}", ProteusRobotModuleInterface);
+        AZ_CLASS_ALLOCATOR(ProteusRobotEditorModule, AZ::SystemAllocator, 0);
+
+        ProteusRobotEditorModule()
+        {
+            // 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 the SerializeContext, BehaviorContext and EditContext.
+            // This happens through the [MyComponent]::Reflect() function.
+            m_descriptors.insert(m_descriptors.end(), {
+                ProteusRobotEditorSystemComponent::CreateDescriptor(),
+            });
+        }
+
+        /**
+         * Add required SystemComponents to the SystemEntity.
+         * Non-SystemComponents should not be added here
+         */
+        AZ::ComponentTypeList GetRequiredSystemComponents() const override
+        {
+            return AZ::ComponentTypeList {
+                azrtti_typeid<ProteusRobotEditorSystemComponent>(),
+            };
+        }
+    };
+}// namespace ProteusRobot
+
+AZ_DECLARE_MODULE_CLASS(Gem_ProteusRobot, ProteusRobot::ProteusRobotEditorModule)

+ 54 - 0
Gems/ProteusRobot/Code/Source/Tools/ProteusRobotEditorSystemComponent.cpp

@@ -0,0 +1,54 @@
+
+#include <AzCore/Serialization/SerializeContext.h>
+#include "ProteusRobotEditorSystemComponent.h"
+
+namespace ProteusRobot
+{
+    void ProteusRobotEditorSystemComponent::Reflect(AZ::ReflectContext* context)
+    {
+        if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
+        {
+            serializeContext->Class<ProteusRobotEditorSystemComponent, ProteusRobotSystemComponent>()
+                ->Version(0);
+        }
+    }
+
+    ProteusRobotEditorSystemComponent::ProteusRobotEditorSystemComponent() = default;
+
+    ProteusRobotEditorSystemComponent::~ProteusRobotEditorSystemComponent() = default;
+
+    void ProteusRobotEditorSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
+    {
+        BaseSystemComponent::GetProvidedServices(provided);
+        provided.push_back(AZ_CRC_CE("ProteusRobotEditorService"));
+    }
+
+    void ProteusRobotEditorSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
+    {
+        BaseSystemComponent::GetIncompatibleServices(incompatible);
+        incompatible.push_back(AZ_CRC_CE("ProteusRobotEditorService"));
+    }
+
+    void ProteusRobotEditorSystemComponent::GetRequiredServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& required)
+    {
+        BaseSystemComponent::GetRequiredServices(required);
+    }
+
+    void ProteusRobotEditorSystemComponent::GetDependentServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& dependent)
+    {
+        BaseSystemComponent::GetDependentServices(dependent);
+    }
+
+    void ProteusRobotEditorSystemComponent::Activate()
+    {
+        ProteusRobotSystemComponent::Activate();
+        AzToolsFramework::EditorEvents::Bus::Handler::BusConnect();
+    }
+
+    void ProteusRobotEditorSystemComponent::Deactivate()
+    {
+        AzToolsFramework::EditorEvents::Bus::Handler::BusDisconnect();
+        ProteusRobotSystemComponent::Deactivate();
+    }
+
+} // namespace ProteusRobot

+ 33 - 0
Gems/ProteusRobot/Code/Source/Tools/ProteusRobotEditorSystemComponent.h

@@ -0,0 +1,33 @@
+
+#pragma once
+
+#include <AzToolsFramework/API/ToolsApplicationAPI.h>
+
+#include <Clients/ProteusRobotSystemComponent.h>
+
+namespace ProteusRobot
+{
+    /// System component for ProteusRobot editor
+    class ProteusRobotEditorSystemComponent
+        : public ProteusRobotSystemComponent
+        , protected AzToolsFramework::EditorEvents::Bus::Handler
+    {
+        using BaseSystemComponent = ProteusRobotSystemComponent;
+    public:
+        AZ_COMPONENT(ProteusRobotEditorSystemComponent, "{1AD25C4B-B8F7-4C54-BF46-3F5A9E02E90B}", BaseSystemComponent);
+        static void Reflect(AZ::ReflectContext* context);
+
+        ProteusRobotEditorSystemComponent();
+        ~ProteusRobotEditorSystemComponent();
+
+    private:
+        static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
+        static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
+        static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required);
+        static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent);
+
+        // AZ::Component
+        void Activate() override;
+        void Deactivate() override;
+    };
+} // namespace ProteusRobot

+ 4 - 0
Gems/ProteusRobot/Code/Tests/Clients/ProteusRobotTest.cpp

@@ -0,0 +1,4 @@
+
+#include <AzTest/AzTest.h>
+
+AZ_UNIT_TEST_HOOK(DEFAULT_UNIT_TEST_ENV);

+ 4 - 0
Gems/ProteusRobot/Code/Tests/Tools/ProteusRobotEditorTest.cpp

@@ -0,0 +1,4 @@
+
+#include <AzTest/AzTest.h>
+
+AZ_UNIT_TEST_HOOK(DEFAULT_UNIT_TEST_ENV);

+ 4 - 0
Gems/ProteusRobot/Code/proteusrobot_api_files.cmake

@@ -0,0 +1,4 @@
+
+set(FILES
+    Include/ProteusRobot/ProteusRobotBus.h
+)

+ 4 - 0
Gems/ProteusRobot/Code/proteusrobot_editor_api_files.cmake

@@ -0,0 +1,4 @@
+
+
+set(FILES
+)

+ 5 - 0
Gems/ProteusRobot/Code/proteusrobot_editor_private_files.cmake

@@ -0,0 +1,5 @@
+
+set(FILES
+    Source/Tools/ProteusRobotEditorSystemComponent.cpp
+    Source/Tools/ProteusRobotEditorSystemComponent.h
+)

+ 4 - 0
Gems/ProteusRobot/Code/proteusrobot_editor_shared_files.cmake

@@ -0,0 +1,4 @@
+
+set(FILES
+    Source/Tools/ProteusRobotEditorModule.cpp
+)

+ 4 - 0
Gems/ProteusRobot/Code/proteusrobot_editor_tests_files.cmake

@@ -0,0 +1,4 @@
+
+set(FILES
+    Tests/Tools/ProteusRobotEditorTest.cpp
+)

+ 6 - 0
Gems/ProteusRobot/Code/proteusrobot_private_files.cmake

@@ -0,0 +1,6 @@
+
+set(FILES
+    Source/ProteusRobotModuleInterface.h
+    Source/Clients/ProteusRobotSystemComponent.cpp
+    Source/Clients/ProteusRobotSystemComponent.h
+)

+ 4 - 0
Gems/ProteusRobot/Code/proteusrobot_shared_files.cmake

@@ -0,0 +1,4 @@
+
+set(FILES
+    Source/Clients/ProteusRobotModule.cpp
+)

+ 4 - 0
Gems/ProteusRobot/Code/proteusrobot_tests_files.cmake

@@ -0,0 +1,4 @@
+
+set(FILES
+    Tests/Clients/ProteusRobotTest.cpp
+)

+ 18 - 0
Gems/ProteusRobot/Registry/assetprocessor_settings.setreg

@@ -0,0 +1,18 @@
+{
+    "Amazon": {
+        "AssetProcessor": {
+            "Settings": {
+                "ScanFolder ProteusRobot/Assets": {
+                    "watch": "@GEMROOT:ProteusRobot@/Assets",
+                    "recursive": 1,
+                    "order": 101
+                },
+                "ScanFolder ProteusRobot/Registry": {
+                    "watch": "@GEMROOT:ProteusRobot@/Registry",
+                    "recursive": 1,
+                    "order": 102
+                }
+            }
+        }
+    }
+}

+ 28 - 0
Gems/ProteusRobot/gem.json

@@ -0,0 +1,28 @@
+{
+    "gem_name": "ProteusRobot",
+    "version": "1.0.0",
+    "display_name": "ProteusRobot",
+    "license": "License used i.e. Apache-2.0 or MIT",
+    "license_url": "Link to the license web site i.e. https://opensource.org/licenses/Apache-2.0",
+    "origin": "The name of the originator or creator",
+    "origin_url": "The website for this Gem",
+    "type": "Code",
+    "summary": "A short description of this Gem",
+    "canonical_tags": [
+        "Gem"
+    ],
+    "user_tags": [
+        "ProteusRobot"
+    ],
+    "platforms": [
+        ""
+    ],
+    "icon_path": "preview.png",
+    "requirements": "Notice of any requirements for this Gem i.e. This requires X other gem",
+    "documentation_url": "Link to any documentation of your Gem",
+    "dependencies": [],
+    "repo_uri": "",
+    "compatible_engines": [],
+    "engine_api_dependencies": [],
+    "restricted": "ProteusRobot"
+}

+ 3 - 0
Gems/ProteusRobot/preview.png

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

+ 5 - 3
Gems/ROS2/Code/Include/ROS2/Frame/ROS2FrameComponent.h

@@ -54,10 +54,11 @@ namespace ROS2
         //! @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.
+        //! Get a transform between this frame and the next frame up in hierarchy.
+        //! @return If the parent frame is found, return a Transform between this frame and the parent.
         //! Otherwise, return a global Transform.
-        const AZ::Transform& GetFrameTransform() const;
+        //! @note Parent frame is not the same as parent Transform: there could be many Transforms in between without ROS2Frame components.
+        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".
@@ -86,6 +87,7 @@ namespace ROS2
         AZStd::string m_frameName = "sensor_frame";
 
         bool m_publishTransform = true;
+        bool m_isDynamic = false;
         AZStd::unique_ptr<ROS2Transform> m_ros2Transform;
     };
 } // namespace ROS2

+ 190 - 0
Gems/ROS2/Code/Include/ROS2/Lidar/LidarRaycasterBus.h

@@ -0,0 +1,190 @@
+/*
+ * 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/Math/Transform.h>
+#include <AzCore/Math/Vector3.h>
+
+namespace ROS2
+{
+    //! Class used for creating typesafe Uuid types.
+    //! It utilizes the phantom types technique.
+    template<typename Tag>
+    class StronglyTypedUuid
+    {
+    public:
+        StronglyTypedUuid() = default;
+        explicit constexpr StronglyTypedUuid(AZ::Uuid value)
+            : m_uuid(value)
+        {
+        }
+
+        constexpr static StronglyTypedUuid CreateNull()
+        {
+            return StronglyTypedUuid(AZ::Uuid::CreateNull());
+        }
+
+        constexpr static StronglyTypedUuid CreateRandom()
+        {
+            return StronglyTypedUuid(AZ::Uuid::CreateRandom());
+        }
+
+        constexpr bool IsNull() const
+        {
+            return m_uuid.IsNull();
+        }
+
+        constexpr bool operator==(const StronglyTypedUuid& rhs) const
+        {
+            return m_uuid == rhs.m_uuid;
+        }
+
+        constexpr bool operator!=(const StronglyTypedUuid& rhs) const
+        {
+            return m_uuid != rhs.m_uuid;
+        }
+
+        constexpr bool operator<(const StronglyTypedUuid& rhs) const
+        {
+            return m_uuid < rhs.m_uuid;
+        }
+
+        constexpr bool operator>(const StronglyTypedUuid& rhs) const
+        {
+            return m_uuid > rhs.m_uuid;
+        }
+
+        constexpr bool operator<=(const StronglyTypedUuid& rhs) const
+        {
+            return m_uuid <= rhs.m_uuid;
+        }
+
+        constexpr bool operator>=(const StronglyTypedUuid& rhs) const
+        {
+            return m_uuid >= rhs.m_uuid;
+        }
+
+        constexpr size_t GetHash() const
+        {
+            return m_uuid.GetHash();
+        }
+
+    private:
+        AZ::Uuid m_uuid;
+    };
+
+    //! Unique id used by lidar raycasters.
+    using LidarId = StronglyTypedUuid<struct LidarIdTag>;
+
+    //! Interface class that allows for communication with a single Lidar instance.
+    class LidarRaycasterRequests
+    {
+    public:
+        AZ_RTTI(LidarRaycasterRequests, "{253a02c8-b6cb-493c-b16f-012ccf9db226}");
+
+        //! Configures ray orientations.
+        //! @param orientations Vector of orientations as Euler angles in radians. Each ray direction is computed by transforming a unit
+        //! vector in the positive z direction first by the y, next by the z axis. The x axis is currently not included in calculations.
+        virtual void ConfigureRayOrientations(const AZStd::vector<AZ::Vector3>& orientations) = 0;
+
+        //! Configures ray maximum travel distance.
+        //! @param range Ray range in meters.
+        virtual void ConfigureRayRange(float range) = 0;
+
+        //! Schedules a raycast that originates from the point described by the lidarTransform.
+        //! @param lidarTransform Current transform from global to lidar reference frame.
+        //! @return Results of the raycast in form of coordinates in 3D space.
+        //! The returned vector size can be anything between zero and size of directions. No hits further than distance will be reported.
+        virtual AZStd::vector<AZ::Vector3> PerformRaycast(const AZ::Transform& lidarTransform) = 0;
+
+        //! Configures ray Gaussian Noise parameters.
+        //! Each call overrides the previous configuration.
+        //! This type of noise is especially useful when trying to simulate real-life lidars, since its noise mimics
+        //! the imperfections arising due to various physical factors e.g. fluctuations in rotary motion of the lidar (angular noise) or
+        //! distance accuracy (distance noise).
+        //! For the the details about Gaussian noise, please refer to https://en.wikipedia.org/wiki/Gaussian_noise.
+        //! You can also check-out the RobotecGPULidar (integrated in the RobotecGPULidar Gem) docs concerning these types of lidar noise at
+        //! https://github.com/RobotecAI/RobotecGPULidar/blob/v11/docs/GaussianNoise.md.
+        //! @param angularNoiseStdDev Angular noise standard deviation.
+        //! @param distanceNoiseStdDevBase Base value for Distance noise standard deviation.
+        //! @param distanceNoiseStdDevRisePerMeter Value by which the distance noise standard deviation increases per meter length from
+        //! the lidar.
+        virtual void ConfigureNoiseParameters(
+            [[maybe_unused]] float angularNoiseStdDev,
+            [[maybe_unused]] float distanceNoiseStdDevBase,
+            [[maybe_unused]] float distanceNoiseStdDevRisePerMeter)
+        {
+            AZ_Assert(false, "This Lidar Implementation does not support noise!");
+        }
+
+        //! Configures Layer ignoring parameters
+        //! @param ignoreLayer Should a specified collision layer be ignored?
+        //! @param layerIndex Index of collision layer to be ignored.
+        virtual void ConfigureLayerIgnoring([[maybe_unused]] bool ignoreLayer, [[maybe_unused]] AZ::u32 layerIndex)
+        {
+            AZ_Assert(false, "This Lidar Implementation does not support collision layer configurations!");
+        }
+
+        //! Excludes entities with given EntityIds from raycasting.
+        //! @param excludedEntities List of entities marked for exclusion.
+        virtual void ExcludeEntities([[maybe_unused]] const AZStd::vector<AZ::EntityId>& excludedEntities)
+        {
+            AZ_Assert(false, "This Lidar Implementation does not support entity exclusion!");
+        }
+
+        //! Configures max range point addition.
+        //! @param includeMaxRange Should the raycaster add points at max range for rays that exceeded their range?
+        virtual void ConfigureMaxRangePointAddition([[maybe_unused]] bool addMaxRangePoints)
+        {
+            AZ_Assert(false, "This Lidar Implementation does not support Max range point addition configuration!");
+        }
+
+    protected:
+        ~LidarRaycasterRequests() = default;
+
+        static void ValidateRayRange([[maybe_unused]] float range)
+        {
+            AZ_Assert(range > 0.0f, "Provided ray range was of incorrect value: Ray range value must be greater than zero.")
+        }
+
+        static void ValidateRayOrientations([[maybe_unused]] const AZStd::vector<AZ::Vector3>& orientations)
+        {
+            AZ_Assert(!orientations.empty(), "Provided ray orientations were of incorrect value: Ray orientations must not be empty.")
+        }
+    };
+
+    class LidarRaycasterBusTraits : public AZ::EBusTraits
+    {
+    public:
+        //////////////////////////////////////////////////////////////////////////
+        // EBusTraits overrides
+        using BusIdType = LidarId;
+        static constexpr AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
+        static constexpr AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
+        //////////////////////////////////////////////////////////////////////////
+    };
+
+    using LidarRaycasterRequestBus = AZ::EBus<LidarRaycasterRequests, LidarRaycasterBusTraits>;
+} // namespace ROS2
+
+// Since we want to use the LidarId type as a Bus Id type,
+// we need to create a specialization for the hash template operator.
+namespace AZStd
+{
+    // hash specialization
+    template <>
+    struct hash<ROS2::LidarId>
+    {
+        constexpr size_t operator()(const ROS2::LidarId& id) const
+        {
+            return id.GetHash();
+        }
+    };
+}

+ 76 - 0
Gems/ROS2/Code/Include/ROS2/Lidar/LidarRegistrarBus.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 <AzCore/EBus/EBus.h>
+#include <AzCore/Interface/Interface.h>
+#include <AzCore/std/string/string.h>
+
+namespace ROS2
+{
+    //! Enum bitwise flags used to describe LidarSystem's feature support.
+    enum LidarSystemFeatures : uint16_t
+    {
+        None                = 0b0000000000000000,
+        Noise               = 0b0000000000000001,
+        CollisionLayers     = 0b0000000000000010,
+        EntityExclusion     = 0b0000000000000100,
+        MaxRangePoints      = 0b0000000000001000,
+        All                 = 0b1111111111111111
+    };
+
+    //! Structure used to hold LidarSystem's metadata.
+    struct LidarSystemMetaData
+    {
+        AZStd::string m_name;
+        AZStd::string m_description;
+        LidarSystemFeatures m_features;
+    };
+
+    //! Interface class that allows for communication with the LidarRegistrarSystemComponent.
+    class LidarRegistrarRequests
+    {
+    public:
+        AZ_RTTI(LidarRegistrarRequests, "{22030dc7-a1db-43bd-b748-0fb9ec43ce2e}");
+
+        //! Registers a new lidar system under the provided name.
+        //! To obtain the busId of a lidarSystem use the AZ_CRC macro as follows.
+        //! @code
+        //! AZ::Crc32 busId = AZ_CRC(<lidarSystemName>);
+        //! @endcode
+        //! @param name Name of the newly registered lidar system.
+        //! @param description Further information about the lidar system.
+        virtual void RegisterLidarSystem(const char* name, const char* description, const LidarSystemFeatures& features) = 0;
+
+        //! Returns A list of all registered lidar systems.
+        //! @return A vector of registered lidar systems' names.
+        virtual AZStd::vector<AZStd::string> GetRegisteredLidarSystems() const = 0;
+
+        //! Returns metadata of a registered lidar system.
+        //! If no lidar system with provided name was found returns nullptr.
+        //! @param name Name of a registered lidar system.
+        //! @return Pointer to the metadata of a lidar system with the provided name.
+        virtual const LidarSystemMetaData* GetLidarSystemMetaData(const AZStd::string& name) const = 0;
+
+    protected:
+        ~LidarRegistrarRequests() = default;
+    };
+
+    class LidarRegistrarBusTraits : public AZ::EBusTraits
+    {
+    public:
+        //////////////////////////////////////////////////////////////////////////
+        // EBusTraits overrides
+        static constexpr AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
+        static constexpr AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
+        //////////////////////////////////////////////////////////////////////////
+    };
+
+    using LidarRegistrarRequestBus = AZ::EBus<LidarRegistrarRequests, LidarRegistrarBusTraits>;
+    using LidarRegistrarInterface = AZ::Interface<LidarRegistrarRequests>;
+} // namespace ROS2

+ 43 - 0
Gems/ROS2/Code/Include/ROS2/Lidar/LidarSystemBus.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/Component/EntityId.h>
+#include <AzCore/EBus/EBus.h>
+#include <ROS2/Lidar/LidarRaycasterBus.h>
+
+namespace ROS2
+{
+    //! Interface class that allows for communication with a given Lidar System (implementation).
+    class LidarSystemRequests
+    {
+    public:
+        AZ_RTTI(LidarSystemRequests, "{007871d1-2783-4382-977b-558f436c54a5}");
+
+        //! Creates a new Lidar.
+        //! @param lidarEntityId EntityId holding the ROS2LidarSensorComponent.
+        //! @return A unique Id of the newly created Lidar.
+        virtual LidarId CreateLidar(AZ::EntityId lidarEntityId) = 0;
+
+    protected:
+        ~LidarSystemRequests() = default;
+    };
+
+    class LidarSystemBusTraits : public AZ::EBusTraits
+    {
+    public:
+        //////////////////////////////////////////////////////////////////////////
+        // EBusTraits overrides
+        using BusIdType = AZ::Crc32;
+        static constexpr AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
+        static constexpr AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
+        //////////////////////////////////////////////////////////////////////////
+    };
+
+    using LidarSystemRequestBus = AZ::EBus<LidarSystemRequests, LidarSystemBusTraits>;
+} // namespace ROS2

+ 2 - 2
Gems/ROS2/Code/Include/ROS2/Manipulator/MotorizedJointComponent.h

@@ -12,7 +12,7 @@
 #include <AzCore/Component/TickBus.h>
 #include <AzCore/Math/Transform.h>
 #include <AzCore/Math/Vector2.h>
-#include <ROS2/VehicleDynamics/DriveModels/PidConfiguration.h>
+#include <ROS2/Utilities/Controllers/PidConfiguration.h>
 
 namespace ROS2
 {
@@ -67,7 +67,7 @@ namespace ROS2
         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.
+        Controllers::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.

+ 2 - 2
Gems/ROS2/Code/Include/ROS2/VehicleDynamics/DriveModels/PidConfiguration.h → Gems/ROS2/Code/Include/ROS2/Utilities/Controllers/PidConfiguration.h

@@ -10,7 +10,7 @@
 #include <AzCore/Serialization/SerializeContext.h>
 #include <control_toolbox/pid.hpp>
 
-namespace ROS2::VehicleDynamics
+namespace ROS2::Controllers
 {
     //! A wrapper for ROS 2 control_toolbox pid controller.
     //! @see <a href="https://github.com/ros-controls/control_toolbox">control_toolbox</a>.
@@ -40,4 +40,4 @@ namespace ROS2::VehicleDynamics
 
         control_toolbox::Pid m_pid; //!< PID implementation object from control_toolbox (of ros2_control).
     };
-} // namespace ROS2::VehicleDynamics
+} // namespace ROS2::Controllers

+ 42 - 6
Gems/ROS2/Code/Source/Frame/ROS2FrameComponent.cpp

@@ -7,6 +7,7 @@
  */
 
 #include <AzCore/Component/Entity.h>
+#include <AzCore/Component/EntityUtils.h>
 #include <AzCore/Serialization/EditContext.h>
 #include <AzCore/Serialization/EditContextConstants.inl>
 #include <AzCore/Serialization/SerializeContext.h>
@@ -14,6 +15,7 @@
 #include <ROS2/ROS2Bus.h>
 #include <ROS2/ROS2GemUtilities.h>
 #include <ROS2/Utilities/ROS2Names.h>
+
 namespace ROS2
 {
     namespace Internal
@@ -48,6 +50,7 @@ namespace ROS2
             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
@@ -57,6 +60,17 @@ namespace ROS2
             // Found the component!
             return component;
         }
+
+        //! Checks whether the entity has a component of the given type
+        //! @param entity pointer to entity
+        //! @param typeId type of the component
+        //! @returns true if entity has component with given type
+        static bool CheckIfEntityHasComponentOfType(const AZ::Entity* entity, const AZ::Uuid typeId)
+        {
+            auto components = AZ::EntityUtils::FindDerivedComponents(entity, typeId);
+            return !components.empty();
+        }
+
     } // namespace Internal
 
     void ROS2FrameComponent::Activate()
@@ -65,9 +79,26 @@ namespace ROS2
 
         if (m_publishTransform)
         {
+            AZ_TracePrintf("ROS2FrameComponent", "Setting up %s", GetFrameID().data());
+
+            // The frame will always be dynamic if it's a top entity.
+            if (IsTopLevel())
+            {
+                m_isDynamic = true;
+            }
+            // Otherwise it'll be dynamic when it has joints and it's not a fixed joint.
+            else
+            {
+                const bool hasJoints = Internal::CheckIfEntityHasComponentOfType(
+                    m_entity, AZ::Uuid("{B01FD1D2-1D91-438D-874A-BF5EB7E919A8}")); // Physx::JointComponent;
+                const bool hasFixedJoints = Internal::CheckIfEntityHasComponentOfType(
+                    m_entity, AZ::Uuid("{02E6C633-8F44-4CEE-AE94-DCB06DE36422}")); // Physx::FixedJointComponent
+                m_isDynamic = hasJoints && !hasFixedJoints;
+            }
+
             AZ_TracePrintf(
                 "ROS2FrameComponent",
-                "Setting up %s transfrom between parent %s and child %s to be published %s\n",
+                "Setting up %s transform between parent %s and child %s to be published %s\n",
                 IsDynamic() ? "dynamic" : "static",
                 GetParentFrameID().data(),
                 GetFrameID().data(),
@@ -114,20 +145,25 @@ namespace ROS2
 
     bool ROS2FrameComponent::IsDynamic() const
     {
-        return IsTopLevel();
+        return m_isDynamic;
     }
-
     const ROS2FrameComponent* ROS2FrameComponent::GetParentROS2FrameComponent() const
     {
         return Internal::GetFirstROS2FrameAncestor(GetEntity());
     }
 
-    const AZ::Transform& ROS2FrameComponent::GetFrameTransform() const
+    AZ::Transform ROS2FrameComponent::GetFrameTransform() const
     {
         auto* transformInterface = Internal::GetEntityTransformInterface(GetEntity());
-        if (GetParentROS2FrameComponent() != nullptr)
+        if (const auto* parentFrame = GetParentROS2FrameComponent(); parentFrame != nullptr)
         {
-            return transformInterface->GetLocalTM();
+            auto* ancestorTransformInterface = Internal::GetEntityTransformInterface(parentFrame->GetEntity());
+            AZ_Assert(ancestorTransformInterface, "No transform interface for an entity with a ROS2Frame component, which requires it!");
+
+            const auto worldFromAncestor = ancestorTransformInterface->GetWorldTM();
+            const auto worldFromThis = transformInterface->GetWorldTM();
+            const auto ancestorFromWorld = worldFromAncestor.GetInverse();
+            return ancestorFromWorld * worldFromThis;
         }
         return transformInterface->GetWorldTM();
     }

+ 86 - 30
Gems/ROS2/Code/Source/Lidar/LidarRaycaster.cpp

@@ -5,49 +5,101 @@
  * 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 <AzCore/Component/Component.h>
 #include <AzFramework/Physics/Common/PhysicsSceneQueries.h>
 #include <AzFramework/Physics/PhysicsScene.h>
 #include <AzFramework/Physics/PhysicsSystem.h>
 #include <AzFramework/Physics/Shape.h>
+#include <Lidar/LidarRaycaster.h>
+#include <Lidar/LidarTemplateUtils.h>
 
 namespace ROS2
 {
-    void LidarRaycaster::SetRaycasterScene(const AzPhysics::SceneHandle& handle)
+    static AzPhysics::SceneHandle GetPhysicsSceneFromEntityId(const AZ::EntityId& entityId)
     {
-        m_sceneHandle = handle;
+        auto* physicsSystem = AZ::Interface<AzPhysics::SystemInterface>::Get();
+        auto foundBody = physicsSystem->FindAttachedBodyHandleFromEntityId(entityId);
+        AzPhysics::SceneHandle 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");
+        return lidarPhysicsSceneHandle;
     }
 
-    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
+    LidarRaycaster::LidarRaycaster(LidarId busId, AZ::EntityId sceneEntityId)
+        : m_busId{ busId }
+        , m_sceneEntityId{ sceneEntityId }
     {
-        AZStd::vector<AZ::Vector3> results;
+        ROS2::LidarRaycasterRequestBus::Handler::BusConnect(busId);
+    }
+
+    LidarRaycaster::LidarRaycaster(LidarRaycaster&& lidarRaycaster)
+        : m_busId{ lidarRaycaster.m_busId }
+        , m_sceneEntityId{ lidarRaycaster.m_sceneEntityId }
+        , m_sceneHandle{ lidarRaycaster.m_sceneHandle }
+        , m_range{ lidarRaycaster.m_range }
+        , m_addMaxRangePoints{ lidarRaycaster.m_addMaxRangePoints }
+        , m_rayRotations{ AZStd::move(lidarRaycaster.m_rayRotations) }
+        , m_ignoreLayer{ lidarRaycaster.m_ignoreLayer }
+        , m_ignoredLayerIndex{ lidarRaycaster.m_ignoredLayerIndex }
+    {
+        lidarRaycaster.BusDisconnect();
+        lidarRaycaster.m_busId = LidarId::CreateNull();
+
+        ROS2::LidarRaycasterRequestBus::Handler::BusConnect(m_busId);
+    }
+
+    LidarRaycaster::~LidarRaycaster()
+    {
+        ROS2::LidarRaycasterRequestBus::Handler::BusDisconnect();
+    }
+
+    void LidarRaycaster::ConfigureRayOrientations(const AZStd::vector<AZ::Vector3>& orientations)
+    {
+        ValidateRayOrientations(orientations);
+        m_rayRotations = orientations;
+    }
+
+    void LidarRaycaster::ConfigureRayRange(float range)
+    {
+        ValidateRayRange(range);
+        m_range = range;
+    }
+
+    AZStd::vector<AZ::Vector3> LidarRaycaster::PerformRaycast(const AZ::Transform& lidarTransform)
+    {
+        AZ_Assert(!m_rayRotations.empty(), "Ray poses are not configured. Unable to Perform a raycast.");
+        AZ_Assert(m_range > 0.0f, "Ray range is not configured. Unable to Perform a raycast.");
+
         if (m_sceneHandle == AzPhysics::InvalidSceneHandle)
         {
-            AZ_Warning("LidarRaycaster", false, "No valid scene handle");
-            return results;
+            m_sceneHandle = GetPhysicsSceneFromEntityId(m_sceneEntityId);
         }
 
+        const AZStd::vector<AZ::Vector3> rayDirections =
+            LidarTemplateUtils::RotationsToDirections(m_rayRotations, lidarTransform.GetEulerRadians());
+
+        const AZ::Vector3 lidarPosition = lidarTransform.GetTranslation();
+
+        AZStd::vector<AZ::Vector3> results;
         AzPhysics::SceneQueryRequests requests;
-        requests.reserve(directions.size());
-        results.reserve(directions.size());
-        for (const AZ::Vector3& direction : directions)
+        requests.reserve(rayDirections.size());
+        results.reserve(rayDirections.size());
+        for (const AZ::Vector3& direction : rayDirections)
         {
             AZStd::shared_ptr<AzPhysics::RayCastRequest> request = AZStd::make_shared<AzPhysics::RayCastRequest>();
-            request->m_start = start;
+            request->m_start = lidarPosition;
             request->m_direction = direction;
-            request->m_distance = distance;
+            request->m_distance = m_range;
             request->m_reportMultipleHits = false;
-            request->m_filterCallback =
-                [ignoreLayer, ignoredLayerIndex](const AzPhysics::SimulatedBody* simBody, const Physics::Shape* shape)
+            request->m_filterCallback = [ignoredLayerIndex = this->m_ignoredLayerIndex, ignoreLayer = this->m_ignoreLayer](
+                                            const AzPhysics::SimulatedBody* simBody, const Physics::Shape* shape)
             {
                 if (ignoreLayer && (shape->GetCollisionLayer().GetIndex() == ignoredLayerIndex))
                 {
@@ -63,26 +115,30 @@ namespace ROS2
 
         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");
+        AZ_Assert(requestResults.size() == rayDirections.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
+                results.push_back(requestResult.m_hits[0].m_position);
             }
-            else if (m_addPointsMaxRange)
+            else if (m_addMaxRangePoints)
             {
-                results.push_back(globalToLidarTM.TransformPoint(start + directions[i] * distance));
+                const AZ::Vector3 maxPoint = lidarTransform.TransformPoint(rayDirections[i] * m_range);
+                results.push_back(maxPoint);
             }
         }
         return results;
     }
 
-    void LidarRaycaster::SetAddPointsMaxRange(bool addPointsMaxRange)
+    void LidarRaycaster::ConfigureLayerIgnoring(bool ignoreLayer, AZ::u32 layerIndex)
     {
-        m_addPointsMaxRange = addPointsMaxRange;
+        m_ignoreLayer = ignoreLayer;
+        m_ignoredLayerIndex = layerIndex;
+    }
+    void LidarRaycaster::ConfigureMaxRangePointAddition(bool addMaxRangePoints)
+    {
+        m_addMaxRangePoints = addMaxRangePoints;
     }
-
 } // namespace ROS2

+ 24 - 31
Gems/ROS2/Code/Source/Lidar/LidarRaycaster.h

@@ -12,44 +12,37 @@
 #include <AzCore/Math/Vector3.h>
 #include <AzCore/std/containers/vector.h>
 #include <AzFramework/Physics/PhysicsScene.h>
+#include <ROS2/Lidar/LidarRaycasterBus.h>
 
 namespace ROS2
 {
-    //! A simple implementation of Lidar operation in terms of raycasting.
-    class LidarRaycaster
+    class LidarRaycaster : protected LidarRaycasterRequestBus::Handler
     {
     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);
+        LidarRaycaster(LidarId busId, AZ::EntityId sceneEntityId);
+        LidarRaycaster(LidarRaycaster&& lidarSystem);
+        LidarRaycaster(const LidarRaycaster& lidarSystem) = default;
+        ~LidarRaycaster() override;
 
-        //! 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);
+    protected:
+        // LidarRaycasterRequestBus overrides
+        void ConfigureRayOrientations(const AZStd::vector<AZ::Vector3>& orientations) override;
+        void ConfigureRayRange(float range) override;
+        AZStd::vector<AZ::Vector3> PerformRaycast(const AZ::Transform& lidarTransform) override;
+        void ConfigureLayerIgnoring(bool ignoreLayer, AZ::u32 layerIndex) override;
+        void ConfigureMaxRangePointAddition(bool addMaxRangePoints) override;
 
     private:
-        AzPhysics::SceneHandle m_sceneHandle;
-        bool m_addPointsMaxRange{ false };
+        LidarId m_busId;
+        //! EntityId that is used to acquire the physics scene handle.
+        AZ::EntityId m_sceneEntityId;
+        AzPhysics::SceneHandle m_sceneHandle{ AzPhysics::InvalidSceneHandle };
+
+        float m_range{ 1.0f };
+        bool m_addMaxRangePoints{ false };
+        AZStd::vector<AZ::Vector3> m_rayRotations{ { AZ::Vector3::CreateZero() } };
+
+        bool m_ignoreLayer{ false };
+        AZ::u32 m_ignoredLayerIndex{ 0 };
     };
 } // namespace ROS2

+ 53 - 0
Gems/ROS2/Code/Source/Lidar/LidarRegistrarEditorSystemComponent.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 <AzCore/Serialization/SerializeContext.h>
+#include <Lidar/LidarRegistrarEditorSystemComponent.h>
+
+namespace ROS2
+{
+    void LidarRegistrarEditorSystemComponent::Reflect(AZ::ReflectContext* context)
+    {
+        if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
+        {
+            serializeContext->Class<LidarRegistrarEditorSystemComponent, LidarRegistrarSystemComponent>()->Version(0);
+        }
+    }
+
+    void LidarRegistrarEditorSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
+    {
+        LidarRegistrarSystemComponent::GetProvidedServices(provided);
+        provided.push_back(AZ_CRC_CE("LidarRegistrarEditorService"));
+    }
+
+    void LidarRegistrarEditorSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
+    {
+        LidarRegistrarSystemComponent::GetIncompatibleServices(incompatible);
+        incompatible.push_back(AZ_CRC_CE("LidarRegistrarEditorService"));
+    }
+
+    void LidarRegistrarEditorSystemComponent::GetRequiredServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& required)
+    {
+        LidarRegistrarSystemComponent::GetRequiredServices(required);
+    }
+
+    void LidarRegistrarEditorSystemComponent::GetDependentServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& dependent)
+    {
+        LidarRegistrarSystemComponent::GetDependentServices(dependent);
+    }
+
+    void LidarRegistrarEditorSystemComponent::Activate()
+    {
+        LidarRegistrarSystemComponent::Activate();
+    }
+
+    void LidarRegistrarEditorSystemComponent::Deactivate()
+    {
+        LidarRegistrarSystemComponent::Deactivate();
+    }
+} // namespace ROS2

+ 34 - 0
Gems/ROS2/Code/Source/Lidar/LidarRegistrarEditorSystemComponent.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 <AzToolsFramework/Entity/EditorEntityContextBus.h>
+#include <Lidar/LidarRegistrarSystemComponent.h>
+
+namespace ROS2
+{
+    class LidarRegistrarEditorSystemComponent : public LidarRegistrarSystemComponent
+    {
+    public:
+        AZ_COMPONENT(LidarRegistrarEditorSystemComponent, "{7f11b599-5ace-4498-a9a4-ad280c92bacc}", LidarRegistrarSystemComponent);
+        static void Reflect(AZ::ReflectContext* context);
+
+        static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
+        static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
+        static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required);
+        static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent);
+
+        LidarRegistrarEditorSystemComponent() = default;
+        ~LidarRegistrarEditorSystemComponent() = default;
+
+    private:
+        void Activate() override;
+        void Deactivate() override;
+    };
+} // namespace ROS2

+ 112 - 0
Gems/ROS2/Code/Source/Lidar/LidarRegistrarSystemComponent.cpp

@@ -0,0 +1,112 @@
+/*
+ * 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 <Lidar/LidarRegistrarSystemComponent.h>
+#include <AzCore/Serialization/EditContext.h>
+#include <AzCore/Serialization/SerializeContext.h>
+
+namespace ROS2
+{
+    LidarRegistrarSystemComponent::LidarRegistrarSystemComponent()
+    {
+        if (!LidarRegistrarInterface::Get())
+        {
+            LidarRegistrarInterface::Register(this);
+        }
+    }
+
+    LidarRegistrarSystemComponent::~LidarRegistrarSystemComponent()
+    {
+        if (LidarRegistrarInterface::Get() == this)
+        {
+            LidarRegistrarInterface::Unregister(this);
+        }
+    }
+
+    void LidarRegistrarSystemComponent::Init()
+    {
+    }
+
+    void LidarRegistrarSystemComponent::Activate()
+    {
+        m_physxLidarSystem.Activate();
+    }
+
+    void LidarRegistrarSystemComponent::Deactivate()
+    {
+        m_physxLidarSystem.Deactivate();
+    }
+
+    void LidarRegistrarSystemComponent::Reflect(AZ::ReflectContext* context)
+    {
+        if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
+        {
+            serializeContext->Class<LidarRegistrarSystemComponent, AZ::Component>()->Version(0);
+
+            if (AZ::EditContext* editContext = serializeContext->GetEditContext())
+            {
+                editContext
+                    ->Class<LidarRegistrarSystemComponent>(
+                        "Lidar Registrar", "Manages the LidarSystem registration and stores their metadata.")
+                    ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
+                    ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("System"))
+                    ->Attribute(AZ::Edit::Attributes::Category, "ROS2")
+                    ->Attribute(AZ::Edit::Attributes::AutoExpand, true);
+            }
+        }
+    }
+
+    void LidarRegistrarSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
+    {
+        provided.push_back(AZ_CRC_CE("LidarRegistrarService"));
+    }
+
+    void LidarRegistrarSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
+    {
+        incompatible.push_back(AZ_CRC_CE("LidarRegistrarService"));
+    }
+
+    void LidarRegistrarSystemComponent::GetRequiredServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& required)
+    {
+        required.push_back(AZ_CRC_CE("ROS2Service"));
+    }
+
+    void LidarRegistrarSystemComponent::GetDependentServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& dependent)
+    {
+    }
+
+    void LidarRegistrarSystemComponent::RegisterLidarSystem(const char* name, const char* description, const LidarSystemFeatures& features)
+    {
+        AZ_Assert(
+            !m_registeredLidarSystems.contains(AZ_CRC(name)),
+            "A lidar system with the provided name already exists. Please choose a different name.");
+        m_registeredLidarSystems.emplace(AZ_CRC(name), LidarSystemMetaData{ name, description, features });
+    }
+
+    AZStd::vector<AZStd::string> LidarRegistrarSystemComponent::GetRegisteredLidarSystems() const
+    {
+        AZStd::vector<AZStd::string> lidarSystemList;
+        lidarSystemList.reserve(m_registeredLidarSystems.size());
+        for (const auto& lidarSystem : m_registeredLidarSystems)
+        {
+            lidarSystemList.push_back(lidarSystem.second.m_name);
+        }
+
+        return lidarSystemList;
+    }
+
+    const LidarSystemMetaData* LidarRegistrarSystemComponent::GetLidarSystemMetaData(const AZStd::string& name) const
+    {
+        if (auto lidarSystem = m_registeredLidarSystems.find(AZ_CRC(name)); lidarSystem != m_registeredLidarSystems.end())
+        {
+            return &lidarSystem->second;
+        }
+
+        return nullptr;
+    }
+} // namespace ROS2

+ 48 - 0
Gems/ROS2/Code/Source/Lidar/LidarRegistrarSystemComponent.h

@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) Contributors to the Open 3D Engine Project.
+ * For complete copyright and license terms please see the LICENSE at the root of this distribution.
+ *
+ * SPDX-License-Identifier: Apache-2.0 OR MIT
+ *
+ */
+
+#pragma once
+
+#include <AzCore/Component/Component.h>
+#include <Lidar/LidarSystem.h>
+#include <ROS2/Lidar/LidarRegistrarBus.h>
+
+namespace ROS2
+{
+    //! A Component that manages LidarSystems' registration and storage of their metadata.
+    class LidarRegistrarSystemComponent
+        : public AZ::Component
+        , protected LidarRegistrarRequestBus::Handler
+    {
+    public:
+        AZ_COMPONENT(LidarRegistrarSystemComponent, "{78cba3f1-db2c-46de-9c3d-c40dd72f2f1e}");
+        static void Reflect(AZ::ReflectContext* context);
+
+        static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
+        static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
+        static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required);
+        static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent);
+
+        LidarRegistrarSystemComponent();
+        virtual ~LidarRegistrarSystemComponent();
+
+    protected:
+        void Init() override;
+        void Activate() override;
+        void Deactivate() override;
+
+        // LidarRegistrarRequestBus overrides
+        void RegisterLidarSystem(const char* name, const char* description, const LidarSystemFeatures& features) override;
+        AZStd::vector<AZStd::string> GetRegisteredLidarSystems() const override;
+        const LidarSystemMetaData* GetLidarSystemMetaData(const AZStd::string& name) const override;
+
+    private:
+        LidarSystem m_physxLidarSystem;
+        AZStd::unordered_map<AZ::Crc32, LidarSystemMetaData> m_registeredLidarSystems;
+    };
+} // namespace ROS2

+ 56 - 0
Gems/ROS2/Code/Source/Lidar/LidarSystem.cpp

@@ -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
+ *
+ */
+#include <Lidar/LidarSystem.h>
+#include <ROS2/Lidar/LidarRegistrarBus.h>
+
+namespace ROS2
+{
+    LidarSystem::LidarSystem(LidarSystem&& lidarSystem)
+        : m_lidars{ AZStd::move(lidarSystem.m_lidars) }
+    {
+        lidarSystem.BusDisconnect();
+    }
+
+    LidarSystem& LidarSystem::operator=(LidarSystem&& lidarSystem)
+    {
+        lidarSystem.BusDisconnect();
+        return lidarSystem;
+    }
+
+    void LidarSystem::Activate()
+    {
+        static constexpr const char* Description = "Collider-based lidar implementation that uses the PhysX engine's raycasting.";
+        static constexpr auto SupportedFeatures =
+            aznumeric_cast<LidarSystemFeatures>(LidarSystemFeatures::CollisionLayers | LidarSystemFeatures::MaxRangePoints);
+
+        LidarSystemRequestBus::Handler::BusConnect(AZ_CRC(SystemName));
+
+        auto* lidarRegistrarInterface = ROS2::LidarRegistrarInterface::Get();
+        AZ_Assert(lidarRegistrarInterface != nullptr, "The Lidar Registrar interface was inaccessible.");
+
+        if (!lidarRegistrarInterface->GetLidarSystemMetaData(SystemName))
+        {
+            lidarRegistrarInterface->RegisterLidarSystem(SystemName, Description, SupportedFeatures);
+        }
+    }
+
+    void LidarSystem::Deactivate()
+    {
+        if (LidarSystemRequestBus::Handler::BusIsConnectedId(AZ_CRC(SystemName)))
+        {
+            LidarSystemRequestBus::Handler::BusDisconnect();
+        }
+    }
+
+    LidarId LidarSystem::CreateLidar(AZ::EntityId lidarEntityId)
+    {
+        LidarId lidarId = LidarId::CreateRandom();
+        m_lidars.emplace_back(lidarId, lidarEntityId);
+        return lidarId;
+    }
+} // namespace ROS2

+ 38 - 0
Gems/ROS2/Code/Source/Lidar/LidarSystem.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 <Lidar/LidarRaycaster.h>
+#include <ROS2/Lidar/LidarSystemBus.h>
+
+namespace ROS2
+{
+    class LidarSystem : protected ROS2::LidarSystemRequestBus::Handler
+    {
+    public:
+        LidarSystem() = default;
+        LidarSystem(LidarSystem&& lidarSystem);
+        LidarSystem& operator=(LidarSystem&& lidarSystem);
+        LidarSystem(const LidarSystem& lidarSystem) = default;
+        LidarSystem& operator=(const LidarSystem& lidarSystem) = default;
+
+
+        ~LidarSystem() = default;
+
+        void Activate();
+        void Deactivate();
+
+    private:
+        static constexpr const char* SystemName = "Scene Queries";
+
+        // LidarSystemRequestBus overrides
+        LidarId CreateLidar(AZ::EntityId lidarEntityId) override;
+
+        AZStd::vector<LidarRaycaster> m_lidars;
+    };
+} // namespace ROS2

+ 44 - 7
Gems/ROS2/Code/Source/Lidar/LidarTemplate.cpp

@@ -6,14 +6,50 @@
  *
  */
 
-#include "LidarTemplate.h"
+#include <Lidar/LidarTemplate.h>
 #include <AzCore/Serialization/EditContext.h>
-#include <AzCore/Serialization/EditContextConstants.inl>
 
 namespace ROS2
 {
+    void LidarTemplate::NoiseParameters::Reflect(AZ::ReflectContext* context)
+    {
+        if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
+        {
+            serializeContext->Class<NoiseParameters>()
+                ->Version(1)
+                ->Field("Angular noise standard deviation", &NoiseParameters::m_angularNoiseStdDev)
+                ->Field("Distance noise standard deviation base", &NoiseParameters::m_distanceNoiseStdDevBase)
+                ->Field("Distance noise standard deviation slope", &NoiseParameters::m_distanceNoiseStdDevRisePerMeter);
+
+            if (AZ::EditContext* editContext = serializeContext->GetEditContext())
+            {
+                editContext->Class<NoiseParameters>("Noise Parameters", "Noise Parameters")
+                    ->DataElement(
+                        AZ::Edit::UIHandlers::Default,
+                        &NoiseParameters::m_angularNoiseStdDev,
+                        "Angular noise std dev [Deg]",
+                        "Angular noise standard deviation")
+                    ->Attribute(AZ::Edit::Attributes::Min, 0.0f)
+                    ->Attribute(AZ::Edit::Attributes::Max, 180.0f)
+                    ->DataElement(
+                        AZ::Edit::UIHandlers::Default,
+                        &NoiseParameters::m_distanceNoiseStdDevBase,
+                        "Distance noise std dev base [m]",
+                        "Distance noise standard deviation base")
+                    ->Attribute(AZ::Edit::Attributes::Min, 0.0f)
+                    ->DataElement(
+                        AZ::Edit::UIHandlers::Default,
+                        &NoiseParameters::m_distanceNoiseStdDevRisePerMeter,
+                        "Distance noise std dev slope [m]",
+                        "Distance noise standard deviation slope")
+                    ->Attribute(AZ::Edit::Attributes::Min, 0.0f);
+            }
+        }
+    }
+
     void LidarTemplate::Reflect(AZ::ReflectContext* context)
     {
+        NoiseParameters::Reflect(context);
         if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
         {
             serializeContext->Class<LidarTemplate>()
@@ -26,7 +62,7 @@ namespace ROS2
                 ->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);
+                ->Field("Noise Parameters", &LidarTemplate::m_noiseParameters);
 
             if (AZ::EditContext* ec = serializeContext->GetEditContext())
             {
@@ -52,13 +88,14 @@ namespace ROS2
                     ->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::Min, 0.001f)
                     ->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");
+                        &LidarTemplate::m_noiseParameters,
+                        "Noise parameters",
+                        "Parameters for Noise Configuration")
+                    ->Attribute(AZ::Edit::Attributes::Visibility, &LidarTemplate::m_showNoiseConfig);
             }
         }
     }

+ 23 - 3
Gems/ROS2/Code/Source/Lidar/LidarTemplate.h

@@ -25,7 +25,26 @@ namespace ROS2
 
         enum class LidarModel
         {
-            Generic3DLidar
+            Custom3DLidar,
+            Ouster_OS0_64,
+            Ouster_OS1_64,
+            Ouster_OS2_64,
+            Velodyne_Puck,
+            Velodyne_HDL_32E,
+        };
+
+        struct NoiseParameters
+        {
+        public:
+            AZ_TYPE_INFO(LidarNoiseParameters, "{58c007ad-320f-49df-bc20-6419159ee176}");
+            static void Reflect(AZ::ReflectContext* context);
+
+            //! Angular noise standard deviation, in degrees
+            float m_angularNoiseStdDev = 0.0f;
+            //! Distance noise standard deviation base value, in meters
+            float m_distanceNoiseStdDevBase = 0.0f;
+            //! Distance noise standard deviation increase per meter distance travelled, in meters
+            float m_distanceNoiseStdDevRisePerMeter = 0.0f;
         };
 
         LidarModel m_model;
@@ -45,7 +64,8 @@ namespace ROS2
         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;
+
+        NoiseParameters m_noiseParameters;
+        bool m_showNoiseConfig = false;
     };
 } // namespace ROS2

+ 144 - 32
Gems/ROS2/Code/Source/Lidar/LidarTemplateUtils.cpp

@@ -6,33 +6,135 @@
  *
  */
 
-#include "LidarTemplateUtils.h"
-#include <AzCore/Math/MathUtils.h>
-#include <AzCore/Math/Matrix4x4.h>
+#include <Lidar/LidarTemplateUtils.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;
-        }
+        static const std::unordered_map<LidarTemplate::LidarModel, LidarTemplate> templates = {
+            {
+                LidarTemplate::LidarModel::Custom3DLidar,
+                {
+                    /*.m_model = */ LidarTemplate::LidarModel::Custom3DLidar,
+                    /*.m_name = */ "CustomLidar",
+                    /*.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,
+                    /*.m_noiseParameters = */
+                    {
+                        /*.m_angularNoiseStdDev = */ 0.0f,
+                        /*.m_distanceNoiseStdDevBase = */ 0.01f,
+                        /*.m_distanceNoiseStdDevRisePerMeter = */ 0.001f,
+                    },
+                },
+            },
+            {
+                LidarTemplate::LidarModel::Ouster_OS0_64,
+                {
+                    /*.m_model = */ LidarTemplate::LidarModel::Ouster_OS0_64,
+                    /*.m_name = */ "Ouster OS0-64",
+                    /*.m_minHAngle = */ -180.0f,
+                    /*.m_maxHAngle = */ 180.0f,
+                    /*.m_minVAngle = */ -45.0f,
+                    /*.m_maxVAngle = */ 45.0f,
+                    /*.m_layers = */ 64,
+                    /*.m_numberOfIncrements = */ 2048,
+                    /*.m_maxRange = */ 47.5f,
+                    /*.m_noiseParameters = */
+                    {
+                        /*.m_angularNoiseStdDev = */ 0.0f,
+                        /*.m_distanceNoiseStdDevBase = */ 0.0f,
+                        /*.m_distanceNoiseStdDevRisePerMeter = */ 0.002f,
+                    },
+                },
+            },
+            {
+                LidarTemplate::LidarModel::Ouster_OS1_64,
+                {
+                    /*.m_model = */ LidarTemplate::LidarModel::Ouster_OS1_64,
+                    /*.m_name = */ "Ouster OS1-64",
+                    /*.m_minHAngle = */ -180.0f,
+                    /*.m_maxHAngle = */ 180.0f,
+                    /*.m_minVAngle = */ 22.5f,
+                    /*.m_maxVAngle = */ -22.5f,
+                    /*.m_layers = */ 64,
+                    /*.m_numberOfIncrements = */ 2048,
+                    /*.m_maxRange = */ 120.0f,
+                    /*.m_noiseParameters = */
+                    {
+                        /*.m_angularNoiseStdDev = */ 0.0f,
+                        /*.m_distanceNoiseStdDevBase = */ 0.002f,
+                        /*.m_distanceNoiseStdDevRisePerMeter = */ 0.0008f,
+                    },
+                },
+            },
+            {
+                LidarTemplate::LidarModel::Ouster_OS2_64,
+                {
+                    /*.m_model = */ LidarTemplate::LidarModel::Ouster_OS1_64,
+                    /*.m_name = */ "Ouster OS1-64",
+                    /*.m_minHAngle = */ -180.0f,
+                    /*.m_maxHAngle = */ 180.0f,
+                    /*.m_minVAngle = */ 11.25f,
+                    /*.m_maxVAngle = */ -11.25f,
+                    /*.m_layers = */ 64,
+                    /*.m_numberOfIncrements = */ 2048,
+                    /*.m_maxRange = */ 225.0f,
+                    /*.m_noiseParameters = */
+                    {
+                        /*.m_angularNoiseStdDev = */ 0.0f,
+                        /*.m_distanceNoiseStdDevBase = */ 0.006f,
+                        /*.m_distanceNoiseStdDevRisePerMeter = */ 0.001f,
+                    },
+                },
+            },
+            {
+                LidarTemplate::LidarModel::Velodyne_Puck,
+                {
+                    /*.m_model = */ LidarTemplate::LidarModel::Velodyne_Puck,
+                    /*.m_name = */ "Velodyne Puck (VLP-16)",
+                    /*.m_minHAngle = */ -180.0f,
+                    /*.m_maxHAngle = */ 180.0f,
+                    /*.m_minVAngle = */ 15.0f,
+                    /*.m_maxVAngle = */ -15.0f,
+                    /*.m_layers = */ 16,
+                    /*.m_numberOfIncrements = */ 1800, // For 0.2 angular resolution
+                    /*.m_maxRange = */ 100.0f,
+                    /*.m_noiseParameters = */
+                    {
+                        /*.m_angularNoiseStdDev = */ 0.0f,
+                        /*.m_distanceNoiseStdDevBase = */ 0.03f,
+                        /*.m_distanceNoiseStdDevRisePerMeter = */ 0.001f,
+                    },
+                },
+            },
+            {
+                LidarTemplate::LidarModel::Velodyne_HDL_32E,
+                {
+                    /*.m_model = */ LidarTemplate::LidarModel::Velodyne_HDL_32E,
+                    /*.m_name = */ "Velodyne HDL-32E",
+                    /*.m_minHAngle = */ -180.0f,
+                    /*.m_maxHAngle = */ 180.0f,
+                    /*.m_minVAngle = */ 10.67f,
+                    /*.m_maxVAngle = */ -30.67f,
+                    /*.m_layers = */ 32,
+                    /*.m_numberOfIncrements = */ 1800, // For 0.2 angular resolution
+                    /*.m_maxRange = */ 100.0f,
+                    /*.m_noiseParameters = */
+                    {
+                        /*.m_angularNoiseStdDev = */ 0.0f,
+                        /*.m_distanceNoiseStdDevBase = */ 0.02f,
+                        /*.m_distanceNoiseStdDevRisePerMeter = */ 0.001f,
+                    },
+                },
+            },
+        };
 
         auto it = templates.find(model);
         if (it == templates.end())
@@ -48,8 +150,7 @@ namespace ROS2
         return t.m_layers * t.m_numberOfIncrements;
     }
 
-    AZStd::vector<AZ::Vector3> LidarTemplateUtils::PopulateRayDirections(
-        const LidarTemplate& lidarTemplate, const AZ::Vector3& rootRotation)
+    AZStd::vector<AZ::Vector3> LidarTemplateUtils::PopulateRayRotations(const LidarTemplate& lidarTemplate)
     {
         const float minVertAngle = AZ::DegToRad(lidarTemplate.m_minVAngle);
         const float maxVertAngle = AZ::DegToRad(lidarTemplate.m_maxVAngle);
@@ -59,23 +160,34 @@ namespace ROS2
         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;
-
+        AZStd::vector<AZ::Vector3> rotations;
         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);
+                const float pitch = minVertAngle + layer * verticalStep;
+                const float yaw = minHorAngle + incr * horizontalStep;
 
-                directions.push_back(AZ::Vector3(x, y, z));
+                rotations.emplace_back(AZ::Vector3(0.0f, pitch, yaw));
             }
         }
 
+        return rotations;
+    }
+
+    AZStd::vector<AZ::Vector3> LidarTemplateUtils::RotationsToDirections(
+        const AZStd::vector<AZ::Vector3>& rotations, const AZ::Vector3& rootRotation)
+    {
+        AZStd::vector<AZ::Vector3> directions;
+        directions.reserve(rotations.size());
+        for (const auto& angle : rotations)
+        {
+            const auto rotation = AZ::Quaternion::CreateFromEulerRadiansZYX(
+                { 0.0f, -(angle.GetY() + rootRotation.GetY()), angle.GetZ() + rootRotation.GetZ() });
+
+            directions.emplace_back(rotation.TransformVector(AZ::Vector3::CreateAxisX()));
+        }
+
         return directions;
     }
 } // namespace ROS2

+ 10 - 5
Gems/ROS2/Code/Source/Lidar/LidarTemplateUtils.h

@@ -7,9 +7,9 @@
  */
 #pragma once
 
-#include "LidarTemplate.h"
 #include <AzCore/Math/Vector3.h>
 #include <AzCore/std/containers/vector.h>
+#include <Lidar/LidarTemplate.h>
 
 namespace ROS2
 {
@@ -26,10 +26,15 @@ namespace ROS2
         //! @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.
+        //! Compute ray Rotation angles based on lidar model.
+        //! @param lidarTemplate Lidar model to use. Note that different models will produce different number of rays.
+        //! @return Ray rotations angles as Euler angles in radians.
+        AZStd::vector<AZ::Vector3> PopulateRayRotations(const LidarTemplate& lidarTemplate);
+
+        //! Compute ray directions from rotations.
+        //! @param rotations Rotations as Euler angles in radians to compute directions from.
         //! @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);
+        //! @return Ray directions constructed by transforming an X axis unit vector by the provided rotations.
+        AZStd::vector<AZ::Vector3> RotationsToDirections(const AZStd::vector<AZ::Vector3>& rotations, const AZ::Vector3& rootRotation);
     }; // namespace LidarTemplateUtils
 } // namespace ROS2

+ 150 - 43
Gems/ROS2/Code/Source/Lidar/ROS2LidarSensorComponent.cpp

@@ -7,13 +7,8 @@
  */
 
 #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>
@@ -34,9 +29,12 @@ namespace ROS2
             serialize->Class<ROS2LidarSensorComponent, ROS2SensorComponent>()
                 ->Version(1)
                 ->Field("lidarModel", &ROS2LidarSensorComponent::m_lidarModel)
+                ->Field("lidarImplementation", &ROS2LidarSensorComponent::m_lidarSystem)
                 ->Field("LidarParameters", &ROS2LidarSensorComponent::m_lidarParameters)
                 ->Field("IgnoreLayer", &ROS2LidarSensorComponent::m_ignoreLayer)
-                ->Field("IgnoredLayerIndex", &ROS2LidarSensorComponent::m_ignoredLayerIndex);
+                ->Field("IgnoredLayerIndex", &ROS2LidarSensorComponent::m_ignoredLayerIndex)
+                ->Field("ExcludedEntities", &ROS2LidarSensorComponent::m_excludedEntities)
+                ->Field("PointsAtMax", &ROS2LidarSensorComponent::m_addPointsAtMax);
 
             if (AZ::EditContext* ec = serialize->GetEditContext())
             {
@@ -46,30 +44,100 @@ namespace 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")
+                    ->EnumAttribute(LidarTemplate::LidarModel::Custom3DLidar, "Custom Lidar")
+                    ->EnumAttribute(LidarTemplate::LidarModel::Ouster_OS0_64, "Ouster OS0-64")
+                    ->EnumAttribute(LidarTemplate::LidarModel::Ouster_OS1_64, "Ouster OS1-64")
+                    ->EnumAttribute(LidarTemplate::LidarModel::Ouster_OS2_64, "Ouster OS2-64")
+                    ->EnumAttribute(LidarTemplate::LidarModel::Velodyne_Puck, "Velodyne Puck (VLP-16)")
+                    ->EnumAttribute(LidarTemplate::LidarModel::Velodyne_HDL_32E, "Velodyne HDL-32E")
+                    ->DataElement(
+                        AZ::Edit::UIHandlers::ComboBox,
+                        &ROS2LidarSensorComponent::m_lidarSystem,
+                        "Lidar Implementation",
+                        "Select a lidar implementation out of registered ones.")
+                    ->Attribute(AZ::Edit::Attributes::ChangeNotify, &ROS2LidarSensorComponent::OnLidarImplementationSelected)
+                    ->Attribute(AZ::Edit::Attributes::StringList, &ROS2LidarSensorComponent::FetchLidarSystemList)
                     ->DataElement(
                         AZ::Edit::UIHandlers::EntityId,
                         &ROS2LidarSensorComponent::m_lidarParameters,
                         "Lidar parameters",
-                        "Configuration of Generic lidar")
+                        "Configuration of Custom lidar")
                     ->Attribute(AZ::Edit::Attributes::Visibility, &ROS2LidarSensorComponent::IsConfigurationVisible)
                     ->DataElement(
                         AZ::Edit::UIHandlers::ComboBox,
                         &ROS2LidarSensorComponent::m_ignoreLayer,
                         "Ignore layer",
                         "Should we ignore selected layer index")
+                    ->Attribute(AZ::Edit::Attributes::Visibility, &ROS2LidarSensorComponent::IsIgnoredLayerConfigurationVisible)
                     ->DataElement(
                         AZ::Edit::UIHandlers::Default,
                         &ROS2LidarSensorComponent::m_ignoredLayerIndex,
                         "Ignored layer index",
-                        "Layer index to ignore");
+                        "Layer index to ignore")
+                    ->Attribute(AZ::Edit::Attributes::Visibility, &ROS2LidarSensorComponent::IsIgnoredLayerConfigurationVisible)
+                    ->DataElement(
+                        0, &ROS2LidarSensorComponent::m_excludedEntities, "Excluded Entities", "List of entities excluded from raycasting.")
+                    ->Attribute(AZ::Edit::Attributes::AutoExpand, true)
+                    ->Attribute(AZ::Edit::Attributes::ContainerCanBeModified, true)
+                    ->Attribute(AZ::Edit::Attributes::Visibility, &ROS2LidarSensorComponent::IsEntityExclusionVisible)
+                    ->DataElement(
+                        AZ::Edit::UIHandlers::Default,
+                        &ROS2LidarSensorComponent::m_addPointsAtMax,
+                        "Points at Max",
+                        "If set true LiDAR will produce points at max range for free space")
+                    ->Attribute(AZ::Edit::Attributes::Visibility, &ROS2LidarSensorComponent::IsMaxPointsConfigurationVisible);
             }
         }
     }
+    AZStd::string GetDefaultLidarSystem()
+    {
+        const auto& lidarSystemList = LidarRegistrarInterface::Get()->GetRegisteredLidarSystems();
+        if (lidarSystemList.empty())
+        {
+            AZ_Warning("ROS2LidarSensorComponent", false, "No LIDAR system for the sensor to use.");
+            return AZStd::string();
+        }
+        return lidarSystemList.front();
+    }
+
+    void ROS2LidarSensorComponent::FetchLidarImplementationFeatures()
+    {
+        if (m_lidarSystem.empty())
+        {
+            m_lidarSystem = GetDefaultLidarSystem();
+        }
+        const auto* lidarMetaData = LidarRegistrarInterface::Get()->GetLidarSystemMetaData(m_lidarSystem);
+        AZ_Warning("ROS2LidarSensorComponent", lidarMetaData, "No metadata for \"%s\"", m_lidarSystem.c_str());
+        if (lidarMetaData)
+        {
+            m_lidarSystemFeatures = LidarRegistrarInterface::Get()->GetLidarSystemMetaData(m_lidarSystem)->m_features;
+        }
+    }
 
     bool ROS2LidarSensorComponent::IsConfigurationVisible() const
     {
-        return m_lidarModel == LidarTemplate::LidarModel::Generic3DLidar;
+        return m_lidarModel == LidarTemplate::LidarModel::Custom3DLidar;
+    }
+
+    bool ROS2LidarSensorComponent::IsIgnoredLayerConfigurationVisible() const
+    {
+        return m_lidarSystemFeatures & LidarSystemFeatures::CollisionLayers;
+    }
+
+    bool ROS2LidarSensorComponent::IsEntityExclusionVisible() const
+    {
+        return m_lidarSystemFeatures & LidarSystemFeatures::EntityExclusion;
+    }
+
+    bool ROS2LidarSensorComponent::IsMaxPointsConfigurationVisible() const
+    {
+        return m_lidarSystemFeatures & LidarSystemFeatures::MaxRangePoints;
+    }
+
+    AZStd::vector<AZStd::string> ROS2LidarSensorComponent::FetchLidarSystemList()
+    {
+        FetchLidarImplementationFeatures();
+        return LidarRegistrarInterface::Get()->GetRegisteredLidarSystems();
     }
 
     AZ::Crc32 ROS2LidarSensorComponent::OnLidarModelSelected()
@@ -78,6 +146,63 @@ namespace ROS2
         return AZ::Edit::PropertyRefreshLevels::EntireTree;
     }
 
+    AZ::Crc32 ROS2LidarSensorComponent::OnLidarImplementationSelected()
+    {
+        FetchLidarImplementationFeatures();
+        return AZ::Edit::PropertyRefreshLevels::EntireTree;
+    }
+
+    void ROS2LidarSensorComponent::ConnectToLidarRaycaster()
+    {
+        if (auto raycasterId = m_implementationToRaycasterMap.find(m_lidarSystem); raycasterId != m_implementationToRaycasterMap.end())
+        {
+            m_lidarRaycasterId = raycasterId->second;
+            return;
+        }
+
+        m_lidarRaycasterId = LidarId::CreateNull();
+        LidarSystemRequestBus::EventResult(
+            m_lidarRaycasterId, AZ_CRC(m_lidarSystem), &LidarSystemRequestBus::Events::CreateLidar, GetEntityId());
+        AZ_Assert(!m_lidarRaycasterId.IsNull(), "Could not access selected Lidar System.");
+
+        m_implementationToRaycasterMap.emplace(m_lidarSystem, m_lidarRaycasterId);
+    }
+
+    void ROS2LidarSensorComponent::ConfigureLidarRaycaster()
+    {
+        FetchLidarImplementationFeatures();
+        LidarRaycasterRequestBus::Event(m_lidarRaycasterId, &LidarRaycasterRequestBus::Events::ConfigureRayOrientations, m_lastRotations);
+        LidarRaycasterRequestBus::Event(
+            m_lidarRaycasterId, &LidarRaycasterRequestBus::Events::ConfigureRayRange, m_lidarParameters.m_maxRange);
+
+        if (m_lidarSystemFeatures & LidarSystemFeatures::Noise)
+        {
+            LidarRaycasterRequestBus::Event(
+                m_lidarRaycasterId,
+                &LidarRaycasterRequestBus::Events::ConfigureNoiseParameters,
+                m_lidarParameters.m_noiseParameters.m_angularNoiseStdDev,
+                m_lidarParameters.m_noiseParameters.m_distanceNoiseStdDevBase,
+                m_lidarParameters.m_noiseParameters.m_distanceNoiseStdDevRisePerMeter);
+        }
+
+        if (m_lidarSystemFeatures & LidarSystemFeatures::CollisionLayers)
+        {
+            LidarRaycasterRequestBus::Event(
+                m_lidarRaycasterId, &LidarRaycasterRequestBus::Events::ConfigureLayerIgnoring, m_ignoreLayer, m_ignoredLayerIndex);
+        }
+
+        if (m_lidarSystemFeatures & LidarSystemFeatures::EntityExclusion)
+        {
+            LidarRaycasterRequestBus::Event(m_lidarRaycasterId, &LidarRaycasterRequestBus::Events::ExcludeEntities, m_excludedEntities);
+        }
+
+        if (m_lidarSystemFeatures & LidarSystemFeatures::MaxRangePoints)
+        {
+            LidarRaycasterRequestBus::Event(
+                m_lidarRaycasterId, &LidarRaycasterRequestBus::Events::ConfigureMaxRangePointAddition, m_addPointsAtMax);
+        }
+    }
+
     ROS2LidarSensorComponent::ROS2LidarSensorComponent()
     {
         TopicConfiguration pc;
@@ -109,21 +234,6 @@ namespace ROS2
         }
     }
 
-    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();
@@ -133,13 +243,18 @@ namespace ROS2
         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);
+
+        m_lastRotations = LidarTemplateUtils::PopulateRayRotations(m_lidarParameters);
+
+        FetchLidarImplementationFeatures();
+        ConnectToLidarRaycaster();
+        ConfigureLidarRaycaster();
+
         ROS2SensorComponent::Activate();
     }
 
@@ -151,18 +266,10 @@ namespace ROS2
 
     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);
+        LidarRaycasterRequestBus::EventResult(
+            m_lastScanResults, m_lidarRaycasterId, &LidarRaycasterRequestBus::Events::PerformRaycast, entityTransform->GetWorldTM());
         if (m_lastScanResults.empty())
         {
             AZ_TracePrintf("Lidar Sensor Component", "No results from raycast\n");
@@ -171,13 +278,13 @@ namespace ROS2
 
         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);
-            }
+        }
+
+        const auto inverseLidarTM = entityTransform->GetWorldTM().GetInverse();
+        for (auto& point : m_lastScanResults)
+        {
+            point = inverseLidarTM.TransformPoint(point);
         }
 
         auto* ros2Frame = Utils::GetGameOrEditorComponent<ROS2FrameComponent>(GetEntity());

+ 28 - 10
Gems/ROS2/Code/Source/Lidar/ROS2LidarSensorComponent.h

@@ -7,11 +7,13 @@
  */
 #pragma once
 
-#include "LidarRaycaster.h"
-#include "LidarTemplate.h"
-#include "LidarTemplateUtils.h"
 #include <Atom/RPI.Public/AuxGeom/AuxGeomDraw.h>
 #include <AzCore/Serialization/SerializeContext.h>
+#include <Lidar/LidarRaycaster.h>
+#include <Lidar/LidarTemplate.h>
+#include <Lidar/LidarTemplateUtils.h>
+#include <ROS2/Lidar/LidarRegistrarBus.h>
+#include <ROS2/Lidar/LidarSystemBus.h>
 #include <ROS2/Sensor/ROS2SensorComponent.h>
 #include <rclcpp/publisher.hpp>
 #include <sensor_msgs/msg/point_cloud2.hpp>
@@ -40,15 +42,28 @@ namespace ROS2
         // ROS2SensorComponent overrides
         void FrequencyTick() override;
         void Visualise() override;
-        //////////////////////////////////////////////////////////////////////////
-        void SetPhysicsScene();
 
-        AZ::Crc32 OnLidarModelSelected();
         bool IsConfigurationVisible() const;
+        bool IsIgnoredLayerConfigurationVisible() const;
+        bool IsEntityExclusionVisible() const;
+        bool IsMaxPointsConfigurationVisible() const;
+
+        AZ::Crc32 OnLidarModelSelected();
+        AZ::Crc32 OnLidarImplementationSelected();
+        void FetchLidarImplementationFeatures();
+        AZStd::vector<AZStd::string> FetchLidarSystemList();
+        void ConnectToLidarRaycaster();
+        void ConfigureLidarRaycaster();
+
+        LidarSystemFeatures m_lidarSystemFeatures;
+        LidarTemplate::LidarModel m_lidarModel = LidarTemplate::LidarModel::Custom3DLidar;
+        LidarTemplate m_lidarParameters = LidarTemplateUtils::GetTemplate(LidarTemplate::LidarModel::Custom3DLidar);
+        AZStd::vector<AZ::Vector3> m_lastRotations;
 
-        LidarTemplate::LidarModel m_lidarModel = LidarTemplate::LidarModel::Generic3DLidar;
-        LidarTemplate m_lidarParameters = LidarTemplateUtils::GetTemplate(LidarTemplate::LidarModel::Generic3DLidar);
-        LidarRaycaster m_lidarRaycaster;
+        AZStd::string m_lidarSystem;
+        // A structure that maps each lidar implementation busId to the busId of a raycaster created by this LidarSensorComponent.
+        AZStd::unordered_map<AZStd::string, LidarId> m_implementationToRaycasterMap;
+        LidarId m_lidarRaycasterId;
         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
@@ -57,7 +72,10 @@ namespace ROS2
 
         AZStd::vector<AZ::Vector3> m_lastScanResults;
 
-        unsigned int m_ignoredLayerIndex = 0;
+        AZ::u32 m_ignoredLayerIndex = 0;
         bool m_ignoreLayer = false;
+        AZStd::vector<AZ::EntityId> m_excludedEntities;
+
+        bool m_addPointsAtMax = false;
     };
 } // namespace ROS2

+ 7 - 1
Gems/ROS2/Code/Source/ROS2EditorModule.cpp

@@ -5,6 +5,7 @@
  * SPDX-License-Identifier: Apache-2.0 OR MIT
  *
  */
+#include <Lidar/LidarRegistrarEditorSystemComponent.h>
 #include <ROS2EditorSystemComponent.h>
 #include <ROS2ModuleInterface.h>
 #include <RobotImporter/ROS2RobotImporterEditorSystemComponent.h>
@@ -25,7 +26,11 @@ namespace ROS2
             // EditContext. This happens through the [MyComponent]::Reflect() function.
             m_descriptors.insert(
                 m_descriptors.end(),
-                { ROS2EditorSystemComponent::CreateDescriptor(), ROS2RobotImporterEditorSystemComponent::CreateDescriptor() });
+                {
+                    ROS2EditorSystemComponent::CreateDescriptor(),
+                    LidarRegistrarEditorSystemComponent::CreateDescriptor(),
+                    ROS2RobotImporterEditorSystemComponent::CreateDescriptor(),
+                });
         }
 
         /**
@@ -36,6 +41,7 @@ namespace ROS2
         {
             return AZ::ComponentTypeList{
                 azrtti_typeid<ROS2EditorSystemComponent>(),
+                azrtti_typeid<LidarRegistrarEditorSystemComponent>(),
                 azrtti_typeid<ROS2RobotImporterEditorSystemComponent>(),
             };
         }

+ 7 - 1
Gems/ROS2/Code/Source/ROS2ModuleInterface.h

@@ -13,6 +13,7 @@
 #include <Camera/ROS2CameraSensorComponent.h>
 #include <GNSS/ROS2GNSSSensorComponent.h>
 #include <Imu/ROS2ImuSensorComponent.h>
+#include <Lidar/LidarRegistrarSystemComponent.h>
 #include <Lidar/ROS2LidarSensorComponent.h>
 #include <Odometry/ROS2OdometrySensorComponent.h>
 #include <ROS2/Frame/ROS2FrameComponent.h>
@@ -47,6 +48,7 @@ namespace ROS2
             m_descriptors.insert(
                 m_descriptors.end(),
                 { ROS2SystemComponent::CreateDescriptor(),
+                  LidarRegistrarSystemComponent::CreateDescriptor(),
                   ROS2RobotImporterSystemComponent::CreateDescriptor(),
                   ROS2SensorComponent::CreateDescriptor(),
                   ROS2ImuSensorComponent::CreateDescriptor(),
@@ -71,7 +73,11 @@ namespace ROS2
         //! Add required SystemComponents to the SystemEntity.
         AZ::ComponentTypeList GetRequiredSystemComponents() const override
         {
-            return AZ::ComponentTypeList{ azrtti_typeid<ROS2SystemComponent>(), azrtti_typeid<ROS2RobotImporterSystemComponent>() };
+            return AZ::ComponentTypeList{
+                azrtti_typeid<ROS2SystemComponent>(),
+                azrtti_typeid<LidarRegistrarSystemComponent>(),
+                azrtti_typeid<ROS2RobotImporterSystemComponent>(),
+            };
         }
     };
 } // namespace ROS2

+ 2 - 0
Gems/ROS2/Code/Source/ROS2SystemComponent.cpp

@@ -9,6 +9,7 @@
 
 #include <ROS2/Communication/QoS.h>
 #include <ROS2/Communication/TopicConfiguration.h>
+#include <ROS2/Utilities/Controllers/PidConfiguration.h>
 #include <VehicleDynamics/VehicleModelComponent.h>
 
 #include <Atom/RPI.Public/Pass/PassSystemInterface.h>
@@ -27,6 +28,7 @@ namespace ROS2
         QoS::Reflect(context);
         TopicConfiguration::Reflect(context);
         VehicleDynamics::VehicleModelComponent::Reflect(context);
+        ROS2::Controllers::PidConfiguration::Reflect(context);
         if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context))
         {
             serialize->Class<ROS2SystemComponent, AZ::Component>()->Version(0);

Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels