瀏覽代碼

initial commit. Basic geometry node is working but still buggy.

Signed-off-by: Jason Dela Cruz <[email protected]>
Jason Dela Cruz 2 年之前
父節點
當前提交
31b8b8624e
共有 100 個文件被更改,包括 7833 次插入0 次删除
  1. 6 0
      Gems/O3DE/GeomNodes/3rdParty/FindBridge.cmake
  2. 5 0
      Gems/O3DE/GeomNodes/3rdParty/Platform/Windows/bridge_windows.cmake
  3. 11 0
      Gems/O3DE/GeomNodes/CMakeLists.txt
  4. 211 0
      Gems/O3DE/GeomNodes/Code/CMakeLists.txt
  5. 31 0
      Gems/O3DE/GeomNodes/Code/Include/GeomNodes/GeomNodesBus.h
  6. 4 0
      Gems/O3DE/GeomNodes/Code/Platform/Android/PAL_android.cmake
  7. 3 0
      Gems/O3DE/GeomNodes/Code/Platform/Android/geomnodes_api_files.cmake
  8. 8 0
      Gems/O3DE/GeomNodes/Code/Platform/Android/geomnodes_private_files.cmake
  9. 8 0
      Gems/O3DE/GeomNodes/Code/Platform/Android/geomnodes_shared_files.cmake
  10. 4 0
      Gems/O3DE/GeomNodes/Code/Platform/Linux/PAL_linux.cmake
  11. 3 0
      Gems/O3DE/GeomNodes/Code/Platform/Linux/geomnodes_api_files.cmake
  12. 3 0
      Gems/O3DE/GeomNodes/Code/Platform/Linux/geomnodes_editor_api_files.cmake
  13. 8 0
      Gems/O3DE/GeomNodes/Code/Platform/Linux/geomnodes_private_files.cmake
  14. 8 0
      Gems/O3DE/GeomNodes/Code/Platform/Linux/geomnodes_shared_files.cmake
  15. 4 0
      Gems/O3DE/GeomNodes/Code/Platform/Mac/PAL_mac.cmake
  16. 3 0
      Gems/O3DE/GeomNodes/Code/Platform/Mac/geomnodes_api_files.cmake
  17. 3 0
      Gems/O3DE/GeomNodes/Code/Platform/Mac/geomnodes_editor_api_files.cmake
  18. 8 0
      Gems/O3DE/GeomNodes/Code/Platform/Mac/geomnodes_private_files.cmake
  19. 8 0
      Gems/O3DE/GeomNodes/Code/Platform/Mac/geomnodes_shared_files.cmake
  20. 4 0
      Gems/O3DE/GeomNodes/Code/Platform/Windows/PAL_windows.cmake
  21. 3 0
      Gems/O3DE/GeomNodes/Code/Platform/Windows/geomnodes_api_files.cmake
  22. 3 0
      Gems/O3DE/GeomNodes/Code/Platform/Windows/geomnodes_editor_api_files.cmake
  23. 8 0
      Gems/O3DE/GeomNodes/Code/Platform/Windows/geomnodes_private_files.cmake
  24. 8 0
      Gems/O3DE/GeomNodes/Code/Platform/Windows/geomnodes_shared_files.cmake
  25. 4 0
      Gems/O3DE/GeomNodes/Code/Platform/iOS/PAL_ios.cmake
  26. 3 0
      Gems/O3DE/GeomNodes/Code/Platform/iOS/geomnodes_api_files.cmake
  27. 8 0
      Gems/O3DE/GeomNodes/Code/Platform/iOS/geomnodes_private_files.cmake
  28. 8 0
      Gems/O3DE/GeomNodes/Code/Platform/iOS/geomnodes_shared_files.cmake
  29. 4 0
      Gems/O3DE/GeomNodes/Code/Source/Editor/Commons.h
  30. 538 0
      Gems/O3DE/GeomNodes/Code/Source/Editor/Components/GeomNodesEditorComponent.cpp
  31. 94 0
      Gems/O3DE/GeomNodes/Code/Source/Editor/Components/GeomNodesEditorComponent.h
  32. 56 0
      Gems/O3DE/GeomNodes/Code/Source/Editor/Components/GeomNodesEditorSystemComponent.cpp
  33. 36 0
      Gems/O3DE/GeomNodes/Code/Source/Editor/Components/GeomNodesEditorSystemComponent.h
  34. 33 0
      Gems/O3DE/GeomNodes/Code/Source/Editor/EBus/GeomNodesBus.h
  35. 22 0
      Gems/O3DE/GeomNodes/Code/Source/Editor/EBus/IpcHandlerBus.h
  36. 23 0
      Gems/O3DE/GeomNodes/Code/Source/Editor/EBus/ValidatorBus.h
  37. 108 0
      Gems/O3DE/GeomNodes/Code/Source/Editor/Math/MathHelper.cpp
  38. 54 0
      Gems/O3DE/GeomNodes/Code/Source/Editor/Math/MathHelper.h
  39. 41 0
      Gems/O3DE/GeomNodes/Code/Source/Editor/Modules/GeomNodesEditorModule.cpp
  40. 207 0
      Gems/O3DE/GeomNodes/Code/Source/Editor/Rendering/Atom/GNAttributeBuffer.h
  41. 193 0
      Gems/O3DE/GeomNodes/Code/Source/Editor/Rendering/Atom/GNBuffer.h
  42. 279 0
      Gems/O3DE/GeomNodes/Code/Source/Editor/Rendering/GNMeshData.cpp
  43. 67 0
      Gems/O3DE/GeomNodes/Code/Source/Editor/Rendering/GNMeshData.h
  44. 119 0
      Gems/O3DE/GeomNodes/Code/Source/Editor/Rendering/GNModelData.cpp
  45. 28 0
      Gems/O3DE/GeomNodes/Code/Source/Editor/Rendering/GNModelData.h
  46. 310 0
      Gems/O3DE/GeomNodes/Code/Source/Editor/Rendering/GNRenderMesh.cpp
  47. 113 0
      Gems/O3DE/GeomNodes/Code/Source/Editor/Rendering/GNRenderMesh.h
  48. 37 0
      Gems/O3DE/GeomNodes/Code/Source/Editor/Rendering/GNRenderMeshInterface.h
  49. 83 0
      Gems/O3DE/GeomNodes/Code/Source/Editor/Rendering/GNRenderModel.cpp
  50. 41 0
      Gems/O3DE/GeomNodes/Code/Source/Editor/Rendering/GNRenderModel.h
  51. 99 0
      Gems/O3DE/GeomNodes/Code/Source/Editor/Systems/GNInstance.cpp
  52. 39 0
      Gems/O3DE/GeomNodes/Code/Source/Editor/Systems/GNInstance.h
  53. 261 0
      Gems/O3DE/GeomNodes/Code/Source/Editor/Systems/GNParamContext.cpp
  54. 194 0
      Gems/O3DE/GeomNodes/Code/Source/Editor/Systems/GNParamContext.h
  55. 535 0
      Gems/O3DE/GeomNodes/Code/Source/Editor/Systems/GNProperty.cpp
  56. 315 0
      Gems/O3DE/GeomNodes/Code/Source/Editor/Systems/GNProperty.h
  57. 77 0
      Gems/O3DE/GeomNodes/Code/Source/Editor/Systems/GeomNodesSystem.cpp
  58. 37 0
      Gems/O3DE/GeomNodes/Code/Source/Editor/Systems/GeomNodesSystem.h
  59. 33 0
      Gems/O3DE/GeomNodes/Code/Source/Editor/UI/FunctorValidator.cpp
  60. 37 0
      Gems/O3DE/GeomNodes/Code/Source/Editor/UI/FunctorValidator.h
  61. 51 0
      Gems/O3DE/GeomNodes/Code/Source/Editor/UI/GeomNodesValidator.cpp
  62. 36 0
      Gems/O3DE/GeomNodes/Code/Source/Editor/UI/GeomNodesValidator.h
  63. 103 0
      Gems/O3DE/GeomNodes/Code/Source/Editor/UI/PropertyFileSelect.cpp
  64. 55 0
      Gems/O3DE/GeomNodes/Code/Source/Editor/UI/PropertyFileSelect.h
  65. 232 0
      Gems/O3DE/GeomNodes/Code/Source/Editor/UI/PropertyFuncValBrowseEdit.cpp
  66. 84 0
      Gems/O3DE/GeomNodes/Code/Source/Editor/UI/PropertyFuncValBrowseEdit.h
  67. 23 0
      Gems/O3DE/GeomNodes/Code/Source/Editor/UI/UI_common.h
  68. 112 0
      Gems/O3DE/GeomNodes/Code/Source/Editor/UI/Utils.cpp
  69. 20 0
      Gems/O3DE/GeomNodes/Code/Source/Editor/UI/Utils.h
  70. 38 0
      Gems/O3DE/GeomNodes/Code/Source/Editor/UI/ValidationHandler.cpp
  71. 21 0
      Gems/O3DE/GeomNodes/Code/Source/Editor/UI/ValidationHandler.h
  72. 135 0
      Gems/O3DE/GeomNodes/Code/Source/Editor/UI/Validators.cpp
  73. 24 0
      Gems/O3DE/GeomNodes/Code/Source/Editor/UI/Validators.h
  74. 83 0
      Gems/O3DE/GeomNodes/Code/Source/GeomNodes/Components/GeomNodesSystemComponent.cpp
  75. 47 0
      Gems/O3DE/GeomNodes/Code/Source/GeomNodes/Components/GeomNodesSystemComponent.h
  76. 17 0
      Gems/O3DE/GeomNodes/Code/Source/GeomNodes/Modules/GeomNodesModule.cpp
  77. 36 0
      Gems/O3DE/GeomNodes/Code/Source/GeomNodesModuleInterface.h
  78. 4 0
      Gems/O3DE/GeomNodes/Code/Tests/Clients/GeomNodesTest.cpp
  79. 4 0
      Gems/O3DE/GeomNodes/Code/Tests/Tools/GeomNodesEditorTest.cpp
  80. 4 0
      Gems/O3DE/GeomNodes/Code/geomnodes_api_files.cmake
  81. 4 0
      Gems/O3DE/GeomNodes/Code/geomnodes_editor_api_files.cmake
  82. 47 0
      Gems/O3DE/GeomNodes/Code/geomnodes_editor_private_files.cmake
  83. 4 0
      Gems/O3DE/GeomNodes/Code/geomnodes_editor_shared_files.cmake
  84. 4 0
      Gems/O3DE/GeomNodes/Code/geomnodes_editor_tests_files.cmake
  85. 6 0
      Gems/O3DE/GeomNodes/Code/geomnodes_private_files.cmake
  86. 4 0
      Gems/O3DE/GeomNodes/Code/geomnodes_shared_files.cmake
  87. 4 0
      Gems/O3DE/GeomNodes/Code/geomnodes_tests_files.cmake
  88. 58 0
      Gems/O3DE/GeomNodes/External/Bridge/Bridge.cpp
  89. 39 0
      Gems/O3DE/GeomNodes/External/Bridge/Bridge.h
  90. 31 0
      Gems/O3DE/GeomNodes/External/Bridge/CMakeLists.txt
  91. 1210 0
      Gems/O3DE/GeomNodes/External/Bridge/Ipc.cpp
  92. 264 0
      Gems/O3DE/GeomNodes/External/Bridge/Ipc.h
  93. 6 0
      Gems/O3DE/GeomNodes/External/Bridge/bridge_files.cmake
  94. 15 0
      Gems/O3DE/GeomNodes/External/CMakeLists.txt
  95. 6 0
      Gems/O3DE/GeomNodes/External/README.md
  96. 59 0
      Gems/O3DE/GeomNodes/External/Scripts/GeomNodes.py
  97. 30 0
      Gems/O3DE/GeomNodes/External/Scripts/__init__.py
  98. 141 0
      Gems/O3DE/GeomNodes/External/Scripts/lib_loader.py
  99. 177 0
      Gems/O3DE/GeomNodes/External/Scripts/mesh_data_builder.py
  100. 94 0
      Gems/O3DE/GeomNodes/External/Scripts/messages.py

+ 6 - 0
Gems/O3DE/GeomNodes/3rdParty/FindBridge.cmake

@@ -0,0 +1,6 @@
+# ly_add_external_target(
+#     NAME Bridge
+#     VERSION
+#     3RDPARTY_ROOT_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/../External/Bridge
+#     INCLUDE_DIRECTORIES .
+# )

+ 5 - 0
Gems/O3DE/GeomNodes/3rdParty/Platform/Windows/bridge_windows.cmake

@@ -0,0 +1,5 @@
+set(BRLIB_LIBS ${BASE_PATH}/lib/Bridge.lib)
+
+set(BRLIB_RUNTIME_DEPENDENCIES 
+    ${BASE_PATH}/bin/Bridge.dll
+)

+ 11 - 0
Gems/O3DE/GeomNodes/CMakeLists.txt

@@ -0,0 +1,11 @@
+
+set(gem_path ${CMAKE_CURRENT_LIST_DIR})
+set(gem_json ${gem_path}/gem.json)
+o3de_restricted_path(${gem_json} gem_restricted_path gem_parent_relative_path)
+
+o3de_pal_dir(pal_dir ${CMAKE_CURRENT_LIST_DIR}/Platform/${PAL_PLATFORM_NAME} "${gem_restricted_path}" "${gem_path}" "${gem_parent_relative_path}")
+
+ly_add_external_target_path(${CMAKE_CURRENT_LIST_DIR}/3rdParty)
+
+add_subdirectory(External)
+add_subdirectory(Code)

+ 211 - 0
Gems/O3DE/GeomNodes/Code/CMakeLists.txt

@@ -0,0 +1,211 @@
+
+# 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/GeomNodes/Code/Platform/<platorm_name>  or
+#            <restricted_folder>/<platform_name>/Gems/GeomNodes/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)
+
+# The GeomNodes.API target declares the common interface that users of this gem should depend on in their targets
+ly_add_target(
+    NAME GeomNodes.API INTERFACE
+    NAMESPACE Gem
+    FILES_CMAKE
+        geomnodes_api_files.cmake
+        ${pal_dir}/geomnodes_api_files.cmake
+    INCLUDE_DIRECTORIES
+        INTERFACE
+            Include
+    BUILD_DEPENDENCIES
+        INTERFACE
+           AZ::AzCore
+)
+
+# The GeomNodes.Private.Object target is an internal target
+# It should not be used outside of this Gems CMakeLists.txt
+ly_add_target(
+    NAME GeomNodes.Private.Object STATIC
+    NAMESPACE Gem
+    FILES_CMAKE
+        geomnodes_private_files.cmake
+        ${pal_dir}/geomnodes_private_files.cmake
+    TARGET_PROPERTIES
+        O3DE_PRIVATE_TARGET TRUE
+    INCLUDE_DIRECTORIES
+        PRIVATE
+            Include
+            Source
+    BUILD_DEPENDENCIES
+        PUBLIC
+            AZ::AzCore
+            AZ::AzFramework
+)
+
+# Here add GeomNodes target, it depends on the Private Object library and Public API interface
+ly_add_target(
+    NAME GeomNodes ${PAL_TRAIT_MONOLITHIC_DRIVEN_MODULE_TYPE}
+    NAMESPACE Gem
+    FILES_CMAKE
+        geomnodes_shared_files.cmake
+        ${pal_dir}/geomnodes_shared_files.cmake
+    INCLUDE_DIRECTORIES
+        PUBLIC
+            Include
+        PRIVATE
+            Source
+    BUILD_DEPENDENCIES
+        PUBLIC
+            Gem::GeomNodes.API
+        PRIVATE
+            Gem::GeomNodes.Private.Object
+)
+
+# By default, we will specify that the above target GeomNodes 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 GeomNodes.Clients NAMESPACE Gem TARGETS Gem::GeomNodes)
+ly_create_alias(NAME GeomNodes.Servers NAMESPACE Gem TARGETS Gem::GeomNodes)
+
+# For the Client and Server variants of GeomNodes Gem, an alias to the GeomNodes.API target will be made
+ly_create_alias(NAME GeomNodes.Clients.API NAMESPACE Gem TARGETS Gem::GeomNodes.API)
+ly_create_alias(NAME GeomNodes.Servers.API NAMESPACE Gem TARGETS Gem::GeomNodes.API)
+
+# If we are on a host platform, we want to add the host tools targets like the GeomNodes.Editor MODULE target
+if(PAL_TRAIT_BUILD_HOST_TOOLS)
+    # The GeomNodes.Editor.API target can be used by other gems that want to interact with the GeomNodes.Editor module
+    ly_add_target(
+        NAME GeomNodes.Editor.API INTERFACE
+        NAMESPACE Gem
+        FILES_CMAKE
+            geomnodes_editor_api_files.cmake
+            ${pal_dir}/geomnodes_editor_api_files.cmake
+        INCLUDE_DIRECTORIES
+            INTERFACE
+                Include
+        BUILD_DEPENDENCIES
+            INTERFACE
+                AZ::AzToolsFramework
+    )
+
+    # The GeomNodes.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 GeomNodes.Editor.Private.Object STATIC
+        NAMESPACE Gem
+        AUTOMOC
+        FILES_CMAKE
+            geomnodes_editor_private_files.cmake
+        TARGET_PROPERTIES
+            O3DE_PRIVATE_TARGET TRUE
+        INCLUDE_DIRECTORIES
+            PRIVATE
+                Include
+                Source
+        # COMPILE_DEFINITIONS
+        #     PRIVATE
+        #         DEBUG_SCRIPT_USING_ID=123456
+        BUILD_DEPENDENCIES
+            PUBLIC
+                AZ::AzToolsFramework
+                Gem::Atom_RPI.Public
+                Gem::Atom_Feature_Common.Static
+                Gem::AtomLyIntegration_CommonFeatures.Static
+                $<TARGET_OBJECTS:Gem::GeomNodes.Private.Object>
+                GeomNodes::Bridge
+    )
+
+    ly_add_target(
+        NAME GeomNodes.Editor GEM_MODULE
+        NAMESPACE Gem
+        AUTOMOC
+        FILES_CMAKE
+            geomnodes_editor_shared_files.cmake
+        INCLUDE_DIRECTORIES
+            PRIVATE
+                Source
+            PUBLIC
+                Include
+        BUILD_DEPENDENCIES
+            PUBLIC
+                Gem::GeomNodes.Editor.API
+            PRIVATE
+                Gem::GeomNodes.Editor.Private.Object
+    )
+
+    # By default, we will specify that the above target GeomNodes 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 GeomNodes.Tools    NAMESPACE Gem TARGETS Gem::GeomNodes.Editor)
+    ly_create_alias(NAME GeomNodes.Builders NAMESPACE Gem TARGETS Gem::GeomNodes.Editor)
+
+    # For the Tools and Builders variants of GeomNodes Gem, an alias to the GeomNodes.Editor API target will be made
+    ly_create_alias(NAME GeomNodes.Tools.API NAMESPACE Gem TARGETS Gem::GeomNodes.Editor.API)
+    ly_create_alias(NAME GeomNodes.Builders.API NAMESPACE Gem TARGETS Gem::GeomNodes.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 GeomNodes.Tests
+    if(PAL_TRAIT_GEOMNODES_TEST_SUPPORTED)
+        # We support GeomNodes.Tests on this platform, add dependency on the Private Object target
+        ly_add_target(
+            NAME GeomNodes.Tests ${PAL_TRAIT_TEST_TARGET_TYPE}
+            NAMESPACE Gem
+            FILES_CMAKE
+                geomnodes_tests_files.cmake
+            INCLUDE_DIRECTORIES
+                PRIVATE
+                    Tests
+                    Source
+            BUILD_DEPENDENCIES
+                PRIVATE
+                    AZ::AzTest
+                    AZ::AzFramework
+                    Gem::GeomNodes.Private.Object
+        )
+
+        # Add GeomNodes.Tests to googletest
+        ly_add_googletest(
+            NAME Gem::GeomNodes.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_GEOMNODES_EDITOR_TEST_SUPPORTED)
+            # We support GeomNodes.Editor.Tests on this platform, add GeomNodes.Editor.Tests target which depends on
+            # private GeomNodes.Editor.Private.Object target
+            ly_add_target(
+                NAME GeomNodes.Editor.Tests ${PAL_TRAIT_TEST_TARGET_TYPE}
+                NAMESPACE Gem
+                FILES_CMAKE
+                    geomnodes_editor_tests_files.cmake
+                INCLUDE_DIRECTORIES
+                    PRIVATE
+                        Tests
+                        Source
+                BUILD_DEPENDENCIES
+                    PRIVATE
+                        AZ::AzTest
+                        Gem::GeomNodes.Private.Object
+            )
+
+            # Add GeomNodes.Editor.Tests to googletest
+            ly_add_googletest(
+                NAME Gem::GeomNodes.Editor.Tests
+            )
+        endif()
+    endif()
+endif()

+ 31 - 0
Gems/O3DE/GeomNodes/Code/Include/GeomNodes/GeomNodesBus.h

@@ -0,0 +1,31 @@
+
+#pragma once
+
+#include <AzCore/EBus/EBus.h>
+#include <AzCore/Interface/Interface.h>
+
+namespace GeomNodes
+{
+    class GeomNodesRequests
+    {
+    public:
+        AZ_RTTI(GeomNodesRequests, "{B09157F9-452C-41AE-91F5-578D6EB2C425}");
+        virtual ~GeomNodesRequests() = default;
+        // Put your public methods here
+    };
+    
+    class GeomNodesBusTraits
+        : public AZ::EBusTraits
+    {
+    public:
+        //////////////////////////////////////////////////////////////////////////
+        // EBusTraits overrides
+        static constexpr AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
+        static constexpr AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
+        //////////////////////////////////////////////////////////////////////////
+    };
+
+    using GeomNodesRequestBus = AZ::EBus<GeomNodesRequests, GeomNodesBusTraits>;
+    using GeomNodesInterface = AZ::Interface<GeomNodesRequests>;
+
+} // namespace GeomNodes

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

@@ -0,0 +1,4 @@
+
+set(PAL_TRAIT_GEOMNODES_SUPPORTED TRUE)
+set(PAL_TRAIT_GEOMNODES_TEST_SUPPORTED FALSE)
+set(PAL_TRAIT_GEOMNODES_EDITOR_TEST_SUPPORTED FALSE)

+ 3 - 0
Gems/O3DE/GeomNodes/Code/Platform/Android/geomnodes_api_files.cmake

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

+ 8 - 0
Gems/O3DE/GeomNodes/Code/Platform/Android/geomnodes_private_files.cmake

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

+ 8 - 0
Gems/O3DE/GeomNodes/Code/Platform/Android/geomnodes_shared_files.cmake

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

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

@@ -0,0 +1,4 @@
+
+set(PAL_TRAIT_GEOMNODES_SUPPORTED TRUE)
+set(PAL_TRAIT_GEOMNODES_TEST_SUPPORTED FALSE)
+set(PAL_TRAIT_GEOMNODES_EDITOR_TEST_SUPPORTED FALSE)

+ 3 - 0
Gems/O3DE/GeomNodes/Code/Platform/Linux/geomnodes_api_files.cmake

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

+ 3 - 0
Gems/O3DE/GeomNodes/Code/Platform/Linux/geomnodes_editor_api_files.cmake

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

+ 8 - 0
Gems/O3DE/GeomNodes/Code/Platform/Linux/geomnodes_private_files.cmake

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

+ 8 - 0
Gems/O3DE/GeomNodes/Code/Platform/Linux/geomnodes_shared_files.cmake

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

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

@@ -0,0 +1,4 @@
+
+set(PAL_TRAIT_GEOMNODES_SUPPORTED TRUE)
+set(PAL_TRAIT_GEOMNODES_TEST_SUPPORTED FALSE)
+set(PAL_TRAIT_GEOMNODES_EDITOR_TEST_SUPPORTED FALSE)

+ 3 - 0
Gems/O3DE/GeomNodes/Code/Platform/Mac/geomnodes_api_files.cmake

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

+ 3 - 0
Gems/O3DE/GeomNodes/Code/Platform/Mac/geomnodes_editor_api_files.cmake

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

+ 8 - 0
Gems/O3DE/GeomNodes/Code/Platform/Mac/geomnodes_private_files.cmake

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

+ 8 - 0
Gems/O3DE/GeomNodes/Code/Platform/Mac/geomnodes_shared_files.cmake

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

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

@@ -0,0 +1,4 @@
+
+set(PAL_TRAIT_GEOMNODES_SUPPORTED TRUE)
+set(PAL_TRAIT_GEOMNODES_TEST_SUPPORTED FALSE)
+set(PAL_TRAIT_GEOMNODES_EDITOR_TEST_SUPPORTED FALSE)

+ 3 - 0
Gems/O3DE/GeomNodes/Code/Platform/Windows/geomnodes_api_files.cmake

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

+ 3 - 0
Gems/O3DE/GeomNodes/Code/Platform/Windows/geomnodes_editor_api_files.cmake

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

+ 8 - 0
Gems/O3DE/GeomNodes/Code/Platform/Windows/geomnodes_private_files.cmake

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

+ 8 - 0
Gems/O3DE/GeomNodes/Code/Platform/Windows/geomnodes_shared_files.cmake

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

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

@@ -0,0 +1,4 @@
+
+set(PAL_TRAIT_GEOMNODES_SUPPORTED TRUE)
+set(PAL_TRAIT_GEOMNODES_TEST_SUPPORTED FALSE)
+set(PAL_TRAIT_GEOMNODES_EDITOR_TEST_SUPPORTED FALSE)

+ 3 - 0
Gems/O3DE/GeomNodes/Code/Platform/iOS/geomnodes_api_files.cmake

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

+ 8 - 0
Gems/O3DE/GeomNodes/Code/Platform/iOS/geomnodes_private_files.cmake

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

+ 8 - 0
Gems/O3DE/GeomNodes/Code/Platform/iOS/geomnodes_shared_files.cmake

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

+ 4 - 0
Gems/O3DE/GeomNodes/Code/Source/Editor/Commons.h

@@ -0,0 +1,4 @@
+#pragma once
+
+#include <AzCore/Math/Crc.h>
+#include <AzCore/std/string/string.h>

+ 538 - 0
Gems/O3DE/GeomNodes/Code/Source/Editor/Components/GeomNodesEditorComponent.cpp

@@ -0,0 +1,538 @@
+#include "Editor/Components/GeomNodesEditorComponent.h"
+
+#include "Editor/UI/UI_common.h"
+#include "Editor/UI/Validators.h"
+#include <AzToolsFramework/API/ToolsApplicationAPI.h>
+#include <AzCore/Utils/Utils.h>
+#include <Editor/Systems/GNProperty.h>
+#include <AzCore/JSON/prettywriter.h>
+#include <AzCore/JSON/stringbuffer.h>
+#include <Atom/Feature/Mesh/MeshFeatureProcessorInterface.h>
+#include <Atom/RPI.Public/Scene.h>
+#include <AzCore/Component/NonUniformScaleBus.h>
+
+namespace GeomNodes
+{
+    static void* blendFunctor = reinterpret_cast<void*>(&SelectBlendFromFileDialog);
+
+    GeomNodesEditorComponent::GeomNodesEditorComponent()
+    {
+    }
+
+    GeomNodesEditorComponent::~GeomNodesEditorComponent()
+    {
+    }
+
+    void GeomNodesEditorComponent::Reflect(AZ::ReflectContext* context)
+    {
+        if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
+        {
+            serializeContext->Class<GeomNodesEditorComponent, AZ::Component>()
+                ->Version(1)
+                ->Field("GNParamContext", &GeomNodesEditorComponent::m_paramContext)
+                ->Field("BlenderFile", &GeomNodesEditorComponent::m_blenderFile);
+            
+            GNParamContext::Reflect(context);
+
+            AZ::EditContext* ec = serializeContext->GetEditContext();
+            if (ec)
+            {
+                ec->Class<GeomNodesEditorComponent>(
+                      "Geometry Node",
+                      "The Geometry Node component allows you to load a blend file with geometry node and tweak exposed parameters. ")
+                    ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
+                    ->Attribute(AZ::Edit::Attributes::Category, "Blender")
+                    ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c))
+                    ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("UI", 0x27ff46b0))
+                    ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Level", 0x9aeacc13))
+                    ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Layer", 0xe4db211a))
+                    ->Attribute(AZ::Edit::Attributes::AutoExpand, true)
+                    ->DataElement(
+                        Handlers::FileSelect,
+                        &GeomNodesEditorComponent::m_blenderFile,
+                        "Blender File",
+                        "Blender file with Geometry Nodes")
+                        ->Attribute(Attributes::FuncValidator, ConvertFunctorToVoid(&Validators::ValidBlenderOrEmpty))
+                        ->Attribute(Attributes::SelectFunction, blendFunctor)
+                    ->Attribute(Attributes::ValidationChange, &GeomNodesEditorComponent::OnPathChange)
+                    ->DataElement(nullptr, &GeomNodesEditorComponent::m_paramContext, "Geom Nodes Parameters", "Parameter template")
+                    ->SetDynamicEditDataProvider(&GeomNodesEditorComponent::GetParamsEditData)
+                        ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
+                    ;
+
+                ec->Class<GNParamContext>("Geom Nodes Parameter Context", "Adding exposed Geometry Nodes parameters to the entity!")
+                    ->DataElement(nullptr, &GNParamContext::m_group, "Properties", "Geometry Nodes properties")
+                    ->Attribute(AZ::Edit::Attributes::AutoExpand, true)
+                    ;
+
+                ec->Class<GNPropertyGroup>("Geom Nodes Property group", "This is a  property group")
+                    ->ClassElement(AZ::Edit::ClassElements::EditorData, "GNPropertyGroup's class attributes.")
+                    ->Attribute(AZ::Edit::Attributes::NameLabelOverride, &GNPropertyGroup::m_name)
+                    ->Attribute(AZ::Edit::Attributes::AutoExpand, true)
+                    ->DataElement(nullptr, &GNPropertyGroup::m_properties, "m_properties", "Properties in this property group")
+                    ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
+                    ->DataElement(nullptr, &GNPropertyGroup::m_groups, "m_groups", "Subgroups in this property group")
+                    ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly);
+
+                ec->Class<GNProperty>("GeomNodes Property", "Base class for Geometry Nodes properties")
+                    ->ClassElement(AZ::Edit::ClassElements::EditorData, "GNPropertyGroup's class attributes.")
+                    ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly);
+
+                ec->Class<GNParamBoolean>("Geom Nodes Property (bool)", "A Geom Nodes boolean property")
+                    ->ClassElement(AZ::Edit::ClassElements::EditorData, "GNPropertyGroup's class attributes.")
+                        ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
+                    ->DataElement(nullptr, &GNParamBoolean::m_value, "m_value", "A boolean")
+                    ->Attribute(AZ::Edit::Attributes::ChangeNotify, &GeomNodesEditorComponent::OnParamChange)
+                    ->Attribute(AZ::Edit::Attributes::NameLabelOverride, &GNParamBoolean::m_name);
+
+                ec->Class<GNParamInt>("Geom Nodes Property (int)", "A Geom Nodes int property")
+                    ->ClassElement(AZ::Edit::ClassElements::EditorData, "GNPropertyGroup's class attributes.")
+                        ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
+                    ->DataElement(nullptr, &GNParamInt::m_value, "m_value", "An int")
+                    ->Attribute(AZ::Edit::Attributes::ChangeNotify, &GeomNodesEditorComponent::OnParamChange)
+                    ->Attribute(AZ::Edit::Attributes::NameLabelOverride, &GNParamInt::m_name);
+
+                ec->Class<GNParamValue>("Geom Nodes Property (double)", "A Geom Nodes double property")
+                    ->ClassElement(AZ::Edit::ClassElements::EditorData, "GNPropertyGroup's class attributes.")
+                        ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
+                    ->DataElement(nullptr, &GNParamValue::m_value, "m_value", "A double/value")
+                        ->Attribute(AZ::Edit::Attributes::ChangeNotify, &GeomNodesEditorComponent::OnParamChange)
+                        ->Attribute(AZ::Edit::Attributes::NameLabelOverride, &GNParamValue::m_name);
+
+                ec->Class<GNParamString>("Geom Nodes Property (string)", "A Geom Nodes string property")
+                    ->ClassElement(AZ::Edit::ClassElements::EditorData, "GNPropertyGroup's class attributes.")
+                        ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
+                    ->DataElement(nullptr, &GNParamString::m_value, "m_value", "A string")
+                        ->Attribute(AZ::Edit::Attributes::ChangeNotify, &GeomNodesEditorComponent::OnParamChange)
+                        ->Attribute(AZ::Edit::Attributes::NameLabelOverride, &GNParamString::m_name);
+            }
+        }
+    }
+
+    void GeomNodesEditorComponent::OnPathChange(const AZStd::string& path)
+    {
+        if (!path.empty())
+        {
+            bool bClearParams = (m_instance && (!m_instance->IsValid() || !m_instance->IsSamePath(path)));
+            if (bClearParams)
+            {
+                m_paramContext.m_group.Clear();
+                ClearDataElements();
+            }
+
+            if (!m_instance || bClearParams)
+            {
+                if (m_instance)
+                    delete m_instance;
+
+                const AZ::IO::FixedMaxPath gemPath = AZ::Utils::GetGemPath("GeomNodes");
+                const AZ::IO::FixedMaxPath exePath = AZ::Utils::GetExecutableDirectory();
+                AZStd::string scriptPath = AZStd::string::format(R"(%s\External\Scripts\__init__.py)", gemPath.c_str());
+
+                m_instance = new GNInstance;
+                m_instance->Init(path, scriptPath, exePath.c_str(), GetEntityId());
+                m_initialized = false;
+                if (m_instance->IsValid())
+                {
+                    //AZ::EntityId entityId = AZ::EntityId(123456);
+                    auto entityId = GetEntityId();
+                    Ipc::IpcHandlerNotificationBus::Handler::BusConnect(entityId);
+                }
+            }
+        }
+    }
+
+    void GeomNodesEditorComponent::OnParamChange()
+    {
+        auto gnParam = reinterpret_cast<GNParamString*>(m_paramContext.m_group.GetProperty(Field::Objects));
+        if (gnParam->m_value != m_currentObject)
+        {
+            m_currentObject = gnParam->m_value;
+            m_paramContext.m_group.Clear(); // clear the group/properties
+            CreateDataElements(m_paramContext.m_group);
+
+            // TODO: send a message to blender since we need to update the object rendered
+        }
+        else
+        {
+            if(m_instance && !m_instance->IsValid())
+            {
+                m_instance->RestartProcess();
+            }
+
+            auto msg = AZStd::string::format(
+                R"JSON(
+                    {
+                        "%s": [ %s ],
+                        "%s": "%s",
+                        "Frame": 0,
+                        "ParamUpdate": true
+                    }
+                )JSON"
+                , Field::Params
+                , m_paramContext.m_group
+                    .GetGroup(m_currentObject.c_str())
+                    ->GetProperties().c_str()
+                , Field::Object
+                , m_currentObject.c_str()
+            );
+
+            m_instance->SendIPCMsg(msg);
+        }
+
+        AZ_Printf("GeomNodesEditorComponent", "Parameter has changed");
+    }
+
+    void GeomNodesEditorComponent::OnMessageReceived(const AZ::u8* content, const AZ::u64 length)
+    {
+        rapidjson::Document jsonDocument;
+        jsonDocument.Parse((const char*)content, length);
+        if (!jsonDocument.HasParseError())
+        {
+            //TODO: need to put these hard coded text into one place
+            if (jsonDocument.HasMember(Field::Initialized))
+            {
+                AZStd::string msg;
+                if (!m_initialized)
+                {
+                    msg = R"JSON(
+                        {
+                            "FetchObjectParams": true 
+                        }
+                    )JSON";
+                    m_initialized = true;
+                }
+                else
+                {
+                    // send messages that are queued.
+                }
+                m_instance->SendIPCMsg(msg);
+            }
+            else if (jsonDocument.HasMember(Field::ObjectNames) && jsonDocument.HasMember(Field::Objects))
+            {
+                LoadObjects(jsonDocument[Field::ObjectNames], jsonDocument[Field::Objects]);
+                CreateDataElements(m_paramContext.m_group);
+
+                // Send message for fetching the 3D data
+                // Will just call OnParamChange since it's basically the same request
+                OnParamChange();
+            }
+            else if (jsonDocument.HasMember(Field::SHMOpen) && jsonDocument.HasMember(Field::MapId))
+            {
+                
+                AZ::u64 mapId = jsonDocument[Field::MapId].GetInt64();
+                GNModelData modelData(mapId);
+                auto msg = AZStd::string::format(
+                    R"JSON(
+                    {
+                        "%s": true,
+                        "%s": %llu
+                    }
+                    )JSON",
+                    Field::SHMClose,
+                    Field::MapId,
+                    mapId);
+                m_instance->SendIPCMsg(msg);
+
+                if (m_renderModel.get() == nullptr)
+                {
+                    m_renderModel = AZStd::make_unique<GNRenderModel>(GetEntityId());
+                }
+
+                m_renderModel->QueueBuildMeshes(modelData);
+            }
+        }
+        else
+        {
+            AZ_Warning("GeomNodesEditorComponent", false, "Message is not in JSON format!");
+        }
+    }
+
+    void GeomNodesEditorComponent::LoadObjects(const rapidjson::Value& objectNameArray, const rapidjson::Value& objectArray)
+    {
+        
+        // Populate m_enumValues that will store the list of object names
+        LoadObjectNames(objectNameArray);
+        
+        // Load and save our param list object from json. Need this so it's faster to switch between objects and not need to send a request via IPC.
+        LoadParams(objectArray);
+
+        // TODO: load from save point if any
+        m_currentObject = m_enumValues[0];
+    }
+
+    void GeomNodesEditorComponent::LoadObjectNames(const rapidjson::Value& objectNames)
+    {
+        AZ_Assert(objectNames.IsArray(), "Passed JSON Value is not an array!");
+
+        uint32_t idx = 0;
+        m_enumValues.clear();
+        for (rapidjson::Value::ConstValueIterator itr = objectNames.Begin(); itr != objectNames.End(); ++itr, idx++)
+        {
+            m_enumValues.push_back(itr->GetString());
+        }
+
+        AZ_Assert(!m_enumValues.empty(), "No Object found! There should be at least one.");
+    }
+
+    void GeomNodesEditorComponent::LoadParams(const rapidjson::Value& objectArray)
+    {
+        for (rapidjson::Value::ConstValueIterator itr = objectArray.Begin(); itr != objectArray.End(); ++itr)
+        {
+            const char* objectName = (*itr)[Field::Object].GetString();
+
+            rapidjson::StringBuffer jsonDataBuffer;
+            rapidjson::PrettyWriter<rapidjson::StringBuffer> jsonDatawriter(jsonDataBuffer);
+            (*itr).Accept(jsonDatawriter);
+
+            m_objectInfos.insert(AZStd::make_pair(objectName, jsonDataBuffer.GetString()));
+        }
+    }
+
+    void GeomNodesEditorComponent::CreateDataElements(GNPropertyGroup& group)
+    {
+        ClearDataElements();
+
+        // Create the combo box that will show the object names.
+        CreateObjectNames(m_enumValues, group);
+
+        // Create the currently selected Object parameters and attributes. Load only the first or saved object.
+        CreateParam(m_currentObject, group);
+
+        EBUS_EVENT(AzToolsFramework::ToolsApplicationEvents::Bus, InvalidatePropertyDisplay, AzToolsFramework::Refresh_EntireTree);
+        AzToolsFramework::ToolsApplicationRequests::Bus::Broadcast(
+            &AzToolsFramework::ToolsApplicationRequests::Bus::Events::AddDirtyEntity, GetEntityId());
+    }
+
+    void GeomNodesEditorComponent::CreateObjectNames(const StringVector& enumValues, GNPropertyGroup& group)
+    {
+        ElementInfo ei;
+        ei.m_editData.m_name = CacheString(Field::Objects);
+        ei.m_editData.m_description = "";
+        ei.m_editData.m_elementId = AZ::Edit::UIHandlers::ComboBox;
+        ei.m_sortOrder = FLT_MAX;
+
+        auto gnParam = aznew GNParamString(Field::Objects, "");
+        gnParam->m_value = m_currentObject;
+        
+        ei.m_editData.m_attributes.push_back(
+            AZ::Edit::AttributePair(AZ::Edit::Attributes::StringList, aznew AZ::AttributeContainerType<StringVector>(enumValues)));
+
+        group.m_properties.emplace_back(gnParam);
+
+        AddDataElement(gnParam, ei);
+    }
+
+    void GeomNodesEditorComponent::CreateParam(const AZStd::string& objectName, GNPropertyGroup& group)
+    {
+        auto it = m_objectInfos.find(objectName);
+        if (it != m_objectInfos.end())
+        {
+            rapidjson::Document jsonDocument;
+            jsonDocument.Parse((const char*)it->second.c_str(), it->second.size());
+            if (!jsonDocument.HasParseError())
+            {
+                GNPropertyGroup* subGroup = group.GetGroup(objectName.c_str());
+                if (subGroup == nullptr)
+                {
+                    group.m_groups.emplace_back();
+                    subGroup = &group.m_groups.back();
+                    subGroup->m_name = objectName;
+                }
+                LoadProperties(jsonDocument[Field::Params], *subGroup);
+            }
+        }
+    }
+
+    bool GeomNodesEditorComponent::LoadProperties(const rapidjson::Value& paramVal, GNPropertyGroup& group)
+    {
+        // parse params
+        for (rapidjson::Value::ConstValueIterator itr = paramVal.Begin(); itr != paramVal.End(); ++itr)
+        {
+            //set this up so the context can do it's own parsing of the current GN param JSON object.
+            GNParamDataContext gndc;
+            gndc.SetParamObject(itr);
+
+            auto propertyName = gndc.GetParamName();
+            auto paramType = gndc.GetParamType();
+                
+            // default value will differ based on the type
+
+            if (GNProperty* gnParam = m_paramContext.ConstructGNParam(gndc, paramType, propertyName))
+            {
+                group.m_properties.emplace_back(gnParam);
+
+                ElementInfo ei;
+                ei.m_editData.m_name = CacheString(propertyName);
+                ei.m_editData.m_description = "";
+                ei.m_editData.m_elementId = AZ::Edit::UIHandlers::Default;
+                ei.m_sortOrder = FLT_MAX;
+
+                // Load any attributes
+                LoadAttribute(paramType, ei.m_editData, group.m_properties.back());
+
+                AddDataElement(gnParam, ei);
+            }
+            else
+            {
+                AZ_Warning("GeomNodes", false, "We support only boolean, number, and string as properties %s!", propertyName);
+            }
+        }
+
+        //TODO: parse materials
+
+        //// Remove all old properties, every confirmed property will have
+        //// a corresponding Element data
+        // RemovedOldProperties(m_scriptComponent.m_properties);
+
+        // SortProperties(m_scriptComponent.m_properties);
+        
+        return true;
+    }
+
+    void GeomNodesEditorComponent::LoadAttribute(ParamType type, AZ::Edit::ElementData& ed, GNProperty* prop)
+    {
+        switch (type)
+        {
+        case ParamType::Int:
+            if (prop->m_isMinSet)
+            {
+                int value = ((GNParamInt*)prop)->m_min;
+                ed.m_attributes.push_back(AZ::Edit::AttributePair(AZ::Crc32("min"), aznew AZ::Edit::AttributeData<int>(value)));
+            }
+
+            if (prop->m_isMaxSet)
+            {
+                int value = ((GNParamInt*)prop)->m_max;
+                ed.m_attributes.push_back(AZ::Edit::AttributePair(AZ::Crc32("max"), aznew AZ::Edit::AttributeData<int>(value)));
+            }
+            break;
+        case ParamType::Value:
+            if (prop->m_isMinSet)
+            {
+                double value = ((GNParamValue*)prop)->m_min;
+                ed.m_attributes.push_back(AZ::Edit::AttributePair(AZ::Crc32("min"), aznew AZ::Edit::AttributeData<double>(value)));
+            }
+
+            if (prop->m_isMaxSet)
+            {
+                double value = ((GNParamValue*)prop)->m_max;
+                ed.m_attributes.push_back(AZ::Edit::AttributePair(AZ::Crc32("max"), aznew AZ::Edit::AttributeData<double>(value)));
+            }
+            break;
+        }
+    }
+
+    void GeomNodesEditorComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
+    {
+        provided.push_back(AZ_CRC_CE("GeomNodesEditorSerivce"));
+    }
+
+    void GeomNodesEditorComponent::GetIncompatibleServices(
+        [[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& incompatible)
+    {
+        incompatible.push_back(AZ_CRC("GeomNodesEditorSerivce"));
+    }
+
+    void GeomNodesEditorComponent::Init()
+    {
+    }
+
+    void GeomNodesEditorComponent::Activate()
+    {
+        AZ::TransformNotificationBus::Handler::BusConnect(GetEntityId());
+    }
+
+    void GeomNodesEditorComponent::Deactivate()
+    {
+        // BUG: this gets called when a component is added so deal with it properly as it destroys any current instance we have.
+        Clear();
+
+        if (m_instance)
+        {
+            delete m_instance;
+            m_instance = nullptr;
+        }
+    }
+
+    void GeomNodesEditorComponent::Clear()
+    {
+        m_enumValues.clear();
+        ClearDataElements();
+        AZ::TransformNotificationBus::Handler::BusDisconnect();
+        Ipc::IpcHandlerNotificationBus::Handler::BusDisconnect(GetEntityId());
+        m_renderModel.reset();
+        
+    }
+
+    const AZ::Edit::ElementData* GeomNodesEditorComponent::GetParamsEditData(
+        const void* handlerPtr, const void* elementPtr, const AZ::Uuid& elementType)
+    {
+        const GeomNodesEditorComponent* owner = reinterpret_cast<const GeomNodesEditorComponent*>(handlerPtr);
+        return owner->GetDataElement(elementPtr, elementType);
+    }
+
+    void GeomNodesEditorComponent::AddDataElement(GNProperty* gnParam, ElementInfo& ei)
+    {
+        // add the attributes to the map of default values
+        ei.m_uuid = gnParam->GetDataTypeUuid();
+        ei.m_isAttributeOwner = true;
+        m_dataElements.insert(AZStd::make_pair(gnParam->GetDataAddress(), ei));
+
+        // Also register to the script property itself, so friendly data can be displayed at its own level.
+        ei.m_uuid = azrtti_typeid(gnParam);
+        ei.m_isAttributeOwner = false;
+        m_dataElements.insert(AZStd::make_pair(gnParam, ei));
+    }
+
+    const char* GeomNodesEditorComponent::CacheString(const char* str)
+    {
+        if (str == nullptr)
+        {
+            return nullptr;
+        }
+
+        return m_cachedStrings.insert(AZStd::make_pair(str, AZStd::string(str))).first->second.c_str();
+    }
+
+    void GeomNodesEditorComponent::ClearDataElements()
+    {
+        for (auto it = m_dataElements.begin(); it != m_dataElements.end(); ++it)
+        {
+            if (it->second.m_isAttributeOwner)
+            {
+                it->second.m_editData.ClearAttributes();
+            }
+        }
+
+        m_dataElements.clear();
+
+        // The display tree might still be holding onto pointers to our attributes that we just cleared above, so force a refresh to remove
+        // them. However, only force the refresh if we have a valid entity.  If we don't have an entity, this component isn't currently
+        // being shown or edited, so a refresh is at best superfluous, and at worst could cause a feedback loop of infinite refreshes.
+        if (GetEntity())
+        {
+            AzToolsFramework::ToolsApplicationEvents::Bus::Broadcast(
+                &AzToolsFramework::ToolsApplicationEvents::InvalidatePropertyDisplay, AzToolsFramework::Refresh_EntireTree);
+        }
+    }
+
+    const AZ::Edit::ElementData* GeomNodesEditorComponent::GetDataElement(
+        [[maybe_unused]] const void* element, [[maybe_unused]] const AZ::Uuid& typeUuid) const
+    {
+        auto it = m_dataElements.find(element);
+        if (it != m_dataElements.end())
+        {
+            if (it->second.m_uuid == typeUuid)
+            {
+                return &it->second.m_editData;
+            }
+        }
+        return nullptr;
+    }
+
+    void GeomNodesEditorComponent::OnTransformChanged([[maybe_unused]] const AZ::Transform& local, [[maybe_unused]] const AZ::Transform& world)
+    {
+       //TODO:
+    }
+}

+ 94 - 0
Gems/O3DE/GeomNodes/Code/Source/Editor/Components/GeomNodesEditorComponent.h

@@ -0,0 +1,94 @@
+#pragma once
+
+#include <AzToolsFramework/ToolsComponents/EditorComponentBase.h>
+#include <AzCore/Serialization/EditContext.h>
+#include "Editor/Systems/GNInstance.h"
+#include "Editor/Systems/GNParamContext.h"
+#include <Editor/EBus/IpcHandlerBus.h>
+#include <AzCore/Component/TransformBus.h>
+#include <Editor/Rendering/GNRenderModel.h>
+
+namespace GeomNodes
+{
+    class GeomNodesEditorComponent
+        : public AzToolsFramework::Components::EditorComponentBase
+        , private Ipc::IpcHandlerNotificationBus::Handler
+        , private AZ::TransformNotificationBus::Handler
+    {
+    public:
+        AZ_EDITOR_COMPONENT(GeomNodesEditorComponent, "{E59507EF-9EBB-4F6C-8D89-92DCA57722E5}");
+
+        static void Reflect(AZ::ReflectContext* context);
+
+        static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
+
+        static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
+
+        GeomNodesEditorComponent();
+        virtual ~GeomNodesEditorComponent();
+
+        void Init() override;
+        void Activate() override;
+        void Deactivate() override;
+
+    protected:
+        //got this from ScriptEditorComponent
+        struct ElementInfo
+        {
+            AZ::Uuid m_uuid;                    // Type uuid for the class field that should use this edit data.
+            AZ::Edit::ElementData m_editData;   // Edit metadata (name, description, attribs, etc).
+            bool m_isAttributeOwner;            // True if this ElementInfo owns the internal attributes. We can use a single
+                                                // ElementInfo for more than one class field, but only one owns the Attributes.
+            float m_sortOrder;                  // Sort order of the property as defined by using the "order" attribute, by default the order is FLT_MAX
+                                                // which means alphabetical sort will be used
+        };
+
+        typedef AZStd::vector<AZStd::string> StringVector;
+        
+        void Clear();
+        void OnPathChange(const AZStd::string& path);
+        void OnParamChange();
+        void OnMessageReceived(const AZ::u8* content, const AZ::u64 length) override;
+
+        void LoadObjects(const rapidjson::Value& objectNameArray, const rapidjson::Value& objectArray);
+        void LoadObjectNames(const rapidjson::Value& objectNames);
+        void LoadParams(const rapidjson::Value& objectArray);
+        void CreateDataElements(GNPropertyGroup& group);
+        void CreateObjectNames(const StringVector& enumValues, GNPropertyGroup& group);
+        void CreateParam(const AZStd::string& objectName, GNPropertyGroup& group);
+        bool LoadProperties(const rapidjson::Value& paramVal, GNPropertyGroup& group);
+        void LoadAttribute(ParamType type, AZ::Edit::ElementData& ed, GNProperty* prop);
+
+        void ClearDataElements();
+
+        const AZ::Edit::ElementData* GetDataElement(const void* element, const AZ::Uuid& typeUuid) const;
+
+        static const AZ::Edit::ElementData* GetParamsEditData(
+            const void* handlerPtr, const void* elementPtr, const AZ::Uuid& elementType);
+
+        void AddDataElement(GNProperty* gnParam, ElementInfo& ei);
+
+        const char* CacheString(const char* str);
+
+        void OnTransformChanged(const AZ::Transform& local, const AZ::Transform& world) override;
+        
+        AZStd::unordered_map<const void*, AZStd::string> m_cachedStrings;
+
+        AZStd::unordered_map<const void*, ElementInfo> m_dataElements;
+
+        AZStd::unordered_map<AZStd::string, AZStd::string> m_objectInfos;
+
+        StringVector m_enumValues;
+
+        GNParamContext m_paramContext;
+
+        AZStd::string m_blenderFile;
+        AZStd::string m_currentObject;
+
+        GNInstance* m_instance = nullptr;
+
+        AZStd::unique_ptr<GNRenderModel> m_renderModel;
+
+        bool m_initialized = false;
+    };
+}

+ 56 - 0
Gems/O3DE/GeomNodes/Code/Source/Editor/Components/GeomNodesEditorSystemComponent.cpp

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

+ 36 - 0
Gems/O3DE/GeomNodes/Code/Source/Editor/Components/GeomNodesEditorSystemComponent.h

@@ -0,0 +1,36 @@
+
+#pragma once
+
+#include <AzToolsFramework/API/ToolsApplicationAPI.h>
+
+#include <GeomNodes/Components/GeomNodesSystemComponent.h>
+#include "Editor/Systems/GeomNodesSystem.h"
+
+namespace GeomNodes
+{
+    /// System component for GeomNodes editor
+    class GeomNodesEditorSystemComponent
+        : public GeomNodesSystemComponent
+        , protected AzToolsFramework::EditorEvents::Bus::Handler
+    {
+        using BaseSystemComponent = GeomNodesSystemComponent;
+    public:
+        AZ_COMPONENT(GeomNodesEditorSystemComponent, "{3B2D4C6C-C359-4890-8D78-0DDA7864131C}", BaseSystemComponent);
+        static void Reflect(AZ::ReflectContext* context);
+
+        GeomNodesEditorSystemComponent();
+        ~GeomNodesEditorSystemComponent();
+
+    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;
+
+        GeomNodesSystem m_system;
+    };
+} // namespace GeomNodes

+ 33 - 0
Gems/O3DE/GeomNodes/Code/Source/Editor/EBus/GeomNodesBus.h

@@ -0,0 +1,33 @@
+#pragma once
+
+#include <AzCore/EBus/EBus.h>
+#include <AzCore/std/smart_ptr/unique_ptr.h>
+
+// This is similar to the WhiteBox implementation
+namespace GeomNodes
+{
+    class GNRenderMeshInterface;
+
+    //! Function object alias for creating a GNRenderMeshInterface.
+    //! @note Used by SetGNRenderMeshInterfaceBuilder in GeomNodesRequests.
+    using GNRenderMeshInterfaceBuilderFn = AZStd::function<AZStd::unique_ptr<GNRenderMeshInterface>(AZ::EntityId)>;
+
+    //! system level requests.
+    class GeomNodesRequests : public AZ::EBusTraits
+    {
+    public:
+        // EBusTraits overrides ...
+        static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
+        static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
+
+        //! Create a render mesh for use with geometry nodes data.
+        virtual AZStd::unique_ptr<GNRenderMeshInterface> CreateGNRenderMeshInterface(AZ::EntityId) = 0;
+        //! Control what concrete implementation of GNRenderMeshInterface CreateGNRenderMeshInterface returns.
+        virtual void SetGNRenderMeshInterfaceBuilder(GNRenderMeshInterfaceBuilderFn builder) = 0;
+
+    protected:
+        ~GeomNodesRequests() = default;
+    };
+
+    using WhiteBoxRequestBus = AZ::EBus<GeomNodesRequests>;
+} // namespace GeomNodes

+ 22 - 0
Gems/O3DE/GeomNodes/Code/Source/Editor/EBus/IpcHandlerBus.h

@@ -0,0 +1,22 @@
+#pragma once
+
+#include <AzCore/EBus/EBus.h>
+
+namespace Ipc
+{
+    class IpcHandlerNotifications : public AZ::EBusTraits
+    {
+    public:
+        //////////////////////////////////////////////////////////////////////////
+        // EBusTraits overrides
+        static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
+        using BusIdType = AZ::EntityId;
+        //////////////////////////////////////////////////////////////////////////
+
+        virtual ~IpcHandlerNotifications() {}
+
+        virtual void OnMessageReceived(const AZ::u8* content, const AZ::u64 length) = 0;
+    };
+
+    using IpcHandlerNotificationBus = AZ::EBus<IpcHandlerNotifications>;
+}

+ 23 - 0
Gems/O3DE/GeomNodes/Code/Source/Editor/EBus/ValidatorBus.h

@@ -0,0 +1,23 @@
+#pragma once
+
+#include "Editor/UI/FunctorValidator.h"
+
+#include <AzCore/EBus/EBus.h>
+
+namespace GeomNodes
+{
+    class ValidatorTraits
+        : public AZ::EBusTraits
+    {
+    public:
+        using Bus = AZ::EBus<ValidatorTraits>;
+
+        // Bus Configuration
+        static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
+
+        virtual FunctorValidator* GetValidator(FunctorValidator::FunctorType) = 0;
+        virtual void TrackValidator(FunctorValidator*) = 0;
+    };
+
+    typedef AZ::EBus<ValidatorTraits> ValidatorBus;
+} // namespace GeomNodes

+ 108 - 0
Gems/O3DE/GeomNodes/Code/Source/Editor/Math/MathHelper.cpp

@@ -0,0 +1,108 @@
+#include <Editor/Math/MathHelper.h>
+
+namespace GeomNodes
+{
+    constexpr auto SMALL_NUMBER = (1.e-8f);
+    AZ::Vector3 MathHelper::ExtractScalingFromMatrix44(AZ::Matrix4x4& m)
+    {
+        AZ::Vector3 Scale3D(0, 0, 0);
+
+        // For each row, find magnitude, and if its non-zero re-scale so its unit length.
+        const float SquareSum0 = (m.GetElement(0, 0) * m.GetElement(0, 0)) + (m.GetElement(0, 1) * m.GetElement(0, 1)) +
+            (m.GetElement(0, 2) * m.GetElement(0, 2));
+        const float SquareSum1 = (m.GetElement(1, 0) * m.GetElement(1, 0)) + (m.GetElement(1, 1) * m.GetElement(1, 1)) +
+            (m.GetElement(1, 2) * m.GetElement(1, 2));
+        const float SquareSum2 = (m.GetElement(2, 0) * m.GetElement(2, 0)) + (m.GetElement(2, 1) * m.GetElement(2, 1)) +
+            (m.GetElement(2, 2) * m.GetElement(2, 2));
+
+        if (SquareSum0 > SMALL_NUMBER)
+        {
+            float Scale0 = AZ::Sqrt(SquareSum0);
+            Scale3D.SetElement(0, Scale0);
+            float InvScale0 = 1.f / Scale0;
+            m.SetElement(0, 0, m.GetElement(0, 0) * InvScale0);
+            m.SetElement(0, 1, m.GetElement(0, 1) * InvScale0);
+            m.SetElement(0, 2, m.GetElement(0, 2) * InvScale0);
+        }
+        else
+        {
+            Scale3D.SetElement(0, 0);
+        }
+
+        if (SquareSum1 > SMALL_NUMBER)
+        {
+            float Scale1 = AZ::Sqrt(SquareSum1);
+            Scale3D.SetElement(1, Scale1);
+            float InvScale1 = 1.f / Scale1;
+
+            m.SetElement(1, 0, m.GetElement(1, 0) * InvScale1);
+            m.SetElement(1, 1, m.GetElement(1, 1) * InvScale1);
+            m.SetElement(1, 2, m.GetElement(1, 2) * InvScale1);
+        }
+        else
+        {
+            Scale3D.SetElement(1, 0);
+        }
+
+        if (SquareSum2 > SMALL_NUMBER)
+        {
+            float Scale2 = AZ::Sqrt(SquareSum2);
+            Scale3D.SetElement(2, Scale2);
+            float InvScale2 = 1.f / Scale2;
+
+            m.SetElement(2, 0, m.GetElement(2, 0) * InvScale2);
+            m.SetElement(2, 1, m.GetElement(2, 1) * InvScale2);
+            m.SetElement(2, 2, m.GetElement(2, 2) * InvScale2);
+        }
+        else
+        {
+            Scale3D.SetElement(2, 0);
+        }
+
+        return Scale3D;
+    }
+        
+    AZ::Matrix4x4 MathHelper::ConvertTransformAndScaleToMat4(const AZ::Transform& transform, const AZ::Vector3& nonUniformScale)
+    {
+        const AZ::Vector3& o3deTranslation = transform.GetTranslation();
+        const AZ::Quaternion& o3deRotation = transform.GetRotation();
+        AZ::Vector3 newScale = transform.GetUniformScale() * nonUniformScale;
+
+        AZ::Matrix4x4 newTransform;
+        newTransform.SetTranslation(o3deTranslation);
+        newTransform.SetRotationPartFromQuaternion(o3deRotation);
+        newTransform.MultiplyByScale(newScale);
+        
+        return newTransform;
+    }
+
+    std::size_t MathHelper::Align(std::size_t location, std::size_t align)
+    {
+        //AZ_Assert(((0 != align) && !(align & (align - 1))), "non-power of 2 alignment");
+        return ((location + (align - 1)) & ~(align - 1));
+    }
+    AZ::Vector2 MathHelper::Vec2fToVec2(const Vector2f& vec)
+    {
+        return AZ::Vector2(vec[0], vec[1]);
+    }
+    AZ::Vector3 MathHelper::Vec3fToVec3(const Vector3f& vec)
+    {
+        return AZ::Vector3(vec[0], vec[1], vec[2]);
+    }
+    AZ::Vector4 MathHelper::Vec4fToVec4(const Vector4f& vec)
+    {
+        return AZ::Vector4(vec[0], vec[1], vec[2], vec[3]);
+    }
+    Vector2f MathHelper::Vec2ToVec2f(const AZ::Vector2& vec)
+    {
+        return Vector2f{ vec.GetX(), vec.GetY() };
+    }
+    Vector3f MathHelper::Vec3ToVec3f(const AZ::Vector3& vec)
+    {
+        return Vector3f{ vec.GetX(), vec.GetY(), vec.GetZ() };
+    }
+    Vector4f MathHelper::Vec4ToVec4f(const AZ::Vector4& vec)
+    {
+        return Vector4f{ vec.GetX(), vec.GetY(), vec.GetZ(), vec.GetW() };
+    }
+}

+ 54 - 0
Gems/O3DE/GeomNodes/Code/Source/Editor/Math/MathHelper.h

@@ -0,0 +1,54 @@
+#pragma once
+
+#include <AzCore/Math/Transform.h>
+#include <AzCore/Math/Vector3.h>
+#include <AzCore/Math/Vector4.h>
+#include <AzCore/Math/Quaternion.h>
+#include <AzCore/Math/Matrix4x4.h>
+#include <AzCore/std/containers/vector.h>
+#include <AzCore/Utils/TypeHash.h>
+
+namespace GeomNodes
+{
+    using Vector4f = AZStd::array<float, 4>;
+    using Vector3f = AZStd::array<float, 3>;
+    using Vector2f = AZStd::array<float, 2>;
+
+    using U32Vector = AZStd::vector<AZ::u32>;
+    using S32Vector = AZStd::vector<AZ::s32>;
+    using S64Vector = AZStd::vector<AZ::s64>;
+    using Vert2Vector = AZStd::vector<Vector2f>;
+    using Vert3Vector = AZStd::vector<Vector3f>;
+    using Vert4Vector = AZStd::vector<Vector4f>;
+    using Mat4Vector = AZStd::vector<AZ::Matrix4x4>;
+
+    using UniqueKey = AZStd::tuple<AZ::s32, Vector3f, Vector2f, Vector4f>;
+
+    struct MathHelper
+    {
+        static AZ::Vector3 ExtractScalingFromMatrix44(AZ::Matrix4x4& m);
+        static AZ::Matrix4x4 ConvertTransformAndScaleToMat4(const AZ::Transform& transform, const AZ::Vector3& nonUniformScale);
+        static std::size_t Align(std::size_t location, std::size_t align);
+        static AZ::Vector2 Vec2fToVec2(const Vector2f& vec);
+        static AZ::Vector3 Vec3fToVec3(const Vector3f& vec);
+        static AZ::Vector4 Vec4fToVec4(const Vector4f& vec);
+        static Vector2f Vec2ToVec2f(const AZ::Vector2& vec);
+        static Vector3f Vec3ToVec3f(const AZ::Vector3& vec);
+        static Vector4f Vec4ToVec4f(const AZ::Vector4& vec);
+    };
+} // namespace GeomNodes
+
+namespace AZStd
+{
+    template<>
+    struct hash<GeomNodes::UniqueKey>
+    {
+        typedef GeomNodes::UniqueKey argument_type;
+        typedef AZStd::size_t result_type;
+        AZ_FORCE_INLINE size_t operator()(const GeomNodes::UniqueKey& id) const
+        {
+            AZStd::hash<AZ::u32> hasher;
+            return hasher(static_cast<AZ::u32>(AZ::TypeHash32(id)));
+        }
+    };
+} // namespace AZStd

+ 41 - 0
Gems/O3DE/GeomNodes/Code/Source/Editor/Modules/GeomNodesEditorModule.cpp

@@ -0,0 +1,41 @@
+
+#include <GeomNodesModuleInterface.h>
+#include "Editor/Components/GeomNodesEditorSystemComponent.h"
+#include "Editor/Components/GeomNodesEditorComponent.h"
+
+namespace GeomNodes
+{
+    class GeomNodesEditorModule
+        : public GeomNodesModuleInterface
+    {
+    public:
+        AZ_RTTI(GeomNodesEditorModule, "{49C42A73-EF4E-4D42-8ECF-0ADE7F942CCD}", GeomNodesModuleInterface);
+        AZ_CLASS_ALLOCATOR(GeomNodesEditorModule, AZ::SystemAllocator, 0);
+
+        GeomNodesEditorModule()
+        {
+            // 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(), {
+                    GeomNodesEditorSystemComponent::CreateDescriptor(),
+                    GeomNodesEditorComponent::CreateDescriptor(),
+            });
+        }
+
+        /**
+         * Add required SystemComponents to the SystemEntity.
+         * Non-SystemComponents should not be added here
+         */
+        AZ::ComponentTypeList GetRequiredSystemComponents() const override
+        {
+            return AZ::ComponentTypeList {
+                azrtti_typeid<GeomNodesEditorSystemComponent>(),
+                azrtti_typeid<GeomNodesEditorComponent>(),
+            };
+        }
+    };
+}// namespace GeomNodes
+
+AZ_DECLARE_MODULE_CLASS(Gem_GeomNodes, GeomNodes::GeomNodesEditorModule)

+ 207 - 0
Gems/O3DE/GeomNodes/Code/Source/Editor/Rendering/Atom/GNAttributeBuffer.h

@@ -0,0 +1,207 @@
+#pragma once
+
+#include "GNBuffer.h"
+
+#include <Atom/RHI.Reflect/ShaderSemantic.h>
+#include <Atom/RPI.Reflect/Model/ModelLodAssetCreator.h>
+
+namespace GeomNodes
+{
+    //! Attributes for mesh vertices.
+    enum class AttributeType
+    {
+        Position,
+        Normal,
+        Tangent,
+        Bitangent,
+        UV,
+        Color
+    };
+
+    //! The number of attributes required by the mesh.
+    inline constexpr uint32_t NumAttributes = 6;
+
+    //! Trait to describe mesh vertex attribute format.
+    template<AttributeType AttributeTypeT>
+    struct AttributeTrait
+    {
+    };
+
+    //! Attribute trait specialization for vertex position attribute.
+    template<>
+    struct AttributeTrait<AttributeType::Position>
+    {
+        static constexpr const char* ShaderSemantic = "POSITION";
+        using BufferType = Vector3Buffer;
+    };
+
+    //! Attribute trait specialization for vertex normal attribute
+    template<>
+    struct AttributeTrait<AttributeType::Normal>
+    {
+        static constexpr const char* ShaderSemantic = "NORMAL";
+        using BufferType = Vector3Buffer;
+    };
+
+    //! Attribute trait specialization for vertex tangent attribute.
+    template<>
+    struct AttributeTrait<AttributeType::Tangent>
+    {
+        static constexpr const char* ShaderSemantic = "TANGENT";
+        using BufferType = Vector4Buffer;
+    };
+
+    //! Attribute trait specialization for vertex bitangent attribute.
+    template<>
+    struct AttributeTrait<AttributeType::Bitangent>
+    {
+        static constexpr const char* ShaderSemantic = "BITANGENT";
+        using BufferType = Vector3Buffer;
+    };
+
+    //! Attribute trait specialization for vertex uv attribute.
+    template<>
+    struct AttributeTrait<AttributeType::UV>
+    {
+        static constexpr const char* ShaderSemantic = "UV";
+        using BufferType = Vector2Buffer;
+    };
+
+    //! Attribute trait specialization for vertex color attribute.
+    template<>
+    struct AttributeTrait<AttributeType::Color>
+    {
+        static constexpr const char* ShaderSemantic = "COLOR";
+        using BufferType = Vector4Buffer;
+    };
+
+    //! Buffer to hold mesh vertex attribute data.
+    template<AttributeType AttributeTypeT>
+    class AttributeBuffer
+    {
+    public:
+        using Trait = AttributeTrait<AttributeTypeT>;
+
+        //! Construct a new Attribute Buffer object from the specified data.
+        template<typename VertexStreamDataType>
+        AttributeBuffer(const AZStd::vector<VertexStreamDataType>& data);
+
+        //! Retrieves the buffer asset.
+        const AZ::Data::Asset<AZ::RPI::BufferAsset>& GetBuffer() const;
+
+        //! Retrieves the buffer view descriptor.
+        const AZ::RHI::BufferViewDescriptor& GetBufferViewDescriptor() const;
+
+        //! Retrieves the buffer asset view.
+        const AZ::RPI::BufferAssetView& GetBufferAssetView() const;
+
+        //! Retrieves the attribute's shader semantic.
+        const AZ::RHI::ShaderSemantic& GetShaderSemantic() const;
+
+        //! Adds this attribute buffer to the Lod.
+        void AddLodStreamBuffer(AZ::RPI::ModelLodAssetCreator& modelLodCreator) const;
+
+        //! Adds this attribute buffer to the mesh.
+        void AddMeshStreamBuffer(AZ::RPI::ModelLodAssetCreator& modelLodCreator) const;
+
+        //! Returns true of the attribute buffer is valid, otherwise false.
+        bool IsValid() const;
+
+        //! Update the attribute buffer contents with the new data.
+        template<typename VertexStreamDataType>
+        bool UpdateData(const AZStd::vector<VertexStreamDataType>& data);
+
+    private:
+        typename Trait::BufferType m_buffer;
+        AZ::RHI::ShaderSemantic m_shaderSemantic;
+    };
+
+    template<AttributeType AttributeTypeT>
+    template<typename VertexStreamDataType>
+    AttributeBuffer<AttributeTypeT>::AttributeBuffer(const AZStd::vector<VertexStreamDataType>& data)
+        : m_buffer(data)
+        , m_shaderSemantic(AZ::Name(Trait::ShaderSemantic))
+    {
+        if (!IsValid())
+        {
+            AZ_Error(
+                "AttributeBuffer", false, "Couldn't create buffer for attribute %s",
+                m_shaderSemantic.ToString().c_str());
+        }
+    }
+
+    template<AttributeType AttributeTypeT>
+    const AZ::Data::Asset<AZ::RPI::BufferAsset>& AttributeBuffer<AttributeTypeT>::GetBuffer() const
+    {
+        return m_buffer.GetBuffer();
+    }
+
+    template<AttributeType AttributeTypeT>
+    const AZ::RHI::BufferViewDescriptor& AttributeBuffer<AttributeTypeT>::GetBufferViewDescriptor() const
+    {
+        return m_buffer.GetBufferViewDescriptor();
+    }
+
+    template<AttributeType AttributeTypeT>
+    const AZ::RPI::BufferAssetView& AttributeBuffer<AttributeTypeT>::GetBufferAssetView() const
+    {
+        return m_buffer.GetBufferAssetView();
+    }
+
+    template<AttributeType AttributeTypeT>
+    const AZ::RHI::ShaderSemantic& AttributeBuffer<AttributeTypeT>::GetShaderSemantic() const
+    {
+        return m_shaderSemantic;
+    }
+
+    template<AttributeType AttributeTypeT>
+    void AttributeBuffer<AttributeTypeT>::AddLodStreamBuffer(AZ::RPI::ModelLodAssetCreator& modelLodCreator) const
+    {
+        modelLodCreator.AddLodStreamBuffer(GetBuffer());
+    }
+
+    template<AttributeType AttributeTypeT>
+    void AttributeBuffer<AttributeTypeT>::AddMeshStreamBuffer(AZ::RPI::ModelLodAssetCreator& modelLodCreator) const
+    {
+        modelLodCreator.AddMeshStreamBuffer(GetShaderSemantic(), AZ::Name(), GetBufferAssetView());
+    }
+
+    template<AttributeType AttributeTypeT>
+    bool AttributeBuffer<AttributeTypeT>::IsValid() const
+    {
+        return m_buffer.IsValid();
+    }
+
+    template<AttributeType AttributeTypeT>
+    template<typename VertexStreamDataType>
+    bool AttributeBuffer<AttributeTypeT>::UpdateData(const AZStd::vector<VertexStreamDataType>& data)
+    {
+        if (!m_buffer.UpdateData(data))
+        {
+            AZ_Error(
+                "AttributeBuffer", false, "Couldn't update buffer for attribute %s",
+                m_shaderSemantic.ToString().c_str());
+            return false;
+        }
+
+        return true;
+    }
+
+    //! Attribute buffer alias for position attributes.
+    using PositionAttribute = AttributeBuffer<AttributeType::Position>;
+
+    //! Attribute buffer alias for normal attributes.
+    using NormalAttribute = AttributeBuffer<AttributeType::Normal>;
+
+    //! Attribute buffer alias for tangent attributes.
+    using TangentAttribute = AttributeBuffer<AttributeType::Tangent>;
+
+    //! Attribute buffer alias for bitangent attributes.
+    using BitangentAttribute = AttributeBuffer<AttributeType::Bitangent>;
+
+    //! Attribute buffer alias for uv attributes.
+    using UVAttribute = AttributeBuffer<AttributeType::UV>;
+
+    //! Attribute buffer alias for color attributes.
+    using ColorAttribute = AttributeBuffer<AttributeType::Color>;
+} // namespace GeomNodes

+ 193 - 0
Gems/O3DE/GeomNodes/Code/Source/Editor/Rendering/Atom/GNBuffer.h

@@ -0,0 +1,193 @@
+#pragma once
+
+#include "Editor/Math/MathHelper.h"
+
+#include <Atom/RPI.Public/Buffer/Buffer.h>
+#include <Atom/RPI.Reflect/Buffer/BufferAsset.h>
+#include <Atom/RPI.Reflect/Buffer/BufferAssetCreator.h>
+#include <Atom/RPI.Reflect/Buffer/BufferAssetView.h>
+//#include <AzCore/Math/PackedVector3.h>
+//#include <AzCore/Math/Vector2.h>
+//#include <AzCore/Math/Vector3.h>
+//#include <AzCore/Math/Vector4.h>
+
+namespace GeomNodes
+{
+    //! Get the Atom format for the specified vertex stream type.
+    template<typename VertexStreamDataType>
+    constexpr AZ::RHI::Format GetFormatForVertexStreamDataType()
+    {
+        if (std::is_same_v<VertexStreamDataType, uint32_t>)
+        {
+            return AZ::RHI::Format::R32_UINT;
+        }
+        else if (std::is_same_v<VertexStreamDataType, Vector2f>)
+        {
+            return AZ::RHI::Format::R32G32_FLOAT;
+        }
+        else if (std::is_same_v<VertexStreamDataType, Vector3f>)
+        {
+            return AZ::RHI::Format::R32G32B32_FLOAT;
+        }
+        else if (std::is_same_v<VertexStreamDataType, Vector4f>)
+        {
+            return AZ::RHI::Format::R32G32B32A32_FLOAT;
+        }
+        else
+        {
+            // will result in a compile time assertion failure in the Buffer class.
+            return AZ::RHI::Format::Unknown;
+        }
+    }
+
+    //! Buffer for holding vertex attribute data to be transferred to the GPU for mesh rendering.
+    template<typename VertexStreamDataType>
+    class Buffer
+    {
+    public:
+        //! Constructs the buffer from the specified data in vertex stream format.
+        Buffer(const AZStd::vector<VertexStreamDataType>& data);
+
+        //! Retrieves the buffer asset.
+        const AZ::Data::Asset<AZ::RPI::BufferAsset>& GetBuffer() const;
+
+        //! Retrieves the buffer view descriptor.
+        const AZ::RHI::BufferViewDescriptor& GetBufferViewDescriptor() const;
+
+        //! Retrieves the buffer asset view.
+        const AZ::RPI::BufferAssetView& GetBufferAssetView() const;
+
+        //! Returns true of the buffer is valid, otherwise false.
+        bool IsValid() const;
+
+        //! Update the buffer contents with the new data.
+        bool UpdateData(const AZStd::vector<VertexStreamDataType>& data);
+
+    private:
+        AZ::Data::Asset<AZ::RPI::BufferAsset> m_buffer;
+        AZ::RHI::BufferViewDescriptor m_bufferViewDescriptor;
+        AZ::RPI::BufferAssetView m_bufferAssetView;
+        bool m_isValid = false;
+
+        //! The format used by the buffer (must be one of the supported types in GetFormatForVertexStreamDataType).
+        static constexpr auto VertexStreamFormat = GetFormatForVertexStreamDataType<VertexStreamDataType>();
+        static_assert(
+            VertexStreamFormat != AZ::RHI::Format::Unknown, "Cannot initialize a buffer with an unknown format.");
+    };
+
+    template<typename VertexStreamDataType>
+    Buffer<VertexStreamDataType>::Buffer(const AZStd::vector<VertexStreamDataType>& data)
+    {
+        const uint32_t elementCount = static_cast<uint32_t>(data.size());
+        const uint32_t elementSize = sizeof(VertexStreamDataType);
+        const uint32_t bufferSize = elementCount * elementSize;
+
+        // create a buffer view spanning the entire buffer
+        m_bufferViewDescriptor = AZ::RHI::BufferViewDescriptor::CreateTyped(0, elementCount, VertexStreamFormat);
+
+        // specify the data format for vertex stream data
+        AZ::RHI::BufferDescriptor bufferDescriptor;
+        bufferDescriptor.m_bindFlags = AZ::RHI::BufferBindFlags::InputAssembly | AZ::RHI::BufferBindFlags::ShaderRead;
+        bufferDescriptor.m_byteCount = bufferSize;
+        bufferDescriptor.m_alignment = elementSize;
+
+        // create the buffer with the specified data
+        AZ::RPI::BufferAssetCreator bufferAssetCreator;
+        bufferAssetCreator.Begin(AZ::Uuid::CreateRandom());
+        bufferAssetCreator.SetUseCommonPool(AZ::RPI::CommonBufferPoolType::StaticInputAssembly);
+        bufferAssetCreator.SetBuffer(data.data(), bufferDescriptor.m_byteCount, bufferDescriptor);
+        bufferAssetCreator.SetBufferViewDescriptor(m_bufferViewDescriptor);
+
+        if (!bufferAssetCreator.End(m_buffer))
+        {
+            AZ_Error("Buffer", false, "Couldn't create buffer asset.");
+        }
+        else if (!m_buffer.IsReady())
+        {
+            AZ_Error("Buffer", false, "Asset is not ready.");
+        }
+        else if (!m_buffer.Get())
+        {
+            AZ_Error("Buffer", false, "Asset is nullptr.");
+        }
+        else
+        {
+            m_bufferAssetView = AZ::RPI::BufferAssetView{m_buffer, m_bufferViewDescriptor};
+            m_isValid = true;
+        }
+    }
+
+    template<typename VertexStreamDataType>
+    const AZ::Data::Asset<AZ::RPI::BufferAsset>& Buffer<VertexStreamDataType>::GetBuffer() const
+    {
+        return m_buffer;
+    }
+
+    template<typename VertexStreamDataType>
+    const AZ::RHI::BufferViewDescriptor& Buffer<VertexStreamDataType>::GetBufferViewDescriptor() const
+    {
+        return m_bufferViewDescriptor;
+    }
+
+    template<typename VertexStreamDataType>
+    const AZ::RPI::BufferAssetView& Buffer<VertexStreamDataType>::GetBufferAssetView() const
+    {
+        return m_bufferAssetView;
+    }
+
+    template<typename VertexStreamDataType>
+    bool Buffer<VertexStreamDataType>::IsValid() const
+    {
+        return m_isValid;
+        ;
+    }
+
+    template<typename VertexStreamDataType>
+    bool Buffer<VertexStreamDataType>::UpdateData(const AZStd::vector<VertexStreamDataType>& data)
+    {
+        if (!IsValid())
+        {
+            AZ_Error("UpdateData", false, "Buffer is not valid.");
+            return false;
+        }
+
+        auto buffer = AZ::RPI::Buffer::FindOrCreate(m_buffer);
+
+        if (!buffer)
+        {
+            AZ_Error("UpdateData", false, "Buffer could not be found.");
+            return false;
+        }
+
+        const uint32_t elementCount = static_cast<uint32_t>(data.size());
+        const uint32_t elementSize = sizeof(VertexStreamDataType);
+        const uint32_t bufferSize = elementCount * elementSize;
+
+        if (bufferSize > m_bufferViewDescriptor.m_elementCount * m_bufferViewDescriptor.m_elementSize)
+        {
+            AZ_Error("UpdateData", false, "Specfied buffer update exceeds capacity.");
+            return false;
+        }
+
+        if (!buffer->UpdateData(data.data(), bufferSize))
+        {
+            AZ_Error("UpdateData", false, "Buffer could not be updated.");
+            m_isValid = false;
+            return false;
+        }
+
+        return true;
+    }
+
+    //! Buffer alias for unsigned 32 bit integer indices.
+    using IndexBuffer = Buffer<uint32_t>;
+
+    //! Buffer alias for Vector2f vertices.
+    using Vector2Buffer = Buffer<Vector2f>;
+
+    //! Buffer alias for Vert3Vector vertices.
+    using Vector3Buffer = Buffer<Vector3f>;
+
+    //! Buffer alias for Vert4Vector vertices.
+    using Vector4Buffer = Buffer<Vector4f>;
+} // namespace GeomNodes

+ 279 - 0
Gems/O3DE/GeomNodes/Code/Source/Editor/Rendering/GNMeshData.cpp

@@ -0,0 +1,279 @@
+#include <Editor/Rendering/GNMeshData.h>
+#include <AzCore/std/containers/unordered_map.h>
+#include "Bridge.h"
+
+namespace GeomNodes
+{
+    GNMeshData::GNMeshData(
+        const Vert3Vector& positions,
+        const Vert3Vector& normals,
+        const S32Vector& indices,
+        const S32Vector& triangleLoops,
+        const S32Vector& loops,
+        const Vert2Vector& uvs,
+        const Vert4Vector& colors,
+        const S32Vector& /*materialIndices*/,
+        AZ::u64 hash,
+        bool isIndexedUVs,
+        bool isIndexedColors)
+    {
+        m_positions = positions;
+        m_normals = normals;
+        m_loops = loops;
+        m_hash = hash;
+        m_colors = colors;
+        m_uvs = uvs;
+		m_indices.resize(indices.size());
+		AZStd::transform(indices.begin(), indices.end(), m_indices.begin(),
+			[](int x)
+		{
+			return aznumeric_cast<AZ::u32>(x);
+		});
+
+
+        // we still need to build all the vertices, uvs, normals and colors as what we have is incomplete. 
+        // the initial data are shared and is done this way so the data coming from blender will be small.
+        Vert2Vector finalUVs;
+        Vert3Vector finalPositions, finalNormals;
+		Vert4Vector finalColors;
+
+        finalPositions.reserve(m_positions.size());
+        finalNormals.reserve(m_normals.size());
+        finalUVs.reserve(m_positions.size());
+        finalColors.reserve(m_positions.size());
+
+        AZ_Assert(m_normals.size() == m_indices.size(), "normals and indices count should match!");
+
+        // iterate through the indices
+        AZStd::unordered_map<UniqueKey, AZ::s32> uniqueKeys;
+        for (int i = 0; i < m_indices.size(); i++)
+        {
+            // build unique keys based on the current index, uv, normal and color.
+            auto* indexPtr = &m_indices[i];
+            const auto& normal = m_normals[i];
+            UniqueKey key(
+                *indexPtr,
+                normal,
+                m_uvs[isIndexedUVs ? *indexPtr : triangleLoops[i]],
+                m_colors[isIndexedColors ? *indexPtr : triangleLoops[i]]);
+            
+            auto iter = uniqueKeys.find(key);
+            if (iter == uniqueKeys.end())
+            {
+				finalPositions.emplace_back(m_positions[*indexPtr]);
+
+				if (isIndexedUVs)
+				{
+					finalUVs.emplace_back(m_uvs[*indexPtr]);
+				}
+
+				if (isIndexedColors)
+				{
+					finalColors.emplace_back(m_colors[*indexPtr]);
+				}
+
+                // update the indexes using the new one
+                *indexPtr = m_loops[triangleLoops[i]] = aznumeric_cast<AZ::s32>(finalPositions.size() - 1);
+				finalNormals.emplace_back(normal);
+				uniqueKeys.emplace(AZStd::make_pair<UniqueKey, AZ::s32>(key, i));
+            }
+            else
+            {
+				auto splitVertexIdx = iter->second;
+                *indexPtr = m_indices[splitVertexIdx];
+				m_loops[triangleLoops[i]] = m_loops[triangleLoops[splitVertexIdx]];
+            }
+        }
+
+        m_positions = finalPositions;
+        m_normals = finalNormals;
+
+		AZStd::vector<AZ::s32> cornerIdxList;
+		cornerIdxList.resize(m_loops.size());
+		for (AZ::s32 i = 0; i < m_loops.size(); i++)
+		{
+			cornerIdxList[m_loops[i]] = i;
+		}
+
+        if (isIndexedUVs)
+        {
+            m_uvs = finalUVs;
+
+			std::transform(m_uvs.begin(), m_uvs.end(), m_uvs.begin(),
+				[](Vector2f& uv) {
+				uv[1] = 1.f - uv[1];
+				return uv;
+			});
+        }
+        else {
+			AZStd::vector<Vector2f> indexedUVs;
+			indexedUVs.reserve(m_positions.size());
+			for (AZ::s32 i = 0; i < m_positions.size(); i++)
+			{
+				const auto cornerIdx = cornerIdxList[i];
+				indexedUVs.emplace_back(Vector2f{ m_uvs[cornerIdx][0], 1.f - m_uvs[cornerIdx][1] });
+			}
+
+			m_uvs = indexedUVs;
+        }
+        
+        if (isIndexedColors)
+        {
+            m_colors = finalColors;
+        }
+        
+        // calculate the aabb
+        for (const auto& vert : m_positions)
+        {
+            m_aabb.AddPoint(AZ::Vector3(vert[0], vert[1], vert[2]));
+        }
+
+        CalculateTangents();
+    }
+
+    void GNMeshData::CalculateTangents()
+    {
+        m_tangents.resize(m_positions.size());
+        m_bitangents.resize(m_positions.size());
+
+        for (AZ::s32 i = 0; i < m_indices.size() / 3; ++i)
+        {
+            const AZ::Vector3 V0 = MathHelper::Vec3fToVec3(m_positions[m_indices[i + 0]]);
+            const AZ::Vector3 V1 = MathHelper::Vec3fToVec3(m_positions[m_indices[i + 1]]);
+            const AZ::Vector3 V2 = MathHelper::Vec3fToVec3(m_positions[m_indices[i + 2]]);
+
+            const AZ::Vector2 UV0 = MathHelper::Vec2fToVec2(m_uvs[m_indices[i + 0]]);
+            const AZ::Vector2 UV1 = MathHelper::Vec2fToVec2(m_uvs[m_indices[i + 1]]);
+            const AZ::Vector2 UV2 = MathHelper::Vec2fToVec2(m_uvs[m_indices[i + 2]]);
+
+            // Calculate edge vectors of the triangle
+            AZ::Vector3 edge1 = V1 - V0;
+            AZ::Vector3 edge2 = V2 - V0;
+
+            // Calculate delta UVs
+            AZ::Vector2 deltaUV1 = UV1 - UV0;
+            AZ::Vector2 deltaUV2 = UV2 - UV0;
+
+            // Calculate the determinant to calculate the direction of the tangent and bitangent
+            float det = 1.0f / (deltaUV1.GetX() * deltaUV2.GetY() - deltaUV2.GetX() * deltaUV1.GetY());
+
+            // Calculate the tangent vector
+            AZ::Vector3 tangent = (edge1 * deltaUV2.GetX() - edge2 * deltaUV1.GetY()) * det;
+            tangent.Normalize();
+
+            // Calculate the bitangent vector
+            AZ::Vector3 bitangent = (edge2 * deltaUV1.GetY() - edge1 * deltaUV2.GetX()) * det;
+            bitangent.Normalize();
+
+            // Calculate the handedness of the tangent space
+            float tangentW = (edge1.Cross(edge2).Dot(tangent) < 0.0f) ? -1.0f : 1.0f;
+
+            AZ::Vector4 tangent4 = AZ::Vector4(tangent, tangentW);
+            m_tangents[m_indices[i]] = MathHelper::Vec4ToVec4f(tangent4);
+            m_bitangents[m_indices[i]] = MathHelper::Vec3ToVec3f(bitangent);
+            m_tangents[m_indices[i + 1]] = MathHelper::Vec4ToVec4f(tangent4);
+            m_bitangents[m_indices[i + 1]] = MathHelper::Vec3ToVec3f(bitangent);
+            m_tangents[m_indices[i + 2]] = MathHelper::Vec4ToVec4f(tangent4);
+            m_bitangents[m_indices[i + 2]] = MathHelper::Vec3ToVec3f(bitangent);
+        }
+    }
+
+    const AZ::u32 GNMeshData::VertexCount() const
+    {
+        return aznumeric_cast<AZ::u32>(m_indices.size());
+    }
+
+    const U32Vector& GNMeshData::GetIndices() const
+    {
+        return m_indices;
+    }
+
+    const Vert3Vector& GNMeshData::GetPositions() const
+    {
+        return m_positions;
+    }
+
+    const Vert3Vector& GNMeshData::GetNormals() const
+    {
+        return m_normals;
+    }
+
+    const Vert4Vector& GNMeshData::GetTangents() const
+    {
+        return m_tangents;
+    }
+
+    const Vert3Vector& GNMeshData::GetBitangents() const
+    {
+        return m_bitangents;
+    }
+
+    const Vert2Vector& GNMeshData::GetUVs() const
+    {
+        return m_uvs;
+    }
+
+    const Vert4Vector& GNMeshData::GetColors() const
+    {
+        return m_colors;
+    }
+
+    const Mat4Vector& GNMeshData::GetInstances() const
+    {
+        return m_instances;
+    }
+
+    const AZ::Matrix4x4 GNMeshData::GetTransform() const
+    {
+        return m_transform;
+    }
+
+    const AZ::Transform GNMeshData::GetO3DETransform() const
+    {
+        return m_o3deTransform;
+    }
+
+    const AZ::Vector3 GNMeshData::GetO3DEScale() const
+    {
+        return m_o3deScale;
+    }
+
+    void GNMeshData::GetO3DETransformAndScale(const AZ::Matrix4x4& mat4Transform, AZ::Transform& transform, AZ::Vector3& scale)
+    {
+        AZ::Quaternion o3deQuarternion = AZ::Quaternion::CreateFromMatrix4x4(mat4Transform);
+        AZ::Vector3 o3deTranslation = mat4Transform.GetTranslation();
+        scale = mat4Transform.RetrieveScale();
+        transform = AZ::Transform::CreateFromQuaternionAndTranslation(o3deQuarternion, o3deTranslation);
+    }
+
+    void GNMeshData::AddInstance(const AZ::Matrix4x4& mat4)
+    {
+        m_instances.emplace_back(mat4);
+    }
+
+    void GNMeshData::SetTransform(const AZ::Matrix4x4& transform)
+    {
+        m_transform = transform;
+
+        auto transposedMatrix = m_transform.GetTranspose();
+
+        // removed the scale from the 4x4 matrix first before creating the rotation. Note: Matrix4x4 has an ExtractScale method but it doesn't seem to worn well with non-uniform scale.
+        m_o3deScale = MathHelper::ExtractScalingFromMatrix44(transposedMatrix);
+        auto rotation = AZ::Quaternion::CreateFromMatrix4x4(transposedMatrix);
+        rotation = AZ::Quaternion(rotation.GetX(), rotation.GetY(), -rotation.GetZ(), rotation.GetW()); // Flip the Z rotation.
+        rotation.Normalize();
+
+        auto translation = m_transform.GetTranslation();
+        m_o3deTransform = AZ::Transform::CreateFromQuaternionAndTranslation(rotation, translation);
+    }
+
+    AZ::Aabb GNMeshData::GetAabb() const
+    {
+        return m_aabb;
+    }
+
+    AZ::s64 GNMeshData::GetHash() const
+    {
+        return m_hash;
+    }
+} // namespace GeomNodes

+ 67 - 0
Gems/O3DE/GeomNodes/Code/Source/Editor/Rendering/GNMeshData.h

@@ -0,0 +1,67 @@
+#pragma once
+
+#include <Editor/Math/MathHelper.h>
+#include <AzCore/Math/Aabb.h>
+namespace GeomNodes
+{
+    class GNMeshData
+    {
+    public:
+        explicit GNMeshData(
+            const Vert3Vector& positions
+            , const Vert3Vector& normals
+            , const S32Vector& indices
+            , const S32Vector& triangleLoops
+            , const S32Vector& loops
+            , const Vert2Vector& uvs
+            , const Vert4Vector& colors
+            , const S32Vector& materialIndices
+            , AZ::u64 hash
+            , bool isIndexedUVs
+            , bool isIndexedColors);
+
+        ~GNMeshData() = default;
+
+        const AZ::u32 VertexCount() const;
+        const U32Vector& GetIndices() const;
+        const Vert3Vector& GetPositions() const;
+        const Vert3Vector& GetNormals() const;
+        const Vert4Vector& GetTangents() const;
+        const Vert3Vector& GetBitangents() const;
+        const Vert2Vector& GetUVs() const;
+        const Vert4Vector& GetColors() const;
+        const Mat4Vector& GetInstances() const;
+        const AZ::Matrix4x4 GetTransform() const;
+        const AZ::Transform GetO3DETransform() const;
+        const AZ::Vector3 GetO3DEScale() const;
+        static void GetO3DETransformAndScale(const AZ::Matrix4x4& mat4Transform, AZ::Transform& transform, AZ::Vector3& scale);
+
+        void AddInstance(const AZ::Matrix4x4& mat4);
+        void SetTransform(const AZ::Matrix4x4& transform);
+        
+        AZ::Aabb GetAabb() const;
+        AZ::s64 GetHash() const;
+
+    private:
+        void CalculateTangents();
+
+        U32Vector m_indices;
+        S32Vector m_loops;
+
+        Vert3Vector m_positions;
+        Vert3Vector m_normals;
+        Vert4Vector m_tangents;
+        Vert3Vector m_bitangents;
+        Vert2Vector m_uvs;
+        Vert4Vector m_colors;
+        
+        AZ::Aabb m_aabb = AZ::Aabb::CreateNull();
+        AZ::s64 m_hash = 0;
+
+        Mat4Vector m_instances;
+        AZ::Matrix4x4 m_transform;
+        AZ::Transform m_o3deTransform = AZ::Transform::CreateIdentity();
+        AZ::Vector3 m_o3deScale = AZ::Vector3::CreateOne();
+        bool bO3DETransformCalculated = false;
+    };
+}

+ 119 - 0
Gems/O3DE/GeomNodes/Code/Source/Editor/Rendering/GNModelData.cpp

@@ -0,0 +1,119 @@
+#include <Editor/Rendering/GNModelData.h>
+#include "Bridge.h"
+
+namespace GeomNodes
+{
+    GNModelData::GNModelData()
+    {
+    }
+
+    GNModelData::GNModelData(AZ::u64 mapId)
+    {
+        if (OpenSHM(mapId))
+        {
+            AZ::s32 meshCount = Read<AZ::s32>(mapId);
+            AZ::s32 instanceCount = Read<AZ::s32>(mapId);
+
+            AZ_Printf("GNInstance", "meshCount : %i, instanceCount : %i", meshCount, instanceCount);
+
+            for ([[maybe_unused]] AZ::s32 meshIdx = 0; meshIdx < meshCount; meshIdx++)
+            {
+                auto positions = ReadArray<Vector3f>(mapId);
+                auto normals = ReadArray<Vector3f>(mapId);
+                auto indices = ReadArray<AZ::s32>(mapId);
+                auto triangleLoops = ReadArray<AZ::s32>(mapId);
+                auto loops = ReadArray<AZ::s32>(mapId);
+                auto hash = Read<AZ::s64>(mapId);
+
+                auto colors = ReadArray<Vector4f>(mapId);
+                bool bIndexedColors = Read<bool>(mapId);
+                auto uvs = ReadArray<Vector2f>(mapId);
+                bool bIndexedUVs = Read<bool>(mapId);
+                auto materialIndices = ReadArray<AZ::s32>(mapId);
+                [[maybe_unused]] auto UsedMaterialsJson = ReadArray<char>(mapId);
+
+                if (positions.empty())
+                {
+                    continue;
+                }
+                GNMeshData meshData(positions, normals, indices, triangleLoops, loops, uvs, colors, materialIndices, hash, bIndexedUVs, bIndexedColors);
+                m_meshes.push_back(meshData);
+            }
+
+            for ([[maybe_unused]] AZ::s32 instanceIdx = 0; instanceIdx < instanceCount; instanceIdx++)
+            {
+                AZ::s64 hash = Read<AZ::s64>(mapId);
+
+                [[maybe_unused]] AZ::Matrix4x4 LocalMatrix = Read<AZ::Matrix4x4>(mapId);
+                [[maybe_unused]] AZ::Matrix4x4 WorldMatrix = Read<AZ::Matrix4x4>(mapId);
+                AZ::Matrix4x4 instanceMatrix = LocalMatrix;
+                
+                for (auto& mesh : m_meshes)
+                {
+                    if (mesh.GetHash() == hash)
+                    {
+                        mesh.AddInstance(instanceMatrix);
+                    }
+                }
+            }
+
+            // process the mesh instances
+
+            AZStd::vector<GNMeshData> meshInstances;
+            for (auto& mesh : m_meshes)
+            {
+                int idx = 0;
+                for (auto& instance : mesh.GetInstances())
+                {
+                    if (idx == 0)
+                    {
+                        mesh.SetTransform(instance);
+                    }
+                    else
+                    {
+                        auto& meshInstance = meshInstances.emplace_back(mesh);
+                        meshInstance.SetTransform(instance);
+                    }
+
+                    idx++;
+                }
+            }
+
+            m_meshes.insert(m_meshes.end(), meshInstances.begin(), meshInstances.end());
+
+            ClearSHM(mapId);
+        }
+    }
+
+    const AZ::u32 GNModelData::MeshCount() const
+    {
+        return aznumeric_cast<AZ::u32>(m_meshes.size());
+    }
+
+    const GNModelData::MeshList GNModelData::GetMeshes() const
+    {
+        return m_meshes;
+    }
+
+    template<typename T>
+    AZStd::vector<T> GNModelData::ReadArray(AZ::u64 mapId)
+    {
+        AZ::u64 length;
+        void* address;
+        ReadSHM(mapId, &address, &length);
+
+        T* array = static_cast<T*>(address);
+        return AZStd::vector<T>(array, array + (length / sizeof(T)));
+    }
+
+    template<typename T>
+    T GNModelData::Read(AZ::u64 mapId)
+    {
+        AZ::u64 length;
+        void* address;
+        ReadSHM(mapId, &address, &length);
+        T value{};
+        memcpy(&value, address, length);
+        return value;
+    }
+} // namespace GeomNodes

+ 28 - 0
Gems/O3DE/GeomNodes/Code/Source/Editor/Rendering/GNModelData.h

@@ -0,0 +1,28 @@
+#pragma once
+
+#include <Editor/Rendering/GNMeshData.h>
+
+namespace GeomNodes
+{
+    class GNModelData
+    {
+    public:
+        using MeshList = AZStd::vector<GNMeshData>;
+
+        GNModelData();
+        GNModelData(AZ::u64 mapId);
+        ~GNModelData() = default;
+
+        const AZ::u32 MeshCount() const;
+        const MeshList GetMeshes() const;
+
+    private:
+        template<typename T>
+        AZStd::vector<T> ReadArray(AZ::u64 mapId);
+
+        template<typename T>
+        T Read(AZ::u64 mapId);
+
+        MeshList m_meshes;
+    };
+}

+ 310 - 0
Gems/O3DE/GeomNodes/Code/Source/Editor/Rendering/GNRenderMesh.cpp

@@ -0,0 +1,310 @@
+#include "GNRenderMesh.h"
+
+//#include <Rendering/Atom/WhiteBoxMeshAtomData.h>
+//#include <Rendering/WhiteBoxRenderData.h>
+//#include <Util/WhiteBoxMathUtil.h>
+//#include <Viewport/WhiteBoxViewportConstants.h>
+
+#include <Atom/RPI.Public/Model/Model.h>
+#include <Atom/RPI.Public/Scene.h>
+#include <Atom/RPI.Reflect/Asset/AssetUtils.h>
+#include <Atom/RPI.Reflect/Model/ModelAssetCreator.h>
+#include <Atom/RPI.Reflect/Model/ModelLodAssetCreator.h>
+#include <Atom/RPI.Reflect/ResourcePoolAssetCreator.h>
+#include <AtomLyIntegration/CommonFeatures/Material/MaterialComponentBus.h>
+#include <AzCore/Math/PackedVector3.h>
+
+namespace GeomNodes
+{
+    GNRenderMesh::GNRenderMesh(AZ::EntityId entityId)
+        : m_entityId(entityId)
+    {
+        //AZ::Render::MeshHandleStateRequestBus::Handler::BusConnect(m_entityId);
+    }
+
+    GNRenderMesh::~GNRenderMesh()
+    {
+        if (m_meshHandle.IsValid() && m_meshFeatureProcessor)
+        {
+            m_meshFeatureProcessor->ReleaseMesh(m_meshHandle);
+            /*AZ::Render::MeshHandleStateNotificationBus::Event(
+                m_entityId, &AZ::Render::MeshHandleStateNotificationBus::Events::OnMeshHandleSet, &m_meshHandle);*/
+        }
+
+        //AZ::Render::MeshHandleStateRequestBus::Handler::BusDisconnect();
+        AZ::TickBus::Handler::BusDisconnect();
+    }
+
+    bool GNRenderMesh::AreAttributesValid() const
+    {
+        bool attributesAreValid = true;
+
+        for (const auto& attribute : m_attributes)
+        {
+            AZStd::visit(
+                [&attributesAreValid](const auto& att)
+                {
+                    if (!att->IsValid())
+                    {
+                        attributesAreValid = false;
+                    }
+                },
+                attribute);
+        }
+
+        return attributesAreValid;
+    }
+
+    bool GNRenderMesh::CreateMeshBuffers(const GNMeshData& meshData)
+    {
+        m_indexBuffer = AZStd::make_unique<IndexBuffer>(meshData.GetIndices());
+
+        CreateAttributeBuffer<AttributeType::Position>(meshData.GetPositions());
+        CreateAttributeBuffer<AttributeType::Normal>(meshData.GetNormals());
+        CreateAttributeBuffer<AttributeType::Tangent>(meshData.GetTangents());
+        CreateAttributeBuffer<AttributeType::Bitangent>(meshData.GetBitangents());
+        CreateAttributeBuffer<AttributeType::UV>(meshData.GetUVs());
+        CreateAttributeBuffer<AttributeType::Color>(meshData.GetColors());
+
+        return AreAttributesValid();
+    }
+
+    bool GNRenderMesh::UpdateMeshBuffers(const GNMeshData& meshData)
+    {
+        UpdateAttributeBuffer<AttributeType::Position>(meshData.GetPositions());
+        UpdateAttributeBuffer<AttributeType::Normal>(meshData.GetNormals());
+        UpdateAttributeBuffer<AttributeType::Tangent>(meshData.GetTangents());
+        UpdateAttributeBuffer<AttributeType::Bitangent>(meshData.GetBitangents());
+        UpdateAttributeBuffer<AttributeType::UV>(meshData.GetUVs());
+        UpdateAttributeBuffer<AttributeType::Color>(meshData.GetColors());
+
+        return AreAttributesValid();
+    }
+
+    void GNRenderMesh::AddLodBuffers(AZ::RPI::ModelLodAssetCreator& modelLodCreator)
+    {
+        modelLodCreator.SetLodIndexBuffer(m_indexBuffer->GetBuffer());
+
+        for (auto& attribute : m_attributes)
+        {
+            AZStd::visit(
+                [&modelLodCreator](auto& att)
+                {
+                    att->AddLodStreamBuffer(modelLodCreator);
+                },
+                attribute);
+        }
+    }
+
+    void GNRenderMesh::AddMeshBuffers(AZ::RPI::ModelLodAssetCreator& modelLodCreator)
+    {
+        modelLodCreator.SetMeshIndexBuffer(m_indexBuffer->GetBufferAssetView());
+
+        for (auto& attribute : m_attributes)
+        {
+            AZStd::visit(
+                [&modelLodCreator](auto&& att)
+                {
+                    att->AddMeshStreamBuffer(modelLodCreator);
+                },
+                attribute);
+        }
+    }
+
+    bool GNRenderMesh::CreateLodAsset(const GNMeshData& meshData)
+    {
+        if (!CreateMeshBuffers(meshData))
+        {
+            return false;
+        }
+
+        AZ::RPI::ModelLodAssetCreator modelLodCreator;
+        modelLodCreator.Begin(AZ::Data::AssetId(AZ::Uuid::CreateRandom()));
+        AddLodBuffers(modelLodCreator);
+        modelLodCreator.BeginMesh();
+        modelLodCreator.SetMeshAabb(meshData.GetAabb());
+
+        modelLodCreator.SetMeshMaterialSlot(OneMaterialSlotId);
+
+        AddMeshBuffers(modelLodCreator);
+        modelLodCreator.EndMesh();
+
+        if (!modelLodCreator.End(m_lodAsset))
+        {
+            AZ_Error("CreateLodAsset", false, "Couldn't create LoD asset.");
+            return false;
+        }
+
+        if (!m_lodAsset.IsReady())
+        {
+            AZ_Error("CreateLodAsset", false, "LoD asset is not ready.");
+            return false;
+        }
+
+        if (!m_lodAsset.Get())
+        {
+            AZ_Error("CreateLodAsset", false, "LoD asset is nullptr.");
+            return false;
+        }
+
+        return true;
+    }
+
+    void GNRenderMesh::CreateModelAsset()
+    {
+        AZ::RPI::ModelAssetCreator modelCreator;
+        modelCreator.Begin(AZ::Data::AssetId(AZ::Uuid::CreateRandom()));
+        modelCreator.SetName(ModelName);
+        modelCreator.AddLodAsset(AZStd::move(m_lodAsset));
+
+        if (auto materialAsset = AZ::RPI::AssetUtils::LoadAssetByProductPath<AZ::RPI::MaterialAsset>(TexturedMaterialPath.data()))
+        {
+            auto materialOverrideInstance = AZ::RPI::Material::FindOrCreate(materialAsset);
+            auto& materialAssignment = m_materialMap[AZ::Render::DefaultMaterialAssignmentId];
+            materialAssignment.m_materialAsset = materialAsset;
+            materialAssignment.m_materialInstance = materialOverrideInstance;
+
+            AZ::RPI::ModelMaterialSlot materialSlot;
+            materialSlot.m_stableId = OneMaterialSlotId;
+            materialSlot.m_defaultMaterialAsset = materialAsset;
+            modelCreator.AddMaterialSlot(materialSlot);
+        }
+        else
+        {
+            AZ_Error("CreateLodAsset", false, "Could not load material.");
+            return;
+        }
+
+        modelCreator.End(m_modelAsset);
+    }
+
+    bool GNRenderMesh::CreateModel()
+    {
+        m_model = AZ::RPI::Model::FindOrCreate(m_modelAsset);
+        m_meshFeatureProcessor = AZ::RPI::Scene::GetFeatureProcessorForEntity<AZ::Render::MeshFeatureProcessorInterface>(m_entityId);
+
+        if (!m_meshFeatureProcessor)
+        {
+            AZ_Error("MeshComponentController", m_meshFeatureProcessor, "Unable to find a MeshFeatureProcessorInterface on the entityId.");
+            return false;
+        }
+
+        m_meshFeatureProcessor->ReleaseMesh(m_meshHandle);
+        m_meshHandle = m_meshFeatureProcessor->AcquireMesh(AZ::Render::MeshHandleDescriptor{ m_modelAsset });
+        /*AZ::Render::MeshHandleStateNotificationBus::Event(
+            m_entityId, &AZ::Render::MeshHandleStateNotificationBus::Events::OnMeshHandleSet, &m_meshHandle);*/
+
+        return true;
+    }
+
+    bool GNRenderMesh::MeshRequiresFullRebuild([[maybe_unused]] const GNMeshData& meshData) const
+    {
+        return meshData.VertexCount() != m_vertexCount;
+    }
+
+    bool GNRenderMesh::CreateMesh(const GNMeshData& meshData)
+    {
+        if (!CreateLodAsset(meshData))
+        {
+            return false;
+        }
+
+        CreateModelAsset();
+
+        if (!CreateModel())
+        {
+            return false;
+        }
+
+        m_vertexCount = meshData.VertexCount();
+
+        return true;
+    }
+
+    bool GNRenderMesh::DoesMeshRequireFullRebuild([[maybe_unused]] const GNMeshData& meshData) const
+    {
+        // this has been disabled due to a some recent updates with Atom that a) cause visual artefacts
+        // when updating the buffers and b) have a big performance boost when rebuilding from scratch anyway.
+        //
+        // this method for building the mesh will probably be replace anyway when the Atom DynamicDraw support
+        // comes online.
+        return true; // meshData.VertexCount() != m_vertexCount;
+    }
+
+    void GNRenderMesh::BuildMesh(const GNMeshData& meshData, const AZ::Transform& /*worldFromLocal*/)
+    {
+        if (DoesMeshRequireFullRebuild(meshData))
+        {
+            if (!CreateMesh(meshData))
+            {
+                return;
+            }
+        }
+        else
+        {
+            if (!UpdateMeshBuffers(meshData))
+            {
+                return;
+            }
+        }
+
+        m_transform = meshData.GetO3DETransform();
+        m_scale = meshData.GetO3DEScale();
+    }
+
+    void GNRenderMesh::UpdateTransform(const AZ::Transform& /*worldFromLocal*/, const AZ::Vector3& /*scale*/)
+    {
+        // TODO: multiply this to worldFromLocal transform
+        m_meshFeatureProcessor->SetTransform(m_meshHandle, m_transform, m_scale);
+    }
+
+    //void GNRenderMesh::UpdateMaterial(const WhiteBoxMaterial& material)
+    //{
+    //    if (m_meshFeatureProcessor)
+    //    {
+    //        auto& materialAssignment = m_materialMap[AZ::Render::DefaultMaterialAssignmentId];
+    //        materialAssignment.m_propertyOverrides[AZ::Name("baseColor.color")] = AZ::Color(material.m_tint);
+    //        materialAssignment.m_propertyOverrides[AZ::Name("baseColor.useTexture")] = material.m_useTexture;
+    //        // if ApplyProperties fails, defer updating the material assignment map
+    //        // on the next tick, and try applying properties again
+    //        if (materialAssignment.ApplyProperties())
+    //        {
+    //            if (AZ::TickBus::Handler::BusIsConnected())
+    //            {
+    //                AZ::TickBus::Handler::BusDisconnect();
+    //            }
+    //            m_meshFeatureProcessor->SetMaterialAssignmentMap(m_meshHandle, m_materialMap);
+    //        }
+    //        else if (!AZ::TickBus::Handler::BusIsConnected())
+    //        {
+    //            AZ::TickBus::Handler::BusConnect();
+    //        }
+    //    }
+    //}
+
+    void GNRenderMesh::OnTick([[maybe_unused]] float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time)
+    {
+        auto& materialAssignment = m_materialMap[AZ::Render::DefaultMaterialAssignmentId];
+        if (materialAssignment.ApplyProperties())
+        {
+            m_meshFeatureProcessor->SetMaterialAssignmentMap(m_meshHandle, m_materialMap);
+            AZ::TickBus::Handler::BusDisconnect();
+        }
+    }
+
+    void GNRenderMesh::SetVisiblity(bool visibility)
+    {
+        m_visible = visibility;
+        m_meshFeatureProcessor->SetVisible(m_meshHandle, m_visible);
+    }
+
+    bool GNRenderMesh::IsVisible() const
+    {
+        return m_visible;
+    }
+
+    /*const AZ::Render::MeshFeatureProcessorInterface::MeshHandle* GNRenderMesh::GetMeshHandle() const
+    {
+        return &m_meshHandle;
+    }*/
+} // namespace WhiteBox

+ 113 - 0
Gems/O3DE/GeomNodes/Code/Source/Editor/Rendering/GNRenderMesh.h

@@ -0,0 +1,113 @@
+#pragma once
+#include <AzCore/Component/TickBus.h>
+
+#include <Editor/Rendering/Atom/GNAttributeBuffer.h>
+#include <Editor/Rendering/Atom/GNBuffer.h>
+#include <Editor/Rendering/GNMeshData.h>
+//#include <Editor/Rendering/GNRenderMeshInterface.h>
+
+#include <Atom/Feature/Mesh/MeshFeatureProcessorInterface.h>
+#include <AtomLyIntegration/CommonFeatures/Mesh/MeshHandleStateBus.h>
+#include <AzCore/Component/TransformBus.h>
+#include <AzCore/Name/Name.h>
+
+namespace AZ::RPI
+{
+    class ModelLodAsset;
+    class ModelAsset;
+    class Model;
+} // namespace AZ::RPI
+
+namespace GeomNodes
+{
+    //! A concrete implementation of GNRenderMeshInterface to support Atom rendering for the GeomNodes gem.
+    //! This is very similar tho how WhiteBox's AtomRenderMesh implementation. Would be nice if some classes can be extensible or reusable.
+    class GNRenderMesh
+        :
+        // public GNRenderMeshInterface
+        //, private AZ::Render::MeshHandleStateRequestBus::Handler
+        //, private AZ::TickBus::Handler
+        private AZ::TickBus::Handler
+    {
+    public:
+        // AZ_RTTI(GNRenderMesh, "{4E293CD2-F9E6-417C-92B7-DDAF312F46CF}", GNRenderMeshInterface);
+
+        explicit GNRenderMesh(AZ::EntityId entityId);
+        ~GNRenderMesh();
+
+        // RenderMeshInterface ...
+        void BuildMesh(const GNMeshData& renderData, const AZ::Transform& worldFromLocal);
+        void UpdateTransform(const AZ::Transform& worldFromLocal, const AZ::Vector3& scale = AZ::Vector3::CreateOne());
+        //void UpdateMaterial(const GNMaterial& material);
+        bool IsVisible() const;
+        void SetVisiblity(bool visibility);
+
+        // AZ::TickBus overrides ...
+        void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
+
+    private:
+        //! Creates an attribute buffer in the slot dictated by AttributeTypeT.
+        template<AttributeType AttributeTypeT, typename VertexStreamDataType>
+        void CreateAttributeBuffer(const AZStd::vector<VertexStreamDataType>& data)
+        {
+            const auto attribute_index = static_cast<size_t>(AttributeTypeT);
+            m_attributes[attribute_index] = AZStd::make_unique<AttributeBuffer<AttributeTypeT>>(data);
+        }
+
+        //! Updates an attribute buffer in the slot dictated by AttributeTypeT.
+        template<AttributeType AttributeTypeT, typename VertexStreamDataType>
+        void UpdateAttributeBuffer(const AZStd::vector<VertexStreamDataType>& data)
+        {
+            const auto attribute_index = static_cast<size_t>(AttributeTypeT);
+            auto& att = AZStd::get<attribute_index>(m_attributes[attribute_index]);
+            att->UpdateData(data);
+        }
+
+        // MeshHandleStateRequestBus overrides ...
+        //const AZ::Render::MeshFeatureProcessorInterface::MeshHandle* GetMeshHandle() const override;
+
+        bool CreateMeshBuffers(const GNMeshData& meshData);
+        bool UpdateMeshBuffers(const GNMeshData& meshData);
+        bool MeshRequiresFullRebuild(const GNMeshData& meshData) const;
+        bool CreateMesh(const GNMeshData& meshData);
+        bool CreateLodAsset(const GNMeshData& meshData);
+        void CreateModelAsset();
+        bool CreateModel();
+        void AddLodBuffers(AZ::RPI::ModelLodAssetCreator& modelLodCreator);
+        void AddMeshBuffers(AZ::RPI::ModelLodAssetCreator& modelLodCreator);
+        bool AreAttributesValid() const;
+        bool DoesMeshRequireFullRebuild(const GNMeshData& meshData) const;
+
+        AZ::EntityId m_entityId;
+        AZ::Data::Asset<AZ::RPI::ModelLodAsset> m_lodAsset;
+        AZ::Data::Asset<AZ::RPI::ModelAsset> m_modelAsset;
+        AZ::Data::Instance<AZ::RPI::Model> m_model;
+        AZ::Render::MeshFeatureProcessorInterface* m_meshFeatureProcessor = nullptr;
+        AZ::Render::MeshFeatureProcessorInterface::MeshHandle m_meshHandle;
+        AZ::Render::MaterialAssignmentMap m_materialMap;
+        uint32_t m_vertexCount = 0;
+        AZStd::unique_ptr<IndexBuffer> m_indexBuffer;
+        AZStd::array<
+            AZStd::variant<
+                AZStd::unique_ptr<PositionAttribute>,
+                AZStd::unique_ptr<NormalAttribute>,
+                AZStd::unique_ptr<TangentAttribute>,
+                AZStd::unique_ptr<BitangentAttribute>,
+                AZStd::unique_ptr<UVAttribute>,
+                AZStd::unique_ptr<ColorAttribute>>,
+            NumAttributes>
+            m_attributes;
+        bool m_visible = true;
+
+        //! Default mesh material.
+        static constexpr AZStd::string_view TexturedMaterialPath = "materials/basic_grey.azmaterial";
+        static constexpr AZStd::string_view SolidMaterialPath = "materials/basic_grey.azmaterial";
+        static constexpr AZ::RPI::ModelMaterialSlot::StableId OneMaterialSlotId = 0;
+
+        //! model name.
+        static constexpr AZStd::string_view ModelName = "GeomNodesMesh";
+
+        AZ::Transform m_transform;
+        AZ::Vector3 m_scale;
+    };
+}

+ 37 - 0
Gems/O3DE/GeomNodes/Code/Source/Editor/Rendering/GNRenderMeshInterface.h

@@ -0,0 +1,37 @@
+#include <AzCore/Component/Component.h>
+
+namespace AZ
+{
+    class Transform;
+}
+
+namespace GeomNodes
+{
+    //struct GNMaterial;
+    struct WhiteBoxRenderData;
+
+    //! A generic interface for the GeomNodes Component to communicate
+    //! with regardless of the rendering backend.
+    class GNRenderMeshInterface
+    {
+    public:
+        AZ_RTTI(GNRenderMeshInterface, "{908CB056-4814-42FF-9D60-2D67A720D829}");
+
+        virtual ~GNRenderMeshInterface() = 0;
+
+        //! Take GeomNodes render data and populate the render mesh from it.
+        virtual void BuildMesh(const GNModelData& renderData, const AZ::Transform& worldFromLocal) = 0;
+
+        //! Update the transform of the render mesh.
+        virtual void UpdateTransform(const AZ::Transform& worldFromLocal) = 0;
+
+        //! Update the material of the render mesh.
+        //virtual void UpdateMaterial(const GNMaterial& material) = 0;
+
+        // Return if the GeomNodes mesh is visible or not.
+        virtual bool IsVisible() const = 0;
+
+        //! Set the GeomNodes mesh visible (true) or invisible (false).
+        virtual void SetVisiblity(bool visibility) = 0;
+    };
+} // namespace GeomNodes

+ 83 - 0
Gems/O3DE/GeomNodes/Code/Source/Editor/Rendering/GNRenderModel.cpp

@@ -0,0 +1,83 @@
+#include "GNRenderModel.h"
+
+namespace GeomNodes
+{
+    GNRenderModel::GNRenderModel(AZ::EntityId entityId)
+        : m_entityId(entityId)
+    {
+    }
+
+    GNRenderModel::~GNRenderModel()
+    {
+        ClearMeshes();
+        AZ::TickBus::Handler::BusDisconnect();
+    }
+
+    void GNRenderModel::ClearMeshes()
+    {
+        for (auto& mesh : m_meshes)
+        {
+            delete mesh;
+        }
+
+        m_meshes.clear();
+    }
+
+    void GNRenderModel::BuildMeshes(const GNModelData& renderData, const AZ::Transform& worldFromLocal)
+    {
+        ////TODO: need to optimize this at this is the point where it slows down.
+        ClearMeshes(); // for now we always clear the meshes
+
+        for (auto& meshData : renderData.GetMeshes())
+        {
+            auto renderMesh = aznew GNRenderMesh(m_entityId);
+            renderMesh->BuildMesh(meshData, worldFromLocal);
+            m_meshes.push_back(renderMesh);
+        }
+
+        //AZ::TickBus::Handler::BusConnect(); // connect the TickBus for a one time Update of transform.
+        UpdateTransform(worldFromLocal);
+    }
+
+    void GNRenderModel::UpdateTransform(const AZ::Transform& worldFromLocal)
+    {
+        for (auto& mesh : m_meshes)
+        {
+            mesh->UpdateTransform(worldFromLocal);
+        }
+    }
+
+    bool GNRenderModel::IsVisible() const
+    {
+        if (m_meshes.empty())
+            return false;
+
+        return m_meshes[0]->IsVisible();
+    }
+
+    void GNRenderModel::SetVisiblity(bool visibility)
+    {
+        for (auto& mesh : m_meshes)
+        {
+            mesh->SetVisiblity(visibility);
+        }
+
+    }
+
+    void GNRenderModel::OnTick(float /*deltaTime*/, AZ::ScriptTimePoint /*time*/)
+    {
+        AZ::Transform worldTransform;
+        AZ::TransformBus::EventResult(worldTransform, m_entityId, &AZ::TransformBus::Events::GetWorldTM);
+
+        BuildMeshes(m_modelData, worldTransform);
+        
+        //UpdateTransform(worldTransform);
+        AZ::TickBus::Handler::BusDisconnect();
+    }
+
+    void GNRenderModel::QueueBuildMeshes(const GNModelData& renderData)
+    {
+        m_modelData = renderData;
+        AZ::TickBus::Handler::BusConnect();
+    }
+} // namespace GeomNodes

+ 41 - 0
Gems/O3DE/GeomNodes/Code/Source/Editor/Rendering/GNRenderModel.h

@@ -0,0 +1,41 @@
+#pragma once
+
+#include <AzCore/Component/TickBus.h>
+//#include <Editor/Rendering/GNRenderMeshInterface.h>
+#include <Editor/Rendering/GNModelData.h>
+#include <Editor/Rendering/GNRenderMesh.h>
+
+namespace GeomNodes
+{
+    class GNRenderModel
+        : //public GNRenderMeshInterface
+        //, private AZ::TickBus::Handler
+          private AZ::TickBus::Handler
+    {
+    public:
+        //AZ_RTTI(GNRenderModel, "{510773BC-C8A9-4250-B412-F8525816A257}", GNRenderMeshInterface);
+
+        explicit GNRenderModel(AZ::EntityId entityId);
+        ~GNRenderModel();   
+
+        void ClearMeshes();
+
+        // RenderMeshInterface ...
+        void BuildMeshes(const GNModelData& renderData, const AZ::Transform& worldFromLocal);
+        void UpdateTransform(const AZ::Transform& worldFromLocal);
+        //void UpdateMaterial(const WhiteBoxMaterial& material) override;
+        bool IsVisible() const;
+        void SetVisiblity(bool visibility);
+
+        // AZ::TickBus overrides ...
+        void OnTick(float deltaTime, AZ::ScriptTimePoint time);
+
+        void QueueBuildMeshes(const GNModelData& renderData);
+    private:
+        AZ::EntityId m_entityId;
+
+        AZStd::vector<GNRenderMesh*> m_meshes;
+
+        GNModelData m_modelData;
+    };
+}

+ 99 - 0
Gems/O3DE/GeomNodes/Code/Source/Editor/Systems/GNInstance.cpp

@@ -0,0 +1,99 @@
+#include "GNInstance.h"
+#include "Bridge.h"
+
+namespace GeomNodes
+{
+    GNInstance::~GNInstance()
+    {
+        Cleanup();
+    }
+
+    void GNInstance::Cleanup()
+    {
+        if (m_blenderProcessWatcher)
+        {
+            m_blenderProcessWatcher->TerminateProcess(0);
+
+            m_blenderProcessWatcher = nullptr;
+        }
+
+        AZ::TickBus::Handler::BusDisconnect();
+    }
+
+    bool GNInstance::IsValid()
+    {
+        return m_blenderProcessWatcher && m_blenderProcessWatcher->IsProcessRunning();
+    }
+
+    bool GNInstance::Init(const AZStd::string& filePath, const AZStd::string& scriptPath, const AZStd::string& exePath, AZ::EntityId entityId)
+    {
+        m_entityId = entityId;
+        m_path = filePath;
+        m_scriptPath = scriptPath;
+        m_exePath = exePath;
+
+        AZ::TickBus::Handler::BusConnect();
+        return RestartProcess();
+    }
+
+    void GNInstance::OnTick(float /*deltaTime*/, AZ::ScriptTimePoint /*time*/)
+    {
+        
+    }
+
+    bool GNInstance::IsSamePath(const AZStd::string& path)
+    {
+        if (m_path.empty())
+            return false;
+
+        if (path == m_path)
+            return true;
+
+        return false;
+    }
+
+    void GNInstance::SendIPCMsg(const AZStd::string& content)
+    {
+        //AZ::u64 entityId = 123456;
+        AZ::u64 entityId = (AZ::u64)m_entityId;
+        SendMsg(content.c_str(), content.size(), entityId);
+    }
+
+    bool GNInstance::RestartProcess()
+    {
+        AzFramework::ProcessLauncher::ProcessLaunchInfo processLaunchInfo;
+        //TODO: don't hard code this. Add a way for the user to set this up or the gem detects it automatically.
+        AZStd::string blenderPath = "C:\\Program Files\\Blender Foundation\\Blender 3.3\\blender.exe";
+        processLaunchInfo.m_commandlineParameters = AZStd::string::format(
+            R"(%s --factory-startup -b "%s" -P "%s" -- "%s" %llu)",
+            blenderPath.c_str(),
+            m_path.c_str(),
+            m_scriptPath.c_str(),
+            m_exePath.c_str(),
+            (AZ::u64)m_entityId);
+        processLaunchInfo.m_showWindow = false;
+        processLaunchInfo.m_processPriority = AzFramework::ProcessPriority::PROCESSPRIORITY_NORMAL;
+
+        AzFramework::ProcessWatcher* outProcess = AzFramework::ProcessWatcher::LaunchProcess(
+            processLaunchInfo, AzFramework::ProcessCommunicationType::COMMUNICATOR_TYPE_STDINOUT);
+
+        if (outProcess)
+        {
+            // Stop the previous server if one exists
+            if (m_blenderProcessWatcher)
+            {
+                m_blenderProcessWatcher->TerminateProcess(0);
+            }
+
+            m_blenderProcessWatcher.reset(outProcess);
+        }
+        else
+        {
+            AZ_Error("GNInstance", outProcess, "Blender instance failed to launch! Unable to create AzFramework::ProcessWatcher.");
+            return false;
+        }
+
+        return true;
+    }
+    
+} // namespace GeomNodes

+ 39 - 0
Gems/O3DE/GeomNodes/Code/Source/Editor/Systems/GNInstance.h

@@ -0,0 +1,39 @@
+#pragma once
+
+#include "Editor/Commons.h"
+#include <AzFramework/Process/ProcessWatcher.h>
+#include <AzCore/Component/EntityId.h>
+#include <AzCore/Component/TickBus.h>
+
+namespace GeomNodes
+{
+    class GNInstance
+        : public AZ::TickBus::Handler
+    {
+    public:
+        GNInstance() = default;
+        virtual ~GNInstance();
+
+        bool Init(const AZStd::string& filePath, const AZStd::string& scriptPath, const AZStd::string& exePath, AZ::EntityId entityId);
+
+        void Cleanup();
+
+        bool IsValid();
+
+        void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
+
+        bool IsSamePath(const AZStd::string& path);
+
+        void SendIPCMsg(const AZStd::string& content);
+
+        bool RestartProcess();
+
+    private:
+        AZStd::unique_ptr<AzFramework::ProcessWatcher> m_blenderProcessWatcher = nullptr;
+
+        AZ::EntityId m_entityId;
+        AZStd::string m_path = "";
+        AZStd::string m_scriptPath = "";
+        AZStd::string m_exePath = "";
+    };
+} // namespace GeomNodes

+ 261 - 0
Gems/O3DE/GeomNodes/Code/Source/Editor/Systems/GNParamContext.cpp

@@ -0,0 +1,261 @@
+#include "GNParamContext.h"
+
+#include <AzCore/Serialization/EditContext.h>
+#include <AzCore/Serialization/SerializeContext.h>
+#include <Editor/Systems/GNProperty.h>
+
+namespace GeomNodes
+{
+    // templates
+    bool GNValue<bool>::Read(const rapidjson::Value& val)
+    {
+        return val.GetInt();
+    }
+
+    int GNValue<int>::Read(const rapidjson::Value& val)
+    {
+        return val.GetInt();
+    }
+
+    double GNValue<double>::Read(const rapidjson::Value& val)
+    {
+        return val.GetDouble();
+    }
+
+    const char* GNValue<const char*>::Read(const rapidjson::Value& val)
+    {
+        return val.GetString();
+    }
+
+    // GNParamContext
+    GNParamContext::GNParamContext()
+    {
+        m_impl = aznew GNParamContextImpl();
+        m_group.m_name = "Geometry Nodes Parameters";
+    }
+
+    GNParamContext::~GNParamContext()
+    {
+        m_group.Clear();
+        delete m_impl;
+    }
+
+    void GNParamContext::Reflect(AZ::ReflectContext* reflection)
+    {
+        AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(reflection);
+        if (serializeContext)
+        {
+            // we may have been reflected by GeomNodesEditorComponent already, so check first
+            if (serializeContext->FindClassData(AZ::Uuid("{AA9713B7-70F1-43CB-9F95-5BEC9F44F556}")) == nullptr)
+            {
+                serializeContext->Class<GNParamContext>()->Version(2)->Field("Properties", &GNParamContext::m_group);
+                    
+                serializeContext->Class<GNPropertyGroup>()
+                    ->Field("Name", &GNPropertyGroup::m_name)
+                    ->Field("Properties", &GNPropertyGroup::m_properties)
+                    ->Field("Groups", &GNPropertyGroup::m_groups);
+
+                // reflect all properties
+                GNProperties::Reflect(reflection);
+            }
+        }
+    }
+
+    GNProperty* GNParamContext::ConstructGNParam(GNParamDataContext& gndc, ParamType pType, const char* name)
+    {
+        return m_impl->ConstructGNParam(gndc, pType, name);
+    }
+
+    //=========================================================================
+    // GetGroup
+    //=========================================================================
+    GNPropertyGroup* GNPropertyGroup::GetGroup(const char* groupName)
+    {
+        for (GNPropertyGroup& subGroup : m_groups)
+        {
+            if (subGroup.m_name == groupName)
+            {
+                return &subGroup;
+            }
+        }
+        return nullptr;
+    }
+
+    //=========================================================================
+    // GetProperty
+    //=========================================================================
+    GNProperty* GNPropertyGroup::GetProperty(const char* propertyName)
+    {
+        for (GNProperty* prop : m_properties)
+        {
+            if (prop->m_name == propertyName)
+            {
+                return prop;
+            }
+        }
+        return nullptr;
+    }
+
+    AZStd::string GNPropertyGroup::GetProperties()
+    {
+        AZStd::string jsonString = "";
+
+        AZ::u64 uCtr = m_properties.size();
+        for (GNProperty* prop : m_properties)
+        {
+            jsonString += prop->ToJSONString() ;
+            uCtr--;
+            if (uCtr > 0)
+                jsonString += ", ";
+        }
+
+        return jsonString;
+    }
+
+    //=========================================================================
+    // Clear
+    //=========================================================================
+    void GNPropertyGroup::Clear()
+    {
+        for (GNProperty* prop : m_properties)
+        {
+            delete prop;
+        }
+        m_properties.clear();
+        m_groups.clear();
+    }
+
+    //=========================================================================
+    // ~GNPropertyGroup
+    //=========================================================================
+    GNPropertyGroup::~GNPropertyGroup()
+    {
+        Clear();
+    }
+
+    //=========================================================================
+    // operator=
+    //=========================================================================
+    GNPropertyGroup& GNPropertyGroup::operator=(GNPropertyGroup&& rhs)
+    {
+        m_name.swap(rhs.m_name);
+        m_properties.swap(rhs.m_properties);
+        m_groups.swap(rhs.m_groups);
+
+        return *this;
+    }
+
+    const char* GetEnumString(ParamType value)
+    {
+        switch (value)
+        {
+        case ParamType::Bool:
+            return "BOOLEAN";
+        case ParamType::Int:
+            return "INT";
+        case ParamType::Value:
+            return "VALUE";
+        case ParamType::String:
+            return "STRING";
+        }
+        
+        return "UNKNOWN";
+    }
+
+    ParamType GetTypeFromString(const char* value)
+    {
+        if (strcmp("BOOLEAN", value) == 0)
+        {
+            return ParamType::Bool;
+        }
+        else if (strcmp("INT", value) == 0)
+        {
+            return ParamType::Int;
+        }
+        else if (strcmp("VALUE", value) == 0)
+        {
+            return ParamType::Value;
+        }
+        else if (strcmp("STRING", value) == 0)
+        {
+            return ParamType::String;
+        }
+
+        return ParamType::Unknown;
+    }
+
+    // GNParamDataContext
+    
+    bool GNParamDataContext::IsNil(int index) const
+    {
+        return index == (int)ParamType::Unknown;
+    }
+
+    bool GNParamDataContext::IsBoolean(int index) const
+    {
+        return index == (int)ParamType::Bool;
+    }
+
+    bool GNParamDataContext::IsInt(int index) const
+    {
+        return index == (int)ParamType::Int;
+    }
+
+    bool GNParamDataContext::IsValue(int index) const
+    {
+        return index == (int)ParamType::Value;
+    }
+
+    bool GNParamDataContext::IsString(int index) const
+    {
+        return index == (int)ParamType::String;
+    }
+
+    const char* GNParamDataContext::GetParamName()
+    {
+        if (m_curParamObj == nullptr)
+            return nullptr;
+
+        return (*m_curParamObj)[Field::Name].GetString();
+    }
+
+    ParamType GNParamDataContext::GetParamType()
+    {
+        if (m_curParamObj == nullptr)
+            return ParamType::Unknown;
+
+        return GetTypeFromString((*m_curParamObj)[Field::Type].GetString());
+    }
+
+    // GNParamContextImpl
+    GNParamContextImpl::GNParamContextImpl()
+        : m_paramFactories({
+              // Nil
+              &GNParamNil::TryCreateProperty,
+
+              // Values
+              &GNParamBoolean::TryCreateProperty,
+              &GNParamInt::TryCreateProperty,
+              &GNParamValue::TryCreateProperty,
+              &GNParamString::TryCreateProperty,
+          })
+    {
+    }
+
+    GNProperty* GNParamContextImpl::ConstructGNParam(GNParamDataContext& gndc, ParamType pType, const char* name)
+    {
+        GNProperty* gnParam = nullptr;
+
+        for (ParamTypeFactory factory : m_paramFactories)
+        {
+            gnParam = factory(gndc, (int)pType, name);
+
+            if (gnParam != nullptr)
+            {
+                break;
+            }
+        }
+
+        return gnParam;
+    }
+} // namespace GeomNodes

+ 194 - 0
Gems/O3DE/GeomNodes/Code/Source/Editor/Systems/GNParamContext.h

@@ -0,0 +1,194 @@
+#pragma once
+
+#include <AzCore/RTTI/ReflectContext.h>
+#include <AzCore/JSON/document.h>
+#include <AzCore/JSON/rapidjson.h>
+
+namespace GeomNodes
+{
+    class GNProperty;
+    class GNParamContextImpl;
+    class GNParamContext;
+
+    namespace Field
+    {
+        static constexpr char Initialized[] = "Initialized";
+        static constexpr char ObjectNames[] = "ObjectNames";
+        static constexpr char Objects[] = "Objects";
+        static constexpr char Object[] = "Object";
+        static constexpr char SHMOpen[] = "SHMOpen";
+        static constexpr char SHMClose[] = "SHMClose";
+        static constexpr char MapId[] = "MapId";
+
+        static constexpr char Params[] = "Params";
+        static constexpr char Materials[] = "Materials";
+        static constexpr char Id[] = "Id";
+        static constexpr char Name[] = "Name";
+        static constexpr char Type[] = "Type";
+        static constexpr char DefaultValue[] = "DefaultValue";
+        static constexpr char Value[] = "Value";
+        static constexpr char MinValue[] = "MinValue";
+        static constexpr char MaxValue[] = "MaxValue";
+    }
+
+    enum class ParamType : AZ::u8
+    {
+        Bool,
+        Int,
+        Value,
+        String,
+        StringComboBox,
+
+        Unknown
+    };
+
+    const char* GetEnumString(ParamType value);
+    ParamType GetTypeFromString(const char* value);
+
+    template<class T>
+    struct GNValue
+    {
+        typedef typename AZStd::remove_const<typename AZStd::remove_reference<typename AZStd::remove_pointer<T>::type>::type>::type ValueType;
+    };
+
+    template<>
+    struct GNValue<bool>
+    {
+        static const bool isNativeValueType = true; // We use native type for internal representation
+        static bool Read(const rapidjson::Value& val);
+    };
+
+    template<>
+    struct GNValue<const char*>
+    {
+        static const bool isNativeValueType = true; // We use native type for internal representation
+        static const char* Read(const rapidjson::Value& val);
+    };
+
+    template<>
+    struct GNValue<int>
+    {
+        static const bool isNativeValueType = true; // We use native type for internal representation
+        static int Read(const rapidjson::Value& val);
+    };
+
+    template<>
+    struct GNValue<double>
+    {
+        static const bool isNativeValueType = true; // We use native type for internal representation
+        static double Read(const rapidjson::Value& val);
+    };
+
+    struct GNPropertyGroup
+    {
+        AZ_TYPE_INFO(GNPropertyGroup, "{439E8395-77B5-4BC6-94D6-5A0F51DBE9FD}");
+        AZStd::string m_name;
+        AZStd::vector<GNProperty*> m_properties;
+        AZStd::vector<GNPropertyGroup> m_groups;
+
+        // Get the pointer to the specified group in m_groups. Returns nullptr if not found.
+        GNPropertyGroup* GetGroup(const char* groupName);
+        // Get the pointer to the specified property in m_properties. Returns nullptr if not found.
+        GNProperty* GetProperty(const char* propertyName);
+        // Generate JSON string of all properties/parameters. NOTE: only select details are included.
+        AZStd::string GetProperties();
+
+        // Remove all properties and groups
+        void Clear();
+
+        GNPropertyGroup() = default;
+        ~GNPropertyGroup();
+
+        GNPropertyGroup(const GNPropertyGroup& rhs) = delete;
+        GNPropertyGroup& operator=(GNPropertyGroup&) = delete;
+
+    public:
+        GNPropertyGroup(GNPropertyGroup&& rhs)
+        {
+            *this = AZStd::move(rhs);
+        }
+        GNPropertyGroup& operator=(GNPropertyGroup&& rhs);
+    };
+
+    class GNParamDataContext
+    {
+        friend GNParamContext;
+    public:
+        AZ_TYPE_INFO(GNParamDataContext, "{61ED88BA-210A-458B-A5E5-C71C05C05411}");
+
+        GNParamDataContext()
+        {
+        }
+
+        bool IsNil(int index) const;
+        bool IsBoolean(int index) const;
+        bool IsInt(int index) const;
+        bool IsValue(int index) const;
+        bool IsString(int index) const;
+
+        template<class T>
+        bool ReadValue(T& valueRef, const char* key) const;
+
+        void SetParamObject(const rapidjson::Value* value)
+        {
+            m_curParamObj = value;
+        }
+        void ClearParamObject()
+        {
+            m_curParamObj = nullptr;
+        }
+
+        const char* GetParamName();
+        ParamType GetParamType();
+
+    protected:
+        const rapidjson::Value* m_curParamObj;
+    };
+
+    template<class T>
+    inline bool GNParamDataContext::ReadValue(T& valueRef, const char* key) const
+    {
+        if (m_curParamObj == nullptr || !(*m_curParamObj).HasMember(key))
+            return false;
+
+        valueRef = GNValue<T>::Read((*m_curParamObj)[key]);
+        
+        return true;
+    }
+
+    class GNParamContextImpl
+    {
+    public:
+        typedef GNProperty* (*ParamTypeFactory)(GNParamDataContext& context, int valueIndex, const char* name);
+
+        AZ_CLASS_ALLOCATOR(GNParamContextImpl, AZ::SystemAllocator, 0);
+
+        GNParamContextImpl();
+        ~GNParamContextImpl() = default;
+
+        GNProperty* ConstructGNParam(GNParamDataContext& gndc, ParamType pType, const char* name);
+        AZStd::vector<ParamTypeFactory> m_paramFactories;
+    };
+
+    class GNParamContext
+    {
+        friend class GeomNodesEditorComponent;
+
+    public:
+        AZ_CLASS_ALLOCATOR(GNParamContext, AZ::SystemAllocator, 0);
+
+        AZ_TYPE_INFO(GeomNodes::GNParamContext, "{AA9713B7-70F1-43CB-9F95-5BEC9F44F556}");
+
+        GNParamContext();
+        ~GNParamContext();
+
+        static void Reflect(AZ::ReflectContext* reflection);
+
+        GNProperty* ConstructGNParam(GNParamDataContext& gndc, ParamType pType, const char* name);
+
+    protected:
+        GNPropertyGroup m_group;
+
+        GNParamContextImpl* m_impl;
+    };
+} // namespace GeomNodes

+ 535 - 0
Gems/O3DE/GeomNodes/Code/Source/Editor/Systems/GNProperty.cpp

@@ -0,0 +1,535 @@
+#include "GNProperty.h"
+
+namespace GeomNodes
+{
+    void GNProperties::Reflect(AZ::ReflectContext* reflection)
+    {
+        GNProperty::Reflect(reflection);
+
+        GNParamBoolean::Reflect(reflection);
+        GNParamInt::Reflect(reflection);
+        GNParamValue::Reflect(reflection);
+        GNParamString::Reflect(reflection);
+    }
+
+    void GNProperty::Reflect(AZ::ReflectContext* reflection)
+    {
+        AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(reflection);
+
+        if (serializeContext)
+        {
+            serializeContext->Class<GNProperty>()
+                ->Version(2, GNProperty::VersionConverter)
+                ->PersistentId(
+                    [](const void* instance) -> AZ::u64
+                    {
+                        return reinterpret_cast<const GNProperty*>(instance)->m_id;
+                    })
+                ->Field("id", &GNProperty::m_id)
+                ->Field("name", &GNProperty::m_name);
+        }
+    }
+
+    bool GNProperty::VersionConverter(AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& classElement)
+    {
+        if (classElement.GetVersion() == 1)
+        {
+            // Generate persistent Id field.
+            for (int i = 0; i < classElement.GetNumSubElements(); ++i)
+            {
+                AZ::SerializeContext::DataElementNode& elementNode = classElement.GetSubElement(i);
+                if (elementNode.GetName() == AZ_CRC("name", 0x5e237e06))
+                {
+                    AZStd::string name;
+                    if (elementNode.GetData(name))
+                    {
+                        const int idx = classElement.AddElement<AZ::u64>(context, "id");
+                        const AZ::u32 crc = AZ::Crc32(name.c_str());
+                        classElement.GetSubElement(idx).SetData<AZ::u64>(context, crc);
+                    }
+                }
+            }
+        }
+
+        return true;
+    }
+
+    void GNProperty::ReadSetGNId(GNParamDataContext& context)
+    {
+        const char* gnId;
+        if (context.ReadValue(gnId, Field::Id))
+        {
+            m_gnId = gnId;
+        }
+    }
+
+    //////////////////////
+    // GNParamNil
+    //////////////////////
+
+    void GNParamNil::Reflect(AZ::ReflectContext* reflection)
+    {
+        AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(reflection);
+
+        if (serializeContext)
+        {
+            serializeContext->Class<GNParamNil, GNProperty>()->Version(1)->SerializeWithNoData();
+        }
+    }
+
+    GNProperty* GNParamNil::TryCreateProperty(GNParamDataContext& context, int valueIndex, const char* name)
+    {
+        GNProperty* retVal = nullptr;
+        if (context.IsNil(valueIndex))
+        {
+            retVal = aznew GNParamNil(name);
+        }
+
+        return retVal;
+    }
+
+    const void* GNParamNil::GetDataAddress() const
+    {
+        return nullptr;
+    }
+
+    AZ::TypeId GNParamNil::GetDataTypeUuid() const
+    {
+        return AZ::SerializeTypeInfo<void*>::GetUuid();
+    }
+
+    AZStd::string GNParamNil::ToJSONString() const
+    {
+        return AZStd::string();
+    }
+
+    GNParamNil* GNParamNil::Clone(const char* name) const
+    {
+        return aznew GNParamNil(name ? name : m_name.c_str());
+    }
+
+    /*bool GNParamNil::Write(AZ::ScriptContext& context)
+    {
+        lua_pushnil(context.NativeContext());
+        return true;
+    }
+
+    bool GNParamNil::TryRead(GNParamDataContext& context, int valueIndex)
+    {
+        if (context.IsNil(valueIndex))
+        {
+            return true;
+        }
+
+        return false;
+    }*/
+
+    void GNParamNil::CloneDataFrom(const GNProperty* gnProperty)
+    {
+        (void)gnProperty;
+    }
+
+    //////////////////////////
+    // GNParamBoolean
+    //////////////////////////
+    void GNParamBoolean::Reflect(AZ::ReflectContext* reflection)
+    {
+        AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(reflection);
+
+        if (serializeContext)
+        {
+            serializeContext->Class<GNParamBoolean, GNProperty>()->Version(1)->Field("value", &GNParamBoolean::m_value);
+        }
+    }
+
+    GNProperty* GNParamBoolean::TryCreateProperty(GNParamDataContext& context, int valueIndex, const char* name)
+    {
+        GNProperty* retVal = nullptr;
+
+        if (context.IsBoolean(valueIndex))
+        {
+            bool value;
+            if (context.ReadValue(value, Field::DefaultValue))
+            {
+                retVal = aznew GNParamBoolean(name, value);
+                retVal->ReadSetGNId(context);
+            }
+        }
+
+        return retVal;
+    }
+    
+
+    bool GNParamBoolean::DoesTypeMatch(GNParamDataContext& context, int valueIndex) const
+    {
+        return context.IsBoolean(valueIndex);
+    }
+
+    GNParamBoolean* GNParamBoolean::Clone(const char* name) const
+    {
+        return aznew GNParamBoolean(name ? name : m_name.c_str(), m_value);
+    }
+
+    /*bool GNParamBoolean::Write(AZ::ScriptContext& context)
+    {
+        AZ::ScriptValue<bool>::StackPush(context.NativeContext(), m_value);
+        return true;
+    }
+
+    bool GNParamBoolean::TryRead(GNParamDataContext& context, int valueIndex)
+    {
+        if (context.IsBoolean(valueIndex))
+        {
+            context.ReadValue(m_value);
+        }
+
+        return false;
+    }*/
+
+    AZStd::string GNParamBoolean::ToJSONString() const
+    {
+        auto jsonString = AZStd::string::format(
+            R"JSON(
+                    {
+                        "%s": "%s",
+                        "%s": %s,
+                        "%s": "%s"
+                    }
+                )JSON"
+            , Field::Id
+            , m_gnId.c_str()
+            , Field::Value
+            , m_value ? "true" : "false"
+            , Field::Type
+            , m_type.c_str()
+        );
+        return jsonString;
+    }
+
+    AZ::TypeId GNParamBoolean::GetDataTypeUuid() const
+    {
+        return AZ::SerializeTypeInfo<bool>::GetUuid();
+    }
+
+    void GNParamBoolean::CloneDataFrom(const GNProperty* gnProperty)
+    {
+        const GNParamBoolean* booleanProperty = azrtti_cast<const GNParamBoolean*>(gnProperty);
+        AZ_Error("GNParamBoolean", booleanProperty, "Invalid call to CloneData. Types must match before clone attempt is made.\n");
+
+        if (booleanProperty)
+        {
+            m_value = booleanProperty->m_value;
+        }
+    }
+
+    /////////////////////////
+    // GNParamInt
+    /////////////////////////
+    void GNParamInt::Reflect(AZ::ReflectContext* reflection)
+    {
+        AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(reflection);
+
+        if (serializeContext)
+        {
+            serializeContext->Class<GNParamInt, GNProperty>()->Version(1)->Field("value", &GNParamInt::m_value);
+        }
+    }
+
+    GNProperty* GNParamInt::TryCreateProperty(GNParamDataContext& context, int valueIndex, const char* name)
+    {
+        GNProperty* retVal = nullptr;
+
+        if (context.IsInt(valueIndex))
+        {
+            int value;
+            if (context.ReadValue(value, Field::DefaultValue))
+            {
+                auto paramInt = aznew GNParamInt(name, value);
+
+                int min, max;
+                if (context.ReadValue(min, Field::MinValue))
+                {
+                    paramInt->SetMinValue(min);
+                }
+
+                if (context.ReadValue(max, Field::MaxValue))
+                {
+                    paramInt->SetMaxValue(max);
+                }
+
+                retVal = paramInt;
+                retVal->ReadSetGNId(context);
+            }
+        }
+
+        return retVal;
+    }
+
+    bool GNParamInt::DoesTypeMatch(GNParamDataContext& context, int valueIndex) const
+    {
+        return context.IsInt(valueIndex);
+    }
+
+    GNParamInt* GNParamInt::Clone(const char* name) const
+    {
+        return aznew GNParamInt(name ? name : m_name.c_str(), m_value);
+    }
+
+    /*bool GNParamInt::Write(AZ::ScriptContext& context)
+    {
+        AZ::ScriptValue<double>::StackPush(context.NativeContext(), m_value);
+        return true;
+    }
+
+    bool GNParamInt::TryRead(GNParamDataContext& sdc, int index)
+    {
+        if (sdc.IsNumber(index))
+        {
+            sdc.ReadValue(m_value);
+            return true;
+        }
+
+        return false;
+    }*/
+
+    AZ::TypeId GNParamInt::GetDataTypeUuid() const
+    {
+        return AZ::SerializeTypeInfo<int>::GetUuid();
+    }
+
+    AZStd::string GNParamInt::ToJSONString() const
+    {
+        auto jsonString = AZStd::string::format(
+            R"JSON(
+                    {
+                        "%s": "%s",
+                        "%s": %i,
+                        "%s": "%s"
+                    }
+                )JSON"
+            , Field::Id
+            , m_gnId.c_str()
+            , Field::Value
+            , m_value
+            , Field::Type
+            , m_type.c_str()
+        );
+        return jsonString;
+    }
+
+    void GNParamInt::CloneDataFrom(const GNProperty* gnProperty)
+    {
+        const GNParamInt* numberProperty = azrtti_cast<const GNParamInt*>(gnProperty);
+        AZ_Error("GNParamValue", numberProperty, "Invalid call to CloneData. Types must match before clone attempt is made.\n");
+
+        if (numberProperty)
+        {
+            m_value = numberProperty->m_value;
+        }
+    }
+
+    /////////////////////////
+    // GNParamValue
+    /////////////////////////
+    void GNParamValue::Reflect(AZ::ReflectContext* reflection)
+    {
+        AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(reflection);
+
+        if (serializeContext)
+        {
+            serializeContext->Class<GNParamValue, GNProperty>()->Version(1)->Field(
+                "value", &GNParamValue::m_value);
+        }
+    }
+
+    GNProperty* GNParamValue::TryCreateProperty(GNParamDataContext& context, int valueIndex, const char* name)
+    {
+        GNProperty* retVal = nullptr;
+
+        if (context.IsValue(valueIndex))
+        {
+            double value;
+            if (context.ReadValue(value, Field::DefaultValue))
+            {
+                auto paramValue = aznew GNParamValue(name, value);
+                double min, max;
+                if (context.ReadValue(min, Field::MinValue))
+                {
+                    paramValue->SetMinValue(min);
+                }
+
+                if (context.ReadValue(max, Field::MaxValue))
+                {
+                    paramValue->SetMaxValue(max);
+                }
+
+                retVal = paramValue;
+                retVal->ReadSetGNId(context);
+            }
+        }
+
+        return retVal;
+    }
+
+    bool GNParamValue::DoesTypeMatch(GNParamDataContext& context, int valueIndex) const
+    {
+        return context.IsValue(valueIndex);
+    }
+
+    GNParamValue* GNParamValue::Clone(const char* name) const
+    {
+        return aznew GNParamValue(name ? name : m_name.c_str(), m_value);
+    }
+
+    /*bool GNParamValue::Write(AZ::ScriptContext& context)
+    {
+        AZ::ScriptValue<double>::StackPush(context.NativeContext(), m_value);
+        return true;
+    }
+
+    bool GNParamValue::TryRead(GNParamDataContext& sdc, int index)
+    {
+        if (sdc.IsNumber(index))
+        {
+            sdc.ReadValue(m_value);
+            return true;
+        }
+
+        return false;
+    }*/
+
+    AZ::TypeId GNParamValue::GetDataTypeUuid() const
+    {
+        return AZ::SerializeTypeInfo<double>::GetUuid();
+    }
+
+    AZStd::string GNParamValue::ToJSONString() const
+    {
+        auto jsonString = AZStd::string::format(
+            R"JSON(
+                    {
+                        "%s": "%s",
+                        "%s": %.17f,
+                        "%s": "%s"
+                    }
+                )JSON"
+            , Field::Id
+            , m_gnId.c_str()
+            , Field::Value
+            , m_value
+            , Field::Type
+            , m_type.c_str()
+        );
+        return jsonString;
+    }
+
+    void GNParamValue::CloneDataFrom(const GNProperty* gnProperty)
+    {
+        const GNParamValue* numberProperty = azrtti_cast<const GNParamValue*>(gnProperty);
+        AZ_Error("GNParamValue", numberProperty, "Invalid call to CloneData. Types must match before clone attempt is made.\n");
+
+        if (numberProperty)
+        {
+            m_value = numberProperty->m_value;
+        }
+    }
+
+    /////////////////////////
+    // GNParamString
+    /////////////////////////
+    void GNParamString::Reflect(AZ::ReflectContext* reflection)
+    {
+        AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(reflection);
+
+        if (serializeContext)
+        {
+            serializeContext->Class<GNParamString, GNProperty>()->Version(1)->Field("value", &GNParamString::m_value);
+        }
+    }
+
+    GNProperty* GNParamString::TryCreateProperty(GNParamDataContext& context, int valueIndex, const char* name)
+    {
+        GNProperty* retVal = nullptr;
+
+        if (context.IsString(valueIndex))
+        {
+            const char* value = nullptr;
+            if (context.ReadValue(value, Field::DefaultValue))
+            {
+                retVal = aznew GNParamString(name, value);
+                retVal->ReadSetGNId(context);
+            }
+        }
+
+        return retVal;
+    }
+
+    bool GNParamString::DoesTypeMatch(GNParamDataContext& context, int valueIndex) const
+    {
+        return context.IsString(valueIndex);
+    }
+
+    GNParamString* GNParamString::Clone(const char* name) const
+    {
+        return aznew GNParamString(name ? name : m_name.c_str(), m_value.c_str());
+    }
+
+    /*bool GNParamString::Write(AZ::ScriptContext& context)
+    {
+        AZ::ScriptValue<const char*>::StackPush(context.NativeContext(), m_value.c_str());
+        return true;
+    }
+
+    bool GNParamString::TryRead(GNParamDataContext& context, int valueIndex)
+    {
+        bool readValue = false;
+
+        if (context.IsString(valueIndex))
+        {
+            const char* value = nullptr;
+            if (context.ReadValue(value))
+            {
+                readValue = true;
+                m_value = value;
+            }
+        }
+
+        return readValue;
+    }*/
+
+    AZ::TypeId GNParamString::GetDataTypeUuid() const
+    {
+        return AZ::SerializeGenericTypeInfo<AZStd::string>::GetClassTypeId();
+    }
+
+    AZStd::string GNParamString::ToJSONString() const
+    {
+        auto jsonString = AZStd::string::format(
+            R"JSON(
+                    {
+                        "%s": "%s",
+                        "%s": "%s",
+                        "%s": "%s"
+                    }
+                )JSON"
+            , Field::Id
+            , m_gnId.c_str()
+            , Field::Value
+            , m_value.c_str()
+            , Field::Type
+            , m_type.c_str()
+        );
+        return jsonString;
+    }
+
+    void GNParamString::CloneDataFrom(const GNProperty* gnProperty)
+    {
+        const GNParamString* stringProperty = azrtti_cast<const GNParamString*>(gnProperty);
+        AZ_Error("GNParamString", stringProperty, "Invalid call to CloneData. Types must match before clone attempt is made.\n");
+
+        if (stringProperty)
+        {
+            m_value = stringProperty->m_value;
+        }
+    }
+} // namespace GeomNodes

+ 315 - 0
Gems/O3DE/GeomNodes/Code/Source/Editor/Systems/GNProperty.h

@@ -0,0 +1,315 @@
+#pragma once
+
+#include <AzCore/Memory/SystemAllocator.h>
+#include <AzCore/Math/Crc.h>
+#include <AzCore/RTTI/TypeInfoSimple.h>
+#include <AzCore/Serialization/SerializeContext.h>
+#include <Editor/Systems/GNParamContext.h>
+#include <AzCore/Serialization/EditContext.h>
+
+namespace AZ
+{
+    class ReflectContext;
+}
+
+namespace GeomNodes
+{
+    
+    class GNProperties
+    {
+    public:
+        static void Reflect(AZ::ReflectContext* reflection);
+    };
+
+    /**
+     * Base class for all script properties.
+     */
+    class GNProperty
+    {
+    public:
+        //static void UpdateScriptProperty(GNParamDataContext& sdc, int valueIndex, GNProperty** targetProperty);
+
+        static void Reflect(AZ::ReflectContext* reflection);
+        static bool VersionConverter(AZ::SerializeContext& context, AZ::SerializeContext::DataElementNode& classElement);
+
+        virtual ~GNProperty()
+        {
+        }
+        AZ_RTTI(GeomNodes::GNProperty, "{71904E43-F0A1-45EA-B87F-4CC5234E1E52}");
+
+        GNProperty()
+        {
+        }
+        GNProperty(const char* name)
+            : m_id(AZ::Crc32(name))
+            , m_name(name)
+        {
+        }
+
+        virtual const void* GetDataAddress() const = 0;
+        virtual AZ::TypeId GetDataTypeUuid() const = 0;
+        virtual AZStd::string ToJSONString() const = 0;
+        /**
+         * Test if the value at the index valueIndex is of the same type as that of the instance of GNProperty's subclass.
+         */
+        virtual bool DoesTypeMatch(GNParamDataContext& /*context*/, int /*valueIndex*/) const
+        {
+            return false;
+        }
+
+        virtual void ReadSetGNId(GNParamDataContext& context);
+        virtual GNProperty* Clone(const char* name = nullptr) const = 0;
+
+        /*virtual bool Write(AZ::ScriptContext& context) = 0;
+        virtual bool TryRead(GNParamDataContext& context, int valueIndex)
+        {
+            (void)context;
+            (void)valueIndex;
+            return false;
+        };*/
+        bool TryUpdate(const GNProperty* gnProperty)
+        {
+            bool allowUpdate = azrtti_typeid(gnProperty) == azrtti_typeid(this);
+
+            if (allowUpdate)
+            {
+                CloneDataFrom(gnProperty);
+            }
+
+            return allowUpdate;
+        }
+
+        AZ::u64         m_id;
+        AZStd::string   m_gnId;                     // Geometry Node Param Id
+        AZStd::string   m_name;                     // Geometry Node Param Name
+        AZStd::string   m_type = "UNKNOWN";         // Geometry Node Param Type
+
+        bool m_isMaxSet = false;
+        bool m_isMinSet = false;
+    protected:
+        virtual void CloneDataFrom(const GNProperty* gnProperty) = 0;
+    };
+
+    class GNParamNil : public GNProperty
+    {
+    public:
+        AZ_CLASS_ALLOCATOR(GNParamNil, AZ::SystemAllocator, 0);
+        AZ_RTTI(GeomNodes::GNParamNil, "{519D98C7-054A-4047-BCEB-28DCD38CFCD4}", GNProperty);
+
+        static void Reflect(AZ::ReflectContext* reflection);
+        static GNProperty* TryCreateProperty(GNParamDataContext& context, int valueIndex, const char* name);
+
+        GNParamNil()
+        {
+        }
+        GNParamNil(const char* name)
+            : GNProperty(name)
+        {
+        }
+
+        const void* GetDataAddress() const override;
+        AZ::TypeId GetDataTypeUuid() const override;
+        AZStd::string ToJSONString() const override;
+
+        GNParamNil* Clone(const char* name) const override;
+
+        /*bool Write(AZ::ScriptContext& context) override;
+        bool TryRead(GNParamDataContext& context, int valueIndex) override;*/
+
+    protected:
+        void CloneDataFrom(const GNProperty* gnProperty) override;
+    };
+
+    class GNParamBoolean : public GNProperty
+    {
+    public:
+        AZ_CLASS_ALLOCATOR(GNParamBoolean, AZ::SystemAllocator, 0);
+        AZ_RTTI(GeomNodes::GNParamBoolean, "{6A05BCAB-50F7-4988-96E1-0EDB6B76C3A3}", GNProperty);
+
+        static void Reflect(AZ::ReflectContext* reflection);
+        static GNProperty* TryCreateProperty(GNParamDataContext& context, int valueIndex, const char* name);
+
+        GNParamBoolean()
+            : m_value(false)
+        {
+            m_type = GetEnumString(ParamType::Bool);
+        }
+        GNParamBoolean(const char* name, bool value)
+            : GNProperty(name)
+            , m_value(value)
+        {
+            m_type = GetEnumString(ParamType::Bool);
+        }
+
+        const void* GetDataAddress() const override
+        {
+            return &m_value;
+        }
+
+        AZStd::string ToJSONString() const override;
+
+        AZ::TypeId GetDataTypeUuid() const override;
+
+        bool DoesTypeMatch(GNParamDataContext& context, int valueIndex) const override;
+
+        GNParamBoolean* Clone(const char* name) const override;
+
+        /*bool Write(AZ::ScriptContext& context) override;
+        bool TryRead(GNParamDataContext& context, int valueIndex) override;*/
+
+        bool m_value;
+
+    protected:
+        void CloneDataFrom(const GNProperty* gnProperty) override;
+    };
+
+    class GNParamInt : public GNProperty
+    {
+    public:
+        AZ_CLASS_ALLOCATOR(GNParamInt, AZ::SystemAllocator, 0);
+        AZ_RTTI(GNParamInt, "{B2457A3D-F30C-43F9-90F0-5BFAFFFD0F59}", GNProperty);
+
+        static void Reflect(AZ::ReflectContext* reflection);
+        static GNProperty* TryCreateProperty(GNParamDataContext& context, int valueIndex, const char* name);
+
+        GNParamInt()
+            : m_value(0)
+        {
+            m_type = GetEnumString(ParamType::Int);
+        }
+        GNParamInt(const char* name, int value)
+            : GNProperty(name)
+            , m_value(value)
+        {
+            m_type = GetEnumString(ParamType::Int);
+        }
+
+        const void* GetDataAddress() const override
+        {
+            return &m_value;
+        }
+        AZ::TypeId GetDataTypeUuid() const override;
+        AZStd::string ToJSONString() const override;
+
+        bool DoesTypeMatch(GNParamDataContext& context, int valueIndex) const override;
+
+        GNParamInt* Clone(const char* name) const override;
+
+        /*bool Write(AZ::ScriptContext& context) override;
+        bool TryRead(GNParamDataContext& context, int valueIndex) override;*/
+
+        void SetMinValue(int min)
+        {
+            m_min = min;
+            m_isMinSet = true;
+        }
+
+        void SetMaxValue(int max)
+        {
+            m_max = max;
+            m_isMaxSet = true;
+        }
+
+        int m_value;
+        int m_max;      // Geometry Node Param Max(int)
+        int m_min;      // Geometry Node Param Min(int)
+
+    protected:
+        void CloneDataFrom(const GNProperty* gnProperty) override;
+    };
+
+    class GNParamValue : public GNProperty
+    {
+    public:
+        AZ_CLASS_ALLOCATOR(GNParamValue, AZ::SystemAllocator, 0);
+        AZ_RTTI(GeomNodes::GNParamValue, "{4790660B-B942-4421-B942-AE27DF67BF4F}", GNProperty);
+
+        static void Reflect(AZ::ReflectContext* reflection);
+        static GNProperty* TryCreateProperty(GNParamDataContext& context, int valueIndex, const char* name);
+
+        GNParamValue()
+            : m_value(0.0f)
+        {
+            m_type = GetEnumString(ParamType::Value);
+        }
+        GNParamValue(const char* name, double value)
+            : GNProperty(name)
+            , m_value(value)
+        {
+            m_type = GetEnumString(ParamType::Value);
+        }
+
+        const void* GetDataAddress() const override
+        {
+            return &m_value;
+        }
+        AZ::TypeId GetDataTypeUuid() const override;
+        AZStd::string ToJSONString() const override;
+
+        bool DoesTypeMatch(GNParamDataContext& context, int valueIndex) const override;
+
+        GNParamValue* Clone(const char* name) const override;
+
+        /*bool Write(AZ::ScriptContext& context) override;
+        bool TryRead(GNParamDataContext& context, int valueIndex) override;*/
+
+        void SetMinValue(double min)
+        {
+            m_min = min;
+            m_isMinSet = true;
+        }
+
+        void SetMaxValue(double max)
+        {
+            m_max = max;
+            m_isMaxSet = true;
+        }
+
+        double m_value;
+        double m_max;       // Geometry Node Param Max(double)
+        double m_min;       // Geometry Node Param Min(double)
+
+    protected:
+        void CloneDataFrom(const GNProperty* gnProperty) override;
+    };
+
+    class GNParamString : public GNProperty
+    {
+    public:
+        AZ_CLASS_ALLOCATOR(GNParamString, AZ::SystemAllocator, 0);
+        AZ_RTTI(GeomNodes::GNParamString, "{9296C827-0281-4DBF-AC1A-B6636BCEC716}", GNProperty);
+
+        static void Reflect(AZ::ReflectContext* reflection);
+        static GNProperty* TryCreateProperty(GNParamDataContext& context, int valueIndex, const char* name);
+
+        GNParamString()
+        {
+            m_type = GetEnumString(ParamType::String);
+        }
+        GNParamString(const char* name, const char* value)
+            : GNProperty(name)
+            , m_value(value)
+        {
+            m_type = GetEnumString(ParamType::String);
+        }
+
+        const void* GetDataAddress() const override
+        {
+            return &m_value;
+        }
+        AZ::TypeId GetDataTypeUuid() const override;
+        AZStd::string ToJSONString() const override;
+
+        bool DoesTypeMatch(GNParamDataContext& context, int valueIndex) const override;
+
+        GNParamString* Clone(const char* name = nullptr) const override;
+
+        /*bool Write(AZ::ScriptContext& context) override;
+        bool TryRead(GNParamDataContext& context, int valueIndex) override;*/
+
+        AZStd::string m_value;
+
+    protected:
+        void CloneDataFrom(const GNProperty* gnProperty) override;
+    };
+} // namespace GeomNodes

+ 77 - 0
Gems/O3DE/GeomNodes/Code/Source/Editor/Systems/GeomNodesSystem.cpp

@@ -0,0 +1,77 @@
+#include "GeomNodesSystem.h"
+#include "Editor/UI/PropertyFileSelect.h"
+#include "Editor/UI/ValidationHandler.h"
+#include "Editor/UI/GeomNodesValidator.h"
+#include "Bridge.h"
+#include <Editor/EBus/IpcHandlerBus.h>
+
+namespace GeomNodes
+{
+    long IpcHandlerCB(AZ::u64 id, const char* data, AZ::u64 length)
+    {
+        AZ_Printf("GeomNodesSystem", "id: %llu data: %s length: %llu", id, data, length);
+        Ipc::IpcHandlerNotificationBus::Event(
+            AZ::EntityId(id), &Ipc::IpcHandlerNotificationBus::Events::OnMessageReceived, reinterpret_cast<const AZ::u8*>(data), length);
+
+        return 0;
+    }
+
+    GeomNodesSystem::GeomNodesSystem()
+        : m_propertyHandlers()
+        , m_validator(AZStd::make_unique<Validator>())
+        , m_validationHandler(AZStd::make_unique<ValidationHandler>())
+        {
+        }
+
+    GeomNodesSystem::~GeomNodesSystem()
+    {
+    }
+
+    void GeomNodesSystem::OnActivate()
+    {
+        Init(SERVER_ID, IpcHandlerCB); 
+
+        RegisterHandlersAndBuses();
+    }
+
+    void GeomNodesSystem::OnDeactivate()
+    {
+        UnregisterHandlersAndBuses();
+        Uninitialize();
+    }
+
+    FunctorValidator* GeomNodesSystem::GetValidator(FunctorValidator::FunctorType functor)
+    {
+        return m_validator->GetQValidator(functor);
+    }
+
+    void GeomNodesSystem::TrackValidator(FunctorValidator* validator)
+    {
+        m_validator->TrackThisValidator(validator);
+    }
+
+    void GeomNodesSystem::RegisterHandlersAndBuses()
+    {
+        m_propertyHandlers.push_back(PropertyFuncValBrowseEditHandler::Register(m_validationHandler.get()));
+        m_propertyHandlers.push_back(PropertyFileSelectHandler::Register(m_validationHandler.get()));
+        ValidatorBus::Handler::BusConnect();
+
+        //TODO: setup our IPC system. Since this is the gem it will be the server.
+        // only one handler. Then messages will have the sender id(EntityID) which will be used when sending
+        // the messages via the EBUS. when a component is registered as an EBUS handler they will receive
+        // the correct messages.
+    }
+
+    void GeomNodesSystem::UnregisterHandlersAndBuses()
+    {
+        ValidatorBus::Handler::BusDisconnect();
+
+        for (AzToolsFramework::PropertyHandlerBase* handler : m_propertyHandlers)
+        {
+            AzToolsFramework::PropertyTypeRegistrationMessages::Bus::Broadcast(
+                &AzToolsFramework::PropertyTypeRegistrationMessages::Bus::Handler::UnregisterPropertyType,
+                handler);
+            delete handler;
+        }
+    }
+}

+ 37 - 0
Gems/O3DE/GeomNodes/Code/Source/Editor/Systems/GeomNodesSystem.h

@@ -0,0 +1,37 @@
+#pragma once
+
+#include <AZCore/std/containers/vector.h>
+#include <AZCore/std/smart_ptr/unique_ptr.h>
+#include <AzToolsFramework/UI/PropertyEditor/PropertyEditorAPI.h>
+#include "Editor/EBus/ValidatorBus.h"
+
+namespace GeomNodes
+{
+    class Validator;
+    class ValidationHandler;
+
+    class GeomNodesSystem
+        : public ValidatorBus::Handler
+    {
+    public:
+        GeomNodesSystem();
+        virtual ~GeomNodesSystem();
+
+        void OnActivate();
+        void OnDeactivate();
+
+        FunctorValidator* GetValidator(FunctorValidator::FunctorType) override;
+        void TrackValidator(FunctorValidator*) override;
+
+    private:
+        // Un/Registers and dis/connect handlers and buses
+        void RegisterHandlersAndBuses();
+        void UnregisterHandlersAndBuses();
+
+        // Allows lookup and contains all allocated QValidators
+        AZStd::unique_ptr<Validator> m_validator;
+
+        AZStd::vector<AzToolsFramework::PropertyHandlerBase*> m_propertyHandlers;
+        AZStd::unique_ptr<ValidationHandler> m_validationHandler;
+    };
+}

+ 33 - 0
Gems/O3DE/GeomNodes/Code/Source/Editor/UI/FunctorValidator.cpp

@@ -0,0 +1,33 @@
+#include "FunctorValidator.h"
+
+namespace GeomNodes
+{
+    FunctorValidator::FunctorValidator(FunctorType functor)
+        : QValidator()
+        , m_functor(functor)
+    {
+    }
+
+    FunctorValidator::FunctorValidator()
+        : QValidator()
+        , m_functor(nullptr)
+    {
+    }
+
+    QValidator::State FunctorValidator::validate(QString& input, [[maybe_unused]] int& pos) const
+    {
+        return m_functor(input).first;
+    }
+
+    FunctorValidator::ReturnType FunctorValidator::ValidateWithErrors(const QString& input) const
+    {
+        return m_functor(input);
+    }
+
+    FunctorValidator::FunctorType FunctorValidator::Functor() const
+    {
+        return m_functor;
+    }
+} // namespace GeomNodes
+
+#include <moc_FunctorValidator.cpp>

+ 37 - 0
Gems/O3DE/GeomNodes/Code/Source/Editor/UI/FunctorValidator.h

@@ -0,0 +1,37 @@
+#pragma once
+
+#if !defined(Q_MOC_RUN)
+#include <AzCore/std/utils.h>
+
+#include <QValidator>
+#endif
+
+// Derived from the same class from PlatformSettingsTool
+
+namespace GeomNodes
+{
+    class FunctorValidator
+        : public QValidator
+    {
+        Q_OBJECT
+
+    public:
+        typedef AZStd::pair<QValidator::State, const QString> ReturnType;
+        typedef ReturnType(* FunctorType)(const QString&);
+
+        FunctorValidator(FunctorType functor);
+
+        // Validates using QValidates api
+        QValidator::State validate(QString& input, int& pos) const override;
+        // Validates and returns the result with an error string if one occurred
+        virtual ReturnType ValidateWithErrors(const QString& input) const;
+        // Returns the function used to validate
+        FunctorType Functor() const;
+
+    protected:
+        FunctorValidator();
+
+        // The function to use for validating
+        FunctorType m_functor;
+    };
+} // namespace ProjectSettingsTool

+ 51 - 0
Gems/O3DE/GeomNodes/Code/Source/Editor/UI/GeomNodesValidator.cpp

@@ -0,0 +1,51 @@
+#include "GeomNodesValidator.h"
+
+#include "Validators.h"
+
+namespace GeomNodes
+{
+    Validator::Validator()
+    {}
+
+    Validator::~Validator()
+    {
+        for (AZStd::pair<FunctorValidator::FunctorType, FunctorValidator*>& pair : m_validatorToQValidator)
+        {
+            delete pair.second;
+        }
+        for (FunctorValidator* qValidator : m_otherValidators)
+        {
+            delete qValidator;
+        }
+    }
+
+    FunctorValidator* Validator::GetQValidator(FunctorValidator::FunctorType validator)
+    {
+        if (validator != nullptr)
+        {
+            auto iter = m_validatorToQValidator.find(validator);
+
+            if (iter != m_validatorToQValidator.end())
+            {
+                return iter->second;
+            }
+            else
+            {
+                FunctorValidator* qValidator = new FunctorValidator(validator);
+                m_validatorToQValidator.insert(
+                    AZStd::pair<FunctorValidator::FunctorType, FunctorValidator*>(validator, qValidator));
+
+                return qValidator;
+            }
+        }
+        else
+        {
+            return nullptr;
+        }
+    }
+
+    void Validator::TrackThisValidator(FunctorValidator* validator)
+    {
+        m_otherValidators.push_back(validator);
+    }
+} // namespace GeomNodes

+ 36 - 0
Gems/O3DE/GeomNodes/Code/Source/Editor/UI/GeomNodesValidator.h

@@ -0,0 +1,36 @@
+#pragma once
+
+#include "FunctorValidator.h"
+
+#include <AzCore/std/string/string.h>
+#include <AzCore/std/containers/unordered_map.h>
+
+
+namespace GeomNodes
+{
+    class Validator
+    {
+    public:
+        typedef FunctorValidator::ReturnType ValidatorReturnType;
+        typedef FunctorValidator::FunctorType ValidatorType;
+
+        Validator();
+        ~Validator();
+
+        // Finds the QValidator for a given validator or makes one and returns it
+        FunctorValidator* GetQValidator(FunctorValidator::FunctorType validator);
+        // Tracks this QValidator and deletes it in the destructor
+        void TrackThisValidator(FunctorValidator* validator);
+
+    private:
+        typedef AZStd::unordered_map<FunctorValidator::FunctorType, FunctorValidator*> ValidatorToQValidatorType;
+        typedef AZStd::list<FunctorValidator*> QValidatorList;
+
+        AZ_DISABLE_COPY_MOVE(Validator);
+
+        // Maps validator functions to QValidators
+        ValidatorToQValidatorType m_validatorToQValidator;
+        // Tracks allocations of other QValidators so they don't leak
+        QValidatorList m_otherValidators;
+    };
+} // namespace GeomNodes

+ 103 - 0
Gems/O3DE/GeomNodes/Code/Source/Editor/UI/PropertyFileSelect.cpp

@@ -0,0 +1,103 @@
+#include "PropertyFileSelect.h"
+
+#include "UI_common.h"
+#include "ValidationHandler.h"
+
+#include <QLayout>
+#include <QPushButton>
+#include <QLineEdit>
+
+namespace GeomNodes
+{
+    PropertyFileSelectCtrl::PropertyFileSelectCtrl(QWidget* pParent)
+        : PropertyFuncValBrowseEditCtrl(pParent)
+        , m_selectFunctor(nullptr)
+    {
+        // Turn on clear button by default
+        browseEdit()->setClearButtonEnabled(true);
+
+        connect(browseEdit(), &AzQtComponents::BrowseEdit::attachedButtonTriggered, this, &PropertyFileSelectCtrl::SelectFile);
+    }
+
+    void PropertyFileSelectCtrl::SelectFile()
+    {
+        if (m_selectFunctor != nullptr)
+        {
+            QString path = m_selectFunctor(browseEdit()->text());
+            if (!path.isEmpty())
+            {
+                SetValueUser(path);
+            }
+        }
+        else
+        {
+            AZ_Assert(false, "No file select functor set.");
+        }
+    }
+
+    void PropertyFileSelectCtrl::ConsumeAttribute(AZ::u32 attrib, AzToolsFramework::PropertyAttributeReader* attrValue, const char* debugName)
+    {
+        if (attrib == Attributes::SelectFunction)
+        {
+            void* functor = nullptr;
+            if (attrValue->Read<void*>(functor))
+            {
+                // This is guaranteed type safe elsewhere so this is safe
+                m_selectFunctor = reinterpret_cast<FileSelectFuncType>(functor);
+            }
+        }
+        else
+        {
+            PropertyFuncValBrowseEditCtrl::ConsumeAttribute(attrib, attrValue, debugName);
+        }
+    }
+
+    //  Handler  ///////////////////////////////////////////////////////////////////
+
+    PropertyFileSelectHandler::PropertyFileSelectHandler(ValidationHandler* valHdlr)
+        : AzToolsFramework::PropertyHandler<AZStd::string, PropertyFileSelectCtrl>()
+        , m_validationHandler(valHdlr)
+    {}
+
+    AZ::u32 PropertyFileSelectHandler::GetHandlerName(void) const
+    {
+        return Handlers::FileSelect;
+    }
+
+    QWidget* PropertyFileSelectHandler::CreateGUI(QWidget* pParent)
+    {
+        PropertyFileSelectCtrl* ctrl = aznew PropertyFileSelectCtrl(pParent);
+        m_validationHandler->AddValidatorCtrl(ctrl);
+        return ctrl;
+    }
+
+    void PropertyFileSelectHandler::ConsumeAttribute(PropertyFileSelectCtrl* GUI, AZ::u32 attrib, AzToolsFramework::PropertyAttributeReader* attrValue, const char* debugName)
+    {
+        GUI->ConsumeAttribute(attrib, attrValue, debugName);
+    }
+
+    void PropertyFileSelectHandler::WriteGUIValuesIntoProperty([[maybe_unused]] size_t index, PropertyFileSelectCtrl* GUI, property_t& instance, [[maybe_unused]] AzToolsFramework::InstanceDataNode* node)
+    {
+        instance = GUI->GetValue().toUtf8().data();
+    }
+
+    bool PropertyFileSelectHandler::ReadValuesIntoGUI([[maybe_unused]] size_t index, PropertyFileSelectCtrl* GUI, const property_t& instance, [[maybe_unused]] AzToolsFramework::InstanceDataNode* node)
+    {
+        GUI->SetNotifyTarget(node->GetParent()->GetInstance(0));
+        GUI->SetValue(instance.data());
+        GUI->ForceValidate();
+        return true;
+    }
+
+    PropertyFileSelectHandler* PropertyFileSelectHandler::Register(ValidationHandler* valHdlr)
+    {
+        PropertyFileSelectHandler* handler = aznew PropertyFileSelectHandler(valHdlr);
+        AzToolsFramework::PropertyTypeRegistrationMessages::Bus::Broadcast(
+            &AzToolsFramework::PropertyTypeRegistrationMessages::Bus::Handler::RegisterPropertyType,
+            handler);
+        return handler;
+    }
+} // namespace GeomNodes
+
+#include <moc_PropertyFileSelect.cpp>
+

+ 55 - 0
Gems/O3DE/GeomNodes/Code/Source/Editor/UI/PropertyFileSelect.h

@@ -0,0 +1,55 @@
+#pragma once
+
+#if !defined(Q_MOC_RUN)
+#include "PropertyFuncValBrowseEdit.h"
+#endif
+
+// Forward declare
+class QPushButton;
+
+namespace GeomNodes
+{
+    // Forward declare
+    class ValidationHandler;
+
+    class PropertyFileSelectCtrl
+        : public PropertyFuncValBrowseEditCtrl
+    {
+        Q_OBJECT
+
+    public:
+        typedef QString(* FileSelectFuncType)(const QString&);
+
+        PropertyFileSelectCtrl(QWidget* pParent = nullptr);
+
+        virtual void ConsumeAttribute(AZ::u32 attrib, AzToolsFramework::PropertyAttributeReader* attrValue, const char* debugName) override;
+
+    protected:
+        void SelectFile();
+
+        QPushButton* m_selectButton;
+        FileSelectFuncType m_selectFunctor;
+    };
+
+    class PropertyFileSelectHandler
+        : public AzToolsFramework::PropertyHandler<AZStd::string, PropertyFileSelectCtrl>
+    {
+        AZ_CLASS_ALLOCATOR(PropertyFileSelectHandler, AZ::SystemAllocator, 0);
+
+    public:
+        PropertyFileSelectHandler(ValidationHandler* valHdlr);
+
+        AZ::u32 GetHandlerName(void) const override;
+        // Need to unregister ourselves
+        bool AutoDelete() const override { return false; }
+
+        QWidget* CreateGUI(QWidget* pParent) override;
+        void ConsumeAttribute(PropertyFileSelectCtrl* GUI, AZ::u32 attrib, AzToolsFramework::PropertyAttributeReader* attrValue, const char* debugName) override;
+        void WriteGUIValuesIntoProperty(size_t index, PropertyFileSelectCtrl* GUI, property_t& instance, AzToolsFramework::InstanceDataNode* node) override;
+        bool ReadValuesIntoGUI(size_t index, PropertyFileSelectCtrl* GUI, const property_t& instance, AzToolsFramework::InstanceDataNode* node)  override;
+        static PropertyFileSelectHandler* Register(ValidationHandler* valHdlr);
+
+    private:
+        ValidationHandler* m_validationHandler;
+    };
+} // namespace GeomNodes

+ 232 - 0
Gems/O3DE/GeomNodes/Code/Source/Editor/UI/PropertyFuncValBrowseEdit.cpp

@@ -0,0 +1,232 @@
+/*
+ * 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 "PropertyFuncValBrowseEdit.h"
+#include "Editor/EBus/ValidatorBus.h"
+#include "ValidationHandler.h"
+
+#include <AzToolsFramework/UI/PropertyEditor/PropertyQTConstants.h>
+
+#include <QLineEdit>
+#include <QHBoxLayout>
+
+namespace GeomNodes
+{
+    PropertyFuncValBrowseEditCtrl::PropertyFuncValBrowseEditCtrl(QWidget* pParent)
+        : QWidget(pParent)
+        , m_validator(nullptr)
+    {
+        QHBoxLayout* layout = new QHBoxLayout(this);
+        m_browseEdit = new AzQtComponents::BrowseEdit(this);
+
+        layout->setSpacing(4);
+        layout->setContentsMargins(1, 0, 1, 0);
+        layout->addWidget(m_browseEdit);
+
+        browseEdit()->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Fixed);
+        browseEdit()->setMinimumWidth(AzToolsFramework::PropertyQTConstant_MinimumWidth);
+        browseEdit()->setFixedHeight(AzToolsFramework::PropertyQTConstant_DefaultHeight);
+
+        browseEdit()->setFocusPolicy(Qt::StrongFocus);
+
+        setLayout(layout);
+        setFocusProxy(browseEdit());
+        setFocusPolicy(browseEdit()->focusPolicy());
+
+        m_browseEdit->setClearButtonEnabled(true);
+        connect(m_browseEdit, &AzQtComponents::BrowseEdit::textChanged, this, &PropertyFuncValBrowseEditCtrl::ValueChangedByUser);
+        connect(m_browseEdit, &AzQtComponents::BrowseEdit::textChanged, this, &PropertyFuncValBrowseEditCtrl::ValidateAndShowErrors);
+        connect(m_browseEdit, &AzQtComponents::BrowseEdit::textChanged, this, [this]([[maybe_unused]] const QString& text)
+            {
+                EBUS_EVENT(AzToolsFramework::PropertyEditorGUIMessages::Bus, RequestWrite, this);
+            });
+    }
+
+    QString PropertyFuncValBrowseEditCtrl::GetValue() const
+    {
+        return m_browseEdit->text();
+    }
+
+    void PropertyFuncValBrowseEditCtrl::SetValue(const QString& value)
+    {
+        m_browseEdit->setText(value);
+    }
+
+    void PropertyFuncValBrowseEditCtrl::SetValueUser(const QString& value)
+    {
+        SetValue(value);
+
+        emit ValueChangedByUser();
+    }
+
+    FunctorValidator* PropertyFuncValBrowseEditCtrl::GetValidator()
+    {
+        return m_validator;
+    }
+
+    void PropertyFuncValBrowseEditCtrl::SetValidator(FunctorValidator* validator)
+    {
+        m_browseEdit->lineEdit()->setValidator(validator);
+        m_validator = validator;
+    }
+
+    void PropertyFuncValBrowseEditCtrl::SetValidator(FunctorValidator::FunctorType validator)
+    {
+        FunctorValidator* val = nullptr;
+        ValidatorBus::BroadcastResult(
+            val,
+            &ValidatorBus::Handler::GetValidator,
+            validator);
+
+        SetValidator(val);
+    }
+
+    bool PropertyFuncValBrowseEditCtrl::ValidateAndShowErrors()
+    {
+        if (m_validator)
+        {
+            auto path = m_browseEdit->text();
+            FunctorValidator::ReturnType result = m_validator->ValidateWithErrors(path);
+            if (result.first == QValidator::Acceptable)
+            {
+                m_browseEdit->lineEdit()->setToolTip("");
+                if (m_validationResultHandler)
+                    m_validationResultHandler->Invoke(m_notifyTarget, path.toStdString().c_str());
+                return true;
+            }
+            else
+            {
+                m_browseEdit->lineEdit()->setToolTip(result.second);
+                m_browseEdit->lineEdit()->setReadOnly(false);
+                if (m_validationResultHandler)
+                    m_validationResultHandler->Invoke(m_notifyTarget, "");
+                return false;
+            }
+        }
+        else
+        {
+            return true;
+        }
+    }
+
+    void PropertyFuncValBrowseEditCtrl::ForceValidate()
+    {
+        // Triggers update for errors
+        m_browseEdit->lineEdit()->textChanged(m_browseEdit->text());
+    }
+
+    void PropertyFuncValBrowseEditCtrl::SetNotifyTarget(void* notifyTarget)
+    {
+        m_notifyTarget = notifyTarget;
+    }
+
+    void PropertyFuncValBrowseEditCtrl::ConsumeAttribute(AZ::u32 attrib, AzToolsFramework::PropertyAttributeReader* attrValue, [[maybe_unused]] const char* debugName)
+    {
+        if (attrib == Attributes::FuncValidator)
+        {
+            void* validator = nullptr;
+            if (attrValue->Read<void*>(validator))
+            {
+                if (validator != nullptr)
+                {
+                    SetValidator(reinterpret_cast<FunctorValidator::FunctorType>(validator));
+                }
+            }
+        }
+        else if (attrib == Attributes::ValidationChange)
+        {
+            ValidationCallbackType* func = azdynamic_cast<ValidationCallbackType*>(attrValue->GetAttribute());
+
+            if (!m_validationResultHandler && func)
+            {
+                m_validationResultHandler = func;
+            }
+            else
+            {
+                m_validationResultHandler = nullptr;
+            }
+        }
+        else if (attrib == Attributes::ClearButton)
+        {
+            bool enable = false;
+            if (attrValue->Read<bool>(enable))
+            {
+                m_browseEdit->lineEdit()->setClearButtonEnabled(enable);
+            }
+        }
+        else if (attrib == Attributes::RemovableReadOnly)
+        {
+            bool readOnly = false;
+            if (attrValue->Read<bool>(readOnly))
+            {
+                m_browseEdit->lineEdit()->setReadOnly(readOnly);
+            }
+        }
+        else if (attrib == Attributes::ObfuscatedText)
+        {
+            bool obfus = false;
+            if (attrValue->Read<bool>(obfus) && obfus)
+            {
+                m_browseEdit->lineEdit()->setEchoMode(QLineEdit::Password);
+            }
+        }
+    }
+
+    AzQtComponents::BrowseEdit* PropertyFuncValBrowseEditCtrl::browseEdit()
+    {
+        return m_browseEdit;
+    }
+
+    //  Handler  ///////////////////////////////////////////////////////////////////
+
+    PropertyFuncValBrowseEditHandler::PropertyFuncValBrowseEditHandler(ValidationHandler* valHdlr)
+        : AzToolsFramework::PropertyHandler <AZStd::string, PropertyFuncValBrowseEditCtrl>()
+        , m_validationHandler(valHdlr
+            )
+    {}
+
+    AZ::u32 PropertyFuncValBrowseEditHandler::GetHandlerName(void) const
+    {
+        return Handlers::QValidatedBrowseEdit;
+    }
+
+    QWidget* PropertyFuncValBrowseEditHandler::CreateGUI(QWidget* pParent)
+    {
+        PropertyFuncValBrowseEditCtrl* ctrl = aznew PropertyFuncValBrowseEditCtrl(pParent);
+        m_validationHandler->AddValidatorCtrl(ctrl);
+        return ctrl;
+    }
+
+    void PropertyFuncValBrowseEditHandler::ConsumeAttribute(PropertyFuncValBrowseEditCtrl* GUI, AZ::u32 attrib, AzToolsFramework::PropertyAttributeReader* attrValue, const char* debugName)
+    {
+        GUI->ConsumeAttribute(attrib, attrValue, debugName);
+    }
+
+    void PropertyFuncValBrowseEditHandler::WriteGUIValuesIntoProperty([[maybe_unused]] size_t index, PropertyFuncValBrowseEditCtrl* GUI, property_t& instance, [[maybe_unused]] AzToolsFramework::InstanceDataNode* node)
+    {
+        instance = GUI->GetValue().toUtf8().data();
+    }
+
+    bool PropertyFuncValBrowseEditHandler::ReadValuesIntoGUI([[maybe_unused]] size_t index, PropertyFuncValBrowseEditCtrl* GUI, const property_t& instance, [[maybe_unused]] AzToolsFramework::InstanceDataNode* node)
+    {
+        GUI->SetValue(instance.data());
+        GUI->ForceValidate();
+        return true;
+    }
+
+    PropertyFuncValBrowseEditHandler* PropertyFuncValBrowseEditHandler::Register(ValidationHandler* valHdlr)
+    {
+        PropertyFuncValBrowseEditHandler* handler = aznew PropertyFuncValBrowseEditHandler(valHdlr);
+        AzToolsFramework::PropertyTypeRegistrationMessages::Bus::Broadcast(
+            &AzToolsFramework::PropertyTypeRegistrationMessages::Bus::Handler::RegisterPropertyType,
+            handler);
+        return handler;
+    }
+} // namespace GeomNodes
+
+#include <moc_PropertyFuncValBrowseEdit.cpp>

+ 84 - 0
Gems/O3DE/GeomNodes/Code/Source/Editor/UI/PropertyFuncValBrowseEdit.h

@@ -0,0 +1,84 @@
+#pragma once
+
+#if !defined(Q_MOC_RUN)
+#include "FunctorValidator.h"
+
+#include <AzToolsFramework/UI/PropertyEditor/PropertyEditorAPI.h>
+#include <AzQtComponents/Components/Widgets/BrowseEdit.h>
+#endif
+
+#include "UI_common.h"
+
+namespace GeomNodes
+{
+    // Forward Declare
+    class ValidationHandler;
+    
+    class PropertyFuncValBrowseEditCtrl
+        : public QWidget
+    {
+        Q_OBJECT
+
+    public:
+        AZ_CLASS_ALLOCATOR(PropertyFuncValBrowseEditCtrl, AZ::SystemAllocator, 0);
+
+        using ValidationCallbackType = AZ::Edit::AttributeFunction<void(const AZStd::string&)>;
+
+        PropertyFuncValBrowseEditCtrl(QWidget* pParent = nullptr);
+
+        virtual QString GetValue() const;
+        // Sets value programmtically and triggers validation
+        virtual void SetValue(const QString& value);
+        // Sets value as if user set it
+        void SetValueUser(const QString& value);
+        // Returns pointer to the validator used
+        FunctorValidator* GetValidator();
+        // Sets the validator for the lineedit
+        void SetValidator(FunctorValidator* validator);
+        // Sets the validator for the linedit
+        void SetValidator(FunctorValidator::FunctorType validator);
+        // Returns false if invalid and returns shows error as tooltip
+        bool ValidateAndShowErrors();
+        // Forces the values to up validated and style updated
+        void ForceValidate();
+        virtual void SetNotifyTarget(void* notifyTarget);
+
+        // Returns a pointer to the BrowseEdit object.
+        AzQtComponents::BrowseEdit* browseEdit();
+
+        virtual void ConsumeAttribute(AZ::u32 attrib, AzToolsFramework::PropertyAttributeReader* attrValue, const char* debugName);
+
+    signals:
+        void ValueChangedByUser();
+
+    protected:
+        // Keeps track of the validator so no const_casts must be done
+        FunctorValidator* m_validator;
+        ValidationCallbackType* m_validationResultHandler = nullptr;
+        void* m_notifyTarget = nullptr;
+
+        AzQtComponents::BrowseEdit* m_browseEdit = nullptr;
+    };
+
+    class PropertyFuncValBrowseEditHandler
+        : public AzToolsFramework::PropertyHandler <AZStd::string, PropertyFuncValBrowseEditCtrl>
+    {
+        AZ_CLASS_ALLOCATOR(PropertyFuncValBrowseEditHandler, AZ::SystemAllocator, 0);
+
+    public:
+        PropertyFuncValBrowseEditHandler(ValidationHandler* valHdlr);
+
+        AZ::u32 GetHandlerName(void) const override;
+        // Need to unregister ourselves
+        bool AutoDelete() const override { return false; }
+
+        QWidget* CreateGUI(QWidget* pParent) override;
+        void ConsumeAttribute(PropertyFuncValBrowseEditCtrl* GUI, AZ::u32 attrib, AzToolsFramework::PropertyAttributeReader* attrValue, const char* debugName) override;
+        void WriteGUIValuesIntoProperty(size_t index, PropertyFuncValBrowseEditCtrl* GUI, property_t& instance, AzToolsFramework::InstanceDataNode* node) override;
+        bool ReadValuesIntoGUI(size_t index, PropertyFuncValBrowseEditCtrl* GUI, const property_t& instance, AzToolsFramework::InstanceDataNode* node)  override;
+        static PropertyFuncValBrowseEditHandler* Register(ValidationHandler* valHdlr);
+
+    private:
+        ValidationHandler* m_validationHandler;
+    };
+} // namespace GeomNodes

+ 23 - 0
Gems/O3DE/GeomNodes/Code/Source/Editor/UI/UI_common.h

@@ -0,0 +1,23 @@
+#pragma once
+
+#include "Editor/Commons.h"
+#include "Utils.h"
+
+namespace GeomNodes
+{
+    namespace Attributes
+    {
+        static const AZ::Crc32 FuncValidator = AZ_CRC("GNFuncValidator");
+        static const AZ::Crc32 SelectFunction = AZ_CRC("GNSelectFunction");
+        static const AZ::Crc32 ObfuscatedText = AZ_CRC("GNObfuscatedText");
+        static const AZ::Crc32 ClearButton = AZ_CRC("GNClearButton");
+        static const AZ::Crc32 RemovableReadOnly = AZ_CRC("GNRemovableReadOnly");
+        static const AZ::Crc32 ValidationChange = AZ_CRC("GNValidationChange");
+    } // namespace Attributes
+
+    namespace Handlers
+    {
+        static const AZ::Crc32 FileSelect = AZ_CRC("GNFileSelect");
+        static const AZ::Crc32 QValidatedBrowseEdit = AZ_CRC("GNQValBrowseEdit");
+    } // namespace Handlers
+} // namespace GeomNodes

+ 112 - 0
Gems/O3DE/GeomNodes/Code/Source/Editor/UI/Utils.cpp

@@ -0,0 +1,112 @@
+#include "Utils.h"
+
+#include <AzCore/IO/SystemFile.h>
+#include <AzCore/std/algorithm.h>
+#include <AzCore/Utils/Utils.h>
+
+#include <QFileDialog>
+
+namespace
+{
+    template<typename StringType>
+    void ToUnixPath(StringType& path)
+    {
+        AZStd::replace(path.begin(), path.end(), '\\', '/');
+    }
+
+    template<typename StringType>
+    StringType GetAbsoluteEngineRoot()
+    {
+        AZ::IO::FixedMaxPath engineRoot = AZ::Utils::GetEnginePath();
+
+        if (engineRoot.empty())
+        {
+            return "";
+        }
+
+        StringType engineRootString(engineRoot.c_str());
+        ToUnixPath(engineRootString);
+        return engineRootString;
+    }
+
+    template<typename StringType>
+    StringType GetAbsoluteProjectRoot()
+    {
+        AZ::IO::FixedMaxPath projectRoot = AZ::Utils::GetProjectPath();
+
+        if (projectRoot.empty())
+        {
+            return "";
+        }
+
+        StringType projectRootString(projectRoot.c_str());
+        ToUnixPath(projectRootString);
+        return projectRootString;
+    }
+
+    template<typename StringType>
+    StringType GetProjectName();
+
+    template<>
+    AZStd::string GetProjectName()
+    {
+        auto projectName = AZ::Utils::GetProjectName();
+        return AZStd::string{projectName.c_str()};
+    }
+
+    template<>
+    QString GetProjectName()
+    {
+        auto projectName = AZ::Utils::GetProjectName();
+        return QString::fromUtf8(projectName.c_str(), aznumeric_cast<int>(projectName.size()));
+    }
+}
+
+namespace GeomNodes
+{
+    void* ConvertFunctorToVoid(AZStd::pair<QValidator::State, const QString> (*func)(const QString&))
+    {
+        return reinterpret_cast<void*>(func);
+    }
+
+    void* ConvertFunctorToVoid(void (*func)(const AZStd::string&))
+    {
+        return reinterpret_cast<void*>(func);
+    }
+
+    QString SelectBlendFromFileDialog(const QString& currentFile)
+    {
+        // The selected file must be relative to this path
+        QString defaultPath = GetAbsoluteEngineRoot<QString>();
+        QString startPath;
+
+        // Choose the starting path for file dialog
+        if (currentFile != "")
+        {
+            if (currentFile.contains(defaultPath))
+            {
+                startPath = currentFile;
+            }
+            else
+            {
+                startPath = defaultPath + currentFile;
+            }
+        }
+        else
+        {
+            startPath = defaultPath;
+        }
+
+        QString pickedPath = QFileDialog::getOpenFileName(nullptr, QObject::tr("Select Blend file"),
+            startPath, QObject::tr("Blender File (*.blend)"));
+        ToUnixPath(pickedPath);
+
+        // Remove the default relative path
+        if (pickedPath.contains(defaultPath))
+        {
+            pickedPath = pickedPath.mid(defaultPath.length());
+        }
+
+        return pickedPath;
+    }
+}

+ 20 - 0
Gems/O3DE/GeomNodes/Code/Source/Editor/UI/Utils.h

@@ -0,0 +1,20 @@
+#pragma once
+
+#include <AzCore/std/string/string.h>
+
+#include <QString>
+#include <QValidator>
+
+// Derived from ProjectSettingsTool
+namespace GeomNodes
+{
+    void* ConvertFunctorToVoid(AZStd::pair<QValidator::State, const QString>(*func)(const QString&));
+    void* ConvertFunctorToVoid(void (*func)(const AZStd::string&));
+    AZStd::string GetEngineRoot();
+    AZStd::string GetProjectRoot();
+    AZStd::string GetProjectName();
+    
+    // Open file dialogs for each file type and return the result
+    // CurrentFile is where the dialog opens
+    QString SelectBlendFromFileDialog(const QString& currentFile);
+}

+ 38 - 0
Gems/O3DE/GeomNodes/Code/Source/Editor/UI/ValidationHandler.cpp

@@ -0,0 +1,38 @@
+#include "ValidationHandler.h"
+
+//#include "PropertyFuncValLineEdit.h"
+#include "PropertyFuncValBrowseEdit.h"
+
+namespace GeomNodes
+{
+    /*void ValidationHandler::AddValidatorCtrl(PropertyFuncValLineEditCtrl* ctrl)
+    {
+        m_validators.push_back(ctrl);
+    }*/
+
+    void ValidationHandler::AddValidatorCtrl(PropertyFuncValBrowseEditCtrl* ctrl)
+    {
+        m_browseEditValidators.push_back(ctrl);
+    }
+
+    bool ValidationHandler::AllValid()
+    {
+        // for (PropertyFuncValLineEditCtrl* ctrl : m_validators)
+        // {
+        //     if (!ctrl->ValidateAndShowErrors())
+        //     {
+        //         return false;
+        //     }
+        // }
+
+        for (PropertyFuncValBrowseEditCtrl* ctrl : m_browseEditValidators)
+        {
+            if (!ctrl->ValidateAndShowErrors())
+            {
+                return false;
+            }
+        }
+
+        return true;
+    }
+} // namespace GeomNodes

+ 21 - 0
Gems/O3DE/GeomNodes/Code/Source/Editor/UI/ValidationHandler.h

@@ -0,0 +1,21 @@
+#pragma once
+
+#include <AzCore/std/containers/vector.h>
+
+namespace GeomNodes
+{
+    // Forward Declare
+    class PropertyFuncValLineEditCtrl;
+    class PropertyFuncValBrowseEditCtrl;
+
+    class ValidationHandler
+    {
+    public:
+        //void AddValidatorCtrl(PropertyFuncValLineEditCtrl* ctrl);
+        void AddValidatorCtrl(PropertyFuncValBrowseEditCtrl* ctrl);
+        bool AllValid();
+    private:
+        //AZStd::vector<PropertyFuncValLineEditCtrl*> m_validators;
+        AZStd::vector<PropertyFuncValBrowseEditCtrl*> m_browseEditValidators;
+    };
+} // namespace GeomNodes

+ 135 - 0
Gems/O3DE/GeomNodes/Code/Source/Editor/UI/Validators.cpp

@@ -0,0 +1,135 @@
+#include "Validators.h"
+
+#include <QDir>
+#include <QMimeDatabase>
+#include <QRegularExpression>
+
+#define STANDARD_SUCCESS GeomNodes::FunctorValidator::ReturnType(QValidator::Acceptable, "");
+
+namespace
+{
+    using RetType = GeomNodes::FunctorValidator::ReturnType;
+
+    static const int noMaxLength = -1;
+    static const char* blenderMimeType = "application/x-blender";
+    static const char* stringEmpty = "String is empty";
+
+    // Returns true if string is valid android package and apple bundle identifier
+    RetType RegularExpressionValidator(const QString& pattern, const QString& name, int maxLength = noMaxLength)
+    {
+        if (maxLength != noMaxLength && name.length() > maxLength)
+        {
+            return RetType(QValidator::Invalid, QObject::tr("Cannot be longer than %1 characters.").arg(QString::number(maxLength)));
+        }
+        else if (name.isEmpty())
+        {
+            return RetType(QValidator::Intermediate, QObject::tr(stringEmpty));
+        }
+
+        QRegularExpression regex(pattern);
+
+        QRegularExpressionMatch match = regex.match(name, 0, QRegularExpression::PartialPreferCompleteMatch);
+
+        if (match.hasMatch())
+        {
+            if (match.capturedLength(0) == name.length())
+            {
+                return STANDARD_SUCCESS;
+            }
+            else
+            {
+                return RetType(QValidator::Intermediate, "Input incorrect.");
+            }
+        }
+        if (match.hasPartialMatch())
+        {
+            return RetType(QValidator::Intermediate, QObject::tr("Partially matches requirements."));
+        }
+        else
+        {
+            return RetType(QValidator::Invalid, QObject::tr("Fails to match requirements at all."));
+        }
+    }
+}
+
+namespace GeomNodes
+{
+    namespace Validators
+    {
+        namespace Internal
+        {
+            // Returns true if file is readable and the correct mime type
+            RetType FileReadableAndCorrectType(const QString& path, const QString& fileType)
+            {
+                QDir dirPath(path);
+
+                if (dirPath.isReadable())
+                {
+                    QMimeDatabase mimeDB;
+                    QMimeType mimeType = mimeDB.mimeTypeForFile(path);
+
+                    std::string mimeName = mimeType.name().toStdString();
+                    if (mimeType.name() == fileType)
+                    {
+                        return STANDARD_SUCCESS;
+                    }
+                    else
+                    {
+                        return RetType(
+                            QValidator::Intermediate, QObject::tr("File type should be %1, but is %2.").arg(fileType).arg(mimeType.name()));
+                    }
+                }
+                else
+                {
+                    return RetType(QValidator::Intermediate, QObject::tr("File is not readable."));
+                }
+            }
+        } // namespace Internal
+
+        // Returns true if valid cross platform file or directory name
+        RetType FileName(const QString& name)
+        {
+            // There was a known issue on android with '.' used in directory names
+            // causing problems so it has been omitted from use
+            return RegularExpressionValidator("[\\w,-]+", name);
+        }
+
+        RetType FileNameOrEmpty(const QString& name)
+        {
+            if (IsNotEmpty(name).first == QValidator::Acceptable)
+            {
+                return FileName(name);
+            }
+            else
+            {
+                return STANDARD_SUCCESS;
+            }
+        }
+
+        // Returns true if string isn't empty
+        RetType IsNotEmpty(const QString& value)
+        {
+            if (!value.isEmpty())
+            {
+                return STANDARD_SUCCESS;
+            }
+            else
+            {
+                return RetType(QValidator::Intermediate, QObject::tr(stringEmpty));
+            }
+        }
+
+        // Returns true if path is empty or a valid blend file relative to <build dir>
+        RetType ValidBlenderOrEmpty(const QString& path)
+        {
+            if (IsNotEmpty(path).first == QValidator::Acceptable)
+            {
+                return Internal::FileReadableAndCorrectType(path, blenderMimeType);
+            }
+            else
+            {
+                return STANDARD_SUCCESS;
+            }
+        }
+    }
+}

+ 24 - 0
Gems/O3DE/GeomNodes/Code/Source/Editor/UI/Validators.h

@@ -0,0 +1,24 @@
+#pragma once
+
+#include "FunctorValidator.h"
+
+namespace GeomNodes
+{
+    namespace Validators
+    {
+        namespace Internal
+        {
+            // Returns true if file is readable and the correct mime type
+            FunctorValidator::ReturnType FileReadableAndCorrectType(const QString& path, const QString& fileType);
+        } // namespace Internal
+
+        // Returns true if valid cross platform file or directory name
+        FunctorValidator::ReturnType FileName(const QString& name);
+        // Returns true if valid cross platform file or directory name or empty
+        FunctorValidator::ReturnType FileNameOrEmpty(const QString& name);
+        // Returns true if string isn't empty
+        FunctorValidator::ReturnType IsNotEmpty(const QString& value);
+        // Returns true if path is empty or a valid blend file relative to <build dir>
+        FunctorValidator::ReturnType ValidBlenderOrEmpty(const QString& path);
+    }
+}

+ 83 - 0
Gems/O3DE/GeomNodes/Code/Source/GeomNodes/Components/GeomNodesSystemComponent.cpp

@@ -0,0 +1,83 @@
+
+#include "GeomNodesSystemComponent.h"
+
+#include <AzCore/Serialization/SerializeContext.h>
+#include <AzCore/Serialization/EditContext.h>
+#include <AzCore/Serialization/EditContextConstants.inl>
+
+namespace GeomNodes
+{
+    void GeomNodesSystemComponent::Reflect(AZ::ReflectContext* context)
+    {
+        if (AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context))
+        {
+            serialize->Class<GeomNodesSystemComponent, AZ::Component>()
+                ->Version(0)
+                ;
+
+            if (AZ::EditContext* ec = serialize->GetEditContext())
+            {
+                ec->Class<GeomNodesSystemComponent>("GeomNodes", "[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 GeomNodesSystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
+    {
+        provided.push_back(AZ_CRC_CE("GeomNodesService"));
+    }
+
+    void GeomNodesSystemComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
+    {
+        incompatible.push_back(AZ_CRC_CE("GeomNodesService"));
+    }
+
+    void GeomNodesSystemComponent::GetRequiredServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& required)
+    {
+    }
+
+    void GeomNodesSystemComponent::GetDependentServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& dependent)
+    {
+    }
+
+    GeomNodesSystemComponent::GeomNodesSystemComponent()
+    {
+        if (GeomNodesInterface::Get() == nullptr)
+        {
+            GeomNodesInterface::Register(this);
+        }
+    }
+
+    GeomNodesSystemComponent::~GeomNodesSystemComponent()
+    {
+        if (GeomNodesInterface::Get() == this)
+        {
+            GeomNodesInterface::Unregister(this);
+        }
+    }
+
+    void GeomNodesSystemComponent::Init()
+    {
+    }
+
+    void GeomNodesSystemComponent::Activate()
+    {
+        GeomNodesRequestBus::Handler::BusConnect();
+        AZ::TickBus::Handler::BusConnect();
+    }
+
+    void GeomNodesSystemComponent::Deactivate()
+    {
+        AZ::TickBus::Handler::BusDisconnect();
+        GeomNodesRequestBus::Handler::BusDisconnect();
+    }
+
+    void GeomNodesSystemComponent::OnTick([[maybe_unused]] float deltaTime, [[maybe_unused]] AZ::ScriptTimePoint time)
+    {
+    }
+
+} // namespace GeomNodes

+ 47 - 0
Gems/O3DE/GeomNodes/Code/Source/GeomNodes/Components/GeomNodesSystemComponent.h

@@ -0,0 +1,47 @@
+
+#pragma once
+
+#include <AzCore/Component/Component.h>
+#include <AzCore/Component/TickBus.h>
+#include <GeomNodes/GeomNodesBus.h>
+
+namespace GeomNodes
+{
+    class GeomNodesSystemComponent
+        : public AZ::Component
+        , protected GeomNodesRequestBus::Handler
+        , public AZ::TickBus::Handler
+    {
+    public:
+        AZ_COMPONENT(GeomNodesSystemComponent, "{ED1211E8-0025-4A8A-960F-CF405FBF7077}");
+
+        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);
+
+        GeomNodesSystemComponent();
+        ~GeomNodesSystemComponent();
+
+    protected:
+        ////////////////////////////////////////////////////////////////////////
+        // GeomNodesRequestBus 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 GeomNodes

+ 17 - 0
Gems/O3DE/GeomNodes/Code/Source/GeomNodes/Modules/GeomNodesModule.cpp

@@ -0,0 +1,17 @@
+
+
+#include <GeomNodesModuleInterface.h>
+#include "GeomNodes/Components/GeomNodesSystemComponent.h"
+
+namespace GeomNodes
+{
+    class GeomNodesModule
+        : public GeomNodesModuleInterface
+    {
+    public:
+        AZ_RTTI(GeomNodesModule, "{49C42A73-EF4E-4D42-8ECF-0ADE7F942CCD}", GeomNodesModuleInterface);
+        AZ_CLASS_ALLOCATOR(GeomNodesModule, AZ::SystemAllocator, 0);
+    };
+}// namespace GeomNodes
+
+AZ_DECLARE_MODULE_CLASS(Gem_GeomNodes, GeomNodes::GeomNodesModule)

+ 36 - 0
Gems/O3DE/GeomNodes/Code/Source/GeomNodesModuleInterface.h

@@ -0,0 +1,36 @@
+
+#include <AzCore/Memory/SystemAllocator.h>
+#include <AzCore/Module/Module.h>
+#include <GeomNodes/Components/GeomNodesSystemComponent.h>
+
+namespace GeomNodes
+{
+    class GeomNodesModuleInterface
+        : public AZ::Module
+    {
+    public:
+        AZ_RTTI(GeomNodesModuleInterface, "{2D38307D-709D-4B17-9878-AF59F988FC54}", AZ::Module);
+        AZ_CLASS_ALLOCATOR(GeomNodesModuleInterface, AZ::SystemAllocator, 0);
+
+        GeomNodesModuleInterface()
+        {
+            // 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(), {
+                GeomNodesSystemComponent::CreateDescriptor(),
+                });
+        }
+
+        /**
+         * Add required SystemComponents to the SystemEntity.
+         */
+        AZ::ComponentTypeList GetRequiredSystemComponents() const override
+        {
+            return AZ::ComponentTypeList{
+                azrtti_typeid<GeomNodesSystemComponent>(),
+            };
+        }
+    };
+}// namespace GeomNodes

+ 4 - 0
Gems/O3DE/GeomNodes/Code/Tests/Clients/GeomNodesTest.cpp

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

+ 4 - 0
Gems/O3DE/GeomNodes/Code/Tests/Tools/GeomNodesEditorTest.cpp

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

+ 4 - 0
Gems/O3DE/GeomNodes/Code/geomnodes_api_files.cmake

@@ -0,0 +1,4 @@
+
+set(FILES
+    Include/GeomNodes/GeomNodesBus.h
+)

+ 4 - 0
Gems/O3DE/GeomNodes/Code/geomnodes_editor_api_files.cmake

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

+ 47 - 0
Gems/O3DE/GeomNodes/Code/geomnodes_editor_private_files.cmake

@@ -0,0 +1,47 @@
+
+set(FILES
+    Source/Editor/Commons.h
+    Source/Editor/Math/MathHelper.cpp
+    Source/Editor/Math/MathHelper.h
+    Source/Editor/Components/GeomNodesEditorSystemComponent.cpp
+    Source/Editor/Components/GeomNodesEditorSystemComponent.h
+    Source/Editor/Components/GeomNodesEditorComponent.cpp
+    Source/Editor/Components/GeomNodesEditorComponent.h
+    Source/Editor/EBus/GeomNodesBus.h
+    Source/Editor/EBus/ValidatorBus.h
+    Source/Editor/EBus/IpcHandlerBus.h
+    Source/Editor/UI/Utils.cpp
+    Source/Editor/UI/Utils.h
+    Source/Editor/UI/UI_common.h
+    Source/Editor/UI/FunctorValidator.cpp
+    Source/Editor/UI/FunctorValidator.h
+    Source/Editor/UI/GeomNodesValidator.cpp
+    Source/Editor/UI/GeomNodesValidator.h
+    Source/Editor/UI/Validators.cpp
+    Source/Editor/UI/Validators.h
+    Source/Editor/UI/ValidationHandler.cpp
+    Source/Editor/UI/ValidationHandler.h
+    Source/Editor/UI/PropertyFileSelect.cpp
+    Source/Editor/UI/PropertyFileSelect.h
+    Source/Editor/UI/PropertyFuncValBrowseEdit.cpp
+    Source/Editor/UI/PropertyFuncValBrowseEdit.h
+    Source/Editor/Rendering/Atom/GNAttributeBuffer.h
+    Source/Editor/Rendering/Atom/GNBuffer.h
+    Source/Editor/Rendering/GNMeshData.cpp
+    Source/Editor/Rendering/GNMeshData.h
+    Source/Editor/Rendering/GNModelData.cpp
+    Source/Editor/Rendering/GNModelData.h
+    Source/Editor/Rendering/GNRenderMesh.cpp
+    Source/Editor/Rendering/GNRenderMesh.h
+    Source/Editor/Rendering/GNRenderMeshInterface.h
+    Source/Editor/Rendering/GNRenderModel.cpp
+    Source/Editor/Rendering/GNRenderModel.h
+    Source/Editor/Systems/GeomNodesSystem.cpp
+    Source/Editor/Systems/GeomNodesSystem.h
+    Source/Editor/Systems/GNInstance.cpp
+    Source/Editor/Systems/GNInstance.h
+    Source/Editor/Systems/GNParamContext.cpp
+    Source/Editor/Systems/GNParamContext.h
+    Source/Editor/Systems/GNProperty.cpp
+    Source/Editor/Systems/GNProperty.h
+)

+ 4 - 0
Gems/O3DE/GeomNodes/Code/geomnodes_editor_shared_files.cmake

@@ -0,0 +1,4 @@
+
+set(FILES
+    Source/Editor/Modules/GeomNodesEditorModule.cpp
+)

+ 4 - 0
Gems/O3DE/GeomNodes/Code/geomnodes_editor_tests_files.cmake

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

+ 6 - 0
Gems/O3DE/GeomNodes/Code/geomnodes_private_files.cmake

@@ -0,0 +1,6 @@
+
+set(FILES
+    Source/GeomNodesModuleInterface.h
+    Source/GeomNodes/Components/GeomNodesSystemComponent.cpp
+    Source/GeomNodes/Components/GeomNodesSystemComponent.h
+)

+ 4 - 0
Gems/O3DE/GeomNodes/Code/geomnodes_shared_files.cmake

@@ -0,0 +1,4 @@
+
+set(FILES
+    Source/GeomNodes/Modules/GeomNodesModule.cpp
+)

+ 4 - 0
Gems/O3DE/GeomNodes/Code/geomnodes_tests_files.cmake

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

+ 58 - 0
Gems/O3DE/GeomNodes/External/Bridge/Bridge.cpp

@@ -0,0 +1,58 @@
+#include "Bridge.h"
+#include "Ipc.h"
+
+bool Init(u64 id, HandlerCallback cb)
+{
+    Ipc::Ipc::GetInstance()->Initialize(id, cb);
+    return Ipc::Ipc::GetInstance()->IsInitialized();
+}
+
+void Uninitialize()
+{
+    Ipc::Ipc::GetInstance()->Uninitialize();
+}
+
+void SendMsg(const char* data, u64 length, u64 id)
+{
+    Ipc::Ipc::GetInstance()->SendMsg(Ipc::IPC_MSG_JSON, reinterpret_cast<const AZ::u8*>(data), length, id);
+}
+
+bool IsConnected()
+{
+    return Ipc::Ipc::GetInstance()->IsInitialized() && Ipc::Ipc::GetInstance()->IsServerRunning();
+}
+
+u64 CheckForMsg()
+{
+    return Ipc::Ipc::GetInstance()->CheckForMessage();
+}
+
+bool ReadMsg(char* buffer, u64 length)
+{
+    return Ipc::Ipc::GetInstance()->ReadMessage(buffer, length);
+}
+
+u64 RequestSHM(u64 uSize)
+{
+    return Ipc::Ipc::GetInstance()->RequestSHM(uSize);
+}
+
+bool OpenSHM(u64 mapId)
+{
+    return Ipc::Ipc::GetInstance()->OpenSHM(mapId);
+}
+
+bool ReadSHM(u64 uId, void** address, u64* length)
+{
+    return Ipc::Ipc::GetInstance()->ReadSHM(uId, address, length);
+}
+
+void WriteSHM(u64 uId, const char* source, const u64 length)
+{
+    Ipc::Ipc::GetInstance()->WriteSHM(uId, source, length);
+}
+
+void ClearSHM(u64 uId)
+{
+    Ipc::Ipc::GetInstance()->ClearSHM(uId);
+}

+ 39 - 0
Gems/O3DE/GeomNodes/External/Bridge/Bridge.h

@@ -0,0 +1,39 @@
+#pragma once
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#ifndef SERVER_ID
+#define SERVER_ID 1
+#endif
+
+typedef unsigned long long u64;
+typedef unsigned int u32;
+
+typedef long (*HandlerCallback)(u64, const char*, u64);
+
+
+#define BRIDGE_EXPORT __declspec(dllexport)
+
+__declspec(dllexport) bool Init(u64 id, HandlerCallback cb);
+__declspec(dllexport) void Uninitialize();
+__declspec(dllexport) void SendMsg(const char* data, u64 length, u64 id);
+__declspec(dllexport) bool IsConnected();
+
+// mostly for python use since callback doesn't work atm
+__declspec(dllexport) u64 CheckForMsg();
+__declspec(dllexport) bool ReadMsg(char* buffer, u64 length);
+
+// map related
+__declspec(dllexport) u64 RequestSHM(u64 uSize);
+__declspec(dllexport) bool OpenSHM(u64 mapId);
+__declspec(dllexport) bool ReadSHM(u64 uId, void** address, u64* length);
+__declspec(dllexport) void WriteSHM(u64 uId, const char* source, const u64 length);
+__declspec(dllexport) void ClearSHM(u64 uId);
+
+
+#ifdef __cplusplus
+}
+#endif

+ 31 - 0
Gems/O3DE/GeomNodes/External/Bridge/CMakeLists.txt

@@ -0,0 +1,31 @@
+
+# set(LIB_NAME Bridge)
+# set(LIB_TYPE SHARED)
+
+# add_library(${LIB_NAME} ${LIB_TYPE} 
+#             Bridge.cpp
+#             Bridge.h
+# )
+
+# install(TARGETS ${LIB_NAME}
+#     LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
+#     PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_LIBDIR})
+
+ly_add_target(
+    NAME Bridge SHARED
+    NAMESPACE GeomNodes
+    FILES_CMAKE
+        bridge_files.cmake
+    INCLUDE_DIRECTORIES
+        PUBLIC
+            .
+    BUILD_DEPENDENCIES
+        PRIVATE
+            AZ::AzCore
+        PUBLIC
+            AZ::AzToolsFramework
+    RUNTIME_DEPENDENCIES
+        AZ::AzCore
+)
+
+ly_create_alias(NAME BridgeLib NAMESPACE GeomNodes TARGETS GeomNodes::Bridge)

+ 1210 - 0
Gems/O3DE/GeomNodes/External/Bridge/Ipc.cpp

@@ -0,0 +1,1210 @@
+#include "Ipc.h"
+#include <AzCore/std/time.h>
+#include <AzCore/std/functional.h>
+#include <AzCore/std/containers/set.h>
+#include <AzCore/Component/TickBus.h>
+#include <AzCore/Component/Entity.h> // we just use this for create a random u64 id
+#include <AzCore/std/parallel/spin_mutex.h>
+
+#define SHMEM_NAME "GNIPCSharedMemory"
+
+namespace Ipc
+{
+    //////////////////////////////////////////////////////////////////////////
+    //////////////////////////////////////////////////////////////////////////
+    // Shared Memory ring buffer
+    //////////////////////////////////////////////////////////////////////////
+    //////////////////////////////////////////////////////////////////////////
+    namespace Internal
+    {
+        struct RingData
+        {
+            AZ::u32 m_readOffset;
+            AZ::u32 m_writeOffset;
+            AZ::u32 m_startOffset;
+            AZ::u32 m_endOffset;
+            AZ::u32 m_dataToRead;
+            AZ::u8 m_pad[32 - sizeof(AZStd::spin_mutex)];
+        };
+    } // namespace Internal
+
+    //=========================================================================
+    // SHMRingBuffer
+    // [4/29/2011]
+    //=========================================================================
+    SHMRingBuffer::SHMRingBuffer()
+        : m_info(nullptr)
+    {
+    }
+
+    //=========================================================================
+    // Create
+    // [4/29/2011]
+    //=========================================================================
+    bool SHMRingBuffer::Create(const char* name, unsigned int size, bool openIfCreated)
+    {
+        return SharedMemory::Create(name, size + sizeof(Internal::RingData), openIfCreated) != SharedMemory::CreateFailed;
+    }
+
+    //=========================================================================
+    // Map
+    // [4/28/2011]
+    //=========================================================================
+    bool SHMRingBuffer::Map(AccessMode mode, unsigned int size)
+    {
+        if (SharedMemory::Map(mode, size))
+        {
+            MemoryGuard l(*this);
+            m_info = reinterpret_cast<Internal::RingData*>(m_data);
+            m_data = m_info + 1;
+            m_dataSize -= sizeof(Internal::RingData);
+            if (m_info->m_endOffset == 0) // if endOffset == 0 we have never set the info structure, do this only once.
+            {
+                m_info->m_startOffset = 0;
+                m_info->m_readOffset = m_info->m_startOffset;
+                m_info->m_writeOffset = m_info->m_startOffset;
+                m_info->m_endOffset = m_info->m_startOffset + m_dataSize;
+                m_info->m_dataToRead = 0;
+            }
+            return true;
+        }
+
+        return false;
+    }
+
+    //=========================================================================
+    // UnMap
+    // [4/28/2011]
+    //=========================================================================
+    bool SHMRingBuffer::UnMap()
+    {
+        m_info = nullptr;
+        return SharedMemory::UnMap();
+    }
+
+    //=========================================================================
+    // Write
+    // [4/28/2011]
+    //=========================================================================
+    bool SHMRingBuffer::Write(const void* data, unsigned int dataSize)
+    {
+        AZ_Warning(
+            "AZSystem",
+            !Platform::IsWaitFailed(),
+            "You are writing the ring buffer %s while the Global lock is NOT locked! This can lead to data corruption!",
+            m_name);
+        AZ_Assert(m_info != nullptr, "You need to Create and Map the buffer first!");
+        if (m_info->m_writeOffset >= m_info->m_readOffset)
+        {
+            unsigned int freeSpace = m_dataSize - (m_info->m_writeOffset - m_info->m_readOffset);
+            // if we are full or don't have enough space return false
+            if (m_info->m_dataToRead == m_dataSize || freeSpace < dataSize)
+            {
+                return false;
+            }
+            unsigned int copy1MaxSize = m_info->m_endOffset - m_info->m_writeOffset;
+            unsigned int dataToCopy1 = AZStd::GetMin(copy1MaxSize, dataSize);
+            if (dataToCopy1)
+            {
+                memcpy(reinterpret_cast<char*>(m_data) + m_info->m_writeOffset, data, dataToCopy1);
+            }
+            unsigned int dataToCopy2 = dataSize - dataToCopy1;
+            if (dataToCopy2)
+            {
+                memcpy(
+                    reinterpret_cast<char*>(m_data) + m_info->m_startOffset,
+                    reinterpret_cast<const char*>(data) + dataToCopy1,
+                    dataToCopy2);
+                m_info->m_writeOffset = m_info->m_startOffset + dataToCopy2;
+            }
+            else
+            {
+                m_info->m_writeOffset += dataToCopy1;
+            }
+        }
+        else
+        {
+            unsigned int freeSpace = m_info->m_readOffset - m_info->m_writeOffset;
+            if (freeSpace < dataSize)
+            {
+                return false;
+            }
+            memcpy(reinterpret_cast<char*>(m_data) + m_info->m_writeOffset, data, dataSize);
+            m_info->m_writeOffset += dataSize;
+        }
+        m_info->m_dataToRead += dataSize;
+
+        return true;
+    }
+
+    //=========================================================================
+    // Read
+    // [4/28/2011]
+    //=========================================================================
+    unsigned int SHMRingBuffer::Read(void* data, unsigned int maxDataSize)
+    {
+        AZ_Warning(
+            "AZSystem",
+            !Platform::IsWaitFailed(),
+            "You are reading the ring buffer %s while the Global lock is NOT locked! This can lead to data corruption!",
+            m_name);
+
+        if (m_info->m_dataToRead == 0)
+        {
+            return 0;
+        }
+
+        AZ_Assert(m_info != nullptr, "You need to Create and Map the buffer first!");
+        unsigned int dataRead;
+        if (m_info->m_writeOffset > m_info->m_readOffset)
+        {
+            unsigned int dataToRead = AZStd::GetMin(m_info->m_writeOffset - m_info->m_readOffset, maxDataSize);
+            if (dataToRead)
+            {
+                memcpy(data, reinterpret_cast<char*>(m_data) + m_info->m_readOffset, dataToRead);
+            }
+            m_info->m_readOffset += dataToRead;
+            dataRead = dataToRead;
+        }
+        else
+        {
+            unsigned int dataToRead1 = AZStd::GetMin(m_info->m_endOffset - m_info->m_readOffset, maxDataSize);
+            if (dataToRead1)
+            {
+                maxDataSize -= dataToRead1;
+                memcpy(data, reinterpret_cast<char*>(m_data) + m_info->m_readOffset, dataToRead1);
+            }
+            unsigned int dataToRead2 = AZStd::GetMin(m_info->m_writeOffset - m_info->m_startOffset, maxDataSize);
+            if (dataToRead2)
+            {
+                memcpy(reinterpret_cast<char*>(data) + dataToRead1, reinterpret_cast<char*>(m_data) + m_info->m_startOffset, dataToRead2);
+                m_info->m_readOffset = m_info->m_startOffset + dataToRead2;
+            }
+            else
+            {
+                m_info->m_readOffset += dataToRead1;
+            }
+            dataRead = dataToRead1 + dataToRead2;
+        }
+        m_info->m_dataToRead -= dataRead;
+
+        return dataRead;
+    }
+
+    unsigned int SHMRingBuffer::Read(void** data, unsigned int maxDataSize)
+    {
+        AZ_Warning(
+            "AZSystem",
+            !Platform::IsWaitFailed(),
+            "You are reading the ring buffer %s while the Global lock is NOT locked! This can lead to data corruption!",
+            m_name);
+
+        if (m_info->m_dataToRead == 0)
+        {
+            return 0;
+        }
+
+        AZ_Assert(m_info != nullptr, "You need to Create and Map the buffer first!");
+        unsigned int dataRead = 0;
+        if (m_info->m_writeOffset > m_info->m_readOffset)
+        {
+            unsigned int dataToRead = AZStd::GetMin(m_info->m_writeOffset - m_info->m_readOffset, maxDataSize);
+            if (dataToRead)
+            {
+                *data = reinterpret_cast<char*>(m_data) + m_info->m_readOffset;
+            }
+            m_info->m_readOffset += dataToRead;
+            dataRead = dataToRead;
+        }
+        AZ_Assert(m_info->m_writeOffset >= m_info->m_readOffset, "SHMRingBuffer: not enough data to read");
+
+        m_info->m_dataToRead -= dataRead;
+
+        return dataRead;
+    }
+
+    //=========================================================================
+    // Read
+    // [4/28/2011]
+    //=========================================================================
+    unsigned int SHMRingBuffer::DataToRead() const
+    {
+        return m_info ? m_info->m_dataToRead : 0;
+    }
+
+    //=========================================================================
+    // Read
+    // [4/28/2011]
+    //=========================================================================
+    unsigned int SHMRingBuffer::MaxToWrite() const
+    {
+        return m_info ? (m_dataSize - m_info->m_dataToRead) : 0;
+    }
+
+    //=========================================================================
+    // Clear
+    // [4/19/2013]
+    //=========================================================================
+    void SHMRingBuffer::Clear()
+    {
+        SharedMemory::Clear();
+        if (m_info)
+        {
+            m_info->m_readOffset = m_info->m_startOffset;
+            m_info->m_writeOffset = m_info->m_startOffset;
+            m_info->m_dataToRead = 0;
+        }
+    }
+
+    Ipc* Ipc::m_Instance = nullptr;
+    
+    Ipc::Ipc()
+    {
+        m_OverflowQueue.clear();
+    }
+
+    Ipc::~Ipc()
+    {
+        Uninitialize();
+    }
+
+    void Ipc::CreateOSAllocator()
+    {
+        if (!AZ::AllocatorInstance<AZ::OSAllocator>::IsReady())
+        {
+            AZ::AllocatorInstance<AZ::OSAllocator>::Create();
+            m_isOSAllocatorOwner = true;
+        }
+        m_osAllocator = &AZ::AllocatorInstance<AZ::OSAllocator>::Get();
+    }
+
+    void Ipc::DestroyAllocator()
+    {
+        // kill the system allocator if we created it
+        if (m_isSystemAllocatorOwner)
+        {
+            AZ::Debug::Trace::Instance().Destroy();
+            AZ::AllocatorInstance<AZ::SystemAllocator>::Destroy();
+
+            if (m_fixedMemoryBlock)
+            {
+                m_osAllocator->DeAllocate(m_fixedMemoryBlock);
+            }
+            m_fixedMemoryBlock = nullptr;
+            m_isSystemAllocatorOwner = false;
+        }
+
+        if (m_isOSAllocatorOwner)
+        {
+            AZ::AllocatorInstance<AZ::OSAllocator>::Destroy();
+            m_isOSAllocatorOwner = false;
+        }
+
+        m_osAllocator = nullptr;
+    }
+
+    void Ipc::CreateSystemAllocator()
+    {
+        if (!AZ::AllocatorInstance<AZ::SystemAllocator>::IsReady())
+        {
+            AZ::AllocatorInstance<AZ::SystemAllocator>::Create();
+
+            m_isSystemAllocatorOwner = true;
+        }
+    }
+
+    void Ipc::Initialize(AZ::u64 id, IPCHandler handler)
+    {
+        //MessageBox(NULL, L"Debug-WMain-Install", L"Debug", MB_OK);
+
+        m_uID = id;
+        m_handler = handler;
+
+        bool bServer = id == SERVER_ID;
+
+        CreateOSAllocator();
+        CreateSystemAllocator();
+
+        if (m_handler == nullptr && m_bServer)
+        {
+            AZ_Warning("App", false, "GNIPC: Callback is not set. You'll miss messages..");
+        }
+
+        if (!m_success)
+        {
+            m_success = true;
+
+            AZ::u32 memory_size = sizeof(IpcIdTable) + sizeof(IpcMessageTable) + sizeof(IpcMapTable) + sizeof(IpcMapSortArray);
+            if (m_SharedMem.Create(SHMEM_NAME, memory_size, true) == AZ::SharedMemory::CreateFailed)
+            {
+                AZ_Warning("App", false, "GNIPC: Could not Open IPC buffer, open files will not work.");
+                m_success = false;
+            }
+
+            if ((m_success) && (!m_SharedMem.Map()))
+            {
+                AZ_Warning("App", false, "GNIPC: Could not Map IPC buffer, open files will not work.");
+                m_success = false;
+                m_SharedMem.Close();
+            }
+
+            m_ProcessIDs = (IpcIdTable*)m_SharedMem.Data();
+            m_MsgTable = (IpcMessageTable*)((AZ::u8*)m_ProcessIDs + sizeof(IpcIdTable));
+            m_MapTable = (IpcMapTable*)((AZ::u8*)m_MsgTable + sizeof(IpcMessageTable));
+            m_MapSortArray = (IpcMapSortArray*)((AZ::u8*)m_MapTable + sizeof(IpcMapTable));
+
+            {
+                m_SharedMem.lock();
+                if (bServer && (m_ProcessIDs->pid[0] == 0))
+                {
+                    // we are the server
+                    m_ProcessIDs->pid[0] = m_uID;
+                    m_ProcessIDs->i64Poll[0] = AZStd::GetTimeNowSecond();
+                    m_bServer = true;
+                    m_uIDIdx = 0;
+                }
+                else
+                {
+                    // we are some other process (client);
+                    for (AZ::s32 i = 1; i < IPC_MAX_PID; ++i)
+                    {
+                        if ((m_ProcessIDs->pid[i] == 0) || (m_ProcessIDs->pid[i] == m_uID) ||
+                            !IsAttachedProcessId(m_ProcessIDs->pid[i], true))
+                        {
+                            m_ProcessIDs->pid[i] = m_uID;
+                            m_ProcessIDs->i64Poll[i] = AZStd::GetTimeNowSecond();
+                            m_uIDIdx = i;
+                            break;
+                        }
+                    }
+                }
+
+                if (m_uIDIdx < IPC_MAX_PID)
+                    m_ProcessIDs->uiPrevMsgSequence[m_uIDIdx] = 0;
+
+                m_SharedMem.unlock();
+            }
+
+            if (!m_bServer && (m_uIDIdx == 0))
+            {
+                AZ_Warning("App", false, "GNIPC: Unable to initialize IPC; PID table is full!");
+                m_success = false;
+                m_SharedMem.Close();
+            }
+
+            if (m_success)
+            {
+                // start the poll thread
+                m_PollThread = AZStd::thread(AZStd::bind(&Ipc::ProcessThread, this));
+            }
+        }
+    }
+
+    void Ipc::Uninitialize()
+    {
+        m_ShutdownThread = true;
+        if (m_success)
+        {
+            m_PollThread.join();
+
+            {
+                m_SharedMem.lock();
+                // remove ourself from the table
+                if (m_ProcessIDs->pid[m_uIDIdx] == m_uID)
+                {
+                    m_ProcessIDs->pid[m_uIDIdx] = 0;
+                    m_ProcessIDs->uiPrevMsgSequence[m_uIDIdx] = 0;
+                    m_ProcessIDs->i64Poll[m_uIDIdx] = 0;
+                }
+
+                // remove all pending messages for us in the table
+                for (AZ::u32 j = 0; j < IPC_MAX_MSGS; ++j)
+                {
+                    IPCMessage* message = &m_MsgTable->message[j];
+                    if (message->pid == m_uID)
+                    {
+                        message->pid = 0;
+                        // memset(message, 0, BRIPC_MESSAGE_DATA_SIZE);
+                    }
+                }
+
+                m_SharedMem.unlock();
+            }
+
+            m_SharedMem.UnMap();
+            m_SharedMem.Close();
+        }
+
+        m_handler = nullptr;
+        m_OverflowQueue.clear();
+        m_success = false;
+
+        // free the SHMs
+        for (auto it = m_SharedMemMap.begin(); it != m_SharedMemMap.end(); ++it)
+        {
+            if (it->second)
+            {
+                it->second->UnMap();
+                it->second->Close();
+                delete it->second;
+            }
+        }
+
+        m_SharedMemMap.clear();
+
+
+        AZ_Warning("App", false, "GNIPC: Uninitialized called");
+        DestroyAllocator();
+    }
+
+    void Ipc::SendMsg(AZ::u32 pType, const AZ::u8* pData, AZ::u64 uSize, AZ::u64 id)
+    {
+        if (m_success)
+        {
+            if (uSize <= IPC_MAX_MSG_SIZE)
+            {
+                bool bMutexLocked = m_SharedMem.try_lock();
+                bMutexLocked = m_bServer ? bMutexLocked : (m_uIDIdx != 0 ? bMutexLocked : false);
+                AddMessage(id, pType, pData, uSize, bMutexLocked);
+                
+                if (bMutexLocked)
+                {
+                    m_SharedMem.unlock();
+                }
+            }
+
+            /*if (m_handler)
+                m_handler(0, "SendMessage", 11);*/
+        }
+    }
+
+    AZ::u64 Ipc::CheckForMessage()
+    {
+        AZStd::unique_lock<AZStd::mutex> lock(m_MessageListMutex);
+        AZ::u64 length = 0;
+        if (m_MessagesWaitingToExecute.size())
+        {
+            auto msg = m_MessagesWaitingToExecute.front();
+            length = msg.m_content.size();
+            AZ_Assert(length > 0, "There should be no empty message");
+            if (length == 0)
+                m_MessagesWaitingToExecute.pop(); // pop empty message so there's no chance that queue gets stuck
+        }
+        return length;
+    }
+
+    bool Ipc::ReadMessage(char* buffer, AZ::u64 length)
+    {
+        // https://stackoverflow.com/questions/26277322/passing-arrays-with-ctypes
+        //TODO: need to allocate memory and have another function to release the allocated memory.
+        // or do the second method where we allocate the buffer in python.
+        // modify CheckForMessage that will return the length of the needed buffer.
+        AZStd::unique_lock<AZStd::mutex> lock(m_MessageListMutex);
+        auto msg = m_MessagesWaitingToExecute.front();
+
+        AZ_Assert(msg.m_content.size() == length, "Different message.");
+        memcpy(buffer, msg.m_content.data(), length);
+        m_MessagesWaitingToExecute.pop();
+        return true;
+    }
+
+    // Map functions
+    void sortBySize(AZ::u32* indexes, AZ::u64* sizes, AZ::u32 n)
+    {
+        std::sort(
+            indexes,
+            indexes + n,
+            [&](AZ::u32 i, AZ::u32 j)
+            {
+                return sizes[i] < sizes[j];
+            });
+    }
+
+    bool Ipc::CreateSHMRingBuffer(AZ::u64 mapId, AZ::u64 uSize)
+    {
+        auto shmInstance = aznew SHMRingBuffer;
+        bool bSuccess = true;
+        AZStd::string shmName = AZStd::string::format("%llu", mapId);
+        if (!shmInstance->Create(shmName.c_str(), (unsigned int)uSize, true))
+        {
+            AZ_Warning("Ipc", false, "Could not Open SHM, open files will not work.!");
+            bSuccess = false;
+        }
+
+        if ((m_success) && (!shmInstance->Map()))
+        {
+            AZ_Warning("App", false, "Could not Map SHM, open files will not work.");
+            bSuccess = false;
+            shmInstance->Close();
+        }
+
+        if (bSuccess)
+        {
+            // add the opened map in the SHM map
+            m_SharedMemMap.insert(AZStd::make_pair(mapId, shmInstance));
+        }
+
+        return bSuccess;
+    }
+
+    AZ::u64 Ipc::RequestSHM(AZ::u64 uSize)
+    {
+        AZ::u64 mapId = 0;
+        if (m_success && !m_bServer) // client only
+        {
+            {
+                m_SharedMem.lock();
+                // we always get one MAP_PAGE_SIZE bigger than uSize's whole number.
+                auto alignedSize = ((uSize / MAP_PAGE_SIZE) + 1) * MAP_PAGE_SIZE;
+
+                // look for a free SHM or create one
+                for (AZ::u32 i = 0; i < m_MapSortArray->arraySize; i++)
+                {
+                    auto idx = m_MapSortArray->sortArray[i];
+                    if (m_MapTable->id[idx] == 0 && alignedSize <= m_MapTable->uSize[idx])
+                    {
+                        mapId = m_MapTable->mapID[idx];
+                        m_MapTable->id[idx] = m_uID; // set the id to the this instance m_uID to assign it.
+                        break;
+                    }
+                }
+                
+                if (mapId == 0) // if we are still zero here we create one
+                {
+                    mapId = (AZ::u64)AZ::Entity::MakeId();
+
+                    auto mapTblidx = m_MapSortArray->arraySize; // 
+                    m_MapTable->id[mapTblidx] = m_uID;
+                    m_MapTable->mapID[mapTblidx] = mapId;
+                    m_MapTable->uSize[mapTblidx] = alignedSize;
+                    m_MapSortArray->sortArray[mapTblidx] = mapTblidx; // add the map table index to the sort Array
+                    m_MapSortArray->arraySize += 1;
+
+                    AZ_Assert(m_MapSortArray->arraySize <= IPC_MAX_MAP, "Map table is more than IPC_MAX_MAP(%i)", IPC_MAX_MAP);
+                    // sort the array
+                    sortBySize(m_MapSortArray->sortArray, m_MapTable->uSize, m_MapSortArray->arraySize);
+                }
+
+                m_SharedMem.unlock();
+
+                if (mapId > 0) // create or open the SHM
+                {
+                    CreateSHMRingBuffer(mapId, alignedSize);
+                }
+                else
+                {
+                    AZ_Error("Ipc", false, "Wasn't able to create or open an existing Map!");
+                }
+            }
+            /*else
+            {
+                AZ_Error("Ipc", false, "RequestSHM: failed to lock the SHM!");
+            }*/
+        }
+
+        AZ_Assert(mapId != 0, "RequestSHM : there should be no zero map Id!");
+
+        return mapId; //  if we get zero that means there's an error
+    }
+
+    bool Ipc::OpenSHM(AZ::u64 mapId)
+    {
+        AZ_Assert(mapId != 0, "Opening an SHM with a zero map Id.");
+        bool bSuccess = true;
+        if (m_success && m_bServer)
+        {
+            {
+                m_SharedMem.lock();
+                AZ::u64 uMapSize = 0;
+                // look for the mapId and get the map size
+                for (AZ::u32 i = 0; i < m_MapSortArray->arraySize; i++)
+                {
+                    if (m_MapTable->mapID[i] == mapId) // look for the mapID
+                    {
+                        uMapSize = m_MapTable->uSize[i];
+                        break;
+                    }
+                }
+                m_SharedMem.unlock();
+
+                AZ_Assert(uMapSize != 0, "Opening an SHM with a zero sized map");
+
+                bSuccess = CreateSHMRingBuffer(mapId, uMapSize);
+            }
+        }
+
+        return bSuccess;
+    }
+
+    bool Ipc::ReadSHM(AZ::u64 mapId, void** address, AZ::u64* length)
+    {
+        auto shmIter = m_SharedMemMap.find(mapId);
+        if (shmIter != m_SharedMemMap.end())
+        {
+            auto shmInstance = shmIter->second;
+            {
+                AZ::SharedMemory::MemoryGuard g(*shmInstance);
+                int chunkSize;
+                const int headerSize = sizeof(int);
+                shmInstance->Read(&chunkSize, headerSize);
+                if (chunkSize > 0)
+                {
+                    auto dataRead = shmInstance->Read(address, chunkSize - headerSize);
+                    
+                    if (length)
+                        *length = dataRead;
+
+                    return true;
+                }
+                else
+                {
+                    *length = 0;
+                }
+            }
+        }
+        return false;
+    }
+
+    void Ipc::WriteSHM(AZ::u64 mapId, const char* source, const AZ::u64 length)
+    {
+        auto shmIter = m_SharedMemMap.find(mapId);
+        if (shmIter != m_SharedMemMap.end())
+        {
+            auto shmInstance = shmIter->second;
+            {
+                AZ::SharedMemory::MemoryGuard g(*shmInstance);
+                const int headerSize = sizeof(int);
+                const int chunkSize = (int)length + headerSize;
+
+                shmInstance->Write(&chunkSize, headerSize);
+                shmInstance->Write(source, (AZ::u32)length);
+            }
+        }
+    }
+
+    void Ipc::ClearSHM(AZ::u64 mapId)
+    {
+        if (m_success)
+        {
+            auto shmIter = m_SharedMemMap.find(mapId);
+            if (shmIter != m_SharedMemMap.end())
+            {
+                auto shmInstance = shmIter->second;
+                if (!m_bServer)
+                {
+                    shmInstance->UnMap();
+                    shmInstance->Close();
+
+                    m_SharedMem.lock();
+                    for (AZ::u32 i = 0; i < m_MapSortArray->arraySize; i++)
+                    {
+                        if (m_MapTable->mapID[i] == mapId) // look for the mapID
+                        {
+                            m_MapTable->id[i] = 0; // clear the Id so it can be seen as "free"
+                            break;
+                        }
+                    }
+                    m_SharedMem.unlock();
+                    m_SharedMemMap.erase(shmIter);
+                }
+                else
+                {
+                    shmInstance->lock();
+                    shmInstance->Clear();
+                    shmInstance->unlock();
+                }
+            }
+        }
+    }
+
+    bool Ipc::IsServerRunning()
+    {
+        if (m_bServer)
+            return true;
+
+        if (m_ProcessIDs)
+        {
+            AZ::s64 tNow = AZStd::GetTimeNowSecond();
+            if (tNow - m_ProcessIDs->i64Poll[0] < 10)
+                return true;
+        }
+
+        return false;
+    }
+
+    bool Ipc::IsPeerAttached()
+    {
+        if (m_bServer)
+        {
+            if (m_ProcessIDs)
+            {
+                for (AZ::u32 i = 1; i < IPC_MAX_PID; ++i)
+                {
+                    if ((AZStd::GetTimeNowSecond() - m_ProcessIDs->i64Poll[i]) < 15)
+                    {
+                        if (m_ProcessIDs->i64Poll[i] != 0)
+                            return true;
+                    }
+                }
+            }
+            return false;
+        }
+        else
+            return IsServerRunning();
+    }
+
+    Ipc* Ipc::GetInstance()
+    {
+        if (m_Instance == NULL)
+        {
+            m_Instance = new Ipc;
+        }
+        return m_Instance;
+    }
+
+    void Ipc::PerformServerCleanup(AZStd::sys_time_t ts, AZStd::sys_time_t tLastCleanup)
+    {
+        if ((ts - tLastCleanup) >= 15)
+        {
+            tLastCleanup = ts;
+
+            AZStd::set<AZ::u64> pidForCleanup;
+            if (m_ProcessIDs)
+            {
+                for (AZ::u32 i = 1; i < IPC_MAX_PID; ++i)
+                {
+                    if ((m_ProcessIDs->i64Poll[i] != 0) && ((ts - m_ProcessIDs->i64Poll[i]) > 15))
+                    {
+                        // clean up the data if stagnant
+                        pidForCleanup.insert(m_ProcessIDs->pid[i]);
+                        m_ProcessIDs->pid[i] = 0;
+                        m_ProcessIDs->i64Poll[i] = 0;
+                    }
+                }
+            }
+
+            if (m_MsgTable)
+            {
+                // remove all stagnant messages
+                for (AZ::u32 j = 0; j < IPC_MAX_MSGS; ++j)
+                {
+                    IPCMessage* message = &m_MsgTable->message[j];
+                    if ((pidForCleanup.find(message->pid) != pidForCleanup.end()) ||
+                        ((message->pid > 0) && (message->i64Timestamp != 0) && ((ts - message->i64Timestamp) > 30)))
+                    {
+                        message->pid = 0;
+                    }
+                }
+            }
+
+            if (m_MapTable)
+            {
+                // clean the SHMs as well they could still be assigned to dead processes
+                for (AZ::u32 j = 0; j < IPC_MAX_MAP; ++j)
+                {
+                    auto id = m_MapTable->id[j];
+                    if ((pidForCleanup.find(id) != pidForCleanup.end()))
+                    {
+                        m_MapTable->id[j] = 0;
+                    }
+                }
+            }
+        }
+    }
+
+    void Ipc::PerformClientCheck(AZStd::sys_time_t ts)
+    {
+        if (m_ProcessIDs->pid[m_uIDIdx] == 0)
+        {
+            m_ProcessIDs->pid[m_uIDIdx] = m_uID; // reassert our ownership of the slot
+        }
+        else if (m_ProcessIDs->pid[m_uIDIdx] != m_uID)
+        {
+            // someone took our slot; we probably fell asleep too long
+            m_uIDIdx = 0;
+            {
+                m_SharedMem.lock();
+                for (int32_t i = 1; i < IPC_MAX_PID; ++i)
+                {
+                    if ((m_ProcessIDs->pid[i] == 0) || (m_ProcessIDs->pid[i] == m_uID) || !IsAttachedProcessId(m_ProcessIDs->pid[i], true))
+                    {
+                        m_ProcessIDs->pid[i] = m_uID;
+                        m_ProcessIDs->i64Poll[i] = ts;
+                        m_uIDIdx = i;
+                        break;
+                    }
+                }
+                m_SharedMem.unlock();
+            }
+        }
+    }
+
+    void Ipc::ProcessOverlowQueue(AZStd::sys_time_t ts)
+    {
+        if (!m_OverflowQueue.empty())
+        {
+            {
+                m_SharedMem.lock();
+                // process overflow queue here
+                bool bAdded(false);
+                AZ::u32 pIDx, nMsgIndex, nInitialIdx(m_uMsgAddIdx);
+                AZ::u64 uPID;
+
+                // iterate through each queue present in the map
+                IPCMessageQueueMap::iterator mapIter = m_OverflowQueue.begin();
+                while (mapIter != m_OverflowQueue.end())
+                {
+                    IPCMessageQueue& tQueue = mapIter->second;
+
+                    while (!tQueue.empty())
+                    {
+                        if (!IsAttachedProcessId(tQueue.front().pid, true))
+                        {
+                            // this message is for an inactive PID; discard
+                            tQueue.pop_front();
+                            continue;
+                        }
+
+                        pIDx = GetPIDIdx(tQueue.front().pid);
+                        bAdded = false;
+                        nMsgIndex = (pIDx * 10) + m_uMsgAddIdx;
+                        uPID = m_MsgTable->message[nMsgIndex].pid;
+
+                        if ((uPID == 0) || !IsAttachedProcessId(uPID, true) || ((ts - m_MsgTable->message[nMsgIndex].i64Timestamp) > 30))
+                        {
+                            // Place the queued msg into the shared message table...
+                            IPCMessage tOverflowMsg = tQueue.front();
+                            AZ::u32 idIdx = GetPIDIdx(tOverflowMsg.pid);
+                            tOverflowMsg.uiMsgSequence = m_ProcessIDs->uiPrevMsgSequence[idIdx] + 1;
+                            tOverflowMsg.i64Timestamp = ts;
+
+                            IPCMessage* tMsg = &m_MsgTable->message[nMsgIndex];
+                            memcpy(tMsg, &tOverflowMsg, IPC_MESSAGE_DATA_SIZE + tOverflowMsg.uiMsgSize);
+
+                            ++m_ProcessIDs->uiPrevMsgSequence[idIdx];
+
+                            tQueue.pop_front();
+                            bAdded = true;
+
+                            // NOTE:  Don't use AddMessage() here because it will push right back onto the overflow queue (infinite loop).
+                        }
+
+                        ++m_uMsgAddIdx;
+                        if (m_uMsgAddIdx > 9)
+                            m_uMsgAddIdx = 0;
+
+                        if (m_uMsgAddIdx == nInitialIdx)
+                        {
+                            AZ_Warning(
+                                "App",
+                                false,
+                                "GNIPC: Unable to find room for message from overflow queue; remaining queued messages[%u]!\n",
+                                tQueue.size());
+                            break;
+                        }
+
+                        if (bAdded)
+                            nInitialIdx = m_uMsgAddIdx;
+                    }
+
+                    if (tQueue.empty())
+                    {
+                        mapIter = m_OverflowQueue.erase(mapIter); // empty ID
+                    }
+                    else
+                    {
+                        ++mapIter;
+                    }
+                    
+                }
+
+                m_SharedMem.unlock();
+            }
+        }
+    }
+
+    void Ipc::PollForMessages(IPCMsgSequence& mapMsgSequence)
+    {
+        IpcMessageTable* tMsgList = m_MsgTable;
+        for (AZ::u32 i = 0; i < IPC_MAX_MSGS; ++i)
+        {
+            IPCMessage* tMsg = &tMsgList->message[i];
+            if (tMsg->pid == m_uID)
+            {
+                mapMsgSequence[tMsg->uiMsgSequence] = i;
+            }
+        }
+
+        if (mapMsgSequence.empty())
+        {
+            // process table says we have a new message, but PID entry doesn't display any message for us
+            for (AZ::u32 i = 0; i < IPC_MAX_MSGS; ++i)
+            {
+                IPCMessage* tMsg = &tMsgList->message[i];
+                if ((tMsg->uiMsgSequence <= m_ProcessIDs->uiPrevMsgSequence[m_uIDIdx]) && (tMsg->uiMsgSequence > m_uiPrevMsgSequence))
+                {
+                    mapMsgSequence[tMsg->uiMsgSequence] = i;
+                }
+            }
+        }
+    }
+
+    void Ipc::ExecuteIPCHandlers()
+    {
+        /*MessageContainer batch;
+        {
+            AZStd::lock_guard<AZStd::recursive_mutex> guard(m_MessageListMutex);
+            batch = AZStd::move(m_MessagesWaitingToExecute);
+        }*/
+
+        // iterate through the messages
+        AZStd::unique_lock<AZStd::mutex> lock(m_MessageListMutex);
+
+        while (!m_MessagesWaitingToExecute.empty())
+        {
+            if (!m_handler) break; // no handlers
+
+            auto msg = m_MessagesWaitingToExecute.front();
+            if (!m_bServer)
+            {
+                m_handler(0, "", 0);
+            }
+            else
+            {
+                m_handler(msg.m_id, (const char*)msg.m_content.data(), msg.m_content.size());
+            }
+
+            m_MessagesWaitingToExecute.pop();
+        }
+    }
+
+    void Ipc::ProcessThread()
+    {
+        // AZ::u32 tCounter = 0;
+        auto tLastCleanup = AZStd::GetTimeNowSecond();
+        while (!m_ShutdownThread)
+        {
+            auto ts = AZStd::GetTimeNowSecond();
+
+            if (m_bServer)
+            {
+                PerformServerCleanup(ts, tLastCleanup);
+            }
+            else
+            {
+                PerformClientCheck(ts);
+
+                if (m_uIDIdx == 0)
+                {
+                    // we failed to recover a slot to use; sleep for now
+                    AZ_Warning("App", false, "GNIPC: Unable to recover client slot; sleeping\n");
+                    AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(10));
+                    continue;
+                }
+            }
+
+            m_ProcessIDs->i64Poll[m_uIDIdx] = ts;
+
+            ProcessOverlowQueue(ts);
+
+            // don't poll if the message index hasn't changed
+            if (m_ProcessIDs && (m_ProcessIDs->uiPrevMsgSequence[m_uIDIdx] > m_uiPrevMsgSequence))
+            {
+                m_uLastCmdTime = AZStd::GetTimeNowSecond();
+
+                // poll here
+                IPCMsgSequence mapMsgSequence;
+                PollForMessages(mapMsgSequence);
+
+                if (!mapMsgSequence.empty())
+                {
+                    IpcMessageTable* tMsgList = m_MsgTable;
+                    ByteStream contents;
+                    AZ::u64 uBuffSize;
+
+                    IPCMsgSequence::iterator iter;
+                    for (iter = mapMsgSequence.begin(); iter != mapMsgSequence.end(); ++iter)
+                    {
+                        IPCMessage* tMsg = &tMsgList->message[iter->second];
+                        uBuffSize = AZStd::min(tMsg->uiMsgSize, (size_t)IPC_MAX_MSG_SIZE);
+                        if (uBuffSize > 0)
+                        {
+                            contents.resize_no_construct(uBuffSize);
+                            memcpy(contents.data(), tMsg->ucMsg, uBuffSize);
+
+                            {
+                                AZStd::unique_lock<AZStd::mutex> lock(m_MessageListMutex);
+                                m_MessagesWaitingToExecute.push(WaitingIPCMsg(tMsg->senderID, tMsg->uType, contents));
+                            }
+                        }
+
+                        // remove the message
+                        tMsg->pid = 0; // should be faster than memset
+                        tMsg->senderID = 0;
+
+                        ++m_uiPrevMsgSequence;
+                    }
+
+                    m_uiPrevMsgSequence = m_ProcessIDs->uiPrevMsgSequence[m_uIDIdx];
+
+                    {
+                        AZStd::unique_lock<AZStd::mutex> lock(m_MessageListMutex);
+
+                        while (!m_MessagesWaitingToExecute.empty())
+                        {
+                            if (!m_handler)
+                                break; // no handlers
+
+                            auto msg = m_MessagesWaitingToExecute.front();
+                            if (!m_bServer)
+                            {
+                                m_handler(SERVER_ID, "", 0);
+                            }
+                            else
+                            {
+                                m_handler(msg.m_id, (const char*)msg.m_content.data(), msg.m_content.size());
+                            }
+
+                            m_MessagesWaitingToExecute.pop();
+                        }
+                    }
+                    
+                    //ExecuteIPCHandlers();
+                    //EBUS_QUEUE_FUNCTION(AZ::SystemTickBus, &Ipc::ExecuteIPCHandlers, this);
+                }
+                else
+                {
+                    // still empty; wait for the shared memory segment to refresh(we might be hammerring it)
+                    AZ_Warning(
+                        "App",
+                        false,
+                        "GNIPC: Unable to find message going to seq:%u; sleeping...\n",
+                        m_ProcessIDs->uiPrevMsgSequence[m_uIDIdx]);
+                    AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(10));
+                }
+            }
+            /*else
+            {
+                if (!IsPeerAttached() || ((AZStd::GetTimeNowSecond() - m_uLastCmdTime) > 10))
+                    AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(10));
+
+                AZStd::this_thread::sleep_for(AZStd::chrono::milliseconds(10));
+            }*/
+        }
+    }
+
+    bool Ipc::IsAttachedProcessId(AZ::u64 pid, bool bMutexLocked)
+    {
+        if (pid == 0)
+            return false;
+
+        if (m_ProcessIDs)
+        {
+            for (AZ::u32 i = 0; i < IPC_MAX_PID; ++i)
+            {
+                if (m_ProcessIDs->pid[i] == pid)
+                {
+                    auto ticks = AZStd::GetTimeNowSecond();
+                    if ((ticks - m_ProcessIDs->i64Poll[i]) < 15)
+                    {
+                        return true;
+                    }
+                    else
+                    {
+                        if (bMutexLocked && m_bServer)
+                        {
+                            // clean up the data if stagnant
+                            m_ProcessIDs->pid[i] = 0;
+                            m_ProcessIDs->i64Poll[i] = 0;
+
+                            for (AZ::u32 j = 0; j < IPC_MAX_MSGS; ++j)
+                            {
+                                IPCMessage* message = &m_MsgTable->message[j];
+                                if (message->pid == pid)
+                                {
+                                    message->pid = 0;
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+        return false;
+    }
+    void Ipc::AddMessage(AZ::u64 pID, AZ::u32 pType, const AZ::u8* pData, AZ::u64 uSize, bool bMutexLocked)
+    {
+        bool bAdded = false;
+        if (uSize > IPC_MAX_MSG_SIZE)
+            uSize = IPC_MAX_MSG_SIZE;
+
+        // Attempt to find an empty spot in table for our message to go.
+        if (bMutexLocked && (m_OverflowQueue.find(pID) == m_OverflowQueue.end()))
+        {
+            AZ::s64 tNow = AZStd::GetTimeNowSecond();
+            AZ::u64 dwToSendPid;
+            AZ::u32 i, initialIdx = m_uMsgAddIdx, pIDx = GetPIDIdx(pID);
+
+            while (!bAdded)
+            {
+                i = (pIDx * 10) + m_uMsgAddIdx;
+                dwToSendPid = m_MsgTable->message[i].pid;
+
+                if (dwToSendPid == SERVER_ID
+                    || ((tNow - m_MsgTable->message[i].i64Timestamp) > 30))
+                {
+                    IPCMessage tMsg(pID, m_uID, pType, uSize, m_ProcessIDs->uiPrevMsgSequence[pIDx] + 1, tNow);
+                    IPCMessage* tIPCMsg = &m_MsgTable->message[i];
+                    if ((uSize > 0) && (pData != NULL)) // copy the data
+                        memcpy(tIPCMsg->ucMsg, pData, uSize);
+                    memcpy(tIPCMsg, &tMsg, IPC_MESSAGE_DATA_SIZE);
+
+                    ++m_ProcessIDs->uiPrevMsgSequence[pIDx];
+                    bAdded = true;
+                }
+
+                ++m_uMsgAddIdx;
+                if (m_uMsgAddIdx > IPC_MAX_PID - 1)
+                    m_uMsgAddIdx = 0;
+
+                if (m_uMsgAddIdx == initialIdx)
+                {
+                    AZ_Warning("App", false, "GNIPC: Unable to find room for new message; defaulting to overflow!");
+                    break;
+                }
+            }
+        }
+
+        // table was FULL/mutex failed to acquire lock.  Make sure and put this message into the overflow queue so it can be handled
+        // as soon as we have the room
+        if (!bAdded)
+        {
+            IPCMessageQueue& pidQueue = m_OverflowQueue[pID];
+            // NOTE:  Theoretically we could queue an infinite number of messages here.  Don't hose the users
+            // machine.  Make sure this queue is no bigger than 100 messages.
+            if (pidQueue.size() > IPC_MAX_MSGS)
+            {
+                AZ_Warning("App", false, "GNIPC: Overflow queue for pID[%u] has more than 100 messages!\n", pID);
+            }
+
+            IPCMessage tIPCMsg(pID, m_uID, pType, uSize);
+            if ((uSize > 0) && (pData != NULL))
+                memcpy(tIPCMsg.ucMsg, pData, uSize);
+
+            pidQueue.push_back(tIPCMsg);
+        }
+    }
+
+    AZ::u32 Ipc::GetPIDIdx(AZ::u64 pID)
+    {
+        if (m_ProcessIDs)
+        {
+            for (AZ::u32 i = 0; i < IPC_MAX_PID; ++i)
+            {
+                if (m_ProcessIDs->pid[i] == pID)
+                    return i;
+            }
+        }
+        return (IPC_MAX_PID - 1);
+    }
+} // namespace Ipc

+ 264 - 0
Gems/O3DE/GeomNodes/External/Bridge/Ipc.h

@@ -0,0 +1,264 @@
+#pragma once
+#include <AzCore/IPC/SharedMemory.h>
+#include <AzCore/std/containers/deque.h>
+#include <AzCore/std/containers/queue.h>
+#include <AzCore/std/containers/map.h>
+#include <AzCore/std/function/function_template.h>
+#include <AzCore/std/containers/unordered_map.h>
+#include <AzCore/std/string/string.h>
+//#include <AzCore/std/containers/vector.h>
+#include <AzCore/std/parallel/atomic.h>
+#include <AzCore/std/parallel/thread.h>
+#include <AzCore/std/parallel/mutex.h>
+#include <AzCore/Memory/OSAllocator.h>
+
+#ifndef WINAPI
+#define WINAPI __stdcall
+#endif
+
+#ifndef SERVER_ID
+#define SERVER_ID 1
+#endif
+
+namespace Ipc
+{
+    namespace Internal
+    {
+        struct RingData;
+    } // namespace Internal
+
+    constexpr auto IPC_MAX_PID      = 10;       // Max size of process ID table
+    constexpr auto IPC_MAX_MSGS     = 100;      // Max number of messages stored in queue
+    constexpr auto IPC_MAX_MSG_SIZE = 0x8000;   // Max size of of a message (32KB)
+    constexpr auto IPC_MAX_MAP      = 20;       // Max number of simultaneous extended maps;
+    constexpr auto IPC_MIN_MAP_SIZE = 0x100000; // min map size (1MB);
+    constexpr auto MAP_PAGE_SIZE    = 4096;
+
+    enum IPC_MSG_TYPE
+    {
+        IPC_MSG_JSON,               // normal messages
+        IPC_MSG_SHM                 // Message is SHM map related
+                                    // 
+    };
+
+    // Table of processes that uses this IPC object
+    struct IpcIdTable
+    {
+        AZ::u64 pid[IPC_MAX_PID];
+        AZ::s64 i64Poll[IPC_MAX_PID];
+        AZ::u32 uiPrevMsgSequence[IPC_MAX_PID];
+    };
+
+    struct IpcMapTable
+    {
+        // a table of available maps in the SHM
+        AZ::u64 id[IPC_MAX_MAP];       // owner? if 0 means it's free
+        AZ::u64 mapID[IPC_MAX_MAP];    // a randomly generated ID, it could be a uuid or current tick.
+        AZ::u64 uSize[IPC_MAX_MAP];    // map size; there should be no similar size. I don't think there will
+                                       // be two users that will use a map at the same time
+    };
+
+    struct IpcMapSortArray
+    {
+        AZ::u32 arraySize;              // number of created SHM
+        AZ::u32 sortArray[IPC_MAX_MAP]; // the actual sort array containing indexes mapping to IpcMapTable
+                                        // sort order is ascending
+    };
+
+    // Structure of an element in the IPC_MESSAGE_TABLE
+    struct IPCMessage
+    {
+        IPCMessage() = default;
+        IPCMessage(AZ::u64 pID, AZ::u64 sID, AZ::u32 pType, AZ::u64 uSize, AZ::u32 uMsgSec = 0, AZ::s64 sTimeStamp = 0)
+        {
+            pid = pID;
+            senderID = sID;
+            uType = pType;
+            uiMsgSize = uSize;
+            uiMsgSequence = uMsgSec;
+            i64Timestamp = sTimeStamp;
+        }
+
+        AZ::u64 pid = 0;
+        AZ::u64 senderID = 0;
+        AZ::u32 uType = 0;
+        AZ::u32 uiMsgSequence = 0;
+        AZ::u64 uiMsgSize = 0;
+        AZ::s64 i64Timestamp = 0;
+        AZ::u8 ucMsg[IPC_MAX_MSG_SIZE] = { 0 };
+    };
+
+    #define IPC_MESSAGE_DATA_SIZE 40 // 64+64+32+64+32+64
+
+    // Table of messages to be processed by IPC
+    struct IpcMessageTable
+    {
+        IPCMessage message[IPC_MAX_MSGS];
+        // AZ::u32		crc[BRIPC_MAX_MESSAGES];
+    };
+
+    /*class IpcHandler
+    {
+    public:
+        virtual void HandleMessage(AZ::u32 pType, AZ::u8* pData, AZ::u32 uSize) = 0;
+    };*/
+
+    /**
+     * Same implementation as SharedMemoryRingBuffer but with a public m_info
+     */
+    class SHMRingBuffer : public AZ::SharedMemory
+    {
+        bool m_isSetup;
+
+        SHMRingBuffer(const SHMRingBuffer& rhs);
+        SHMRingBuffer& operator=(const SHMRingBuffer&);
+
+    public:
+        SHMRingBuffer();
+
+        // If return true if we are create
+        bool Create(const char* name, unsigned int size, bool openIfCreated = false);
+
+        /// Maps to the created map. If size == 0 it will map the whole memory.
+        bool Map(AccessMode mode = ReadWrite, unsigned int size = 0);
+        bool UnMap();
+
+        /// IMPORTANT: All functions below are UNSAFE. Don't forget to Lock/Unlock before/after using them.
+
+        /// Returns true is we wrote the data, false if the free memory is insufficient.
+        bool Write(const void* data, unsigned int dataSize);
+        /// Reads data up to the maxDataSize, returns number of bytes red.
+        unsigned int Read(void* data, unsigned int maxDataSize);
+
+        unsigned int Read(void** data, unsigned int maxDataSize);
+
+        /// Get number of bytes to read.
+        unsigned int DataToRead() const;
+        /// Get maximum data we can write.
+        unsigned int MaxToWrite() const;
+        /// Clears the ring buffer data and reset it to initial condition.
+        void Clear();
+
+        Internal::RingData* m_info;
+    };
+
+    class Ipc
+    {
+    public:
+        typedef AZ::u32 IPCHandleType;
+        typedef AZStd::deque<IPCMessage> IPCMessageQueue;
+        typedef AZStd::map<AZ::u64, IPCMessageQueue> IPCMessageQueueMap;
+        typedef long (*IPCHandler)(AZ::u64, const char*, AZ::u64);
+        //typedef AZStd::unordered_map<AZ::u64, IPCHandler> IPCHandlerContainer;
+        typedef AZStd::map<AZ::u32, AZ::u32> IPCMsgSequence;
+        typedef AZStd::vector<AZ::u8> ByteStream;
+        typedef AZStd::map<AZ::u64, SHMRingBuffer*> SHMMap;
+
+        Ipc();
+        virtual ~Ipc();
+
+        void CreateOSAllocator();
+        void DestroyAllocator();
+        void CreateSystemAllocator();
+
+        void Initialize(AZ::u64 id = SERVER_ID, IPCHandler handler = nullptr);
+        void Uninitialize();
+
+        void SendMsg(AZ::u32 pType, const AZ::u8* pData, AZ::u64 uSize, AZ::u64 id = SERVER_ID);
+        bool IsInitialized()
+        {
+            return m_success;
+        }
+
+        AZ::u64 CheckForMessage();
+        bool ReadMessage(char* buffer, AZ::u64 length);
+
+        // Map Functions
+        AZ::u64 RequestSHM(AZ::u64 uSize);
+        bool OpenSHM(AZ::u64 mapId);
+        bool ReadSHM(AZ::u64 mapId, void** address, AZ::u64* length);
+        void WriteSHM(AZ::u64 mapId, const char* source, const AZ::u64 length);
+        void ClearSHM(AZ::u64 mapId);
+
+        bool IsServerRunning();
+        bool IsPeerAttached();
+        
+        static Ipc* GetInstance();
+        static void DestroyInstance()
+        {
+            if (m_Instance != NULL)
+            {
+                delete m_Instance;
+                m_Instance = NULL;
+            }
+        }
+
+        void ProcessThread();
+
+    protected:
+    private:
+        bool IsAttachedProcessId(AZ::u64 pid, bool bMutexLocked);
+        void AddMessage(AZ::u64 pID, AZ::u32 pType, const AZ::u8* pData, AZ::u64 uSize, bool bMutexLocked);
+        AZ::u32 GetPIDIdx(AZ::u64 pID);
+        void PerformServerCleanup(AZStd::sys_time_t ts, AZStd::sys_time_t tLastCleanup);
+        void PerformClientCheck(AZStd::sys_time_t ts);
+        void ProcessOverlowQueue(AZStd::sys_time_t ts);
+        void PollForMessages(IPCMsgSequence& mapMsgSequence);
+        void ExecuteIPCHandlers();
+        bool CreateSHMRingBuffer(AZ::u64 mapId, AZ::u64 uSize);
+
+        static Ipc* m_Instance;
+
+        bool m_bServer = false;
+        bool m_success = false;
+        AZStd::atomic_bool m_ShutdownThread = false;
+        AZ::u64 m_uID = 0;
+        AZ::u32 m_uIDIdx = 0;
+        AZ::u32 m_uServerPID = 0;
+        AZ::u32 m_uiPrevMsgSequence = 0; // The index of the IPC message that was just accomplished
+        AZ::u32 m_uMsgAddIdx = 0;
+        AZ::s64 m_uLastCmdTime = 0;
+
+        AZ::SharedMemory m_SharedMem;   // IPC map
+        SHMMap m_SharedMemMap;          // contains the large allocation of SHM.
+                                        // server: eventually handles the management of all SHMs
+                                        // client: if no available map for the requested size it will create it's own SHM and will not close it until the server has sent a message back to the client.
+
+        AZStd::thread m_PollThread;
+
+        IpcIdTable* m_ProcessIDs;       // Pointer to an instance of IPC_PID_TABLE
+        IpcMessageTable* m_MsgTable;    // Pointer to an instance of IPC_MESSAGE_TABLE
+        IpcMapTable* m_MapTable;        // Point to an instance of IPC_MAP_TABLE
+        IpcMapSortArray* m_MapSortArray;
+        IPCMessageQueueMap m_OverflowQueue; // Extra tables should the m_MsgTable be filled
+        //IPCHandlerContainer m_Handlers;
+        IPCHandler m_handler = nullptr;
+
+        struct WaitingIPCMsg
+        {
+            AZ::u64 m_id;
+            AZ::u32 m_type;
+            ByteStream m_content;
+
+            WaitingIPCMsg(AZ::u64 id, AZ::u32 msgType, const ByteStream& content)
+                : m_id(id)
+                , m_type(msgType)
+                , m_content(content)
+            {
+            }
+        };
+
+        typedef AZStd::queue<WaitingIPCMsg> MessageContainer;
+        MessageContainer m_MessagesWaitingToExecute;
+        AZStd::mutex m_MessageListMutex;
+
+        //
+        bool m_isStarted{ false };
+        bool m_isSystemAllocatorOwner{ false };
+        bool m_isOSAllocatorOwner{ false };
+        void* m_fixedMemoryBlock{ nullptr }; //!< Pointer to the memory block allocator, so we can free it OnDestroy.
+        AZ::IAllocator* m_osAllocator{ nullptr };
+    };
+
+}
+

+ 6 - 0
Gems/O3DE/GeomNodes/External/Bridge/bridge_files.cmake

@@ -0,0 +1,6 @@
+set(FILES
+    Bridge.h
+    Bridge.cpp
+    Ipc.h
+    Ipc.cpp
+)

+ 15 - 0
Gems/O3DE/GeomNodes/External/CMakeLists.txt

@@ -0,0 +1,15 @@
+# cmake_minimum_required(VERSION 3.20)
+
+# project(
+#     Bridge
+#     VERSION 0.0.1
+# )
+
+# set(CMAKE_MSVC_RUNTIME_LIBRARY MultiThreadedDLL)
+# set(CMAKE_INSTALL_PREFIX ${CMAKE_CURRENT_LIST_DIR}/Bridge)
+# set(CMAKE_INSTALL_LIBDIR ${CMAKE_INSTALL_PREFIX})
+# set(CMAKE_INSTALL_BINDIR ${CMAKE_INSTALL_PREFIX})
+
+# set(BUILD_INSTALL_DIR Build)
+
+add_subdirectory(Bridge)

+ 6 - 0
Gems/O3DE/GeomNodes/External/README.md

@@ -0,0 +1,6 @@
+# Bridge
+
+A helper library that handles communication between Blender and O3DE.
+
+#### Compile
+This is hooked with the Gem's cmake settings.

+ 59 - 0
Gems/O3DE/GeomNodes/External/Scripts/GeomNodes.py

@@ -0,0 +1,59 @@
+import bpy
+import sys
+import os
+import time
+import datetime
+import json
+from messages import poll_for_messages
+
+dir = os.path.dirname(bpy.data.filepath)
+if not dir in sys.path:
+    sys.path.append(dir)
+
+
+from lib_loader import init_lib, MessageReader, MessageWriter
+
+# def callback(id, content, length):
+#     print('callback')
+#     #print("id: %d, content: %s, length: %d)" % (id, content, length))
+#     # buffer = bytes(json.dumps({'message' : 'from callback'}), "UTF-8")
+#     # array = np.frombuffer(buffer, np.byte)
+#     # from lib_loader import GNLibs
+#     # GNLibs.SendMsg(array.ctypes.data_as(c.POINTER(c.c_ubyte)), array.nbytes, 0)
+#     return 0
+
+def init(exePath, id):
+    init_lib(exePath, id)
+
+def run():    
+    update_rate = 0.005
+    idle_time = 0
+
+    # make object visible
+    for layer_collection in bpy.context.view_layer.layer_collection.children:
+        layer_collection.exclude = False
+
+    # send message to the gem that script is initialized
+    MessageWriter().from_buffer(bytes(json.dumps({'Initialized' : True}), "UTF-8"))
+    
+    #print(json.dumps(get_geometry_nodes_objs(), indent=4))
+
+    while idle_time < 60:
+        from lib_loader import GNLibs
+        idle_time = idle_time + update_rate
+
+        start = datetime.datetime.now()
+        if poll_for_messages():
+            idle_time = 0
+
+        end = datetime.datetime.now()
+        # print('Export time: ' + str((end-start).seconds + (end-start).microseconds/1000000) + 's', flush=True)
+        if bpy.app.background:
+            time.sleep(update_rate)
+        else:
+            break
+
+    if bpy.app.background:
+        GNLibs.Uninitialize()
+    else:
+        return update_rate

+ 30 - 0
Gems/O3DE/GeomNodes/External/Scripts/__init__.py

@@ -0,0 +1,30 @@
+import sys
+import os
+import bpy
+import json
+
+sys.stdout = open("f:/output.txt", "w")
+
+dir = os.path.dirname(__file__)
+if not dir in sys.path:
+    sys.path.append(dir)
+
+params = sys.argv[sys.argv.index("--") + 1:]
+print('exe path: ' + params[0])
+print('uuid: ' + params[1])
+print('pid: ' + str(os.getpid()))
+
+if __name__ == "__main__":
+    from GeomNodes import init, run
+    init(params[0], params[1])
+
+    # run our loop to watch for updates
+    
+    if not bpy.app.background:
+        bpy.app.timers.register(run)
+    else:
+        run()
+
+# Close the file
+sys.stdout.close()
+sys.stdout = sys.__stdout__

+ 141 - 0
Gems/O3DE/GeomNodes/External/Scripts/lib_loader.py

@@ -0,0 +1,141 @@
+import ctypes
+import os
+import numpy as np
+
+GNLibs = None
+
+callback_type = ctypes.CFUNCTYPE(ctypes.c_long, ctypes.c_ulonglong, ctypes.POINTER(ctypes.c_ubyte), ctypes.c_ulonglong)
+
+def init_lib(exePath, id):
+    lib_path = exePath + '\\Bridge.dll'
+    print('lib path: ' + lib_path)
+    print(type(int(id)))
+
+    global GNLibs
+    
+    GNLibs = ctypes.cdll.LoadLibrary(lib_path)
+    GNLibs.Init.argtypes = [ctypes.c_ulonglong, callback_type]
+    GNLibs.Init.restype = None
+
+    GNLibs.Uninitialize.argtypes = None
+    GNLibs.Uninitialize.restype = None
+
+    GNLibs.SendMsg.argtypes = (ctypes.POINTER(ctypes.c_ubyte), ctypes.c_ulonglong, ctypes.c_ulonglong)
+    GNLibs.SendMsg.restype = None
+
+    GNLibs.IsConnected.argtypes = None
+    GNLibs.IsConnected.restype = ctypes.c_bool
+
+    GNLibs.CheckForMsg.argtypes = None
+    GNLibs.CheckForMsg.restype = ctypes.c_ulonglong
+
+    GNLibs.ReadMsg.argtypes = (ctypes.POINTER(ctypes.c_ubyte), ctypes.c_ulonglong)
+    GNLibs.ReadMsg.restype = ctypes.c_ulonglong
+
+    # Map functions
+    GNLibs.RequestSHM.argtypes = (ctypes.c_ulonglong,)
+    GNLibs.RequestSHM.restype = ctypes.c_ulonglong
+
+    GNLibs.OpenSHM.argtypes = (ctypes.c_ulonglong,)
+    GNLibs.OpenSHM.restype = ctypes.c_bool
+
+    GNLibs.ReadSHM.argtypes = (ctypes.c_ulonglong, ctypes.c_void_p, ctypes.POINTER(ctypes.c_ulonglong))
+    GNLibs.ReadSHM.restype = ctypes.c_bool
+
+    GNLibs.WriteSHM.argtypes = (ctypes.c_ulonglong, ctypes.POINTER(ctypes.c_ubyte), ctypes.c_ulonglong)
+    GNLibs.WriteSHM.restype = None
+
+    GNLibs.ClearSHM.argtypes = (ctypes.c_ulonglong,)
+    GNLibs.ClearSHM.restype = None
+
+    cb_func = callback_type() # passing a null ptr as we don't use callback in python yet(if we figure out how to properly use a callback here)
+    GNLibs.Init(int(id), cb_func)
+
+class MessageReader:
+    buffer = 0
+    length = 0
+    dtype = None
+    
+    def __init__(self, dtype=np.byte) -> None:
+        length = GNLibs.CheckForMsg()
+        if length > 0:
+            buffer = (ctypes.c_ubyte * length)()
+            if GNLibs.ReadMsg(buffer, length):    
+                bytes = buffer
+        else:
+            bytes = b''
+
+        self.buffer = bytes
+        self.length = length
+        self.dtype = dtype
+    
+    def as_array(self, num_components=1):
+        array = np.frombuffer(self.buffer, np.dtype(self.dtype))
+        if num_components > 1:
+            array = array.reshape(array.size//num_components, num_components)
+        return array
+
+    def as_list(self, num_components=1):
+        return self.as_array(num_components).tolist()
+
+    def as_value(self):
+        return self.as_array().item()
+
+    def as_string(self):
+        string = str(self.buffer, encoding = 'ascii')
+        string = string.replace('\x00', '')
+        return string
+
+class MessageWriter:
+    dtype = None
+    
+    def __init__(self, dtype=np.byte) -> None:
+        self.dtype = dtype
+
+    def from_array(self, array):
+        GNLibs.SendMsg(array.ctypes.data_as(ctypes.POINTER(ctypes.c_ubyte)), array.nbytes, 1)
+
+    def from_value(self, value):
+        array = np.asarray(value, self.dtype)
+        GNLibs.SendMsg(array.ctypes.data_as(ctypes.POINTER(ctypes.c_ubyte)), array.nbytes, 1)
+        
+    def from_buffer(self, value):
+        array = np.frombuffer(value, self.dtype)
+        GNLibs.SendMsg(array.ctypes.data_as(ctypes.POINTER(ctypes.c_ubyte)), array.nbytes, 1)
+
+class MapWriter():
+    dtype = None
+
+    def __init__(self, mapId, dtype=np.byte) -> None:
+        self.dtype = dtype
+        self.mapId = mapId
+
+    def from_array(self, array):
+        GNLibs.WriteSHM(self.mapId, array.ctypes.data_as(ctypes.POINTER(ctypes.c_ubyte)), array.nbytes)
+
+    def from_value(self, value):
+        array = np.asarray(value, self.dtype)
+        GNLibs.WriteSHM(self.mapId, array.ctypes.data_as(ctypes.POINTER(ctypes.c_ubyte)), array.nbytes)
+        
+    def from_buffer(self, value):
+        array = np.frombuffer(value, self.dtype)
+        GNLibs.WriteSHM(self.mapId, array.ctypes.data_as(ctypes.POINTER(ctypes.c_ubyte)), array.nbytes)
+
+class MapWriterSize():
+    dtype = None
+
+    def __init__(self, dtype=np.byte) -> None:
+        x = np.array([1], dtype=np.int32)
+        self.chunksize = x.itemsize
+        self.dtype = dtype
+
+    def from_array(self, array):
+        return self.chunksize + array.nbytes
+
+    def from_value(self, value):
+        array = np.asarray(value, self.dtype)
+        return self.chunksize + array.nbytes
+        
+    def from_buffer(self, value):
+        array = np.frombuffer(value, self.dtype)
+        return self.chunksize + array.nbytes

+ 177 - 0
Gems/O3DE/GeomNodes/External/Scripts/mesh_data_builder.py

@@ -0,0 +1,177 @@
+import bpy
+import bpy_types
+import numpy as np
+from mathutils import Matrix
+from utils import get_prop_collection
+
+import json
+import datetime
+from utils import get_geomnodes_obj
+from lib_loader import MapWriterSize, MapWriter
+
+################ MESH DATA BUILDER ############################
+def get_mesh_colors_data(mesh):
+    has_vertex_indexed_colors = False
+
+    if len(mesh.vertex_colors) > 0:
+        colors = get_prop_collection(mesh.vertex_colors[0].data, 'color', 4, np.float32)
+    elif mesh.attributes.get('Col') and len(mesh.attributes.get('Col').data) > 0:
+        attribute_colors = mesh.attributes.get('Col')
+        colors = get_prop_collection(attribute_colors.data, 'color', 4, np.float32)
+        has_vertex_indexed_colors = attribute_colors.domain == 'POINT'
+    else:
+        colors = np.zeros(len(mesh.loops)*4, dtype=np.float32)
+
+    vertex_indexed_colors = np.asarray(has_vertex_indexed_colors, np.bool8) 
+    return colors, vertex_indexed_colors
+
+
+def get_mesh_uv_data(mesh):
+    convert_uvs = False
+    has_vertex_indexed_uvs = False
+
+    if mesh.attributes.get('UVMap') and len(mesh.attributes.get('UVMap').data) > 0:
+        attribute_uvmap = mesh.attributes.get('UVMap')     
+        uv_size = len(attribute_uvmap.data[0].vector)
+        convert_uvs = uv_size == 3
+        has_vertex_indexed_uvs = attribute_uvmap.domain == 'POINT'
+        uvs = get_prop_collection(attribute_uvmap.data, 'vector', uv_size, np.float32)
+    elif mesh.attributes.get('uv_map') and len(mesh.attributes.get('uv_map').data) > 0:
+        attribute_uvmap = mesh.attributes.get('uv_map')
+        uv_size = len(attribute_uvmap.data[0].vector)
+        convert_uvs = uv_size == 3
+        has_vertex_indexed_uvs = attribute_uvmap.domain == 'POINT'
+        uvs = get_prop_collection(attribute_uvmap.data, 'vector', uv_size, np.float32)
+    elif len(mesh.uv_layers) > 0:
+        uvs = get_prop_collection(mesh.uv_layers[0].data, 'uv', 2, np.float32)
+    else:
+        uvs = np.zeros(len(mesh.loops)*2, dtype=np.float32)
+    
+    # make float3 uvs into float2
+    if convert_uvs:
+        uvs = uvs.reshape((int(len(uvs)/3),3))
+        uvs = np.delete(uvs, 2, 1)
+        uvs = np.ravel(uvs)
+
+    vertex_indexed_uvs = np.asarray(has_vertex_indexed_uvs, np.bool8)
+
+    return uvs, vertex_indexed_uvs
+
+def get_materials(mesh):
+    names = []
+    for material in mesh.materials[:]:
+        if material is not None:
+            names += [material.name_full]
+        else:
+            names += ['Default']
+    return names
+
+def get_materials_data(mesh):
+    material_indices = get_prop_collection(mesh.loop_triangles, 'material_index', 1, np.int32)
+    material_names = np.frombuffer(bytes(json.dumps({'materials' : get_materials(mesh)}), "UTF-8"), np.byte)
+
+    return material_indices, material_names
+
+def get_mesh_data(mesh):
+    mesh.calc_loop_triangles()
+    mesh.calc_normals_split()
+
+    vertices = get_prop_collection(mesh.vertices, 'co', 3, np.float32)
+    normals = get_prop_collection(mesh.loop_triangles, 'split_normals', 3*3, np.float32)
+    indices = get_prop_collection(mesh.loop_triangles, 'vertices', 3, np.int32)
+    triangle_loops = get_prop_collection(mesh.loop_triangles, 'loops', 3, np.int32)
+    loops = get_prop_collection(mesh.loops, 'vertex_index', 1, np.int32)
+
+    mesh_hash = np.asarray(hash(mesh), np.int64)
+
+    colors, vertex_indexed_color = get_mesh_colors_data(mesh)
+    uvs, vertex_indexed_uvs = get_mesh_uv_data(mesh)
+    material_indices, material_names = get_materials_data(mesh)
+    
+    return vertices, normals, indices, triangle_loops, loops, mesh_hash, colors, vertex_indexed_color, uvs, vertex_indexed_uvs, material_indices, material_names
+
+def to_instance_arrays(mesh, local_matrix, world_matrix):
+    mesh_hash = np.asarray(hash(mesh), np.int64)
+    local_matrix = np.array(local_matrix, np.float32)
+    world_matrix = np.array(world_matrix, np.float32)
+    
+    return mesh_hash, local_matrix, world_matrix
+
+def build_mesh_data(obj_name):
+    geomnodes_obj = bpy.data.objects.get(obj_name)
+    if geomnodes_obj == None:
+        geomnodes_obj = get_geomnodes_obj()
+
+    if geomnodes_obj.hide_get():
+        geomnodes_obj.hide_set(False, view_layer=bpy.context.view_layer)
+
+    print('Started building Mesh Data')
+    start = datetime.datetime.now()
+    depsgraph = bpy.context.evaluated_depsgraph_get()        
+    eval_geomnodes_data = geomnodes_obj.evaluated_get(depsgraph).data
+
+    mesh_arr = []
+    hash_arr = []
+    instance_arr = []
+    instance_matrix_loc_arr = []
+    instance_matrix_world_arr = []
+    
+    scene_scale_len = bpy.context.scene.unit_settings.scale_length
+
+    # Get number of meshes and instances
+    if geomnodes_obj.type == 'MESH':
+        hash_arr = [hash(geomnodes_obj)]
+        mesh_arr = [eval_geomnodes_data]
+        instance_arr = [eval_geomnodes_data]
+        instance_matrix_loc_arr = [Matrix() * scene_scale_len]
+        instance_matrix_world_arr = [geomnodes_obj.matrix_world.copy() * scene_scale_len]
+
+    for instance in depsgraph.object_instances:
+        if instance.instance_object and instance.parent and instance.parent.original == geomnodes_obj:
+            if instance.object.type == 'MESH':
+                # add only if the instance is not in the hash array
+                if hash(instance.object.data) not in hash_arr:
+                    hash_arr += [hash(instance.object.data)]
+                    mesh_arr += [instance.object.data]
+                # save the instance data and transforms
+                if hash(instance.object.data) in hash_arr:
+                    instance_arr += [instance.object.data]
+                    local_matrix = instance.parent.matrix_world.inverted() @ instance.matrix_world                        
+                    instance_matrix_loc_arr += [local_matrix * scene_scale_len]
+                    instance_matrix_world_arr += [instance.matrix_world.copy() * scene_scale_len]
+                    
+    
+    total_bytes = 0
+    total_bytes += MapWriterSize(np.int32).from_value(len(mesh_arr))
+    total_bytes += MapWriterSize(np.int32).from_value(len(instance_arr))
+    
+    # Export meshes
+    for mesh in mesh_arr:
+        for array in get_mesh_data(mesh):
+            total_bytes += MapWriterSize().from_array(array)
+
+    # Export instances
+    for instance, local_matrix, world_matrix in zip(instance_arr, instance_matrix_loc_arr, instance_matrix_world_arr):
+        for array in to_instance_arrays(instance, local_matrix, world_matrix):
+            total_bytes += MapWriterSize().from_array(array)
+
+    from lib_loader import GNLibs
+    map_id = GNLibs.RequestSHM(total_bytes)
+    print('map_id = ' + str(map_id) + ' total_bytes = ' + str(total_bytes), flush=True)
+    MapWriter(map_id, np.int32).from_value(len(mesh_arr))
+    MapWriter(map_id, np.int32).from_value(len(instance_arr))
+    
+    # Export meshes
+    for mesh in mesh_arr:
+        for array in get_mesh_data(mesh):
+            MapWriter(map_id).from_array(array)
+    
+    # Export Instances
+    for instance, local_matrix, world_matrix in zip(instance_arr, instance_matrix_loc_arr, instance_matrix_world_arr):
+        for array in to_instance_arrays(instance, local_matrix, world_matrix):
+            MapWriter(map_id).from_array(array)
+
+    end = datetime.datetime.now()
+    print('Built Mesh Data and sent in ' + str((end-start).seconds + (end-start).microseconds/1000000) + 's', flush=True)
+    return map_id
+###############################################################

+ 94 - 0
Gems/O3DE/GeomNodes/External/Scripts/messages.py

@@ -0,0 +1,94 @@
+import bpy
+import sys
+import os
+import datetime
+import json
+from lib_loader import MessageReader, MessageWriter
+from params import get_geometry_nodes_objs
+from mesh_data_builder import build_mesh_data
+from utils import get_geomnodes_obj
+
+def import_texture(Id, x, y):
+    print(str(x) + ' ' + str(y), flush=True)
+    image = bpy.data.images.new(Id + "_Image", width=x, height=y)
+    return image
+
+def update_geometry_node_params(geometry_nodes_obj, new_params: dict):    
+    bpy.context.view_layer.objects.active =geometry_nodes_obj
+
+    GN = next(modifier for modifier in bpy.context.object.modifiers if modifier.type == 'NODES')
+
+    for params in new_params['Params']:
+        if params['Type'] == 'VECTOR':
+            vect = params['Value'][0]
+            GN[params['Id']][:] = [vect['X'], vect['Y'], vect['Z']]
+        elif params['Type'] == 'INT':
+            GN[params['Id']] = int(params['Value'])
+        elif params['Type'] == 'VALUE':
+            GN[params['Id']] = float(params['Value'])
+        elif params['Type'] == 'BOOLEAN':
+            GN[params['Id']] = bool(params['Value'])
+        # elif params['Type'] == 'MESH':
+        #     GN[params['Id']] = params['Object']
+        # elif params['Type'] == 'COLLECTION':
+        #     GN[params['Id']] = params['Collection']            
+        elif params['Type'] == 'STRING':
+            GN[params['Id']] = str(params['Value'])
+        # elif params['Type'] == 'IMAGE' or params['Type'] == 'TEXTURE':
+        #     GN[params['Id']] = params['Tex']
+        # elif params['Type'] == 'RGBA':
+        #     vect = params['Value'][0]
+        #     GN[params['Id']][:] = [vect['R'], vect['G'], vect['B'], vect['A']]
+
+    for obj in bpy.data.objects:
+        if obj.data and obj.type == "MESH":
+            obj.data.update()
+
+def update_gn_in_blender(msg_dict):
+    object_name = msg_dict['Object']
+    geometry_nodes_obj = bpy.data.objects.get(object_name)
+    if geometry_nodes_obj == None:
+        geometry_nodes_obj = get_geomnodes_obj()
+
+    print('Updating Geometry Node')
+    
+    start = datetime.datetime.now()
+    update_geometry_node_params(geometry_nodes_obj, msg_dict)
+    end = datetime.datetime.now()
+    
+    print('Updated in ' + str((end-start).seconds + (end-start).microseconds/1000000) + 's')
+
+    return object_name
+
+
+
+
+def poll_for_messages():
+    # poll if there are messages until we exhaust them.
+    msg_str = MessageReader().as_string()
+    if len(msg_str) > 0:
+        try:
+            print(msg_str)
+            msg_dict = json.loads(msg_str)
+        except json.decoder.JSONDecodeError:
+            if msg_str != "":
+                print('couldnt parse json: ' + msg_str)
+        else:
+            if msg_dict is not None:
+                if 'FetchObjectParams' in msg_dict:
+                    MessageWriter().from_buffer(bytes(json.dumps(get_geometry_nodes_objs()), "UTF-8"))
+                elif 'ParamUpdate' in msg_dict:
+                    # based on the parameter updates we pass it to blender to update the objects
+                    object_name = update_gn_in_blender(msg_dict)
+                    # after the updates, build the mesh data to be sent to the gem
+                    map_id = build_mesh_data(object_name)
+                    # inform the gem that the 3D data is available on the SHM.
+                    MessageWriter().from_buffer(bytes(json.dumps({'SHMOpen' : True, 'MapId' : map_id }), "UTF-8"))
+                elif 'SHMClose' in msg_dict:
+                    # after the gem read the data it will send a message to the client so that it could close the SHM instance and free it in the map table.
+                    map_id = msg_dict['MapId']
+                    from lib_loader import GNLibs
+                    GNLibs.ClearSHM(map_id)
+
+        return True
+    return False

部分文件因文件數量過多而無法顯示