Ver Fonte

Physics joints updated to the new API (#1361)

Co-authored-by: Ulugbek Adilbekov <[email protected]>
amzn-sean há 4 anos atrás
pai
commit
9f62d631fb
90 ficheiros alterados com 2792 adições e 1665 exclusões
  1. 40 10
      AutomatedTesting/Gem/PythonTests/physics/C18243584_Joints_HingeSoftLimitsConstrained.py
  2. 2 2
      AutomatedTesting/Levels/Physics/C18243584_Joints_HingeSoftLimitsConstrained/C18243584_Joints_HingeSoftLimitsConstrained.ly
  3. 2 2
      AutomatedTesting/Levels/Physics/C18243586_Joints_HingeLeadFollowerCollide/C18243586_Joints_HingeLeadFollowerCollide.ly
  4. 2 2
      AutomatedTesting/Levels/Physics/C18243589_Joints_BallSoftLimitsConstrained/C18243589_Joints_BallSoftLimitsConstrained.ly
  5. 2 2
      AutomatedTesting/Levels/Physics/C18243591_Joints_BallLeadFollowerCollide/C18243591_Joints_BallLeadFollowerCollide.ly
  6. 36 0
      Code/Framework/AzFramework/AzFramework/Physics/Common/PhysicsJoint.cpp
  7. 143 0
      Code/Framework/AzFramework/AzFramework/Physics/Common/PhysicsJoint.h
  8. 18 1
      Code/Framework/AzFramework/AzFramework/Physics/Common/PhysicsTypes.h
  9. 37 0
      Code/Framework/AzFramework/AzFramework/Physics/Configuration/JointConfiguration.cpp
  10. 51 0
      Code/Framework/AzFramework/AzFramework/Physics/Configuration/JointConfiguration.h
  11. 0 56
      Code/Framework/AzFramework/AzFramework/Physics/Joint.cpp
  12. 0 75
      Code/Framework/AzFramework/AzFramework/Physics/Joint.h
  13. 39 0
      Code/Framework/AzFramework/AzFramework/Physics/PhysicsScene.h
  14. 2 2
      Code/Framework/AzFramework/AzFramework/Physics/Ragdoll.cpp
  15. 4 3
      Code/Framework/AzFramework/AzFramework/Physics/Ragdoll.h
  16. 0 53
      Code/Framework/AzFramework/AzFramework/Physics/SystemBus.h
  17. 3 1
      Code/Framework/AzFramework/AzFramework/Physics/Utils.cpp
  18. 4 2
      Code/Framework/AzFramework/AzFramework/azframework_files.cmake
  19. 0 18
      Gems/Blast/Code/Tests/Mocks/BlastMocks.h
  20. 18 15
      Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/RagdollCommands.cpp
  21. 2 2
      Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/RagdollCommands.h
  22. 12 8
      Gems/EMotionFX/Code/Source/Editor/Plugins/Ragdoll/RagdollJointLimitWidget.cpp
  23. 0 1
      Gems/EMotionFX/Code/Source/Editor/Plugins/Ragdoll/RagdollJointLimitWidget.h
  24. 11 6
      Gems/EMotionFX/Code/Source/Editor/Plugins/Ragdoll/RagdollNodeInspectorPlugin.cpp
  25. 4 2
      Gems/EMotionFX/Code/Source/Editor/Plugins/Ragdoll/RagdollNodeInspectorPlugin.h
  26. 1 1
      Gems/EMotionFX/Code/Tests/D6JointLimitConfiguration.cpp
  27. 3 4
      Gems/EMotionFX/Code/Tests/D6JointLimitConfiguration.h
  28. 36 5
      Gems/EMotionFX/Code/Tests/Mocks/PhysicsSystem.h
  29. 17 4
      Gems/EMotionFX/Code/Tests/ProvidesUI/Ragdoll/CanCopyPasteColliders.cpp
  30. 28 8
      Gems/EMotionFX/Code/Tests/ProvidesUI/Ragdoll/CanCopyPasteJointLimits.cpp
  31. 38 2
      Gems/EMotionFX/Code/Tests/RagdollCommandTests.cpp
  32. 27 0
      Gems/EMotionFX/Code/Tests/UI/CanAddToSimulatedObject.cpp
  33. 28 0
      Gems/EMotionFX/Code/Tests/UI/RagdollEditTests.cpp
  34. 0 2
      Gems/EMotionFX/Code/emotionfx_editor_tests_files.cmake
  35. 2 0
      Gems/EMotionFX/Code/emotionfx_shared_tests_files.cmake
  36. 11 0
      Gems/PhysX/Code/CMakeLists.txt
  37. 22 17
      Gems/PhysX/Code/Editor/EditorJointConfiguration.cpp
  38. 5 4
      Gems/PhysX/Code/Editor/EditorJointConfiguration.h
  39. 107 0
      Gems/PhysX/Code/Include/PhysX/Joint/Configuration/PhysXJointConfiguration.h
  40. 22 44
      Gems/PhysX/Code/Source/BallJointComponent.cpp
  41. 4 5
      Gems/PhysX/Code/Source/BallJointComponent.h
  42. 1 0
      Gems/PhysX/Code/Source/EditorBallJointComponent.cpp
  43. 0 1
      Gems/PhysX/Code/Source/EditorBallJointComponent.h
  44. 1 1
      Gems/PhysX/Code/Source/EditorFixedJointComponent.cpp
  45. 0 1
      Gems/PhysX/Code/Source/EditorFixedJointComponent.h
  46. 1 0
      Gems/PhysX/Code/Source/EditorHingeJointComponent.cpp
  47. 0 1
      Gems/PhysX/Code/Source/EditorHingeJointComponent.h
  48. 0 1
      Gems/PhysX/Code/Source/EditorJointComponent.h
  49. 31 15
      Gems/PhysX/Code/Source/FixedJointComponent.cpp
  50. 7 3
      Gems/PhysX/Code/Source/FixedJointComponent.h
  51. 23 43
      Gems/PhysX/Code/Source/HingeJointComponent.cpp
  52. 4 5
      Gems/PhysX/Code/Source/HingeJointComponent.h
  53. 0 646
      Gems/PhysX/Code/Source/Joint.cpp
  54. 0 311
      Gems/PhysX/Code/Source/Joint.h
  55. 155 0
      Gems/PhysX/Code/Source/Joint/Configuration/PhysXJointConfiguration.cpp
  56. 200 0
      Gems/PhysX/Code/Source/Joint/PhysXJoint.cpp
  57. 126 0
      Gems/PhysX/Code/Source/Joint/PhysXJoint.h
  58. 470 0
      Gems/PhysX/Code/Source/Joint/PhysXJointUtils.cpp
  59. 101 0
      Gems/PhysX/Code/Source/Joint/PhysXJointUtils.h
  60. 55 43
      Gems/PhysX/Code/Source/JointComponent.cpp
  61. 36 15
      Gems/PhysX/Code/Source/JointComponent.h
  62. 16 13
      Gems/PhysX/Code/Source/PhysXCharacters/API/CharacterUtils.cpp
  63. 2 2
      Gems/PhysX/Code/Source/PhysXCharacters/API/Ragdoll.cpp
  64. 20 2
      Gems/PhysX/Code/Source/PhysXCharacters/API/RagdollNode.cpp
  65. 5 4
      Gems/PhysX/Code/Source/PhysXCharacters/API/RagdollNode.h
  66. 1 1
      Gems/PhysX/Code/Source/PhysXCharacters/Components/RagdollComponent.cpp
  67. 2 0
      Gems/PhysX/Code/Source/Platform/Android/PAL_android.cmake
  68. 1 0
      Gems/PhysX/Code/Source/Platform/Linux/PAL_linux.cmake
  69. 1 0
      Gems/PhysX/Code/Source/Platform/Mac/PAL_mac.cmake
  70. 1 0
      Gems/PhysX/Code/Source/Platform/Windows/PAL_windows.cmake
  71. 1 0
      Gems/PhysX/Code/Source/Platform/iOS/PAL_ios.cmake
  72. 105 0
      Gems/PhysX/Code/Source/Scene/PhysXScene.cpp
  73. 9 0
      Gems/PhysX/Code/Source/Scene/PhysXScene.h
  74. 31 0
      Gems/PhysX/Code/Source/Scene/PhysXSceneInterface.cpp
  75. 4 0
      Gems/PhysX/Code/Source/Scene/PhysXSceneInterface.h
  76. 299 0
      Gems/PhysX/Code/Source/System/PhysXJointInterface.cpp
  77. 52 0
      Gems/PhysX/Code/Source/System/PhysXJointInterface.h
  78. 2 0
      Gems/PhysX/Code/Source/System/PhysXSystem.h
  79. 0 45
      Gems/PhysX/Code/Source/SystemComponent.cpp
  80. 0 22
      Gems/PhysX/Code/Source/SystemComponent.h
  81. 7 3
      Gems/PhysX/Code/Source/Utils.cpp
  82. 1 0
      Gems/PhysX/Code/Tests/Benchmarks/PhysXBenchmarksUtilities.h
  83. 33 24
      Gems/PhysX/Code/Tests/Benchmarks/PhysXJointBenchmarks.cpp
  84. 1 1
      Gems/PhysX/Code/Tests/PhysXGenericTestFixture.h
  85. 98 10
      Gems/PhysX/Code/Tests/PhysXJointsTest.cpp
  86. 88 88
      Gems/PhysX/Code/Tests/RagdollConfiguration.xml
  87. 2 2
      Gems/PhysX/Code/Tests/RagdollTests.cpp
  88. 8 2
      Gems/PhysX/Code/physx_files.cmake
  89. 1 1
      Gems/PhysXDebug/Code/Source/SystemComponent.cpp
  90. 7 0
      Gems/ScriptCanvasPhysics/Code/Tests/ScriptCanvasPhysicsTest.cpp

+ 40 - 10
AutomatedTesting/Gem/PythonTests/physics/C18243584_Joints_HingeSoftLimitsConstrained.py

@@ -56,6 +56,7 @@ def C18243584_Joints_HingeSoftLimitsConstrained():
     """
     import os
     import sys
+    import math
 
     import ImportPathHelper as imports
 
@@ -93,22 +94,51 @@ def C18243584_Joints_HingeSoftLimitsConstrained():
     Report.info_vector3(lead.position, "lead initial position:")
     Report.info_vector3(follower.position, "follower initial position:")
     leadInitialPosition = lead.position
-    followerInitialPosition = follower.position
-
-    # 4) Wait for several seconds
-    general.idle_wait(4.0) # wait for lead and follower to move
-
+        
+    # 4) Wait for the follower to move above the lead or Timeout
+    normalizedStartPos = JointsHelper.getRelativeVector(lead.position, follower.position)
+    normalizedStartPos = normalizedStartPos.GetNormalizedSafe()
+
+    class WaitCondition:
+        TARGET_ANGLE = math.radians(45)
+        TARGET_MAX_ANGLE = math.radians(180)
+
+        angleAchieved = 0.0
+        followerMovedAbove45Deg = False #this is expected to be true to pass the test
+        followerMovedAbove180Deg = True #this is expected to be false to pass the test            
+
+        def checkConditionMet(self):
+            #calculate the current follower-lead vector
+            normalVec = JointsHelper.getRelativeVector(lead.position, follower.position)
+            normalVec = normalVec.GetNormalizedSafe()
+            #dot product + acos to get the angle
+            currentAngle = math.acos(normalizedStartPos.Dot(normalVec))
+            #if the angle is now less then last time, it is no longer rising, so end the test.
+            if currentAngle < self.angleAchieved:
+                return True
+            
+            self.angleAchieved = currentAngle
+            self.followerMovedAbove45Deg = currentAngle > self.TARGET_ANGLE
+            self.followerMovedAbove180Deg = currentAngle > self.TARGET_MAX_ANGLE
+            return False
+
+        def isFollowerPositionCorrect(self):
+            return self.followerMovedAbove45Deg and not self.followerMovedAbove180Deg
+
+    waitCondition = WaitCondition()
+
+    MAX_WAIT_TIME = 5.0 #seconds
+    conditionMet = helper.wait_for_condition(lambda: waitCondition.checkConditionMet(), MAX_WAIT_TIME)
+    
     # 5) Check to see if lead and follower behaved as expected
-    Report.info_vector3(lead.position, "lead position after 1 second:")
-    Report.info_vector3(follower.position, "follower position after 1 second:")
+    Report.info_vector3(lead.position, "lead position after test:")
+    Report.info_vector3(follower.position, "follower position after test:")
 
     leadPositionDelta = lead.position.Subtract(leadInitialPosition)
     leadRemainedStill = JointsHelper.vector3SmallerThanScalar(leadPositionDelta, FLOAT_EPSILON)
     Report.critical_result(Tests.check_lead_position, leadRemainedStill)
 
-    followerMovedInXOnly = ((follower.position.x > leadInitialPosition.x) > FLOAT_EPSILON and
-                        (follower.position.z - leadInitialPosition.z) > FLOAT_EPSILON)
-    Report.critical_result(Tests.check_follower_position, followerMovedInXOnly)
+    Report.critical_result(Tests.check_follower_position, conditionMet and waitCondition.isFollowerPositionCorrect())
 
     # 6) Exit Game Mode
     helper.exit_game_mode(Tests.exit_game_mode)

+ 2 - 2
AutomatedTesting/Levels/Physics/C18243584_Joints_HingeSoftLimitsConstrained/C18243584_Joints_HingeSoftLimitsConstrained.ly

@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:16f592487e8973abcf6b696aee6e430924c22daac0d6bb781c9e76153cd932f7
-size 8885
+oid sha256:352a64f523b3246000393309fa7f14955fe554e0b792a4177349f0f2db8a2b62
+size 5901

+ 2 - 2
AutomatedTesting/Levels/Physics/C18243586_Joints_HingeLeadFollowerCollide/C18243586_Joints_HingeLeadFollowerCollide.ly

@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:4d5c38cf9b97ae28c391916e6637aebf767d5ac009df61e730396fe8b116f3e5
-size 6913
+oid sha256:31bd1feb92c3bb8a5c5df4638927a9a80e879329965732c4c32f673c236a8b0a
+size 6021

+ 2 - 2
AutomatedTesting/Levels/Physics/C18243589_Joints_BallSoftLimitsConstrained/C18243589_Joints_BallSoftLimitsConstrained.ly

@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:a6f26f1c1c037fa0b848ac9b3d9a76e476b4853d770f1a77c01714286733b567
-size 6921
+oid sha256:063779c1e80ce22319cb82ff0d7635d3dcbb043b789c3830cc405ffa36d3c0ef
+size 5876

+ 2 - 2
AutomatedTesting/Levels/Physics/C18243591_Joints_BallLeadFollowerCollide/C18243591_Joints_BallLeadFollowerCollide.ly

@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:4ffd3c4ee04fa8a414995c39c7ca79246e3d9b0ceee1ad1b85d61a5298f71495
-size 6940
+oid sha256:5bd3a952841aa924a4869c74fad7ed397667a87948270f0ce35f314e6cf2e14a
+size 5927

+ 36 - 0
Code/Framework/AzFramework/AzFramework/Physics/Common/PhysicsJoint.cpp

@@ -0,0 +1,36 @@
+/*
+* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
+* its licensors.
+*
+* For complete copyright and license terms please see the LICENSE at the root of this
+* distribution (the "License"). All use of this software is governed by the License,
+* or, if provided, by the license below or the license accompanying this file. Do not
+* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+*
+*/
+
+#include <AzFramework/Physics/Common/PhysicsJoint.h>
+
+#include <AzCore/Memory/SystemAllocator.h>
+#include <AzCore/Serialization/SerializeContext.h>
+
+#include <AzFramework/Physics/PhysicsScene.h>
+#include <AzFramework/Physics/PhysicsSystem.h>
+
+namespace AzPhysics
+{
+    AZ_CLASS_ALLOCATOR_IMPL(Joint, AZ::SystemAllocator, 0);
+
+    void Joint::Reflect(AZ::ReflectContext* context)
+    {
+        if (auto* serializeContext = azdynamic_cast<AZ::SerializeContext*>(context))
+        {
+            serializeContext->Class<AzPhysics::Joint>()
+                ->Version(1)
+                ->Field("SceneOwner", &Joint::m_sceneOwner)
+                ->Field("JointHandle", &Joint::m_jointHandle)
+                ;
+        }
+    }
+}

+ 143 - 0
Code/Framework/AzFramework/AzFramework/Physics/Common/PhysicsJoint.h

@@ -0,0 +1,143 @@
+/*
+* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
+* its licensors.
+*
+* For complete copyright and license terms please see the LICENSE at the root of this
+* distribution (the "License"). All use of this software is governed by the License,
+* or, if provided, by the license below or the license accompanying this file. Do not
+* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+*
+*/
+#pragma once
+
+#include <AzCore/Math/Aabb.h>
+#include <AzCore/Math/Crc.h>
+#include <AzCore/Math/Quaternion.h>
+#include <AzCore/Memory/Memory.h>
+#include <AzCore/RTTI/RTTI.h>
+#include <AzCore/std/containers/variant.h>
+#include <AzCore/std/containers/vector.h>
+#include <AzFramework/Physics/Common/PhysicsSimulatedBodyEvents.h>
+#include <AzFramework/Physics/Common/PhysicsSceneQueries.h>
+#include <AzFramework/Physics/Common/PhysicsTypes.h>
+
+namespace AZ
+{
+    class ReflectContext;
+}
+
+namespace AzPhysics
+{
+    struct JointConfiguration;
+
+    //! Base class for all Joints in Physics.
+    struct Joint
+    {
+        AZ_CLASS_ALLOCATOR_DECL;
+        AZ_RTTI(AzPhysics::Joint, "{1EEC9382-3434-4866-9B18-E93F151A6F59}");
+        static void Reflect(AZ::ReflectContext* context);
+
+        virtual ~Joint() = default;
+
+        //! The current Scene the joint is contained.
+        SceneHandle m_sceneOwner = AzPhysics::InvalidSceneHandle;
+
+        //! The handle to this joint.
+        JointHandle m_jointHandle = AzPhysics::InvalidJointHandle;
+
+        //! Helper functions for setting user data.
+        //! @param userData Can be a pointer to any type as internally will be cast to a void*. Object lifetime not managed by the Joint.
+        template<typename T>
+        void SetUserData(T* userData)
+        {
+            m_customUserData = static_cast<void*>(userData);
+        }
+        //! Helper functions for getting the set user data.
+        //! @return Will return a void* to the user data set.
+        void* GetUserData()
+        {
+            return m_customUserData;
+        }
+
+        virtual AZ::Crc32 GetNativeType() const = 0;
+        virtual void* GetNativePointer() const = 0;
+
+        virtual AzPhysics::SimulatedBodyHandle GetParentBodyHandle() const = 0;
+        virtual AzPhysics::SimulatedBodyHandle GetChildBodyHandle() const = 0;
+
+        virtual void SetParentBody(AzPhysics::SimulatedBodyHandle parentBody) = 0;
+        virtual void SetChildBody(AzPhysics::SimulatedBodyHandle childBody) = 0;
+
+        virtual void GenerateJointLimitVisualizationData(
+            [[ maybe_unused ]] float scale,
+            [[ maybe_unused ]] AZ::u32 angularSubdivisions,
+            [[ maybe_unused ]] AZ::u32 radialSubdivisions,
+            [[ maybe_unused ]] AZStd::vector<AZ::Vector3>& vertexBufferOut,
+            [[ maybe_unused ]] AZStd::vector<AZ::u32>& indexBufferOut,
+            [[ maybe_unused ]] AZStd::vector<AZ::Vector3>& lineBufferOut,
+            [[ maybe_unused ]] AZStd::vector<bool>& lineValidityBufferOut) { }
+
+    private:
+        void* m_customUserData = nullptr;
+    };
+
+    //! Alias for a list of non owning weak pointers to Joint objects.
+    using JointList = AZStd::vector<Joint*>;
+
+    //! Interface to access Joint utilities and helper functions
+    class JointHelpersInterface
+    {
+    public:
+        AZ_RTTI(AzPhysics::JointHelpersInterface, "{A511C64D-C8A5-4E8F-9C69-8DC5EFAD0C4C}");
+
+        JointHelpersInterface() = default;
+        virtual ~JointHelpersInterface() = default;
+        AZ_DISABLE_COPY_MOVE(JointHelpersInterface);
+
+        //! Returns a list of supported Joint types
+        virtual const AZStd::vector<AZ::TypeId> GetSupportedJointTypeIds() const = 0;
+
+        //! Returns a TypeID if the request joint type is supported.
+        //! If the Physics backend supports this joint type JointHelpersInterface::GetSupportedJointTypeId will return a AZ::TypeId.
+        virtual AZStd::optional<const AZ::TypeId> GetSupportedJointTypeId(JointType typeEnum) const = 0;
+
+        //! Computes parameters such as joint limit local rotations to give the desired initial joint limit orientation.
+        //! @param jointLimitTypeId The type ID used to identify the particular kind of joint limit configuration to be created.
+        //! @param parentWorldRotation The rotation in world space of the parent world body associated with the joint.
+        //! @param childWorldRotation The rotation in world space of the child world body associated with the joint.
+        //! @param axis Axis used to define the centre for limiting angular degrees of freedom.
+        //! @param exampleLocalRotations A vector (which may be empty) containing example valid rotations in the local space
+        //! of the child world body relative to the parent world body, which may optionally be used to help estimate the extents
+        //! of the joint limit.
+        virtual AZStd::unique_ptr<JointConfiguration> ComputeInitialJointLimitConfiguration(
+            const AZ::TypeId& jointLimitTypeId,
+            const AZ::Quaternion& parentWorldRotation,
+            const AZ::Quaternion& childWorldRotation,
+            const AZ::Vector3& axis,
+            const AZStd::vector<AZ::Quaternion>& exampleLocalRotations) = 0;
+
+        /// Generates joint limit visualization data in appropriate format to pass to DebugDisplayRequests draw functions.
+        /// @param configuration The joint configuration to generate visualization data for.
+        /// @param parentRotation The rotation of the joint's parent body (in the same frame as childRotation).
+        /// @param childRotation The rotation of the joint's child body (in the same frame as parentRotation).
+        /// @param scale Scale factor for the output display data.
+        /// @param angularSubdivisions Level of detail in the angular direction (may be clamped in the implementation).
+        /// @param radialSubdivisions Level of detail in the radial direction (may be clamped in the implementation).
+        /// @param[out] vertexBufferOut Used with indexBufferOut to define triangles to be displayed.
+        /// @param[out] indexBufferOut Used with vertexBufferOut to define triangles to be displayed.
+        /// @param[out] lineBufferOut Used to define lines to be displayed.
+        /// @param[out] lineValidityBufferOut Whether each line in the line buffer is part of a valid or violated limit.
+        virtual void GenerateJointLimitVisualizationData(
+            const JointConfiguration& configuration,
+            const AZ::Quaternion& parentRotation,
+            const AZ::Quaternion& childRotation,
+            float scale,
+            AZ::u32 angularSubdivisions,
+            AZ::u32 radialSubdivisions,
+            AZStd::vector<AZ::Vector3>& vertexBufferOut,
+            AZStd::vector<AZ::u32>& indexBufferOut,
+            AZStd::vector<AZ::Vector3>& lineBufferOut,
+            AZStd::vector<bool>& lineValidityBufferOut) = 0;
+    };
+}

+ 18 - 1
Code/Framework/AzFramework/AzFramework/Physics/Common/PhysicsTypes.h

@@ -51,8 +51,10 @@ namespace AzPhysics
 
     using SceneIndex = AZ::s8;
     using SimulatedBodyIndex = AZ::s32;
+    using JointIndex = AZ::s32;
     static_assert(std::is_signed<SceneIndex>::value
-        && std::is_signed<SimulatedBodyIndex>::value, "SceneIndex and SimulatedBodyIndex must be signed integers.");
+        && std::is_signed<SimulatedBodyIndex>::value
+        && std::is_signed<JointIndex>::value, "SceneIndex, SimulatedBodyIndex and JointIndex must be signed integers.");
     
 
     //! A handle to a Scene within the physics simulation.
@@ -69,12 +71,27 @@ namespace AzPhysics
     static constexpr SimulatedBodyHandle InvalidSimulatedBodyHandle = { AZ::Crc32(), -1 };
     using SimulatedBodyHandleList = AZStd::vector<SimulatedBodyHandle>;
 
+    //! A handle to a Joint within a physics scene.
+    //! A JointHandle is a tuple of a Crc of the scene's name and the index in the Joint list.
+    using JointHandle = AZStd::tuple<AZ::Crc32, JointIndex>;
+    static constexpr JointHandle InvalidJointHandle = { AZ::Crc32(), -1 };
+
     //! Helper used for pairing the ShapeConfiguration and ColliderConfiguration together which is used when creating a Simulated Body.
     using ShapeColliderPair = AZStd::pair<
         AZStd::shared_ptr<Physics::ColliderConfiguration>,
         AZStd::shared_ptr<Physics::ShapeConfiguration>>;
     using ShapeColliderPairList = AZStd::vector<ShapeColliderPair>;
 
+    //! Joint types are used to request for AZ::TypeId with the JointHelpersInterface::GetSupportedJointTypeId.
+    //! If the Physics backend supports this joint type JointHelpersInterface::GetSupportedJointTypeId will return a AZ::TypeId.
+    enum class JointType
+    {
+        D6Joint,
+        FixedJoint,
+        BallJoint,
+        HingeJoint
+    };
+
     //! Flags used to specifying which properties of a body to compute.
     enum class MassComputeFlags : AZ::u8
     {

+ 37 - 0
Code/Framework/AzFramework/AzFramework/Physics/Configuration/JointConfiguration.cpp

@@ -0,0 +1,37 @@
+/*
+* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
+* its licensors.
+*
+* For complete copyright and license terms please see the LICENSE at the root of this
+* distribution (the "License"). All use of this software is governed by the License,
+* or, if provided, by the license below or the license accompanying this file. Do not
+* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+*
+*/
+
+#include <AzFramework/Physics/Configuration/JointConfiguration.h>
+
+#include <AzCore/Memory/SystemAllocator.h>
+#include <AzCore/Serialization/SerializeContext.h>
+
+namespace AzPhysics
+{
+    AZ_CLASS_ALLOCATOR_IMPL(JointConfiguration, AZ::SystemAllocator, 0);
+
+    void JointConfiguration::Reflect(AZ::ReflectContext* context)
+    {
+        if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
+        {
+            serializeContext->Class<JointConfiguration>()
+                ->Version(1)
+                ->Field("Name", &JointConfiguration::m_debugName)
+                ->Field("ParentLocalRotation", &JointConfiguration::m_parentLocalRotation)
+                ->Field("ParentLocalPosition", &JointConfiguration::m_parentLocalPosition)
+                ->Field("ChildLocalRotation", &JointConfiguration::m_childLocalRotation)
+                ->Field("ChildLocalPosition", &JointConfiguration::m_childLocalPosition)
+                ->Field("StartSimulationEnabled", &JointConfiguration::m_startSimulationEnabled)
+                ;
+        }
+    }
+}

+ 51 - 0
Code/Framework/AzFramework/AzFramework/Physics/Configuration/JointConfiguration.h

@@ -0,0 +1,51 @@
+/*
+* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
+* its licensors.
+*
+* For complete copyright and license terms please see the LICENSE at the root of this
+* distribution (the "License"). All use of this software is governed by the License,
+* or, if provided, by the license below or the license accompanying this file. Do not
+* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+*
+*/
+#pragma once
+
+#include <AzCore/Component/EntityId.h>
+#include <AzCore/Math/Quaternion.h>
+#include <AzCore/Math/Vector3.h>
+#include <AzCore/Memory/Memory.h>
+#include <AzCore/RTTI/RTTI.h>
+#include <AzCore/std/containers/vector.h>
+
+namespace AZ
+{
+    class ReflectContext;
+}
+
+namespace AzPhysics
+{
+    //! Base Class of all Physics Joints that will be simulated.
+    struct JointConfiguration
+    {
+        AZ_CLASS_ALLOCATOR_DECL;
+        AZ_RTTI(AzPhysics::JointConfiguration, "{DF91D39A-4901-48C4-9159-93FD2ACA5252}");
+        static void Reflect(AZ::ReflectContext* context);
+
+        JointConfiguration() = default;
+        virtual ~JointConfiguration() = default;
+
+        // Entity/object association.
+        void* m_customUserData = nullptr;
+
+        // Basic initial settings.
+        AZ::Quaternion m_parentLocalRotation = AZ::Quaternion::CreateIdentity(); ///< Parent joint frame relative to parent body.
+        AZ::Vector3 m_parentLocalPosition = AZ::Vector3::CreateZero(); ///< Joint position relative to parent body.
+        AZ::Quaternion m_childLocalRotation = AZ::Quaternion::CreateIdentity(); ///< Child joint frame relative to child body.
+        AZ::Vector3 m_childLocalPosition = AZ::Vector3::CreateZero(); ///< Joint position relative to child body.
+        bool m_startSimulationEnabled = true;
+        
+        // For debugging/tracking purposes only.
+        AZStd::string m_debugName;
+    };
+}

+ 0 - 56
Code/Framework/AzFramework/AzFramework/Physics/Joint.cpp

@@ -1,56 +0,0 @@
-/*
-* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
-* its licensors.
-*
-* For complete copyright and license terms please see the LICENSE at the root of this
-* distribution (the "License"). All use of this software is governed by the License,
-* or, if provided, by the license below or the license accompanying this file. Do not
-* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-*
-*/
-
-#include <AzFramework/Physics/Joint.h>
-#include <AzCore/Serialization/EditContext.h>
-#include <AzCore/Serialization/SerializeContext.h>
-#include <AzFramework/Physics/Common/PhysicsSimulatedBody.h>
-
-namespace Physics
-{
-    const char* JointLimitConfiguration::GetTypeName()
-    {
-        return "Base Joint";
-    }
-
-    void JointLimitConfiguration::Reflect(AZ::ReflectContext* context)
-    {
-        if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
-        {
-            serializeContext->Class<JointLimitConfiguration>()
-                ->Version(1)
-                ->Field("ParentLocalRotation", &JointLimitConfiguration::m_parentLocalRotation)
-                ->Field("ParentLocalPosition", &JointLimitConfiguration::m_parentLocalPosition)
-                ->Field("ChildLocalRotation", &JointLimitConfiguration::m_childLocalRotation)
-                ->Field("ChildLocalPosition", &JointLimitConfiguration::m_childLocalPosition)
-                ;
-
-            AZ::EditContext* editContext = serializeContext->GetEditContext();
-            if (editContext)
-            {
-                editContext->Class<JointLimitConfiguration>(
-                    "Joint Configuration", "")
-                    ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
-                    ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
-                    ->DataElement(AZ::Edit::UIHandlers::Default, &JointLimitConfiguration::m_parentLocalRotation,
-                        "Parent local rotation", "The rotation of the parent joint frame relative to the parent body")
-                    ->DataElement(AZ::Edit::UIHandlers::Default, &JointLimitConfiguration::m_parentLocalPosition,
-                        "Parent local position", "The position of the joint in the frame of the parent body")
-                    ->DataElement(AZ::Edit::UIHandlers::Default, &JointLimitConfiguration::m_childLocalRotation,
-                        "Child local rotation", "The rotation of the child joint frame relative to the child body")
-                    ->DataElement(AZ::Edit::UIHandlers::Default, &JointLimitConfiguration::m_childLocalPosition,
-                        "Child local position", "The position of the joint in the frame of the child body")
-                    ;
-            }
-        }
-    }
-} // namespace Physics

+ 0 - 75
Code/Framework/AzFramework/AzFramework/Physics/Joint.h

@@ -1,75 +0,0 @@
-/*
-* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
-* its licensors.
-*
-* For complete copyright and license terms please see the LICENSE at the root of this
-* distribution (the "License"). All use of this software is governed by the License,
-* or, if provided, by the license below or the license accompanying this file. Do not
-* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-*
-*/
-
-#pragma once
-
-#include <AzCore/Math/Transform.h>
-#include <AzCore/Serialization/SerializeContext.h>
-
-namespace AzPhysics
-{
-    struct SimulatedBody;
-}
-
-namespace Physics
-{
-    class JointLimitConfiguration
-    {
-    public:
-        AZ_CLASS_ALLOCATOR(JointLimitConfiguration, AZ::SystemAllocator, 0);
-        AZ_RTTI(JointLimitConfiguration, "{C9B70C4D-22D7-45AB-9B0A-30A4ED5E42DB}");
-        static void Reflect(AZ::ReflectContext* context);
-
-        JointLimitConfiguration() = default;
-        JointLimitConfiguration(const JointLimitConfiguration&) = default;
-        virtual ~JointLimitConfiguration() = default;
-
-        virtual const char* GetTypeName();
-
-        AZ::Quaternion m_parentLocalRotation = AZ::Quaternion::CreateIdentity(); ///< Parent joint frame relative to parent body.
-        AZ::Vector3 m_parentLocalPosition = AZ::Vector3::CreateZero(); ///< Joint position relative to parent body.
-        AZ::Quaternion m_childLocalRotation = AZ::Quaternion::CreateIdentity(); ///< Child joint frame relative to child body.
-        AZ::Vector3 m_childLocalPosition = AZ::Vector3::CreateZero(); ///< Joint position relative to child body.
-    };
-
-    class Joint
-    {
-    public:
-        AZ_CLASS_ALLOCATOR(Joint, AZ::SystemAllocator, 0);
-        AZ_RTTI(Joint, "{405F517C-E986-4ACB-9606-D5D080DDE987}");
-
-        virtual AzPhysics::SimulatedBody* GetParentBody() const = 0;
-        virtual AzPhysics::SimulatedBody* GetChildBody() const = 0;
-        virtual void SetParentBody(AzPhysics::SimulatedBody* parentBody) = 0;
-        virtual void SetChildBody(AzPhysics::SimulatedBody* childBody) = 0;
-        virtual const AZStd::string& GetName() const = 0;
-        virtual void SetName(const AZStd::string& name) = 0;
-        virtual const AZ::Crc32 GetNativeType() const = 0;
-        virtual void* GetNativePointer() = 0;
-        /// Generates joint limit visualization data in appropriate format to pass to DebugDisplayRequests draw functions.
-        /// @param scale Scale factor for the output display data.
-        /// @param angularSubdivisions Level of detail in the angular direction (may be clamped in the implementation).
-        /// @param radialSubdivisions Level of detail in the radial direction (may be clamped in the implementation).
-        /// @param[out] vertexBufferOut Used with indexBufferOut to define triangles to be displayed.
-        /// @param[out] indexBufferOut Used with vertexBufferOut to define triangles to be displayed.
-        /// @param[out] lineBufferOut Used to define lines to be displayed.
-        /// @param[out] lineValidityBufferOut Whether each line in the line buffer is part of a valid or violated limit.
-        virtual void GenerateJointLimitVisualizationData(
-            float scale,
-            AZ::u32 angularSubdivisions,
-            AZ::u32 radialSubdivisions,
-            AZStd::vector<AZ::Vector3>& vertexBufferOut,
-            AZStd::vector<AZ::u32>& indexBufferOut,
-            AZStd::vector<AZ::Vector3>& lineBufferOut,
-            AZStd::vector<bool>& lineValidityBufferOut) = 0;
-    };
-} // namespace Physics

+ 39 - 0
Code/Framework/AzFramework/AzFramework/Physics/PhysicsScene.h

@@ -16,6 +16,8 @@
 #include <AzFramework/Physics/Common/PhysicsSimulatedBody.h>
 #include <AzFramework/Physics/Common/PhysicsSceneQueries.h>
 #include <AzFramework/Physics/Common/PhysicsTypes.h>
+#include <AzFramework/Physics/Common/PhysicsJoint.h>
+#include <AzFramework/Physics/Configuration/JointConfiguration.h>
 #include <AzFramework/Physics/Configuration/SimulatedBodyConfiguration.h>
 
 namespace AzPhysics
@@ -103,6 +105,26 @@ namespace AzPhysics
         virtual void EnableSimulationOfBody(SceneHandle sceneHandle, SimulatedBodyHandle bodyHandle) = 0;
         virtual void DisableSimulationOfBody(SceneHandle sceneHandle, SimulatedBodyHandle bodyHandle) = 0;
 
+        //! Add a joint to the Scene.
+        //! @param sceneHandle A handle to the scene to add / remove the joint.
+        //! @param jointConfig The config of the joint.
+        //! @param parentBody The parent body of the joint.
+        //! @param childBody The child body of the joint
+        //! @return Returns a handle to the created joint. Will return AzPhyiscs::InvalidJointHandle if it fails.
+        virtual JointHandle AddJoint(SceneHandle sceneHandle, const JointConfiguration* jointConfig, 
+            SimulatedBodyHandle parentBody, SimulatedBodyHandle childBody) = 0;
+
+        //! Get the Raw pointer to the requested joint.
+        //! @param sceneHandle A handle to the scene to get the simulated bodies from.
+        //! @param jointHandle A handle to the joint to retrieve the raw pointer.
+        //! @return A raw pointer to the Joint body. If the either handle is invalid this will return null.
+        virtual Joint* GetJointFromHandle(SceneHandle sceneHandle, JointHandle jointHandle) = 0;
+
+        //! Remove a joint from the Scene.
+        //! @param sceneHandle A handle to the scene to add / remove the joint.
+        //! @param jointHandle A handle to the joint being removed.
+        virtual void RemoveJoint(SceneHandle sceneHandle, JointHandle jointHandle) = 0;
+
         //! Make a blocking query into the scene.
         //! @param sceneHandle A handle to the scene to make the scene query with.
         //! @param request The request to make. Should be one of RayCastRequest || ShapeCastRequest || OverlapRequest
@@ -299,6 +321,23 @@ namespace AzPhysics
         virtual void EnableSimulationOfBody(SimulatedBodyHandle bodyHandle) = 0;
         virtual void DisableSimulationOfBody(SimulatedBodyHandle bodyHandle) = 0;
 
+        //! Add a joint to the Scene.
+        //! @param jointConfig The config of the joint.
+        //! @param parentBody The parent body of the joint.
+        //! @param childBody The child body of the joint
+        //! @return Returns a handle to the created joint. Will return AzPhyiscs::InvalidJointHandle if it fails.
+        virtual JointHandle AddJoint(const JointConfiguration* jointConfig, 
+            SimulatedBodyHandle parentBody, SimulatedBodyHandle childBody) = 0;
+
+        //! Get the Raw pointer to the requested joint.
+        //! @param jointHandle A handle to the joint to retrieve the raw pointer.
+        //! @return A raw pointer to the Joint body. If the either handle is invalid this will return null.
+        virtual Joint* GetJointFromHandle(JointHandle jointHandle) = 0;
+
+        //! Remove a joint from the Scene.
+        //! @param jointHandle A handle to the joint being removed.
+        virtual void RemoveJoint(JointHandle jointHandle) = 0;
+
         //! Make a blocking query into the scene.
         //! @param request The request to make. Should be one of RayCastRequest || ShapeCastRequest || OverlapRequest
         //! @return Returns a structure that contains a list of Hits. Depending on flags set in the request, this may only contain 1 result.

+ 2 - 2
Code/Framework/AzFramework/AzFramework/Physics/Ragdoll.cpp

@@ -36,8 +36,8 @@ namespace Physics
         if (serializeContext)
         {
             serializeContext->Class<RagdollNodeConfiguration, RigidBodyConfiguration>()
-                ->Version(4, &ClassConverters::RagdollNodeConfigConverter)
-                ->Field("JointLimit", &RagdollNodeConfiguration::m_jointLimit)
+                ->Version(5, &ClassConverters::RagdollNodeConfigConverter)
+                ->Field("JointConfig", &RagdollNodeConfiguration::m_jointConfig)
             ;
 
             AZ::EditContext* editContext = serializeContext->GetEditContext();

+ 4 - 3
Code/Framework/AzFramework/AzFramework/Physics/Ragdoll.h

@@ -17,10 +17,11 @@
 #include <AzFramework/Physics/Shape.h>
 #include <AzFramework/Physics/SimulatedBodies/RigidBody.h>
 #include <AzFramework/Physics/RagdollPhysicsBus.h>
-#include <AzFramework/Physics/Joint.h>
 #include <AzFramework/Physics/Common/PhysicsSimulatedBody.h>
+#include <AzFramework/Physics/Common/PhysicsJoint.h>
 #include <AzFramework/Physics/Configuration/RigidBodyConfiguration.h>
 #include <AzFramework/Physics/Configuration/SimulatedBodyConfiguration.h>
+#include <AzFramework/Physics/Configuration/JointConfiguration.h>
 
 namespace Physics
 {
@@ -37,7 +38,7 @@ namespace Physics
         RagdollNodeConfiguration();
         RagdollNodeConfiguration(const RagdollNodeConfiguration& settings) = default;
 
-        AZStd::shared_ptr<JointLimitConfiguration> m_jointLimit;
+        AZStd::shared_ptr<AzPhysics::JointConfiguration> m_jointConfig;
     };
 
     class RagdollConfiguration
@@ -73,7 +74,7 @@ namespace Physics
         virtual AzPhysics::RigidBody& GetRigidBody() = 0;
         virtual ~RagdollNode() = default;
 
-        virtual const AZStd::shared_ptr<Physics::Joint>& GetJoint() const = 0;
+        virtual AzPhysics::Joint* GetJoint() = 0;
         virtual bool IsSimulating() const = 0;
     };
 

+ 0 - 53
Code/Framework/AzFramework/AzFramework/Physics/SystemBus.h

@@ -26,23 +26,15 @@ namespace AZ
 namespace AzPhysics
 {
     struct SimulatedBody;
-    struct RigidBodyConfiguration;
-    struct RigidBody;
 }
 
 namespace Physics
 {
-    class WorldBody;
     class Shape;
     class Material;
-    class MaterialSelection;
     class MaterialConfiguration;
     class ColliderConfiguration;
     class ShapeConfiguration;
-    class JointLimitConfiguration;
-    class Joint;
-    class CharacterConfiguration;
-    class Character;
 
     /// Represents a debug vertex (position & color).
     struct DebugDrawVertex
@@ -148,51 +140,6 @@ namespace Physics
         /// @param nativeMeshObject Pointer to the mesh object.
         virtual void ReleaseNativeMeshObject(void* nativeMeshObject) = 0;
 
-        //////////////////////////////////////////////////////////////////////////
-        //// Joints
-
-        virtual AZStd::vector<AZ::TypeId> GetSupportedJointTypes() = 0;
-        virtual AZStd::shared_ptr<JointLimitConfiguration> CreateJointLimitConfiguration(AZ::TypeId jointType) = 0;
-        virtual AZStd::shared_ptr<Joint> CreateJoint(const AZStd::shared_ptr<JointLimitConfiguration>& configuration,
-            AzPhysics::SimulatedBody* parentBody, AzPhysics::SimulatedBody* childBody) = 0;
-        /// Generates joint limit visualization data in appropriate format to pass to DebugDisplayRequests draw functions.
-        /// @param configuration The joint configuration to generate visualization data for.
-        /// @param parentRotation The rotation of the joint's parent body (in the same frame as childRotation).
-        /// @param childRotation The rotation of the joint's child body (in the same frame as parentRotation).
-        /// @param scale Scale factor for the output display data.
-        /// @param angularSubdivisions Level of detail in the angular direction (may be clamped in the implementation).
-        /// @param radialSubdivisions Level of detail in the radial direction (may be clamped in the implementation).
-        /// @param[out] vertexBufferOut Used with indexBufferOut to define triangles to be displayed.
-        /// @param[out] indexBufferOut Used with vertexBufferOut to define triangles to be displayed.
-        /// @param[out] lineBufferOut Used to define lines to be displayed.
-        /// @param[out] lineValidityBufferOut Whether each line in the line buffer is part of a valid or violated limit.
-        virtual void GenerateJointLimitVisualizationData(
-            const JointLimitConfiguration& configuration,
-            const AZ::Quaternion& parentRotation,
-            const AZ::Quaternion& childRotation,
-            float scale,
-            AZ::u32 angularSubdivisions,
-            AZ::u32 radialSubdivisions,
-            AZStd::vector<AZ::Vector3>& vertexBufferOut,
-            AZStd::vector<AZ::u32>& indexBufferOut,
-            AZStd::vector<AZ::Vector3>& lineBufferOut,
-            AZStd::vector<bool>& lineValidityBufferOut) = 0;
-
-        /// Computes parameters such as joint limit local rotations to give the desired initial joint limit orientation.
-        /// @param jointLimitTypeId The type ID used to identify the particular kind of joint limit configuration to be created.
-        /// @param parentWorldRotation The rotation in world space of the parent world body associated with the joint.
-        /// @param childWorldRotation The rotation in world space of the child world body associated with the joint.
-        /// @param axis Axis used to define the centre for limiting angular degrees of freedom.
-        /// @param exampleLocalRotations A vector (which may be empty) containing example valid rotations in the local space
-        /// of the child world body relative to the parent world body, which may optionally be used to help estimate the extents
-        /// of the joint limit.
-        virtual AZStd::unique_ptr<JointLimitConfiguration> ComputeInitialJointLimitConfiguration(
-            const AZ::TypeId& jointLimitTypeId,
-            const AZ::Quaternion& parentWorldRotation,
-            const AZ::Quaternion& childWorldRotation,
-            const AZ::Vector3& axis,
-            const AZStd::vector<AZ::Quaternion>& exampleLocalRotations) = 0;
-
         //////////////////////////////////////////////////////////////////////////
         //// Cooking
 

+ 3 - 1
Code/Framework/AzFramework/AzFramework/Physics/Utils.cpp

@@ -34,6 +34,7 @@
 #include <AzFramework/Physics/Configuration/SceneConfiguration.h>
 #include <AzFramework/Physics/Configuration/SimulatedBodyConfiguration.h>
 #include <AzFramework/Physics/SimulatedBodies/RigidBody.h>
+#include <AzFramework/Physics/Common/PhysicsJoint.h>
 
 namespace Physics
 {
@@ -121,9 +122,9 @@ namespace Physics
             DefaultMaterialConfiguration::Reflect(context);
             MaterialLibraryAsset::Reflect(context);
             MaterialInfoReflectionWrapper::Reflect(context);
-            JointLimitConfiguration::Reflect(context);
             AzPhysics::SimulatedBodyConfiguration::Reflect(context);
             AzPhysics::RigidBodyConfiguration::Reflect(context);
+            AzPhysics::JointConfiguration::Reflect(context);
             RagdollNodeConfiguration::Reflect(context);
             RagdollConfiguration::Reflect(context);
             CharacterColliderNodeConfiguration::Reflect(context);
@@ -131,6 +132,7 @@ namespace Physics
             AnimationConfiguration::Reflect(context);
             CharacterConfiguration::Reflect(context);
             AzPhysics::SimulatedBody::Reflect(context);
+            AzPhysics::Joint::Reflect(context);
             ReflectSimulatedBodyComponentRequestsBus(context);
             CollisionFilteringRequests::Reflect(context);
             AzPhysics::SceneQuery::ReflectSceneQueryObjects(context);

+ 4 - 2
Code/Framework/AzFramework/AzFramework/azframework_files.cmake

@@ -204,6 +204,8 @@ set(FILES
     Physics/Collision/CollisionLayers.cpp
     Physics/Collision/CollisionGroups.h
     Physics/Collision/CollisionGroups.cpp
+    Physics/Common/PhysicsJoint.h
+    Physics/Common/PhysicsJoint.cpp
     Physics/Common/PhysicsSceneQueries.h
     Physics/Common/PhysicsSceneQueries.cpp
     Physics/Common/PhysicsEvents.h
@@ -215,6 +217,8 @@ set(FILES
     Physics/Common/PhysicsSimulatedBodyEvents.cpp
     Physics/Common/PhysicsTypes.h
     Physics/Components/SimulatedBodyComponentBus.h
+    Physics/Configuration/JointConfiguration.h
+    Physics/Configuration/JointConfiguration.cpp
     Physics/Configuration/CollisionConfiguration.h
     Physics/Configuration/CollisionConfiguration.cpp
     Physics/Configuration/RigidBodyConfiguration.h
@@ -259,8 +263,6 @@ set(FILES
     Physics/Ragdoll.h
     Physics/Utils.h
     Physics/Utils.cpp
-    Physics/Joint.h
-    Physics/Joint.cpp
     Physics/ClassConverters.cpp
     Physics/ClassConverters.h
     Physics/MaterialBus.h

+ 0 - 18
Gems/Blast/Code/Tests/Mocks/BlastMocks.h

@@ -15,7 +15,6 @@
 #include <AzCore/Component/TransformBus.h>
 #include <AzFramework/Entity/GameEntityContextBus.h>
 #include <AzFramework/Physics/Common/PhysicsSceneQueries.h>
-#include <AzFramework/Physics/Joint.h>
 #include <AzFramework/Physics/RigidBodyBus.h>
 #include <AzFramework/Physics/Shape.h>
 #include <AzFramework/Physics/SystemBus.h>
@@ -221,23 +220,6 @@ namespace Blast
             AZStd::vector<AZStd::shared_ptr<Physics::Material>>(const Physics::MaterialSelection&));
         MOCK_METHOD2(
             UpdateMaterialSelection, bool(const Physics::ShapeConfiguration&, Physics::ColliderConfiguration&));
-        MOCK_METHOD0(GetSupportedJointTypes, AZStd::vector<AZ::TypeId>());
-        MOCK_METHOD1(CreateJointLimitConfiguration, AZStd::shared_ptr<Physics::JointLimitConfiguration>(AZ::TypeId));
-        MOCK_METHOD3(
-            CreateJoint,
-            AZStd::shared_ptr<Physics::Joint>(
-                const AZStd::shared_ptr<Physics::JointLimitConfiguration>&, AzPhysics::SimulatedBody*, AzPhysics::SimulatedBody*));
-        MOCK_METHOD10(
-            GenerateJointLimitVisualizationData,
-            void(
-                const Physics::JointLimitConfiguration&, const AZ::Quaternion&, const AZ::Quaternion&, float, AZ::u32,
-                AZ::u32, AZStd::vector<AZ::Vector3>&, AZStd::vector<AZ::u32>&, AZStd::vector<AZ::Vector3>&,
-                AZStd::vector<bool>&));
-        MOCK_METHOD5(
-            ComputeInitialJointLimitConfiguration,
-            AZStd::unique_ptr<Physics::JointLimitConfiguration>(
-                const AZ::TypeId&, const AZ::Quaternion&, const AZ::Quaternion&, const AZ::Vector3&,
-                const AZStd::vector<AZ::Quaternion>&));
         MOCK_METHOD3(CookConvexMeshToFile, bool(const AZStd::string&, const AZ::Vector3*, AZ::u32));
         MOCK_METHOD3(CookConvexMeshToMemory, bool(const AZ::Vector3*, AZ::u32, AZStd::vector<AZ::u8>&));
         MOCK_METHOD5(

+ 18 - 15
Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/RagdollCommands.cpp

@@ -71,12 +71,7 @@ namespace EMotionFX
         newNodeConfig.m_debugName = jointName;
 
         // Create joint limit on default.
-        AZStd::vector<AZ::TypeId> supportedJointLimitTypes;
-        Physics::SystemRequestBus::BroadcastResult(supportedJointLimitTypes, &Physics::SystemRequests::GetSupportedJointTypes);
-        if (!supportedJointLimitTypes.empty())
-        {
-            newNodeConfig.m_jointLimit = CommandRagdollHelpers::CreateJointLimitByType(supportedJointLimitTypes[0], skeleton, joint);
-        }
+        newNodeConfig.m_jointConfig = CommandRagdollHelpers::CreateJointLimitByType(AzPhysics::JointType::D6Joint, skeleton, joint);
 
         if (index)
         {
@@ -91,8 +86,8 @@ namespace EMotionFX
         }
     }
 
-    AZStd::unique_ptr<Physics::JointLimitConfiguration> CommandRagdollHelpers::CreateJointLimitByType(
-        const AZ::TypeId& typeId, const Skeleton* skeleton, const Node* node)
+    AZStd::unique_ptr<AzPhysics::JointConfiguration> CommandRagdollHelpers::CreateJointLimitByType(
+        AzPhysics::JointType jointType, const Skeleton* skeleton, const Node* node)
     {
         const Pose* bindPose = skeleton->GetBindPose();
         const Transform& nodeBindTransform = bindPose->GetModelSpaceTransform(node->GetNodeIndex());
@@ -105,12 +100,20 @@ namespace EMotionFX
         AZ::Vector3 boneDirection = GetBoneDirection(skeleton, node);
         AZStd::vector<AZ::Quaternion> exampleRotationsLocal;
 
-        AZStd::unique_ptr<Physics::JointLimitConfiguration> jointLimitConfig =
-            AZ::Interface<Physics::System>::Get()->ComputeInitialJointLimitConfiguration(
-                typeId, parentBindRotationWorld, nodeBindRotationWorld, boneDirection, exampleRotationsLocal);
+        if (auto* jointHelpers = AZ::Interface<AzPhysics::JointHelpersInterface>::Get())
+        {
+            if (AZStd::optional<const AZ::TypeId> jointTypeId = jointHelpers->GetSupportedJointTypeId(jointType);
+                jointTypeId.has_value())
+            {
+                AZStd::unique_ptr<AzPhysics::JointConfiguration> jointLimitConfig = jointHelpers->ComputeInitialJointLimitConfiguration(
+                    *jointTypeId, parentBindRotationWorld, nodeBindRotationWorld, boneDirection, exampleRotationsLocal);
 
-        AZ_Assert(jointLimitConfig, "Could not create joint limit configuration with type '%s'.", typeId.ToString<AZStd::string>().c_str());
-        return jointLimitConfig;
+                AZ_Assert(jointLimitConfig, "Could not create joint limit configuration.");
+                return jointLimitConfig;
+            }
+        }
+        AZ_Assert(false, "Could not create joint limit configuration.");
+        return nullptr;
     }
 
     void CommandRagdollHelpers::AddJointsToRagdoll(AZ::u32 actorId, const AZStd::vector<AZStd::string>& jointNames,
@@ -532,7 +535,7 @@ namespace EMotionFX
         if (m_serializedJointLimits)
         {
             AZ::Outcome<AZStd::string> oldSerializedJointLimits = SerializeJointLimits(nodeConfig);
-            success |= MCore::ReflectionSerializer::DeserializeMembers(nodeConfig->m_jointLimit.get(), m_serializedJointLimits.value());
+            success |= MCore::ReflectionSerializer::DeserializeMembers(nodeConfig->m_jointConfig.get(), m_serializedJointLimits.value());
             if (success && oldSerializedJointLimits.IsSuccess())
             {
                 m_oldSerializedJointLimits = oldSerializedJointLimits.GetValue();
@@ -565,7 +568,7 @@ namespace EMotionFX
     AZ::Outcome<AZStd::string> CommandAdjustRagdollJoint::SerializeJointLimits(const Physics::RagdollNodeConfiguration* ragdollNodeConfig)
     {
         return MCore::ReflectionSerializer::SerializeMembersExcept(
-            ragdollNodeConfig->m_jointLimit.get(),
+            ragdollNodeConfig->m_jointConfig.get(),
             {"ParentLocalRotation", "ParentLocalPosition", "ChildLocalRotation", "ChildLocalPosition", }
         );
     }

+ 2 - 2
Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/RagdollCommands.h

@@ -45,8 +45,8 @@ namespace EMotionFX
             Physics::RagdollConfiguration& ragdollConfig, const AZStd::optional<size_t>& index,
             AZStd::string& outResult);
 
-        static AZStd::unique_ptr<Physics::JointLimitConfiguration> CreateJointLimitByType(const AZ::TypeId& typeId,
-            const Skeleton* skeleton, const Node* node);
+        static AZStd::unique_ptr<AzPhysics::JointConfiguration> CreateJointLimitByType(
+            AzPhysics::JointType jointType, const Skeleton* skeleton, const Node* node);
 
         static void AddJointsToRagdoll(AZ::u32 actorId, const AZStd::vector<AZStd::string>& jointNames,
             MCore::CommandGroup* commandGroup = nullptr, bool executeInsideCommand = false, bool addDefaultCollider = true);

+ 12 - 8
Gems/EMotionFX/Code/Source/Editor/Plugins/Ragdoll/RagdollJointLimitWidget.cpp

@@ -90,12 +90,15 @@ namespace EMotionFX
 
         if (serializeContext)
         {
-            AZStd::vector<AZ::TypeId> supportedJointLimitTypes;
-            Physics::SystemRequestBus::BroadcastResult(supportedJointLimitTypes, &Physics::SystemRequests::GetSupportedJointTypes);
-            for (const AZ::TypeId& jointLimitType : supportedJointLimitTypes)
+            //D6 joint is the only currently supported joint for ragdoll
+            if (auto* jointHelpers = AZ::Interface<AzPhysics::JointHelpersInterface>::Get())
             {
-                const char* jointLimitName = serializeContext->FindClassData(jointLimitType)->m_editData->m_name;
-                m_typeComboBox->addItem(jointLimitName, jointLimitType.ToString<AZStd::string>().c_str());
+                if (AZStd::optional<const AZ::TypeId> d6jointTypeId = jointHelpers->GetSupportedJointTypeId(AzPhysics::JointType::D6Joint);
+                    d6jointTypeId.has_value())
+                {
+                    const char* jointLimitName = serializeContext->FindClassData(*d6jointTypeId)->m_editData->m_name;
+                    m_typeComboBox->addItem(jointLimitName, (*d6jointTypeId).ToString<AZStd::string>().c_str());
+                }
             }
 
             // Reflected property editor for joint limit
@@ -134,7 +137,7 @@ namespace EMotionFX
         Physics::RagdollNodeConfiguration* ragdollNodeConfig = GetRagdollNodeConfig();
         if (ragdollNodeConfig)
         {
-            Physics::JointLimitConfiguration* jointLimitConfig = ragdollNodeConfig->m_jointLimit.get();
+            AzPhysics::JointConfiguration* jointLimitConfig = ragdollNodeConfig->m_jointConfig.get();
             if (jointLimitConfig)
             {
                 const AZ::TypeId& jointTypeId = jointLimitConfig->RTTI_GetType();
@@ -262,13 +265,14 @@ namespace EMotionFX
         {
             if (type.IsNull())
             {
-                ragdollNodeConfig->m_jointLimit = nullptr;
+                ragdollNodeConfig->m_jointConfig = nullptr;
             }
             else
             {
                 const Node* node = m_nodeIndex.data(SkeletonModel::ROLE_POINTER).value<Node*>();
                 const Skeleton* skeleton = m_nodeIndex.data(SkeletonModel::ROLE_ACTOR_POINTER).value<Actor*>()->GetSkeleton();
-                ragdollNodeConfig->m_jointLimit = CommandRagdollHelpers::CreateJointLimitByType(type, skeleton, node);
+                ragdollNodeConfig->m_jointConfig =
+                    CommandRagdollHelpers::CreateJointLimitByType(AzPhysics::JointType::D6Joint, skeleton, node);
             }
 
             Update();

+ 0 - 1
Gems/EMotionFX/Code/Source/Editor/Plugins/Ragdoll/RagdollJointLimitWidget.h

@@ -13,7 +13,6 @@
 #pragma once
 
 #if !defined(Q_MOC_RUN)
-#include <AzFramework/Physics/Joint.h>
 #include <AzFramework/Physics/Ragdoll.h>
 #include <AzQtComponents/Components/Widgets/Card.h>
 #include <QModelIndex>

+ 11 - 6
Gems/EMotionFX/Code/Source/Editor/Plugins/Ragdoll/RagdollNodeInspectorPlugin.cpp

@@ -514,7 +514,7 @@ namespace EMotionFX
 
             if (renderJointLimits && jointSelected)
             {
-                const AZStd::shared_ptr<Physics::JointLimitConfiguration>& jointLimitConfig = ragdollNode.m_jointLimit;
+                const AZStd::shared_ptr<AzPhysics::JointConfiguration>& jointLimitConfig = ragdollNode.m_jointConfig;
                 if (jointLimitConfig)
                 {
                     const Node* ragdollParentNode = physicsSetup->FindRagdollParentNode(joint);
@@ -528,7 +528,8 @@ namespace EMotionFX
         }
     }
 
-    void RagdollNodeInspectorPlugin::RenderJointLimit(const Physics::JointLimitConfiguration& configuration,
+    void RagdollNodeInspectorPlugin::RenderJointLimit(
+        const AzPhysics::JointConfiguration& configuration,
         const ActorInstance* actorInstance,
         const Node* node,
         const Node* parentNode,
@@ -549,9 +550,12 @@ namespace EMotionFX
         m_indexBuffer.clear();
         m_lineBuffer.clear();
         m_lineValidityBuffer.clear();
-        Physics::SystemRequestBus::Broadcast(&Physics::SystemRequests::GenerateJointLimitVisualizationData,
-            configuration, parentOrientation, childOrientation, s_scale, s_angularSubdivisions, s_radialSubdivisions,
-            m_vertexBuffer, m_indexBuffer, m_lineBuffer, m_lineValidityBuffer);
+        if(auto* jointHelpers = AZ::Interface<AzPhysics::JointHelpersInterface>::Get())
+        {
+            jointHelpers->GenerateJointLimitVisualizationData(
+                configuration, parentOrientation, childOrientation, s_scale, s_angularSubdivisions, s_radialSubdivisions, m_vertexBuffer,
+                m_indexBuffer, m_lineBuffer, m_lineValidityBuffer);
+        }           
 
         Transform jointModelSpaceTransform = currentPose->GetModelSpaceTransform(parentNodeIndex);
         jointModelSpaceTransform.mPosition = currentPose->GetModelSpaceTransform(nodeIndex).mPosition;
@@ -572,7 +576,8 @@ namespace EMotionFX
         }
     }
 
-    void RagdollNodeInspectorPlugin::RenderJointFrame(const Physics::JointLimitConfiguration& configuration,
+    void RagdollNodeInspectorPlugin::RenderJointFrame(
+        const AzPhysics::JointConfiguration& configuration,
         const ActorInstance* actorInstance,
         const Node* node,
         const Node* parentNode,

+ 4 - 2
Gems/EMotionFX/Code/Source/Editor/Plugins/Ragdoll/RagdollNodeInspectorPlugin.h

@@ -60,14 +60,16 @@ namespace EMotionFX
 
         void Render(EMStudio::RenderPlugin* renderPlugin, RenderInfo* renderInfo) override;
         void RenderRagdoll(ActorInstance* actorInstance, bool renderColliders, bool renderJointLimits, EMStudio::RenderPlugin* renderPlugin, RenderInfo* renderInfo);
-        void RenderJointLimit(const Physics::JointLimitConfiguration& jointConfiguration,
+        void RenderJointLimit(
+            const AzPhysics::JointConfiguration& jointConfiguration,
             const ActorInstance* actorInstance,
             const Node* node,
             const Node* parentNode,
             EMStudio::RenderPlugin* renderPlugin,
             EMStudio::EMStudioPlugin::RenderInfo* renderInfo,
             const MCore::RGBAColor& color);
-        void RenderJointFrame(const Physics::JointLimitConfiguration& jointConfiguration,
+        void RenderJointFrame(
+            const AzPhysics::JointConfiguration& jointConfiguration,
             const ActorInstance* actorInstance,
             const Node* node,
             const Node* parentNode,

+ 1 - 1
Gems/EMotionFX/Code/Tests/D6JointLimitConfiguration.cpp

@@ -19,7 +19,7 @@ namespace EMotionFX
     {
         if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
         {
-            serializeContext->Class<D6JointLimitConfiguration, Physics::JointLimitConfiguration>()
+            serializeContext->Class<D6JointLimitConfiguration, AzPhysics::JointConfiguration>()
                 ->Version(1)
                 ->Field("SwingLimitY", &D6JointLimitConfiguration::m_swingLimitY)
                 ->Field("SwingLimitZ", &D6JointLimitConfiguration::m_swingLimitZ)

+ 3 - 4
Gems/EMotionFX/Code/Tests/D6JointLimitConfiguration.h

@@ -12,23 +12,22 @@
 
 #pragma once
 
-#include <AzFramework/Physics/Joint.h>
+#include <AzFramework/Physics/Configuration/JointConfiguration.h>
 
 namespace EMotionFX
 {
     // Add so that RagdollNodeInspectorPlugin::PhysXCharactersGemAvailable() will return the correct value
     // We duplicated the D6JointLimitConfiguration because it doesn't exist in the test environment.
     class D6JointLimitConfiguration
-        : public Physics::JointLimitConfiguration
+        : public AzPhysics::JointConfiguration
     {
     public:
         AZ_CLASS_ALLOCATOR(D6JointLimitConfiguration, AZ::SystemAllocator, 0);
         // This uses the same uuid as the production D6JointLimitConfiguration.
         // The Ragdoll UI uses this UUID to see if physx is available.
-        AZ_RTTI(D6JointLimitConfiguration, "{90C5C23D-16C0-4F23-AD50-A190E402388E}", Physics::JointLimitConfiguration);
+        AZ_RTTI(D6JointLimitConfiguration, "{90C5C23D-16C0-4F23-AD50-A190E402388E}", AzPhysics::JointConfiguration);
 
         static void Reflect(AZ::ReflectContext* context);
-        const char* GetTypeName() override { return "D6 Joint"; }
 
         float m_swingLimitY = 45.0f; ///< Maximum angle in degrees from the Y axis of the joint frame.
         float m_swingLimitZ = 45.0f; ///< Maximum angle in degrees from the Z axis of the joint frame.

+ 36 - 5
Gems/EMotionFX/Code/Tests/Mocks/PhysicsSystem.h

@@ -35,11 +35,6 @@ namespace Physics
         MOCK_METHOD2(CreateShape, AZStd::shared_ptr<Physics::Shape>(const Physics::ColliderConfiguration& colliderConfiguration, const Physics::ShapeConfiguration& configuration));
         MOCK_METHOD1(ReleaseNativeMeshObject, void(void* nativeMeshObject));
         MOCK_METHOD1(CreateMaterial, AZStd::shared_ptr<Physics::Material>(const Physics::MaterialConfiguration& materialConfiguration));
-        MOCK_METHOD0(GetSupportedJointTypes, AZStd::vector<AZ::TypeId>());
-        MOCK_METHOD1(CreateJointLimitConfiguration, AZStd::shared_ptr<Physics::JointLimitConfiguration>(AZ::TypeId jointType));
-        MOCK_METHOD3(CreateJoint, AZStd::shared_ptr<Physics::Joint>(const AZStd::shared_ptr<Physics::JointLimitConfiguration>& configuration, AzPhysics::SimulatedBody* parentBody, AzPhysics::SimulatedBody* childBody));
-        MOCK_METHOD10(GenerateJointLimitVisualizationData, void(const Physics::JointLimitConfiguration& configuration, const AZ::Quaternion& parentRotation, const AZ::Quaternion& childRotation, float scale, AZ::u32 angularSubdivisions, AZ::u32 radialSubdivisions, AZStd::vector<AZ::Vector3>& vertexBufferOut, AZStd::vector<AZ::u32>& indexBufferOut, AZStd::vector<AZ::Vector3>& lineBufferOut, AZStd::vector<bool>& lineValidityBufferOut));
-        MOCK_METHOD5(ComputeInitialJointLimitConfiguration, AZStd::unique_ptr<Physics::JointLimitConfiguration>(const AZ::TypeId& jointLimitTypeId, const AZ::Quaternion& parentWorldRotation, const AZ::Quaternion& childWorldRotation, const AZ::Vector3& axis, const AZStd::vector<AZ::Quaternion>& exampleLocalRotations));
         MOCK_METHOD3(CookConvexMeshToFile, bool(const AZStd::string& filePath, const AZ::Vector3* vertices, AZ::u32 vertexCount));
         MOCK_METHOD3(CookConvexMeshToMemory, bool(const AZ::Vector3* vertices, AZ::u32 vertexCount, AZStd::vector<AZ::u8>& result));
         MOCK_METHOD5(CookTriangleMeshToFile, bool(const AZStd::string& filePath, const AZ::Vector3* vertices, AZ::u32 vertexCount, const AZ::u32* indices, AZ::u32 indexCount));
@@ -72,6 +67,36 @@ namespace Physics
         MOCK_CONST_METHOD0(GetDefaultSceneConfiguration, const AzPhysics::SceneConfiguration& ());
     };
 
+    class MockJointHelpersInterface : AZ::Interface<AzPhysics::JointHelpersInterface>::Registrar
+    {
+    public:
+        MOCK_CONST_METHOD0(GetSupportedJointTypeIds, const AZStd::vector<AZ::TypeId>());
+        MOCK_CONST_METHOD1(GetSupportedJointTypeId, AZStd::optional<const AZ::TypeId>(AzPhysics::JointType typeEnum));
+
+        MOCK_METHOD5(
+            ComputeInitialJointLimitConfiguration,
+            AZStd::unique_ptr<AzPhysics::JointConfiguration>(
+                const AZ::TypeId& jointLimitTypeId,
+                const AZ::Quaternion& parentWorldRotation,
+                const AZ::Quaternion& childWorldRotation,
+                const AZ::Vector3& axis,
+                const AZStd::vector<AZ::Quaternion>& exampleLocalRotations));
+
+        MOCK_METHOD10(
+            GenerateJointLimitVisualizationData,
+            void(
+                const AzPhysics::JointConfiguration& configuration,
+                const AZ::Quaternion& parentRotation,
+                const AZ::Quaternion& childRotation,
+                float scale,
+                AZ::u32 angularSubdivisions,
+                AZ::u32 radialSubdivisions,
+                AZStd::vector<AZ::Vector3>& vertexBufferOut,
+                AZStd::vector<AZ::u32>& indexBufferOut,
+                AZStd::vector<AZ::Vector3>& lineBufferOut,
+                AZStd::vector<bool>& lineValidityBufferOut));
+    };
+
     //Mocked of the AzPhysics Scene Interface. To keep things simple just mocked functions that have a return value OR required for a test.
     class MockPhysicsSceneInterface
         : AZ::Interface<AzPhysics::SceneInterface>::Registrar
@@ -97,6 +122,9 @@ namespace Physics
         void DisableSimulationOfBody(
             [[maybe_unused]] AzPhysics::SceneHandle sceneHandle,
             [[maybe_unused]] AzPhysics::SimulatedBodyHandle bodyHandle) override {}
+        void RemoveJoint(
+            [[maybe_unused]]AzPhysics::SceneHandle sceneHandle,
+            [[maybe_unused]] AzPhysics::JointHandle jointHandle) override {}
         void SuppressCollisionEvents(
             [[maybe_unused]] AzPhysics::SceneHandle sceneHandle,
             [[maybe_unused]] const AzPhysics::SimulatedBodyHandle& bodyHandleA,
@@ -145,6 +173,9 @@ namespace Physics
         MOCK_METHOD2(AddSimulatedBodies, AzPhysics::SimulatedBodyHandleList(AzPhysics::SceneHandle sceneHandle, const AzPhysics::SimulatedBodyConfigurationList& simulatedBodyConfigs));
         MOCK_METHOD2(GetSimulatedBodyFromHandle, AzPhysics::SimulatedBody* (AzPhysics::SceneHandle sceneHandle, AzPhysics::SimulatedBodyHandle bodyHandle));
         MOCK_METHOD2(GetSimulatedBodiesFromHandle, AzPhysics::SimulatedBodyList(AzPhysics::SceneHandle sceneHandle, const AzPhysics::SimulatedBodyHandleList& bodyHandles));
+        MOCK_METHOD4(AddJoint, AzPhysics::JointHandle(AzPhysics::SceneHandle sceneHandle, const AzPhysics::JointConfiguration* jointConfig,
+            AzPhysics::SimulatedBodyHandle parentBody, AzPhysics::SimulatedBodyHandle childBody));
+        MOCK_METHOD2(GetJointFromHandle, AzPhysics::Joint* (AzPhysics::SceneHandle sceneHandle, AzPhysics::JointHandle bodyHandle));
         MOCK_CONST_METHOD1(GetGravity, AZ::Vector3(AzPhysics::SceneHandle sceneHandle));
         MOCK_METHOD2(RegisterSceneSimulationFinishHandler, void(AzPhysics::SceneHandle sceneHandle, AzPhysics::SceneEvents::OnSceneSimulationFinishHandler& handler));
         MOCK_CONST_METHOD2(GetLegacyBody, AzPhysics::SimulatedBody* (AzPhysics::SceneHandle sceneHandle, AzPhysics::SimulatedBodyHandle handle));

+ 17 - 4
Gems/EMotionFX/Code/Tests/ProvidesUI/Ragdoll/CanCopyPasteColliders.cpp

@@ -16,6 +16,7 @@
 #include <AzCore/std/algorithm.h>
 #include <AzFramework/Physics/Character.h>
 #include <AzFramework/Physics/ShapeConfiguration.h>
+#include <AzFramework/Physics/Common/PhysicsJoint.h>
 
 #include <EMotionFX/CommandSystem/Source/ColliderCommands.h>
 #include <EMotionFX/CommandSystem/Source/RagdollCommands.h>
@@ -44,10 +45,21 @@ namespace EMotionFX
 
             D6JointLimitConfiguration::Reflect(GetSerializeContext());
 
-            const AZ::TypeId& jointLimitTypeId = azrtti_typeid<D6JointLimitConfiguration>();
-            EXPECT_CALL(m_physicsSystem, GetSupportedJointTypes)
-                .WillRepeatedly(testing::Return(AZStd::vector<AZ::TypeId>{jointLimitTypeId}));
-            EXPECT_CALL(m_physicsSystem, ComputeInitialJointLimitConfiguration(jointLimitTypeId, _, _, _, _))
+            EXPECT_CALL(m_jointHelpers, GetSupportedJointTypeIds)
+                .WillRepeatedly(testing::Return(AZStd::vector<AZ::TypeId>{ azrtti_typeid<D6JointLimitConfiguration>() }));
+
+            EXPECT_CALL(m_jointHelpers, GetSupportedJointTypeId(_))
+                .WillRepeatedly(
+                    [](AzPhysics::JointType jointType) -> AZStd::optional<const AZ::TypeId>
+                    {
+                        if (jointType == AzPhysics::JointType::D6Joint)
+                        {
+                            return azrtti_typeid<D6JointLimitConfiguration>();
+                        }
+                        return AZStd::nullopt;
+                    });
+
+            EXPECT_CALL(m_jointHelpers, ComputeInitialJointLimitConfiguration(_, _, _, _, _))
                 .WillRepeatedly([]([[maybe_unused]] const AZ::TypeId& jointLimitTypeId,
                                    [[maybe_unused]] const AZ::Quaternion& parentWorldRotation,
                                    [[maybe_unused]] const AZ::Quaternion& childWorldRotation,
@@ -59,6 +71,7 @@ namespace EMotionFX
     private:
         Physics::MockPhysicsSystem m_physicsSystem;
         Physics::MockPhysicsInterface m_physicsInterface;
+        Physics::MockJointHelpersInterface m_jointHelpers;
     };
 
 #if AZ_TRAIT_DISABLE_FAILED_EMOTION_FX_EDITOR_TESTS

+ 28 - 8
Gems/EMotionFX/Code/Tests/ProvidesUI/Ragdoll/CanCopyPasteJointLimits.cpp

@@ -55,10 +55,27 @@ namespace EMotionFX
 
         Physics::MockPhysicsSystem physicsSystem;
         Physics::MockPhysicsInterface physicsInterface;
-        EXPECT_CALL(physicsSystem, GetSupportedJointTypes)
-            .WillRepeatedly(testing::Return(AZStd::vector<AZ::TypeId>{azrtti_typeid<D6JointLimitConfiguration>()}));
-        EXPECT_CALL(physicsSystem, ComputeInitialJointLimitConfiguration(azrtti_typeid<D6JointLimitConfiguration>(), _, _, _, _))
-            .WillRepeatedly([]([[maybe_unused]] const AZ::TypeId& jointLimitTypeId, [[maybe_unused]] const AZ::Quaternion& parentWorldRotation, [[maybe_unused]] const AZ::Quaternion& childWorldRotation, [[maybe_unused]] const AZ::Vector3& axis, [[maybe_unused]] const AZStd::vector<AZ::Quaternion>& exampleLocalRotations) { return AZStd::make_unique<D6JointLimitConfiguration>(); });
+        Physics::MockJointHelpersInterface jointHelpers;
+        EXPECT_CALL(jointHelpers, GetSupportedJointTypeIds)
+            .WillRepeatedly(testing::Return(AZStd::vector<AZ::TypeId>{ azrtti_typeid<D6JointLimitConfiguration>() }));
+        EXPECT_CALL(jointHelpers, GetSupportedJointTypeId(_))
+            .WillRepeatedly(
+                [](AzPhysics::JointType jointType) -> AZStd::optional<const AZ::TypeId>
+                {
+                    if (jointType == AzPhysics::JointType::D6Joint)
+                    {
+                        return azrtti_typeid<D6JointLimitConfiguration>();
+                    }
+                    return AZStd::nullopt;
+                });
+        EXPECT_CALL(jointHelpers, ComputeInitialJointLimitConfiguration(_, _, _, _, _))
+            .WillRepeatedly(
+                []([[maybe_unused]] const AZ::TypeId& jointLimitTypeId,
+                   [[maybe_unused]] const AZ::Quaternion& parentWorldRotation, [[maybe_unused]] const AZ::Quaternion& childWorldRotation,
+                   [[maybe_unused]] const AZ::Vector3& axis, [[maybe_unused]] const AZStd::vector<AZ::Quaternion>& exampleLocalRotations)
+                {
+                    return AZStd::make_unique<D6JointLimitConfiguration>();
+                });
 
         AutoRegisteredActor actor {ActorFactory::CreateAndInit<SimpleJointChainActor>(4)};
 
@@ -70,7 +87,7 @@ namespace EMotionFX
         CommandRagdollHelpers::AddJointsToRagdoll(actor->GetID(), {"rootJoint", "joint1", "joint2", "joint3"});
         const Physics::RagdollConfiguration& ragdollConfig = actor->GetPhysicsSetup()->GetRagdollConfig();
         ASSERT_EQ(ragdollConfig.m_nodes.size(), 4);
-        AZStd::shared_ptr<D6JointLimitConfiguration> rootJointLimit = AZStd::rtti_pointer_cast<D6JointLimitConfiguration>(ragdollConfig.FindNodeConfigByName("rootJoint")->m_jointLimit);
+        AZStd::shared_ptr<D6JointLimitConfiguration> rootJointLimit = AZStd::rtti_pointer_cast<D6JointLimitConfiguration>(ragdollConfig.FindNodeConfigByName("rootJoint")->m_jointConfig);
         ASSERT_TRUE(rootJointLimit);
         // Set the initial joint limits on the rootJoint to be something different
         rootJointLimit->m_swingLimitY = 1.0f;
@@ -119,7 +136,8 @@ namespace EMotionFX
         selectionModel.select(joint1Index, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
 
         // Verify initial state of joint1's limits
-        const AZStd::shared_ptr<D6JointLimitConfiguration> joint1Limit = AZStd::rtti_pointer_cast<D6JointLimitConfiguration>(ragdollConfig.FindNodeConfigByName("joint1")->m_jointLimit);
+        const AZStd::shared_ptr<D6JointLimitConfiguration> joint1Limit =
+            AZStd::rtti_pointer_cast<D6JointLimitConfiguration>(ragdollConfig.FindNodeConfigByName("joint1")->m_jointConfig);
         EXPECT_EQ(joint1Limit->m_swingLimitY, 45.0f);
         EXPECT_EQ(joint1Limit->m_swingLimitZ, 45.0f);
 
@@ -141,10 +159,12 @@ namespace EMotionFX
         selectionModel.select(joint2Index, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
         selectionModel.select(joint3Index, QItemSelectionModel::Select | QItemSelectionModel::Rows);
 
-        const AZStd::shared_ptr<D6JointLimitConfiguration> joint2Limit = AZStd::rtti_pointer_cast<D6JointLimitConfiguration>(ragdollConfig.FindNodeConfigByName("joint2")->m_jointLimit);
+        const AZStd::shared_ptr<D6JointLimitConfiguration> joint2Limit =
+            AZStd::rtti_pointer_cast<D6JointLimitConfiguration>(ragdollConfig.FindNodeConfigByName("joint2")->m_jointConfig);
         EXPECT_EQ(joint2Limit->m_swingLimitY, 45.0f);
         EXPECT_EQ(joint2Limit->m_swingLimitZ, 45.0f);
-        const AZStd::shared_ptr<D6JointLimitConfiguration> joint3Limit = AZStd::rtti_pointer_cast<D6JointLimitConfiguration>(ragdollConfig.FindNodeConfigByName("joint3")->m_jointLimit);
+        const AZStd::shared_ptr<D6JointLimitConfiguration> joint3Limit =
+            AZStd::rtti_pointer_cast<D6JointLimitConfiguration>(ragdollConfig.FindNodeConfigByName("joint3")->m_jointConfig);
         EXPECT_EQ(joint3Limit->m_swingLimitY, 45.0f);
         EXPECT_EQ(joint3Limit->m_swingLimitZ, 45.0f);
 

+ 38 - 2
Gems/EMotionFX/Code/Tests/RagdollCommandTests.cpp

@@ -18,11 +18,47 @@
 #include <EMotionFX/CommandSystem/Source/CommandManager.h>
 #include <EMotionFX/CommandSystem/Source/RagdollCommands.h>
 #include <EMotionFX/CommandSystem/Source/ColliderCommands.h>
-
+#include <Tests/D6JointLimitConfiguration.h>
+#include <Tests/Mocks/PhysicsSystem.h>
 
 namespace EMotionFX
 {
-    using RagdollCommandTests = ActorFixture;
+    class RagdollCommandTests : public ActorFixture
+    {
+    public:
+        void SetUp() override
+        {
+            using ::testing::_;
+
+            ActorFixture::SetUp();
+
+            D6JointLimitConfiguration::Reflect(GetSerializeContext());
+
+            EXPECT_CALL(m_jointHelpers, GetSupportedJointTypeIds)
+                .WillRepeatedly(testing::Return(AZStd::vector<AZ::TypeId>{ azrtti_typeid<D6JointLimitConfiguration>() }));
+
+            EXPECT_CALL(m_jointHelpers, GetSupportedJointTypeId(_))
+                .WillRepeatedly(
+                    [](AzPhysics::JointType jointType) -> AZStd::optional<const AZ::TypeId>
+                    {
+                        if (jointType == AzPhysics::JointType::D6Joint)
+                        {
+                            return azrtti_typeid<D6JointLimitConfiguration>();
+                        }
+                        return AZStd::nullopt;
+                    });
+
+            EXPECT_CALL(m_jointHelpers, ComputeInitialJointLimitConfiguration(_, _, _, _, _))
+                .WillRepeatedly([]([[maybe_unused]] const AZ::TypeId& jointLimitTypeId,
+                                   [[maybe_unused]] const AZ::Quaternion& parentWorldRotation,
+                                   [[maybe_unused]] const AZ::Quaternion& childWorldRotation,
+                                   [[maybe_unused]] const AZ::Vector3& axis,
+                                   [[maybe_unused]] const AZStd::vector<AZ::Quaternion>& exampleLocalRotations)
+                                { return AZStd::make_unique<D6JointLimitConfiguration>(); });
+        }
+    protected:
+        Physics::MockJointHelpersInterface m_jointHelpers;
+    };
 
     AZStd::vector<AZStd::string> GetRagdollJointNames(const Actor* actor)
     {

+ 27 - 0
Gems/EMotionFX/Code/Tests/UI/CanAddToSimulatedObject.cpp

@@ -10,6 +10,7 @@
 *
 */
 
+#include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
 #include <QPushButton>
@@ -29,6 +30,7 @@
 #include <Tests/UI/ModalPopupHandler.h>
 #include <Tests/UI/UIFixture.h>
 #include <Tests/D6JointLimitConfiguration.h>
+#include <Tests/Mocks/PhysicsSystem.h>
 
 namespace EMotionFX
 {
@@ -140,6 +142,31 @@ namespace EMotionFX
 
     TEST_F(CanAddToSimulatedObjectFixture, CanAddCollidersfromRagdoll)
     {
+        using ::testing::_;
+        Physics::MockJointHelpersInterface jointHelpers;
+        EXPECT_CALL(jointHelpers, GetSupportedJointTypeIds)
+            .WillRepeatedly(testing::Return(AZStd::vector<AZ::TypeId>{ azrtti_typeid<D6JointLimitConfiguration>() }));
+
+        EXPECT_CALL(jointHelpers, GetSupportedJointTypeId(_))
+            .WillRepeatedly(
+                [](AzPhysics::JointType jointType) -> AZStd::optional<const AZ::TypeId>
+                {
+                    if (jointType == AzPhysics::JointType::D6Joint)
+                    {
+                        return azrtti_typeid<D6JointLimitConfiguration>();
+                    }
+                    return AZStd::nullopt;
+                });
+
+        EXPECT_CALL(jointHelpers, ComputeInitialJointLimitConfiguration(_, _, _, _, _))
+            .WillRepeatedly(
+                []([[maybe_unused]] const AZ::TypeId& jointLimitTypeId, [[maybe_unused]] const AZ::Quaternion& parentWorldRotation,
+                   [[maybe_unused]] const AZ::Quaternion& childWorldRotation, [[maybe_unused]] const AZ::Vector3& axis,
+                   [[maybe_unused]] const AZStd::vector<AZ::Quaternion>& exampleLocalRotations)
+                {
+                    return AZStd::make_unique<D6JointLimitConfiguration>();
+                });
+
         RecordProperty("test_case_id", "C13291807");
         AutoRegisteredActor actor = ActorFactory::CreateAndInit<SimpleJointChainActor>(7, "CanAddToSimulatedObjectActor");
 

+ 28 - 0
Gems/EMotionFX/Code/Tests/UI/RagdollEditTests.cpp

@@ -10,6 +10,7 @@
 *
 */
 
+#include <gmock/gmock.h>
 #include <gtest/gtest.h>
 
 #include <QtTest>
@@ -27,6 +28,7 @@
 
 #include <Editor/ReselectingTreeView.h>
 #include <Tests/D6JointLimitConfiguration.h>
+#include <Tests/Mocks/PhysicsSystem.h>
 
 namespace EMotionFX
 {
@@ -35,6 +37,8 @@ namespace EMotionFX
       public:
         void SetUp() override
         {
+            using ::testing::_;
+
             SetupQtAndFixtureBase();
 
             AZ::SerializeContext* serializeContext = nullptr;
@@ -42,6 +46,29 @@ namespace EMotionFX
 
             D6JointLimitConfiguration::Reflect(serializeContext);
 
+            EXPECT_CALL(m_jointHelpers, GetSupportedJointTypeIds)
+                .WillRepeatedly(testing::Return(AZStd::vector<AZ::TypeId>{ azrtti_typeid<D6JointLimitConfiguration>() }));
+
+            EXPECT_CALL(m_jointHelpers, GetSupportedJointTypeId(_))
+                .WillRepeatedly(
+                    [](AzPhysics::JointType jointType) -> AZStd::optional<const AZ::TypeId>
+                    {
+                        if (jointType == AzPhysics::JointType::D6Joint)
+                        {
+                            return azrtti_typeid<D6JointLimitConfiguration>();
+                        }
+                        return AZStd::nullopt;
+                    });
+
+            EXPECT_CALL(m_jointHelpers, ComputeInitialJointLimitConfiguration(_, _, _, _, _))
+                .WillRepeatedly(
+                    []([[maybe_unused]] const AZ::TypeId& jointLimitTypeId, [[maybe_unused]] const AZ::Quaternion& parentWorldRotation,
+                       [[maybe_unused]] const AZ::Quaternion& childWorldRotation, [[maybe_unused]] const AZ::Vector3& axis,
+                       [[maybe_unused]] const AZStd::vector<AZ::Quaternion>& exampleLocalRotations)
+                    {
+                        return AZStd::make_unique<D6JointLimitConfiguration>();
+                    });
+
             SetupPluginWindows();
         }
 
@@ -73,6 +100,7 @@ namespace EMotionFX
         QModelIndexList m_indexList;
         ReselectingTreeView* m_treeView;
         EMotionFX::SkeletonOutlinerPlugin* m_skeletonOutliner;
+        Physics::MockJointHelpersInterface m_jointHelpers;
     };
 
 

+ 0 - 2
Gems/EMotionFX/Code/emotionfx_editor_tests_files.cmake

@@ -63,8 +63,6 @@ set(FILES
     Tests/Integration/CanDeleteJackEntity.cpp
     Tests/Bugs/CanDeleteMotionWhenMotionIsBeingBlended.cpp
     Tests/Bugs/CanUndoParameterDeletionAndRestoreBlendTreeConnections.cpp
-    Tests/D6JointLimitConfiguration.cpp
-    Tests/D6JointLimitConfiguration.h
     Tests/Editor/FileManagerTests.cpp
     Tests/Editor/ParametersGroupDefaultValues.cpp
     Tests/Editor/MotionSetLoadEscalation.cpp

+ 2 - 0
Gems/EMotionFX/Code/emotionfx_shared_tests_files.cmake

@@ -21,4 +21,6 @@ set(FILES
     Tests/TestAssetCode/AnimGraphAssetFactory.h
     Tests/TestAssetCode/ActorAssetFactory.h
     Tests/TestAssetCode/MotionSetAssetFactory.h
+    Tests/D6JointLimitConfiguration.cpp
+    Tests/D6JointLimitConfiguration.h
 )

+ 11 - 0
Gems/PhysX/Code/CMakeLists.txt

@@ -156,6 +156,8 @@ endif()
 ################################################################################
 if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
 
+    
+
     ly_add_target(
         NAME PhysX.Tests ${PAL_TRAIT_TEST_TARGET_TYPE}
         NAMESPACE Gem
@@ -174,6 +176,15 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
         RUNTIME_DEPENDENCIES
             Gem::LmbrCentral
     )
+
+    if(PAL_TRAIT_JOINTS_TYPED_TEST_CASE)
+        ly_add_source_properties(
+            SOURCES Tests/PhysXJointsTest.cpp
+            PROPERTY COMPILE_DEFINITIONS 
+            VALUES ENABLE_JOINTS_TYPED_TEST_CASE
+        )
+    endif()
+
     ly_add_googletest(
         NAME Gem::PhysX.Tests
     )

+ 22 - 17
Gems/PhysX/Code/Editor/EditorJointConfiguration.cpp

@@ -144,11 +144,12 @@ namespace PhysX
     {
         return m_standardLimitConfig.m_isLimited;
     }
-    GenericJointLimitsConfiguration EditorJointLimitPairConfig::ToGameTimeConfig() const
+
+    JointLimitProperties EditorJointLimitPairConfig::ToGameTimeConfig() const
     {
-        return GenericJointLimitsConfiguration(m_standardLimitConfig.m_damping
-            , m_standardLimitConfig.m_isLimited
+        return JointLimitProperties(m_standardLimitConfig.m_isLimited
             , m_standardLimitConfig.m_isSoftLimit
+            , m_standardLimitConfig.m_damping
             , m_limitPositive, m_limitNegative
             , m_standardLimitConfig.m_stiffness
             , m_standardLimitConfig.m_tolerance);
@@ -193,11 +194,11 @@ namespace PhysX
         return m_standardLimitConfig.m_isLimited;
     }
 
-    GenericJointLimitsConfiguration EditorJointLimitConeConfig::ToGameTimeConfig() const
+    JointLimitProperties EditorJointLimitConeConfig::ToGameTimeConfig() const
     {
-        return GenericJointLimitsConfiguration(m_standardLimitConfig.m_damping
-            , m_standardLimitConfig.m_isLimited
+        return JointLimitProperties(m_standardLimitConfig.m_isLimited
             , m_standardLimitConfig.m_isSoftLimit
+            , m_standardLimitConfig.m_damping
             , m_limitY
             , m_limitZ
             , m_standardLimitConfig.m_stiffness
@@ -275,27 +276,31 @@ namespace PhysX
             , AzToolsFramework::Refresh_AttributesAndValues);
     }
 
-    GenericJointConfiguration EditorJointConfig::ToGameTimeConfig() const
+    JointGenericProperties EditorJointConfig::ToGenericProperties() const
     {
-        GenericJointConfiguration::GenericJointFlag flags = GenericJointConfiguration::GenericJointFlag::None;
+        JointGenericProperties::GenericJointFlag flags = JointGenericProperties::GenericJointFlag::None;
         if (m_breakable)
         {
-            flags |= GenericJointConfiguration::GenericJointFlag::Breakable;
+            flags |= JointGenericProperties::GenericJointFlag::Breakable;
         }
         if (m_selfCollide)
         {
-            flags |= GenericJointConfiguration::GenericJointFlag::SelfCollide;
+            flags |= JointGenericProperties::GenericJointFlag::SelfCollide;
         }
 
+        return JointGenericProperties(flags, m_forceMax, m_torqueMax);
+    }
+
+    JointComponentConfiguration EditorJointConfig::ToGameTimeConfig() const
+    {
         AZ::Vector3 localRotation(m_localRotation);
 
-        return GenericJointConfiguration(m_forceMax
-            , m_torqueMax
-            , AZ::Transform::CreateFromQuaternionAndTranslation(AZ::Quaternion::CreateFromEulerAnglesDegrees(localRotation),
-                m_localPosition)
-            , m_leadEntity
-            , m_followerEntity
-            , flags);
+        return JointComponentConfiguration(
+            AZ::Transform::CreateFromQuaternionAndTranslation(
+                AZ::Quaternion::CreateFromEulerAnglesDegrees(localRotation),
+                m_localPosition),
+            m_leadEntity,
+            m_followerEntity);
     }
 
     bool EditorJointConfig::IsInComponentMode() const

+ 5 - 4
Gems/PhysX/Code/Editor/EditorJointConfiguration.h

@@ -15,7 +15,7 @@
 #include <AzCore/Component/EntityId.h>
 #include <AzCore/Serialization/SerializeContext.h>
 
-#include <Source/Joint.h>
+#include <Source/JointComponent.h>
 
 namespace PhysX
 {
@@ -68,7 +68,7 @@ namespace PhysX
         static void Reflect(AZ::ReflectContext* context);
 
         bool IsLimited() const;
-        GenericJointLimitsConfiguration ToGameTimeConfig() const;
+        JointLimitProperties ToGameTimeConfig() const;
 
         EditorJointLimitConfig m_standardLimitConfig;
         float m_limitPositive = 45.0f;
@@ -87,7 +87,7 @@ namespace PhysX
         static void Reflect(AZ::ReflectContext* context);
 
         bool IsLimited() const;
-        GenericJointLimitsConfiguration ToGameTimeConfig() const;
+        JointLimitProperties ToGameTimeConfig() const;
 
         EditorJointLimitConfig m_standardLimitConfig;
         float m_limitY = 45.0f;
@@ -105,7 +105,8 @@ namespace PhysX
         static void Reflect(AZ::ReflectContext* context);
 
         void SetLeadEntityId(AZ::EntityId leadEntityId);
-        GenericJointConfiguration ToGameTimeConfig() const;
+        JointGenericProperties ToGenericProperties() const;
+        JointComponentConfiguration ToGameTimeConfig() const;
 
         bool m_breakable = false;
         bool m_displayJointSetup = false;

+ 107 - 0
Gems/PhysX/Code/Include/PhysX/Joint/Configuration/PhysXJointConfiguration.h

@@ -0,0 +1,107 @@
+/*
+* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
+* its licensors.
+*
+* For complete copyright and license terms please see the LICENSE at the root of this
+* distribution (the "License"). All use of this software is governed by the License,
+* or, if provided, by the license below or the license accompanying this file. Do not
+* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+*
+*/
+
+#pragma once
+
+#include <AzFramework/Physics/Configuration/JointConfiguration.h>
+
+namespace PhysX
+{
+    struct D6JointLimitConfiguration
+        : public AzPhysics::JointConfiguration
+    {
+        AZ_CLASS_ALLOCATOR(D6JointLimitConfiguration, AZ::SystemAllocator, 0);
+        AZ_RTTI(D6JointLimitConfiguration, "{88E067B4-21E8-4FFA-9142-6C52605B704C}", AzPhysics::JointConfiguration);
+        static void Reflect(AZ::ReflectContext* context);
+
+        float m_swingLimitY = 45.0f; ///< Maximum angle in degrees from the Y axis of the joint frame.
+        float m_swingLimitZ = 45.0f; ///< Maximum angle in degrees from the Z axis of the joint frame.
+        float m_twistLimitLower = -45.0f; ///< Lower limit in degrees for rotation about the X axis of the joint frame.
+        float m_twistLimitUpper = 45.0f; ///< Upper limit in degrees for rotation about the X axis of the joint frame.
+    };
+
+    //! Properties that are common for several types of joints.
+    struct JointGenericProperties
+    {
+        enum class GenericJointFlag : AZ::u16
+        {
+            None = 0,
+            Breakable = 1,
+            SelfCollide = 1 << 1
+        };
+
+        AZ_CLASS_ALLOCATOR(JointGenericProperties, AZ::SystemAllocator, 0);
+        AZ_TYPE_INFO(JointGenericProperties, "{6CB15399-24F6-4F03-AAEF-1AE013B683E0}");
+        static void Reflect(AZ::ReflectContext* context);
+
+        JointGenericProperties() = default;
+        JointGenericProperties(GenericJointFlag flags, float forceMax, float torqueMax);
+
+        bool IsFlagSet(GenericJointFlag flag) const; ///< Returns if a particular flag is set as a bool.
+
+        /// Flags that indicates if joint is breakable, self-colliding, etc. 
+        /// Converting joint between breakable/non-breakable at game time is allowed.
+        GenericJointFlag m_flags = GenericJointFlag::None;
+        float m_forceMax = 1.0f; ///< Max force joint can tolerate before breaking.
+        float m_torqueMax = 1.0f; ///< Max torque joint can tolerate before breaking.
+    };
+    AZ_DEFINE_ENUM_BITWISE_OPERATORS(PhysX::JointGenericProperties::GenericJointFlag)
+
+    struct JointLimitProperties
+    {
+        AZ_CLASS_ALLOCATOR(JointLimitProperties, AZ::SystemAllocator, 0);
+        AZ_TYPE_INFO(JointLimitProperties, "{31F941CB-6699-48BB-B12D-61874B52B984}");
+        static void Reflect(AZ::ReflectContext* context);
+
+        JointLimitProperties() = default;
+        JointLimitProperties(
+            bool isLimited, bool isSoftLimit, 
+            float damping, float limitFirst, float limitSecond, float stiffness, float tolerance);
+
+        bool m_isLimited = true; ///< Specifies if limits are applied to the joint constraints. E.g. if the swing angles are limited.
+        bool m_isSoftLimit = false; ///< If limit is soft, spring and damping are used, otherwise tolerance is used. Converting between soft/hard limit at game time is allowed.
+        float m_damping = 20.0f; ///< The damping strength of the drive, the force proportional to the velocity error. Used if limit is soft.
+        float m_limitFirst = 45.0f; ///< Positive angle limit in the case of twist angle limits, Y-axis swing limit in the case of cone limits.
+        float m_limitSecond = 45.0f; ///< Negative angle limit in the case of twist angle limits, Z-axis swing limit in the case of cone limits.
+        float m_stiffness = 100.0f; ///< The spring strength of the drive, the force proportional to the position error. Used if limit is soft.
+        float m_tolerance = 0.1f; ///< Distance from the joint at which limits becomes enforced. Used if limit is hard.
+    };
+
+    struct FixedJointConfiguration : public AzPhysics::JointConfiguration 
+    {
+        AZ_CLASS_ALLOCATOR(FixedJointConfiguration, AZ::SystemAllocator, 0);
+        AZ_RTTI(FixedJointConfiguration, "{9BCB368B-8D71-4928-B231-0225907E3BD9}", AzPhysics::JointConfiguration);
+        static void Reflect(AZ::ReflectContext* context);
+
+        JointGenericProperties m_genericProperties;
+    };
+
+    struct BallJointConfiguration : public AzPhysics::JointConfiguration 
+    {
+        AZ_CLASS_ALLOCATOR(BallJointConfiguration, AZ::SystemAllocator, 0);
+        AZ_RTTI(BallJointConfiguration, "{C2DE2479-B752-469D-BE05-900CD2CD8481}", AzPhysics::JointConfiguration);
+        static void Reflect(AZ::ReflectContext* context);
+
+        JointGenericProperties m_genericProperties;
+        JointLimitProperties m_limitProperties;
+    };
+    
+    struct HingeJointConfiguration : public AzPhysics::JointConfiguration 
+    {
+        AZ_CLASS_ALLOCATOR(HingeJointConfiguration, AZ::SystemAllocator, 0);
+        AZ_RTTI(HingeJointConfiguration, "{FB04198E-0BA5-45C2-8343-66DA28ED45EA}", AzPhysics::JointConfiguration);
+        static void Reflect(AZ::ReflectContext* context);
+
+        JointGenericProperties m_genericProperties;
+        JointLimitProperties m_limitProperties;
+    };
+} // namespace PhysX

+ 22 - 44
Gems/PhysX/Code/Source/BallJointComponent.cpp

@@ -16,8 +16,10 @@
 #include <PhysX/MathConversion.h>
 #include <PhysX/PhysXLocks.h>
 #include <AzCore/Component/TransformBus.h>
+#include <AzCore/Interface/Interface.h>
 #include <AzFramework/Physics/SimulatedBodies/RigidBody.h>
 #include <AzFramework/Physics/RigidBodyBus.h>
+#include <AzFramework/Physics/PhysicsScene.h>
 
 #include <PxPhysicsAPI.h>
 
@@ -33,15 +35,17 @@ namespace PhysX
         }
     }
 
-    BallJointComponent::BallJointComponent(const GenericJointConfiguration& config
-        , const GenericJointLimitsConfiguration& swingLimit)
-            : JointComponent(config, swingLimit)
+    BallJointComponent::BallJointComponent(
+        const JointComponentConfiguration& configuration, 
+        const JointGenericProperties& genericProperties,
+        const JointLimitProperties& limitProperties)
+        : JointComponent(configuration, genericProperties, limitProperties)
     {
     }
 
     void BallJointComponent::InitNativeJoint()
     {
-        if (m_joint)
+        if (m_jointHandle != AzPhysics::InvalidJointHandle)
         {
             return;
         }
@@ -52,50 +56,24 @@ namespace PhysX
         {
             return;
         }
-        PHYSX_SCENE_READ_LOCK(leadFollowerInfo.m_followerActor->getScene());
-        m_joint = AZStd::make_shared<BallJoint>(physx::PxSphericalJointCreate(
-            PxGetPhysics(),
-            leadFollowerInfo.m_leadActor,
-            leadFollowerInfo.m_leadLocal,
-            leadFollowerInfo.m_followerActor,
-            leadFollowerInfo.m_followerLocal),
-            leadFollowerInfo.m_leadBody,
-            leadFollowerInfo.m_followerBody);
 
-        InitSwingLimits();
-    }
-
-    void BallJointComponent::InitSwingLimits()
-    {
-        if (!m_joint)
-        {
-            return;
-        }
-
-        physx::PxSphericalJoint* ballJointNative = static_cast<physx::PxSphericalJoint*>(m_joint->GetNativePointer());
-        if (!ballJointNative)
-        {
-            return;
-        }
+        BallJointConfiguration configuration;
+        configuration.m_parentLocalPosition = leadFollowerInfo.m_leadLocal.GetTranslation();
+        configuration.m_parentLocalRotation = leadFollowerInfo.m_leadLocal.GetRotation();
+        configuration.m_childLocalPosition = leadFollowerInfo.m_followerLocal.GetTranslation();
+        configuration.m_childLocalRotation = leadFollowerInfo.m_followerLocal.GetRotation();
 
-        if (!m_limits.m_isLimited)
-        {
-            ballJointNative->setSphericalJointFlag(physx::PxSphericalJointFlag::eLIMIT_ENABLED, false);
-            return;
-        }
+        configuration.m_genericProperties = m_genericProperties;
+        configuration.m_limitProperties = m_limits;
 
-        // Hard limit uses a tolerance value (distance to limit at which limit becomes active).
-        // Soft limit allows angle to exceed limit but springs back with configurable spring stiffness and damping.
-        physx::PxJointLimitCone swingLimit(AZ::DegToRad(m_limits.m_limitFirst)
-            , AZ::DegToRad(m_limits.m_limitSecond)
-            , m_limits.m_tolerance);
-        if (m_limits.m_isSoftLimit)
+        if (auto* sceneInterface = AZ::Interface<AzPhysics::SceneInterface>::Get())
         {
-            swingLimit.stiffness = m_limits.m_stiffness;
-            swingLimit.damping = m_limits.m_damping;
+            m_jointHandle = sceneInterface->AddJoint(
+                leadFollowerInfo.m_followerBody->m_sceneOwner,
+                &configuration,  
+                leadFollowerInfo.m_leadBody->m_bodyHandle, 
+                leadFollowerInfo.m_followerBody->m_bodyHandle);
+            m_jointSceneOwner = leadFollowerInfo.m_followerBody->m_sceneOwner;
         }
-
-        ballJointNative->setLimitCone(swingLimit);
-        ballJointNative->setSphericalJointFlag(physx::PxSphericalJointFlag::eLIMIT_ENABLED, true);
     }
 } // namespace PhysX

+ 4 - 5
Gems/PhysX/Code/Source/BallJointComponent.h

@@ -25,15 +25,14 @@ namespace PhysX
         static void Reflect(AZ::ReflectContext* context);
 
         BallJointComponent() = default;
-        BallJointComponent(const GenericJointConfiguration& config
-            , const GenericJointLimitsConfiguration& swingLimit);
+        BallJointComponent(
+            const JointComponentConfiguration& configuration, 
+            const JointGenericProperties& genericProperties,
+            const JointLimitProperties& limitProperties);
         ~BallJointComponent() = default;
 
     protected:
         // JointComponent
         void InitNativeJoint() override;
-
-    private:
-        void InitSwingLimits();
     };
 } // namespace PhysX

+ 1 - 0
Gems/PhysX/Code/Source/EditorBallJointComponent.cpp

@@ -99,6 +99,7 @@ namespace PhysX
         m_config.m_followerEntity = GetEntityId(); // joint is always in the same entity as the follower body.
         gameEntity->CreateComponent<BallJointComponent>(
             m_config.ToGameTimeConfig(), 
+            m_config.ToGenericProperties(),
             m_swingLimit.ToGameTimeConfig());
     }
 

+ 0 - 1
Gems/PhysX/Code/Source/EditorBallJointComponent.h

@@ -13,7 +13,6 @@
 
 #pragma once
 
-#include <AzFramework/Physics/Joint.h>
 #include <AzFramework/Entity/EntityDebugDisplayBus.h>
 #include <AzToolsFramework/API/ComponentEntitySelectionBus.h>
 #include <AzToolsFramework/ComponentMode/ComponentModeDelegate.h>

+ 1 - 1
Gems/PhysX/Code/Source/EditorFixedJointComponent.cpp

@@ -94,6 +94,6 @@ namespace PhysX
     void EditorFixedJointComponent::BuildGameEntity(AZ::Entity* gameEntity)
     {
         m_config.m_followerEntity = GetEntityId(); // joint is always in the same entity as the follower body.
-        gameEntity->CreateComponent<FixedJointComponent>(m_config.ToGameTimeConfig());
+        gameEntity->CreateComponent<FixedJointComponent>(m_config.ToGameTimeConfig(), m_config.ToGenericProperties());
     }
 }

+ 0 - 1
Gems/PhysX/Code/Source/EditorFixedJointComponent.h

@@ -13,7 +13,6 @@
 
 #pragma once
 
-#include <AzFramework/Physics/Joint.h>
 #include <AzFramework/Entity/EntityDebugDisplayBus.h>
 #include <AzToolsFramework/API/ComponentEntitySelectionBus.h>
 #include <AzToolsFramework/ComponentMode/ComponentModeDelegate.h>

+ 1 - 0
Gems/PhysX/Code/Source/EditorHingeJointComponent.cpp

@@ -99,6 +99,7 @@ namespace PhysX
         m_config.m_followerEntity = GetEntityId(); // joint is always in the same entity as the follower body.
         gameEntity->CreateComponent<HingeJointComponent>(
             m_config.ToGameTimeConfig(), 
+            m_config.ToGenericProperties(),
             m_angularLimit.ToGameTimeConfig());
     }
 

+ 0 - 1
Gems/PhysX/Code/Source/EditorHingeJointComponent.h

@@ -13,7 +13,6 @@
 
 #pragma once
 
-#include <AzFramework/Physics/Joint.h>
 #include <AzFramework/Entity/EntityDebugDisplayBus.h>
 #include <AzToolsFramework/API/ComponentEntitySelectionBus.h>
 #include <AzToolsFramework/ComponentMode/ComponentModeDelegate.h>

+ 0 - 1
Gems/PhysX/Code/Source/EditorJointComponent.h

@@ -13,7 +13,6 @@
 
 #pragma once
 
-#include <AzFramework/Physics/Joint.h>
 #include <AzFramework/Visibility/BoundsBus.h>
 
 #include <AzToolsFramework/API/ComponentEntitySelectionBus.h>

+ 31 - 15
Gems/PhysX/Code/Source/FixedJointComponent.cpp

@@ -17,6 +17,9 @@
 #include <AzCore/Component/TransformBus.h>
 #include <AzFramework/Physics/SimulatedBodies/RigidBody.h>
 #include <AzFramework/Physics/RigidBodyBus.h>
+#include <PhysX/Joint/Configuration/PhysXJointConfiguration.h>
+#include <AzFramework/Physics/PhysicsScene.h>
+#include <AzCore/Interface/Interface.h>
 
 #include <PxPhysicsAPI.h>
 
@@ -33,20 +36,24 @@ namespace PhysX
         }
     }
 
-    FixedJointComponent::FixedJointComponent(const GenericJointConfiguration& config)
-        : JointComponent(config)
+    FixedJointComponent::FixedJointComponent(
+        const JointComponentConfiguration& configuration, 
+        const JointGenericProperties& genericProperties)
+        : JointComponent(configuration, genericProperties)
     {
     }
 
-    FixedJointComponent::FixedJointComponent(const GenericJointConfiguration& config,
-        const GenericJointLimitsConfiguration& limitConfig)
-        : JointComponent(config, limitConfig)
+    FixedJointComponent::FixedJointComponent(
+        const JointComponentConfiguration& configuration, 
+        const JointGenericProperties& genericProperties,
+        const JointLimitProperties& limitProperties)
+        : JointComponent(configuration, genericProperties, limitProperties)
     {
     }
 
     void FixedJointComponent::InitNativeJoint()
     {
-        if (m_joint)
+        if (m_jointHandle != AzPhysics::InvalidJointHandle)
         {
             return;
         }
@@ -57,14 +64,23 @@ namespace PhysX
         {
             return;
         }
-        PHYSX_SCENE_READ_LOCK(leadFollowerInfo.m_followerActor->getScene());
-        m_joint = AZStd::make_shared<FixedJoint>(physx::PxFixedJointCreate(
-            PxGetPhysics(),
-            leadFollowerInfo.m_leadActor,
-            leadFollowerInfo.m_leadLocal,
-            leadFollowerInfo.m_followerActor,
-            leadFollowerInfo.m_followerLocal),
-            leadFollowerInfo.m_leadBody,
-            leadFollowerInfo.m_followerBody);
+
+        FixedJointConfiguration configuration;
+        configuration.m_parentLocalPosition = leadFollowerInfo.m_leadLocal.GetTranslation();
+        configuration.m_parentLocalRotation = leadFollowerInfo.m_leadLocal.GetRotation();
+        configuration.m_childLocalPosition = leadFollowerInfo.m_followerLocal.GetTranslation();
+        configuration.m_childLocalRotation = leadFollowerInfo.m_followerLocal.GetRotation();
+
+        configuration.m_genericProperties = m_genericProperties;
+
+        if (auto* sceneInterface = AZ::Interface<AzPhysics::SceneInterface>::Get())
+        {
+            m_jointHandle = sceneInterface->AddJoint(
+                leadFollowerInfo.m_followerBody->m_sceneOwner,
+                &configuration,  
+                leadFollowerInfo.m_leadBody->m_bodyHandle, 
+                leadFollowerInfo.m_followerBody->m_bodyHandle);
+            m_jointSceneOwner = leadFollowerInfo.m_followerBody->m_sceneOwner;
+        }
     }
 } // namespace PhysX

+ 7 - 3
Gems/PhysX/Code/Source/FixedJointComponent.h

@@ -25,9 +25,13 @@ namespace PhysX
         static void Reflect(AZ::ReflectContext* context);
 
         FixedJointComponent() = default;
-        explicit FixedJointComponent(const GenericJointConfiguration& config);
-        FixedJointComponent(const GenericJointConfiguration& config, 
-            const GenericJointLimitsConfiguration& limitConfig);
+        FixedJointComponent(
+            const JointComponentConfiguration& configuration, 
+            const JointGenericProperties& genericProperties);
+        FixedJointComponent(
+            const JointComponentConfiguration& configuration, 
+            const JointGenericProperties& genericProperties,
+            const JointLimitProperties& limitProperties);
         ~FixedJointComponent() = default;
 
     protected:

+ 23 - 43
Gems/PhysX/Code/Source/HingeJointComponent.cpp

@@ -16,8 +16,10 @@
 #include <PhysX/MathConversion.h>
 #include <PhysX/PhysXLocks.h>
 #include <AzCore/Component/TransformBus.h>
+#include <AzCore/Interface/Interface.h>
 #include <AzFramework/Physics/RigidBodyBus.h>
 #include <AzFramework/Physics/SimulatedBodies/RigidBody.h>
+#include <AzFramework/Physics/PhysicsScene.h>
 
 #include <PxPhysicsAPI.h>
 
@@ -34,67 +36,45 @@ namespace PhysX
         }
     }
 
-    HingeJointComponent::HingeJointComponent(const GenericJointConfiguration& config
-        , const GenericJointLimitsConfiguration& angularLimitConfig)
-            : JointComponent(config, angularLimitConfig)
+    HingeJointComponent::HingeJointComponent(
+        const JointComponentConfiguration& configuration, 
+        const JointGenericProperties& genericProperties,
+        const JointLimitProperties& limitProperties)
+        : JointComponent(configuration, genericProperties, limitProperties)
     {
     }
 
     void HingeJointComponent::InitNativeJoint()
     {
-        if (m_joint)
+        if (m_jointHandle != AzPhysics::InvalidJointHandle)
         {
             return;
         }
         
         JointComponent::LeadFollowerInfo leadFollowerInfo;
         ObtainLeadFollowerInfo(leadFollowerInfo);
-        if (!leadFollowerInfo.m_followerActor)
+        if (leadFollowerInfo.m_followerActor == nullptr ||
+            leadFollowerInfo.m_leadBody == nullptr ||
+            leadFollowerInfo.m_followerBody == nullptr)
         {
             return;
         }
-        PHYSX_SCENE_READ_LOCK(leadFollowerInfo.m_followerActor->getScene());
-        m_joint = AZStd::make_shared<HingeJoint>(physx::PxRevoluteJointCreate(
-            PxGetPhysics(),
-            leadFollowerInfo.m_leadActor,
-            leadFollowerInfo.m_leadLocal,
-            leadFollowerInfo.m_followerActor,
-            leadFollowerInfo.m_followerLocal),
-            leadFollowerInfo.m_leadBody,
-            leadFollowerInfo.m_followerBody);
 
-        InitAngularLimits();
-    }
-
-    void HingeJointComponent::InitAngularLimits()
-    {
-        if (!m_joint)
-        {
-            return;
-        }
-
-        physx::PxRevoluteJoint* revoluteJointNative = static_cast<physx::PxRevoluteJoint*>(m_joint->GetNativePointer());
-        if (!revoluteJointNative)
-        {
-            return;
-        }
+        HingeJointConfiguration configuration;
+        configuration.m_parentLocalPosition = leadFollowerInfo.m_leadLocal.GetTranslation();
+        configuration.m_parentLocalRotation = leadFollowerInfo.m_leadLocal.GetRotation();
+        configuration.m_childLocalPosition = leadFollowerInfo.m_followerLocal.GetTranslation();
+        configuration.m_childLocalRotation = leadFollowerInfo.m_followerLocal.GetRotation();
 
-        if (!m_limits.m_isLimited)
-        {
-            revoluteJointNative->setRevoluteJointFlag(physx::PxRevoluteJointFlag::eLIMIT_ENABLED, false);
-            return;
-        }
+        configuration.m_genericProperties = m_genericProperties;
+        configuration.m_limitProperties = m_limits;
 
-        physx::PxJointAngularLimitPair limitPair(AZ::DegToRad(m_limits.m_limitSecond)
-            , AZ::DegToRad(m_limits.m_limitFirst)
-            , m_limits.m_tolerance);
-        if (m_limits.m_isSoftLimit)
+        if (auto* sceneInterface = AZ::Interface<AzPhysics::SceneInterface>::Get())
         {
-            limitPair.stiffness = m_limits.m_stiffness;
-            limitPair.damping = m_limits.m_damping;
+            m_jointHandle = sceneInterface->AddJoint(
+                leadFollowerInfo.m_followerBody->m_sceneOwner, &configuration, leadFollowerInfo.m_leadBody->m_bodyHandle,
+                leadFollowerInfo.m_followerBody->m_bodyHandle);
+            m_jointSceneOwner = leadFollowerInfo.m_followerBody->m_sceneOwner;
         }
-
-        revoluteJointNative->setLimit(limitPair);
-        revoluteJointNative->setRevoluteJointFlag(physx::PxRevoluteJointFlag::eLIMIT_ENABLED, true);
     }
 } // namespace PhysX

+ 4 - 5
Gems/PhysX/Code/Source/HingeJointComponent.h

@@ -25,15 +25,14 @@ namespace PhysX
         static void Reflect(AZ::ReflectContext* context);
 
         HingeJointComponent() = default;
-        explicit HingeJointComponent(const GenericJointConfiguration& config
-            , const GenericJointLimitsConfiguration& angularLimit);
+        HingeJointComponent(
+            const JointComponentConfiguration& configuration, 
+            const JointGenericProperties& genericProperties,
+            const JointLimitProperties& limitProperties);
         ~HingeJointComponent() = default;
 
     protected:
         // JointComponent
         void InitNativeJoint() override;
-
-    private:
-        void InitAngularLimits();
     };
 } // namespace PhysX

+ 0 - 646
Gems/PhysX/Code/Source/Joint.cpp

@@ -1,646 +0,0 @@
-/*
-* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
-* its licensors.
-*
-* For complete copyright and license terms please see the LICENSE at the root of this
-* distribution (the "License"). All use of this software is governed by the License,
-* or, if provided, by the license below or the license accompanying this file. Do not
-* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-*
-*/
-
-#include <PhysX_precompiled.h>
-#include <Joint.h>
-#include <AzCore/Serialization/EditContext.h>
-#include <Include/PhysX/NativeTypeIdentifiers.h>
-#include <AzCore/Math/MathUtils.h>
-#include <PhysX/PhysXLocks.h>
-#include <AzFramework/Physics/Common/PhysicsSimulatedBody.h>
-
-namespace PhysX
-{
-    namespace JointConstants
-    {
-        // Setting swing limits to very small values can cause extreme stability problems, so clamp above a small
-        // threshold.
-        static const float MinSwingLimitDegrees = 1.0f;
-    } // namespace JointConstants
-
-    AzPhysics::SimulatedBody* Joint::GetParentBody() const
-    {
-        return m_parentBody;
-    }
-
-    AzPhysics::SimulatedBody* Joint::GetChildBody() const
-    {
-        return m_childBody;
-    }
-
-    bool IsAtLeastOneDynamic(AzPhysics::SimulatedBody* body0,
-        AzPhysics::SimulatedBody* body1)
-    {
-        for (const AzPhysics::SimulatedBody* body : { body0, body1 })
-        {
-            if (body)
-            {
-                if (body->GetNativeType() == NativeTypeIdentifiers::RigidBody ||
-                    body->GetNativeType() == NativeTypeIdentifiers::ArticulationLink)
-                {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
-    physx::PxRigidActor* GetPxRigidActor(AzPhysics::SimulatedBody* worldBody)
-    {
-        if (worldBody && static_cast<physx::PxBase*>(worldBody->GetNativePointer())->is<physx::PxRigidActor>())
-        {
-            return static_cast<physx::PxRigidActor*>(worldBody->GetNativePointer());
-        }
-
-        return nullptr;
-    }
-
-    void releasePxJoint(physx::PxJoint* joint)
-    {
-        PHYSX_SCENE_WRITE_LOCK(joint->getScene());
-        joint->userData = nullptr;
-        joint->release();
-    }
-
-    Joint::Joint(physx::PxJoint* pxJoint, AzPhysics::SimulatedBody* parentBody,
-        AzPhysics::SimulatedBody* childBody)
-        : m_parentBody(parentBody)
-        , m_childBody(childBody)
-    {
-        m_pxJoint = PxJointUniquePtr(pxJoint, releasePxJoint);
-    }
-
-    bool Joint::SetPxActors()
-    {
-        physx::PxRigidActor* parentActor = GetPxRigidActor(m_parentBody);
-        physx::PxRigidActor* childActor = GetPxRigidActor(m_childBody);
-        if (!parentActor && !childActor)
-        {
-            AZ_Error("PhysX Joint", false, "Invalid PhysX actors in joint - at least one must be a PxRigidActor.");
-            return false;
-        }
-
-        m_pxJoint->setActors(parentActor, childActor);
-        return true;
-    }
-
-    void Joint::SetParentBody(AzPhysics::SimulatedBody* parentBody)
-    {
-        if (IsAtLeastOneDynamic(parentBody, m_childBody))
-        {
-            m_parentBody = parentBody;
-            SetPxActors();
-        }
-        else
-        {
-            AZ_Warning("PhysX Joint", false, "Call to SetParentBody would result in invalid joint - at least one "
-                "body in a joint must be dynamic.");
-        }
-    }
-
-    void Joint::SetChildBody(AzPhysics::SimulatedBody* childBody)
-    {
-        if (IsAtLeastOneDynamic(m_parentBody, childBody))
-        {
-            m_childBody = childBody;
-            SetPxActors();
-        }
-        else
-        {
-            AZ_Warning("PhysX Joint", false, "Call to SetChildBody would result in invalid joint - at least one "
-                "body in a joint must be dynamic.");
-        }
-    }
-
-    const AZStd::string& Joint::GetName() const
-    {
-        return m_name;
-    }
-
-    void Joint::SetName(const AZStd::string& name)
-    {
-        m_name = name;
-    }
-
-    void* Joint::GetNativePointer()
-    {
-        return m_pxJoint.get();
-    }
-
-    const AZ::Crc32 D6Joint::GetNativeType() const
-    {
-        return NativeTypeIdentifiers::D6Joint;
-    }
-
-    void D6Joint::GenerateJointLimitVisualizationData(
-        float scale,
-        AZ::u32 angularSubdivisions,
-        AZ::u32 radialSubdivisions,
-        [[maybe_unused]] AZStd::vector<AZ::Vector3>& vertexBufferOut,
-        [[maybe_unused]] AZStd::vector<AZ::u32>& indexBufferOut,
-        AZStd::vector<AZ::Vector3>& lineBufferOut,
-        AZStd::vector<bool>& lineValidityBufferOut)
-    {
-        const AZ::u32 angularSubdivisionsClamped = AZ::GetClamp(angularSubdivisions, 4u, 32u);
-        const AZ::u32 radialSubdivisionsClamped = AZ::GetClamp(radialSubdivisions, 1u, 4u);
-
-        const physx::PxD6Joint* joint = static_cast<physx::PxD6Joint*>(m_pxJoint.get());
-        const AZ::Quaternion parentLocalRotation = PxMathConvert(joint->getLocalPose(physx::PxJointActorIndex::eACTOR0).q);
-        const AZ::Quaternion parentWorldRotation = m_parentBody ? m_parentBody->GetOrientation() : AZ::Quaternion::CreateIdentity();
-        const AZ::Quaternion childLocalRotation = PxMathConvert(joint->getLocalPose(physx::PxJointActorIndex::eACTOR1).q);
-        const AZ::Quaternion childWorldRotation = m_childBody ? m_childBody->GetOrientation() : AZ::Quaternion::CreateIdentity();
-
-        const float swingAngleY = joint->getSwingYAngle();
-        const float swingAngleZ = joint->getSwingZAngle();
-        const float swingLimitY = joint->getSwingLimit().yAngle;
-        const float swingLimitZ = joint->getSwingLimit().zAngle;
-        const float twistAngle = joint->getTwist();
-        const float twistLimitLower = joint->getTwistLimit().lower;
-        const float twistLimitUpper = joint->getTwistLimit().upper;
-
-        JointUtils::AppendD6SwingConeToLineBuffer(parentLocalRotation, swingAngleY, swingAngleZ, swingLimitY, swingLimitZ,
-            scale, angularSubdivisionsClamped, radialSubdivisionsClamped, lineBufferOut, lineValidityBufferOut);
-        JointUtils::AppendD6TwistArcToLineBuffer(parentLocalRotation, twistAngle, twistLimitLower, twistLimitUpper,
-            scale, angularSubdivisionsClamped, radialSubdivisionsClamped, lineBufferOut, lineValidityBufferOut);
-        JointUtils::AppendD6CurrentTwistToLineBuffer(parentLocalRotation, twistAngle, twistLimitLower, twistLimitUpper,
-            scale, lineBufferOut, lineValidityBufferOut);
-
-        // draw the X-axis of the child joint frame
-        // make the axis slightly longer than the radius of the twist arc so that it is easy to see
-        float axisLength = 1.25f * scale;
-        AZ::Vector3 childAxis = (parentWorldRotation.GetConjugate() * childWorldRotation * childLocalRotation).TransformVector(
-            AZ::Vector3::CreateAxisX(axisLength));
-        lineBufferOut.push_back(AZ::Vector3::CreateZero());
-        lineBufferOut.push_back(childAxis);
-    }
-
-    const AZ::Crc32 FixedJoint::GetNativeType() const
-    {
-        return NativeTypeIdentifiers::FixedJoint;
-    }
-
-    const AZ::Crc32 HingeJoint::GetNativeType() const
-    {
-        return NativeTypeIdentifiers::HingeJoint;
-    }
-
-    const AZ::Crc32 BallJoint::GetNativeType() const
-    {
-        return NativeTypeIdentifiers::BallJoint;
-    }
-
-    void GenericJointConfiguration::Reflect(AZ::ReflectContext* context)
-    {
-        if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
-        {
-            serializeContext->Class<GenericJointConfiguration>()
-                ->Version(2, &VersionConverter)
-                ->Field("Follower Local Transform", &GenericJointConfiguration::m_localTransformFromFollower)
-                ->Field("Maximum Force", &GenericJointConfiguration::m_forceMax)
-                ->Field("Maximum Torque", &GenericJointConfiguration::m_torqueMax)
-                ->Field("Lead Entity", &GenericJointConfiguration::m_leadEntity)
-                ->Field("Follower Entity", &GenericJointConfiguration::m_followerEntity)
-                ->Field("Flags", &GenericJointConfiguration::m_flags)
-                ;
-        }
-    }
-
-    bool GenericJointConfiguration::VersionConverter(
-        AZ::SerializeContext& context,
-        AZ::SerializeContext::DataElementNode& classElement)
-    {
-        if (classElement.GetVersion() <= 1)
-        {
-            // Convert bool breakable to GenericJointConfiguration::GenericJointFlag
-            const int breakableElementIndex = classElement.FindElement(AZ_CRC("Breakable", 0xb274ecd4));
-            if (breakableElementIndex >= 0)
-            {
-                bool breakable = false;
-                AZ::SerializeContext::DataElementNode& breakableNode = classElement.GetSubElement(breakableElementIndex);
-                breakableNode.GetData(breakable);
-                if (!breakableNode.GetData<bool>(breakable))
-                {
-                    return false;
-                }
-                classElement.RemoveElement(breakableElementIndex);
-                GenericJointConfiguration::GenericJointFlag flags = breakable ? GenericJointConfiguration::GenericJointFlag::Breakable : GenericJointConfiguration::GenericJointFlag::None;
-                classElement.AddElementWithData(context, "Flags", flags);
-            }
-        }
-
-        return true;
-    }
-
-    GenericJointConfiguration::GenericJointConfiguration(float forceMax,
-        float torqueMax,
-        AZ::Transform localTransformFromFollower,
-        AZ::EntityId leadEntity,
-        AZ::EntityId followerEntity,
-        GenericJointFlag flags)
-        : m_forceMax(forceMax)
-        , m_torqueMax(torqueMax)
-        , m_localTransformFromFollower(localTransformFromFollower)
-        , m_leadEntity(leadEntity)
-        , m_followerEntity(followerEntity)
-        , m_flags(flags)
-    {
-    }
-
-    bool GenericJointConfiguration::GetFlag(GenericJointFlag flag)
-    {
-        return static_cast<bool>(m_flags & flag);
-    }
-
-    void GenericJointLimitsConfiguration::Reflect(AZ::ReflectContext* context)
-    {
-        if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
-        {
-            serializeContext->Class<GenericJointLimitsConfiguration>()
-                ->Version(1)
-                ->Field("First Limit", &GenericJointLimitsConfiguration::m_limitFirst)
-                ->Field("Second Limit", &GenericJointLimitsConfiguration::m_limitSecond)
-                ->Field("Tolerance", &GenericJointLimitsConfiguration::m_tolerance)
-                ->Field("Is Limited", &GenericJointLimitsConfiguration::m_isLimited)
-                ->Field("Is Soft Limit", &GenericJointLimitsConfiguration::m_isSoftLimit)
-                ->Field("Damping", &GenericJointLimitsConfiguration::m_damping)
-                ->Field("Spring", &GenericJointLimitsConfiguration::m_stiffness)
-                ;
-        }
-    }
-
-    GenericJointLimitsConfiguration::GenericJointLimitsConfiguration(float damping
-        , bool isLimited
-        , bool isSoftLimit
-        , float limitFirst
-        , float limitSecond
-        , float stiffness
-        , float tolerance)
-        : m_damping(damping)
-        , m_isLimited(isLimited)
-        , m_isSoftLimit(isSoftLimit)
-        , m_limitFirst(limitFirst)
-        , m_limitSecond(limitSecond)
-        , m_stiffness(stiffness)
-        , m_tolerance(tolerance)
-    {
-    }
-
-    AZStd::vector<AZ::TypeId> JointUtils::GetSupportedJointTypes()
-    {
-        return AZStd::vector<AZ::TypeId>
-        {
-            D6JointLimitConfiguration::RTTI_Type()
-        };
-    }
-
-    AZStd::shared_ptr<Physics::JointLimitConfiguration> JointUtils::CreateJointLimitConfiguration([[maybe_unused]] AZ::TypeId jointType)
-    {
-        return AZStd::make_shared<D6JointLimitConfiguration>();
-    }
-
-    AZStd::shared_ptr<Physics::Joint> JointUtils::CreateJoint(const AZStd::shared_ptr<Physics::JointLimitConfiguration>& configuration,
-        AzPhysics::SimulatedBody* parentBody, AzPhysics::SimulatedBody* childBody)
-    {
-        if (!configuration)
-        {
-            AZ_Warning("PhysX Joint", false, "CreateJoint failed - configuration was nullptr.");
-            return nullptr;
-        }
-
-        if (auto d6Config = AZStd::rtti_pointer_cast<D6JointLimitConfiguration>(configuration))
-        {
-            if (!IsAtLeastOneDynamic(parentBody, childBody))
-            {
-                AZ_Warning("PhysX Joint", false, "CreateJoint failed - at least one body must be dynamic.");
-                return nullptr;
-            }
-
-            physx::PxRigidActor* parentActor = GetPxRigidActor(parentBody);
-            physx::PxRigidActor* childActor = GetPxRigidActor(childBody);
-
-            if (!parentActor && !childActor)
-            {
-                AZ_Warning("PhysX Joint", false, "CreateJoint failed - at least one body must be a PxRigidActor.");
-                return nullptr;
-            }
-
-            const physx::PxTransform parentWorldTransform = parentActor ? parentActor->getGlobalPose() : physx::PxTransform(physx::PxIdentity);
-            const physx::PxTransform childWorldTransform = childActor ? childActor->getGlobalPose() : physx::PxTransform(physx::PxIdentity);
-            const physx::PxVec3 childOffset = childWorldTransform.p - parentWorldTransform.p;
-            physx::PxTransform parentLocalTransform(PxMathConvert(d6Config->m_parentLocalRotation).getNormalized());
-            const physx::PxTransform childLocalTransform(PxMathConvert(d6Config->m_childLocalRotation).getNormalized());
-            parentLocalTransform.p = parentWorldTransform.q.rotateInv(childOffset);
-
-            physx::PxD6Joint* joint = PxD6JointCreate(PxGetPhysics(), parentActor, parentLocalTransform,
-                childActor, childLocalTransform);
-
-            joint->setMotion(physx::PxD6Axis::eTWIST, physx::PxD6Motion::eLIMITED);
-            joint->setMotion(physx::PxD6Axis::eSWING1, physx::PxD6Motion::eLIMITED);
-            joint->setMotion(physx::PxD6Axis::eSWING2, physx::PxD6Motion::eLIMITED);
-
-            AZ_Warning("PhysX Joint",
-                d6Config->m_swingLimitY >= JointConstants::MinSwingLimitDegrees && d6Config->m_swingLimitZ >= JointConstants::MinSwingLimitDegrees,
-                "Very small swing limit requested for joint between \"%s\" and \"%s\", increasing to %f degrees to improve stability",
-                parentActor ? parentActor->getName() : "world", childActor ? childActor->getName() : "world",
-                JointConstants::MinSwingLimitDegrees);
-            float swingLimitY = AZ::DegToRad(AZ::GetMax(JointConstants::MinSwingLimitDegrees, d6Config->m_swingLimitY));
-            float swingLimitZ = AZ::DegToRad(AZ::GetMax(JointConstants::MinSwingLimitDegrees, d6Config->m_swingLimitZ));
-            physx::PxJointLimitCone limitCone(swingLimitY, swingLimitZ);
-            joint->setSwingLimit(limitCone);
-
-            float twistLower = AZ::DegToRad(AZStd::GetMin(d6Config->m_twistLimitLower, d6Config->m_twistLimitUpper));
-            float twistUpper = AZ::DegToRad(AZStd::GetMax(d6Config->m_twistLimitLower, d6Config->m_twistLimitUpper));
-            physx::PxJointAngularLimitPair twistLimitPair(twistLower, twistUpper);
-            joint->setTwistLimit(twistLimitPair);
-
-            return AZStd::make_shared<D6Joint>(joint, parentBody, childBody);
-        }
-        else
-        {
-            AZ_Warning("PhysX Joint", false, "Unrecognized joint limit configuration.");
-            return nullptr;
-        }
-    }
-
-    D6JointState JointUtils::CalculateD6JointState(
-        const AZ::Quaternion& parentWorldRotation,
-        const AZ::Quaternion& parentLocalRotation,
-        const AZ::Quaternion& childWorldRotation,
-        const AZ::Quaternion& childLocalRotation)
-    {
-        D6JointState result;
-
-        const AZ::Quaternion parentRotation = parentWorldRotation * parentLocalRotation;
-        const AZ::Quaternion childRotation = childWorldRotation * childLocalRotation;
-        const AZ::Quaternion relativeRotation = parentRotation.GetConjugate() * childRotation;
-        AZ::Quaternion twistQuat = AZ::IsClose(relativeRotation.GetX(), 0.0f, AZ::Constants::FloatEpsilon)
-            ? AZ::Quaternion::CreateIdentity()
-            : AZ::Quaternion(relativeRotation.GetX(), 0.0f, 0.0f, relativeRotation.GetW()).GetNormalized();
-        AZ::Quaternion swingQuat = relativeRotation * twistQuat.GetConjugate();
-
-        // make sure the twist angle has the correct sign for the rotation
-        twistQuat *= AZ::GetSign(twistQuat.GetX());
-        // make sure we get the shortest arcs for the swing degrees of freedom
-        swingQuat *= AZ::GetSign(swingQuat.GetW());
-        // the PhysX swing limits work in terms of tan quarter angles
-        result.m_swingAngleY = 4.0f * atan2f(swingQuat.GetY(), 1.0f + swingQuat.GetW());
-        result.m_swingAngleZ = 4.0f * atan2f(swingQuat.GetZ(), 1.0f + swingQuat.GetW());
-        const float twistAngle = twistQuat.GetAngle();
-        // GetAngle returns an angle in the range 0..2 pi, but the twist limits work in the range -pi..pi
-        const float wrappedTwistAngle = twistAngle > AZ::Constants::Pi ? twistAngle - AZ::Constants::TwoPi : twistAngle;
-        result.m_twistAngle = wrappedTwistAngle;
-
-        return result;
-    }
-
-    bool JointUtils::IsD6SwingValid(
-        float swingAngleY,
-        float swingAngleZ,
-        float swingLimitY,
-        float swingLimitZ)
-    {
-        const float epsilon = AZ::Constants::FloatEpsilon;
-        const float yFactor = tanf(0.25f * swingAngleY) / AZStd::GetMax(epsilon, tanf(0.25f * swingLimitY));
-        const float zFactor = tanf(0.25f * swingAngleZ) / AZStd::GetMax(epsilon, tanf(0.25f * swingLimitZ));
-
-        return (yFactor * yFactor + zFactor * zFactor <= 1.0f + epsilon);
-    }
-
-    void JointUtils::AppendD6SwingConeToLineBuffer(
-        const AZ::Quaternion& parentLocalRotation,
-        float swingAngleY,
-        float swingAngleZ,
-        float swingLimitY,
-        float swingLimitZ,
-        float scale,
-        AZ::u32 angularSubdivisions,
-        AZ::u32 radialSubdivisions,
-        AZStd::vector<AZ::Vector3>& lineBufferOut,
-        AZStd::vector<bool>& lineValidityBufferOut)
-    {
-        const AZ::u32 numLinesSwingCone = angularSubdivisions * (1u + radialSubdivisions);
-        lineBufferOut.reserve(lineBufferOut.size() + 2u * numLinesSwingCone);
-        lineValidityBufferOut.reserve(lineValidityBufferOut.size() + numLinesSwingCone);
-
-        // the orientation quat for a radial line in the cone can be represented in terms of sin and cos half angles
-        // these expressions can be efficiently calculated using tan quarter angles as follows:
-        // writing t = tan(x / 4)
-        // sin(x / 2) = 2 * t / (1 + t * t)
-        // cos(x / 2) = (1 - t * t) / (1 + t * t)
-        const float tanQuarterSwingZ = tanf(0.25f * swingLimitZ);
-        const float tanQuarterSwingY = tanf(0.25f * swingLimitY);
-
-        AZ::Vector3 previousRadialVector = AZ::Vector3::CreateZero();
-        for (AZ::u32 angularIndex = 0; angularIndex <= angularSubdivisions; angularIndex++)
-        {
-            const float angle = AZ::Constants::TwoPi / angularSubdivisions * angularIndex;
-            // the axis about which to rotate the x-axis to get the radial vector for this segment of the cone
-            const AZ::Vector3 rotationAxis(0, -tanQuarterSwingY * sinf(angle), tanQuarterSwingZ * cosf(angle));
-            const float normalizationFactor = rotationAxis.GetLengthSq();
-            const AZ::Quaternion radialVectorRotation = 1.0f / (1.0f + normalizationFactor) *
-                AZ::Quaternion::CreateFromVector3AndValue(2.0f * rotationAxis, 1.0f - normalizationFactor);
-            const AZ::Vector3 radialVector = (parentLocalRotation * radialVectorRotation).TransformVector(AZ::Vector3::CreateAxisX(scale));
-
-            if (angularIndex > 0)
-            {
-                for (AZ::u32 radialIndex = 1; radialIndex <= radialSubdivisions; radialIndex++)
-                {
-                    float radiusFraction = 1.0f / radialSubdivisions * radialIndex;
-                    lineBufferOut.push_back(radiusFraction * radialVector);
-                    lineBufferOut.push_back(radiusFraction * previousRadialVector);
-                }
-            }
-
-            if (angularIndex < angularSubdivisions)
-            {
-                lineBufferOut.push_back(AZ::Vector3::CreateZero());
-                lineBufferOut.push_back(radialVector);
-            }
-
-            previousRadialVector = radialVector;
-        }
-
-        const bool swingValid = IsD6SwingValid(swingAngleY, swingAngleZ, swingLimitY, swingLimitZ);
-        lineValidityBufferOut.insert(lineValidityBufferOut.end(), numLinesSwingCone, swingValid);
-    }
-
-    void JointUtils::AppendD6TwistArcToLineBuffer(
-        const AZ::Quaternion& parentLocalRotation,
-        float twistAngle,
-        float twistLimitLower,
-        float twistLimitUpper,
-        float scale,
-        AZ::u32 angularSubdivisions,
-        AZ::u32 radialSubdivisions,
-        AZStd::vector<AZ::Vector3>& lineBufferOut,
-        AZStd::vector<bool>& lineValidityBufferOut)
-    {
-        const AZ::u32 numLinesTwistArc = angularSubdivisions * (1u + radialSubdivisions) + 1u;
-        lineBufferOut.reserve(lineBufferOut.size() + 2u * numLinesTwistArc);
-
-        AZ::Vector3 previousRadialVector = AZ::Vector3::CreateZero();
-        const float twistRange = twistLimitUpper - twistLimitLower;
-
-        for (AZ::u32 angularIndex = 0; angularIndex <= angularSubdivisions; angularIndex++)
-        {
-            const float angle = twistLimitLower + twistRange / angularSubdivisions * angularIndex;
-            const AZ::Vector3 radialVector = parentLocalRotation.TransformVector(scale * AZ::Vector3(0.0f, cosf(angle), sinf(angle)));
-
-            if (angularIndex > 0)
-            {
-                for (AZ::u32 radialIndex = 1; radialIndex <= radialSubdivisions; radialIndex++)
-                {
-                    const float radiusFraction = 1.0f / radialSubdivisions * radialIndex;
-                    lineBufferOut.push_back(radiusFraction * radialVector);
-                    lineBufferOut.push_back(radiusFraction * previousRadialVector);
-                }
-            }
-
-            lineBufferOut.push_back(AZ::Vector3::CreateZero());
-            lineBufferOut.push_back(radialVector);
-
-            previousRadialVector = radialVector;
-        }
-
-        const bool twistValid = (twistAngle >= twistLimitLower && twistAngle <= twistLimitUpper);
-        lineValidityBufferOut.insert(lineValidityBufferOut.end(), numLinesTwistArc, twistValid);
-    }
-
-    void JointUtils::AppendD6CurrentTwistToLineBuffer(
-        const AZ::Quaternion& parentLocalRotation,
-        float twistAngle,
-        [[maybe_unused]] float twistLimitLower,
-        [[maybe_unused]] float twistLimitUpper,
-        float scale,
-        AZStd::vector<AZ::Vector3>& lineBufferOut,
-        AZStd::vector<bool>& lineValidityBufferOut
-    )
-    {
-        const AZ::Vector3 twistVector = parentLocalRotation.TransformVector(1.25f * scale * AZ::Vector3(0.0f, cosf(twistAngle), sinf(twistAngle)));
-        lineBufferOut.push_back(AZ::Vector3::CreateZero());
-        lineBufferOut.push_back(twistVector);
-        lineValidityBufferOut.push_back(true);
-    }
-
-    void JointUtils::GenerateJointLimitVisualizationData(
-        const Physics::JointLimitConfiguration& configuration,
-        const AZ::Quaternion& parentRotation,
-        const AZ::Quaternion& childRotation,
-        float scale,
-        AZ::u32 angularSubdivisions,
-        AZ::u32 radialSubdivisions,
-        [[maybe_unused]] AZStd::vector<AZ::Vector3>& vertexBufferOut,
-        [[maybe_unused]] AZStd::vector<AZ::u32>& indexBufferOut,
-        AZStd::vector<AZ::Vector3>& lineBufferOut,
-        AZStd::vector<bool>& lineValidityBufferOut)
-    {
-        if (const auto d6JointConfiguration = azrtti_cast<const D6JointLimitConfiguration*>(&configuration))
-        {
-            const AZ::u32 angularSubdivisionsClamped = AZ::GetClamp(angularSubdivisions, 4u, 32u);
-            const AZ::u32 radialSubdivisionsClamped = AZ::GetClamp(radialSubdivisions, 1u, 4u);
-
-            const D6JointState jointState = CalculateD6JointState(parentRotation, d6JointConfiguration->m_parentLocalRotation,
-                childRotation, d6JointConfiguration->m_childLocalRotation);
-            const float swingAngleY = jointState.m_swingAngleY;
-            const float swingAngleZ = jointState.m_swingAngleZ;
-            const float twistAngle = jointState.m_twistAngle;
-            const float swingLimitY = AZ::DegToRad(d6JointConfiguration->m_swingLimitY);
-            const float swingLimitZ = AZ::DegToRad(d6JointConfiguration->m_swingLimitZ);
-            const float twistLimitLower = AZ::DegToRad(d6JointConfiguration->m_twistLimitLower);
-            const float twistLimitUpper = AZ::DegToRad(d6JointConfiguration->m_twistLimitUpper);
-
-            AppendD6SwingConeToLineBuffer(d6JointConfiguration->m_parentLocalRotation, swingAngleY, swingAngleZ, swingLimitY,
-                swingLimitZ, scale, angularSubdivisionsClamped, radialSubdivisionsClamped, lineBufferOut, lineValidityBufferOut);
-            AppendD6TwistArcToLineBuffer(d6JointConfiguration->m_parentLocalRotation, twistAngle, twistLimitLower, twistLimitUpper,
-                scale, angularSubdivisionsClamped, radialSubdivisionsClamped, lineBufferOut, lineValidityBufferOut);
-            AppendD6CurrentTwistToLineBuffer(d6JointConfiguration->m_parentLocalRotation, twistAngle, twistLimitLower,
-                twistLimitUpper, scale, lineBufferOut, lineValidityBufferOut);
-        }
-    }
-
-    AZStd::unique_ptr<Physics::JointLimitConfiguration> JointUtils::ComputeInitialJointLimitConfiguration(
-        const AZ::TypeId& jointLimitTypeId,
-        const AZ::Quaternion& parentWorldRotation,
-        const AZ::Quaternion& childWorldRotation,
-        const AZ::Vector3& axis,
-        const AZStd::vector<AZ::Quaternion>& exampleLocalRotations)
-    {
-        AZ_UNUSED(exampleLocalRotations);
-
-        if (jointLimitTypeId == D6JointLimitConfiguration::RTTI_Type())
-        {
-            const AZ::Vector3& normalizedAxis = axis.IsZero()
-                ? AZ::Vector3::CreateAxisX()
-                : axis.GetNormalized();
-
-            D6JointLimitConfiguration d6JointLimitConfig;
-            const AZ::Quaternion childLocalRotation = AZ::Quaternion::CreateShortestArc(AZ::Vector3::CreateAxisX(),
-                childWorldRotation.GetConjugate().TransformVector(normalizedAxis));
-            d6JointLimitConfig.m_childLocalRotation = childLocalRotation;
-            d6JointLimitConfig.m_parentLocalRotation = parentWorldRotation.GetConjugate() * childWorldRotation * childLocalRotation;
-
-            return AZStd::make_unique<D6JointLimitConfiguration>(d6JointLimitConfig);
-        }
-
-        AZ_Warning("PhysX Joint Utils", false, "Unsupported joint type in ComputeInitialJointLimitConfiguration");
-        return nullptr;
-    }
-
-    const char* D6JointLimitConfiguration::GetTypeName()
-    {
-        return "D6 Joint";
-    }
-
-    void D6JointLimitConfiguration::Reflect(AZ::ReflectContext* context)
-    {
-        if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
-        {
-            serializeContext->Class<D6JointLimitConfiguration, Physics::JointLimitConfiguration>()
-                ->Version(1)
-                ->Field("SwingLimitY", &D6JointLimitConfiguration::m_swingLimitY)
-                ->Field("SwingLimitZ", &D6JointLimitConfiguration::m_swingLimitZ)
-                ->Field("TwistLowerLimit", &D6JointLimitConfiguration::m_twistLimitLower)
-                ->Field("TwistUpperLimit", &D6JointLimitConfiguration::m_twistLimitUpper)
-                ;
-
-            AZ::EditContext* editContext = serializeContext->GetEditContext();
-            if (editContext)
-            {
-                editContext->Class<D6JointLimitConfiguration>(
-                    "PhysX D6 Joint Configuration", "")
-                    ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
-                    ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
-                    ->DataElement(AZ::Edit::UIHandlers::Default, &D6JointLimitConfiguration::m_swingLimitY, "Swing limit Y",
-                        "Maximum angle from the Y axis of the joint frame")
-                    ->Attribute(AZ::Edit::Attributes::Suffix, " degrees")
-                    ->Attribute(AZ::Edit::Attributes::Min, JointConstants::MinSwingLimitDegrees)
-                    ->Attribute(AZ::Edit::Attributes::Max, 180.0f)
-                    ->DataElement(AZ::Edit::UIHandlers::Default, &D6JointLimitConfiguration::m_swingLimitZ, "Swing limit Z",
-                        "Maximum angle from the Z axis of the joint frame")
-                    ->Attribute(AZ::Edit::Attributes::Suffix, " degrees")
-                    ->Attribute(AZ::Edit::Attributes::Min, JointConstants::MinSwingLimitDegrees)
-                    ->Attribute(AZ::Edit::Attributes::Max, 180.0f)
-                    ->DataElement(AZ::Edit::UIHandlers::Default, &D6JointLimitConfiguration::m_twistLimitLower, "Twist lower limit",
-                        "Lower limit for rotation about the X axis of the joint frame")
-                    ->Attribute(AZ::Edit::Attributes::Suffix, " degrees")
-                    ->Attribute(AZ::Edit::Attributes::Min, -180.0f)
-                    ->Attribute(AZ::Edit::Attributes::Max, 180.0f)
-                    ->DataElement(AZ::Edit::UIHandlers::Default, &D6JointLimitConfiguration::m_twistLimitUpper, "Twist upper limit",
-                        "Upper limit for rotation about the X axis of the joint frame")
-                    ->Attribute(AZ::Edit::Attributes::Suffix, " degrees")
-                    ->Attribute(AZ::Edit::Attributes::Min, -180.0f)
-                    ->Attribute(AZ::Edit::Attributes::Max, 180.0f)
-                ;
-            }
-        }
-    }
-} // namespace PhysX

+ 0 - 311
Gems/PhysX/Code/Source/Joint.h

@@ -1,311 +0,0 @@
-/*
-* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
-* its licensors.
-*
-* For complete copyright and license terms please see the LICENSE at the root of this
-* distribution (the "License"). All use of this software is governed by the License,
-* or, if provided, by the license below or the license accompanying this file. Do not
-* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-*
-*/
-
-#pragma once
-
-#include <AzFramework/Physics/Joint.h>
-
-namespace AzPhysics
-{
-    struct SimulatedBody;
-}
-
-namespace PhysX
-{
-    class D6JointLimitConfiguration
-        : public Physics::JointLimitConfiguration
-    {
-    public:
-        AZ_CLASS_ALLOCATOR(D6JointLimitConfiguration, AZ::SystemAllocator, 0);
-        AZ_RTTI(D6JointLimitConfiguration, "{90C5C23D-16C0-4F23-AD50-A190E402388E}", Physics::JointLimitConfiguration);
-        static void Reflect(AZ::ReflectContext* context);
-
-        const char* GetTypeName() override;
-
-        float m_swingLimitY = 45.0f; ///< Maximum angle in degrees from the Y axis of the joint frame.
-        float m_swingLimitZ = 45.0f; ///< Maximum angle in degrees from the Z axis of the joint frame.
-        float m_twistLimitLower = -45.0f; ///< Lower limit in degrees for rotation about the X axis of the joint frame.
-        float m_twistLimitUpper = 45.0f; ///< Upper limit in degrees for rotation about the X axis of the joint frame.
-    };
-
-    class Joint
-        : public Physics::Joint
-    {
-    public:
-        AZ_CLASS_ALLOCATOR(Joint, AZ::SystemAllocator, 0);
-        AZ_RTTI(Joint, "{3C739E22-8EF0-419F-966B-C575A1F5A08B}", Physics::Joint);
-
-        Joint(physx::PxJoint* pxJoint, AzPhysics::SimulatedBody* parentBody,
-            AzPhysics::SimulatedBody* childBody);
-
-        virtual ~Joint() = default;
-
-        AzPhysics::SimulatedBody* GetParentBody() const override;
-        AzPhysics::SimulatedBody* GetChildBody() const override;
-        void SetParentBody(AzPhysics::SimulatedBody* parentBody) override;
-        void SetChildBody(AzPhysics::SimulatedBody* childBody) override;
-        const AZStd::string& GetName() const override;
-        void SetName(const AZStd::string& name) override;
-        void* GetNativePointer() override;
-
-    protected:
-        bool SetPxActors();
-
-        using PxJointUniquePtr = AZStd::unique_ptr<physx::PxJoint, AZStd::function<void(physx::PxJoint*)>>;
-        PxJointUniquePtr m_pxJoint;
-        AzPhysics::SimulatedBody* m_parentBody;
-        AzPhysics::SimulatedBody* m_childBody;
-        AZStd::string m_name;
-    };
-
-    class D6Joint
-        : public Joint
-    {
-    public:
-        AZ_CLASS_ALLOCATOR(D6Joint, AZ::SystemAllocator, 0);
-        AZ_RTTI(D6Joint, "{962C4044-2BD2-4E4C-913C-FB8E85A2A12A}", Joint);
-
-        D6Joint(physx::PxJoint* pxJoint, AzPhysics::SimulatedBody* parentBody,
-            AzPhysics::SimulatedBody* childBody)
-            : Joint(pxJoint, parentBody, childBody)
-        {
-        }
-        virtual ~D6Joint() = default;
-
-        const AZ::Crc32 GetNativeType() const override;
-        void GenerateJointLimitVisualizationData(
-            float scale,
-            AZ::u32 angularSubdivisions,
-            AZ::u32 radialSubdivisions,
-            AZStd::vector<AZ::Vector3>& vertexBufferOut,
-            AZStd::vector<AZ::u32>& indexBufferOut,
-            AZStd::vector<AZ::Vector3>& lineBufferOut,
-            AZStd::vector<bool>& lineValidityBufferOut) override;
-    };
-
-    struct D6JointState
-    {
-        float m_swingAngleY;
-        float m_swingAngleZ;
-        float m_twistAngle;
-    };
-
-    /// A fixed joint locks 2 bodies relative to one another on all axes of freedom.
-    class FixedJoint : public Joint
-    {
-    public:
-        AZ_CLASS_ALLOCATOR(FixedJoint, AZ::SystemAllocator, 0);
-        AZ_TYPE_INFO(FixedJoint, "{203FB99C-7DC5-478A-A52C-A1F2AAF61FB8}");
-
-        FixedJoint(physx::PxJoint* pxJoint, AzPhysics::SimulatedBody* parentBody,
-            AzPhysics::SimulatedBody* childBody)
-            : Joint(pxJoint, parentBody, childBody)
-        {
-        }
-
-        const AZ::Crc32 GetNativeType() const override;
-        void GenerateJointLimitVisualizationData(
-            float /*scale*/,
-            AZ::u32 /*angularSubdivisions*/,
-            AZ::u32 /*radialSubdivisions*/,
-            AZStd::vector<AZ::Vector3>& /*vertexBufferOut*/,
-            AZStd::vector<AZ::u32>& /*indexBufferOut*/,
-            AZStd::vector<AZ::Vector3>& /*lineBufferOut*/,
-            AZStd::vector<bool>& /*lineValidityBufferOut*/) override {}
-    };
-
-    /// A hinge joint locks 2 bodies relative to one another except about the x-axis of the joint between them.
-    class HingeJoint : public Joint
-    {
-    public:
-        AZ_CLASS_ALLOCATOR(HingeJoint, AZ::SystemAllocator, 0);
-        AZ_TYPE_INFO(HingeJoint, "{8EFF1002-B08C-47CE-883C-82F0CF3736E0}");
-
-        HingeJoint(physx::PxJoint* pxJoint, AzPhysics::SimulatedBody* parentBody,
-            AzPhysics::SimulatedBody* childBody)
-            : Joint(pxJoint, parentBody, childBody)
-        {
-        }
-
-        const AZ::Crc32 GetNativeType() const override;
-        void GenerateJointLimitVisualizationData(
-            float /*scale*/,
-            AZ::u32 /*angularSubdivisions*/,
-            AZ::u32 /*radialSubdivisions*/,
-            AZStd::vector<AZ::Vector3>& /*vertexBufferOut*/,
-            AZStd::vector<AZ::u32>& /*indexBufferOut*/,
-            AZStd::vector<AZ::Vector3>& /*lineBufferOut*/,
-            AZStd::vector<bool>& /*lineValidityBufferOut*/) override {}
-    };
-
-    /// A ball joint locks 2 bodies relative to one another except about the y and z axes of the joint between them.
-    class BallJoint : public Joint
-    {
-    public:
-        AZ_CLASS_ALLOCATOR(BallJoint, AZ::SystemAllocator, 0);
-        AZ_TYPE_INFO(BallJoint, "{9FADA1C2-0E2F-4E1B-9E83-6292A1606372}");
-
-        BallJoint(physx::PxJoint* pxJoint, AzPhysics::SimulatedBody* parentBody,
-            AzPhysics::SimulatedBody* childBody)
-            : Joint(pxJoint, parentBody, childBody)
-        {
-        }
-
-        const AZ::Crc32 GetNativeType() const override;
-        void GenerateJointLimitVisualizationData(
-            float /*scale*/,
-            AZ::u32 /*angularSubdivisions*/,
-            AZ::u32 /*radialSubdivisions*/,
-            AZStd::vector<AZ::Vector3>& /*vertexBufferOut*/,
-            AZStd::vector<AZ::u32>& /*indexBufferOut*/,
-            AZStd::vector<AZ::Vector3>& /*lineBufferOut*/,
-            AZStd::vector<bool>& /*lineValidityBufferOut*/) override {}
-    };
-
-    /// Common parameters for all physics joint types.
-    class GenericJointConfiguration
-    {
-    public:
-        enum class GenericJointFlag : AZ::u16
-        {
-            None = 0,
-            Breakable = 1,
-            SelfCollide = 1 << 1
-        };
-
-        AZ_CLASS_ALLOCATOR(GenericJointConfiguration, AZ::SystemAllocator, 0);
-        AZ_TYPE_INFO(GenericJointConfiguration, "{AB2E2F92-0248-48A8-9DDD-21284AF0C1DF}");
-        static void Reflect(AZ::ReflectContext* context);
-        static bool VersionConverter(
-            AZ::SerializeContext& context,
-            AZ::SerializeContext::DataElementNode& classElement);
-
-        GenericJointConfiguration() = default;
-        GenericJointConfiguration(float forceMax,
-            float torqueMax,
-            AZ::Transform localTransformFromFollower,
-            AZ::EntityId leadEntity,
-            AZ::EntityId followerEntity,
-            GenericJointFlag flags);
-
-        bool GetFlag(GenericJointFlag flag); ///< Returns if a particular flag is set as a bool.
-
-        GenericJointFlag m_flags = GenericJointFlag::None; ///< Flags that indicates if joint is breakable, self-colliding, etc.. Converting joint between breakable/non-breakable at game time is allowed.
-        float m_forceMax = 1.0f; ///< Max force joint can tolerate before breaking.
-        float m_torqueMax = 1.0f; ///< Max torque joint can tolerate before breaking.
-        AZ::EntityId m_leadEntity; ///< EntityID for entity containing body that is lead to this joint constraint.
-        AZ::EntityId m_followerEntity; ///< EntityID for entity containing body that is follower to this joint constraint.
-        AZ::Transform m_localTransformFromFollower; ///< Joint's location and orientation in the frame (coordinate system) of the follower entity.
-    };
-    AZ_DEFINE_ENUM_BITWISE_OPERATORS(PhysX::GenericJointConfiguration::GenericJointFlag)
-
-    /// Generic pair of limit values for joint types, e.g. a pair of angular values.
-    /// This is different from JointLimitConfiguration used in non-generic joints for character/ragdoll/animation.
-    class GenericJointLimitsConfiguration
-    {
-    public:
-        AZ_CLASS_ALLOCATOR(GenericJointLimitsConfiguration, AZ::SystemAllocator, 0);
-        AZ_TYPE_INFO(GenericJointLimitsConfiguration, "{9D129B49-F4E6-4F2A-B94D-AC2D6AC6CE02}");
-        static void Reflect(AZ::ReflectContext* context);
-
-        GenericJointLimitsConfiguration() = default;
-        GenericJointLimitsConfiguration(float damping
-            , bool isLimited
-            , bool isSoftLimit
-            , float limitFirst
-            , float limitSecond
-            , float stiffness
-            , float tolerance);
-
-        bool m_isLimited = true; ///< Specifies if limits are applied to the joint constraints. E.g. if the swing angles are limited.
-        bool m_isSoftLimit = false; ///< If limit is soft, spring and damping are used, otherwise tolerance is used. Converting between soft/hard limit at game time is allowed.
-        float m_damping = 20.0f; ///< The damping strength of the drive, the force proportional to the velocity error. Used if limit is soft.
-        float m_limitFirst = 45.0f; ///< Positive angle limit in the case of twist angle limits, Y-axis swing limit in the case of cone limits.
-        float m_limitSecond = 45.0f; ///< Negative angle limit in the case of twist angle limits, Z-axis swing limit in the case of cone limits.
-        float m_stiffness = 100.0f; ///< The spring strength of the drive, the force proportional to the position error. Used if limit is soft.
-        float m_tolerance = 0.1f; ///< Distance from the joint at which limits becomes enforced. Used if limit is hard.
-    };
-
-    class JointUtils
-    {
-    public:
-        static AZStd::vector<AZ::TypeId> GetSupportedJointTypes();
-
-        static AZStd::shared_ptr<Physics::JointLimitConfiguration> CreateJointLimitConfiguration(AZ::TypeId jointType);
-
-        static AZStd::shared_ptr<Physics::Joint> CreateJoint(const AZStd::shared_ptr<Physics::JointLimitConfiguration>& configuration,
-            AzPhysics::SimulatedBody* parentBody, AzPhysics::SimulatedBody* childBody);
-
-        static D6JointState CalculateD6JointState(
-            const AZ::Quaternion& parentWorldRotation,
-            const AZ::Quaternion& parentLocalRotation,
-            const AZ::Quaternion& childWorldRotation,
-            const AZ::Quaternion& childLocalRotation);
-
-        static bool IsD6SwingValid(
-            float swingAngleY,
-            float swingAngleZ,
-            float swingLimitY,
-            float swingLimitZ);
-
-        static void AppendD6SwingConeToLineBuffer(
-            const AZ::Quaternion& parentLocalRotation,
-            float swingAngleY,
-            float swingAngleZ,
-            float swingLimitY,
-            float swingLimitZ,
-            float scale,
-            AZ::u32 angularSubdivisions,
-            AZ::u32 radialSubdivisions,
-            AZStd::vector<AZ::Vector3>& lineBufferOut,
-            AZStd::vector<bool>& lineValidityBufferOut);
-
-        static void AppendD6TwistArcToLineBuffer(
-            const AZ::Quaternion& parentLocalRotation,
-            float twistAngle,
-            float twistLimitLower,
-            float twistLimitUpper,
-            float scale,
-            AZ::u32 angularSubdivisions,
-            AZ::u32 radialSubdivisions,
-            AZStd::vector<AZ::Vector3>& lineBufferOut,
-            AZStd::vector<bool>& lineValidityBufferOut);
-
-        static void AppendD6CurrentTwistToLineBuffer(
-            const AZ::Quaternion& parentLocalRotation,
-            float twistAngle,
-            float twistLimitLower,
-            float twistLimitUpper,
-            float scale,
-            AZStd::vector<AZ::Vector3>& lineBufferOut,
-            AZStd::vector<bool>& lineValidityBufferOut);
-
-        static void GenerateJointLimitVisualizationData(
-            const Physics::JointLimitConfiguration& configuration,
-            const AZ::Quaternion& parentRotation,
-            const AZ::Quaternion& childRotation,
-            float scale,
-            AZ::u32 angularSubdivisions,
-            AZ::u32 radialSubdivisions,
-            AZStd::vector<AZ::Vector3>& vertexBufferOut,
-            AZStd::vector<AZ::u32>& indexBufferOut,
-            AZStd::vector<AZ::Vector3>& lineBufferOut,
-            AZStd::vector<bool>& lineValidityBufferOut);
-
-        static AZStd::unique_ptr<Physics::JointLimitConfiguration> ComputeInitialJointLimitConfiguration(
-            const AZ::TypeId& jointLimitTypeId,
-            const AZ::Quaternion& parentWorldRotation,
-            const AZ::Quaternion& childWorldRotation,
-            const AZ::Vector3& axis,
-            const AZStd::vector<AZ::Quaternion>& exampleLocalRotations);
-    };
-} // namespace PhysX

+ 155 - 0
Gems/PhysX/Code/Source/Joint/Configuration/PhysXJointConfiguration.cpp

@@ -0,0 +1,155 @@
+/*
+* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
+* its licensors.
+*
+* For complete copyright and license terms please see the LICENSE at the root of this
+* distribution (the "License"). All use of this software is governed by the License,
+* or, if provided, by the license below or the license accompanying this file. Do not
+* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+*
+*/
+
+#include <PhysX_precompiled.h>
+#include <PhysX/Joint/Configuration/PhysXJointConfiguration.h>
+#include <AzCore/Serialization/EditContext.h>
+#include <AzFramework/Physics/Common/PhysicsSimulatedBody.h>
+#include <Joint/PhysXJointUtils.h>
+
+namespace PhysX
+{
+    JointGenericProperties::JointGenericProperties(GenericJointFlag flags, float forceMax, float torqueMax)
+        : m_flags(flags)
+        , m_forceMax(forceMax)
+        , m_torqueMax(torqueMax)
+    {
+
+    }
+
+    JointLimitProperties::JointLimitProperties(
+        bool isLimited, bool isSoftLimit, 
+        float damping, float limitFirst, float limitSecond, float stiffness, float tolerance)
+        : m_isLimited(isLimited)
+        , m_isSoftLimit(isSoftLimit)
+        , m_damping(damping)
+        , m_limitFirst(limitFirst)
+        , m_limitSecond(limitSecond)
+        , m_stiffness(stiffness)
+        , m_tolerance(tolerance)
+    {
+
+    }
+
+    bool JointGenericProperties::IsFlagSet(GenericJointFlag flag) const
+    {
+        return static_cast<bool>(m_flags & flag);
+    }
+
+    void D6JointLimitConfiguration::Reflect(AZ::ReflectContext* context)
+    {
+        if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
+        {
+            serializeContext->Class<D6JointLimitConfiguration, AzPhysics::JointConfiguration>()
+                ->Version(1)
+                ->Field("SwingLimitY", &D6JointLimitConfiguration::m_swingLimitY)
+                ->Field("SwingLimitZ", &D6JointLimitConfiguration::m_swingLimitZ)
+                ->Field("TwistLowerLimit", &D6JointLimitConfiguration::m_twistLimitLower)
+                ->Field("TwistUpperLimit", &D6JointLimitConfiguration::m_twistLimitUpper)
+                ;
+
+            if (AZ::EditContext* editContext = serializeContext->GetEditContext())
+            {
+                editContext->Class<D6JointLimitConfiguration>(
+                    "PhysX D6 Joint Configuration", "")
+                    ->ClassElement(AZ::Edit::ClassElements::EditorData, "")
+                    ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
+                    ->DataElement(AZ::Edit::UIHandlers::Default, &D6JointLimitConfiguration::m_swingLimitY, "Swing limit Y",
+                        "Maximum angle from the Y axis of the joint frame")
+                    ->Attribute(AZ::Edit::Attributes::Suffix, " degrees")
+                    ->Attribute(AZ::Edit::Attributes::Min, JointConstants::MinSwingLimitDegrees)
+                    ->Attribute(AZ::Edit::Attributes::Max, 180.0f)
+                    ->DataElement(AZ::Edit::UIHandlers::Default, &D6JointLimitConfiguration::m_swingLimitZ, "Swing limit Z",
+                        "Maximum angle from the Z axis of the joint frame")
+                    ->Attribute(AZ::Edit::Attributes::Suffix, " degrees")
+                    ->Attribute(AZ::Edit::Attributes::Min, JointConstants::MinSwingLimitDegrees)
+                    ->Attribute(AZ::Edit::Attributes::Max, 180.0f)
+                    ->DataElement(AZ::Edit::UIHandlers::Default, &D6JointLimitConfiguration::m_twistLimitLower, "Twist lower limit",
+                        "Lower limit for rotation about the X axis of the joint frame")
+                    ->Attribute(AZ::Edit::Attributes::Suffix, " degrees")
+                    ->Attribute(AZ::Edit::Attributes::Min, -180.0f)
+                    ->Attribute(AZ::Edit::Attributes::Max, 180.0f)
+                    ->DataElement(AZ::Edit::UIHandlers::Default, &D6JointLimitConfiguration::m_twistLimitUpper, "Twist upper limit",
+                        "Upper limit for rotation about the X axis of the joint frame")
+                    ->Attribute(AZ::Edit::Attributes::Suffix, " degrees")
+                    ->Attribute(AZ::Edit::Attributes::Min, -180.0f)
+                    ->Attribute(AZ::Edit::Attributes::Max, 180.0f)
+                ;
+            }
+        }
+    }
+
+    void JointGenericProperties::Reflect(AZ::ReflectContext* context)
+    {
+        if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
+        {
+            serializeContext->Class<JointGenericProperties>()
+                ->Version(1)
+                ->Field("Maximum Force", &JointGenericProperties::m_forceMax)
+                ->Field("Maximum Torque", &JointGenericProperties::m_torqueMax)
+                ->Field("Flags", &JointGenericProperties::m_flags)
+                ;
+        }
+    }
+
+    void JointLimitProperties::Reflect(AZ::ReflectContext* context)
+    {
+        if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
+        {
+            serializeContext->Class<JointLimitProperties>()
+                ->Version(1)
+                ->Field("First Limit", &JointLimitProperties::m_limitFirst)
+                ->Field("Second Limit", &JointLimitProperties::m_limitSecond)
+                ->Field("Tolerance", &JointLimitProperties::m_tolerance)
+                ->Field("Is Limited", &JointLimitProperties::m_isLimited)
+                ->Field("Is Soft Limit", &JointLimitProperties::m_isSoftLimit)
+                ->Field("Damping", &JointLimitProperties::m_damping)
+                ->Field("Spring", &JointLimitProperties::m_stiffness)
+                ;
+        }
+    }
+
+    void FixedJointConfiguration::Reflect(AZ::ReflectContext* context)
+    {
+        if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
+        {
+            serializeContext->Class<FixedJointConfiguration, AzPhysics::JointConfiguration>()
+                ->Version(1)
+                ->Field("Generic Properties", &FixedJointConfiguration::m_genericProperties)
+                ;
+        }
+    }
+
+    void BallJointConfiguration::Reflect(AZ::ReflectContext* context)
+    {
+        if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
+        {
+            serializeContext->Class<BallJointConfiguration, AzPhysics::JointConfiguration>()
+                ->Version(1)
+                ->Field("Generic Properties", &BallJointConfiguration::m_genericProperties)
+                ->Field("Limit Properties", &BallJointConfiguration::m_limitProperties)
+                ;
+        }
+    }
+    
+    void HingeJointConfiguration::Reflect(AZ::ReflectContext* context)
+    {
+        if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
+        {
+            serializeContext->Class<HingeJointConfiguration, AzPhysics::JointConfiguration>()
+                ->Version(1)
+                ->Field("Generic Properties", &HingeJointConfiguration::m_genericProperties)
+                ->Field("Limit Properties", &HingeJointConfiguration::m_limitProperties)
+                ;
+        }
+    }
+} // namespace PhysX

+ 200 - 0
Gems/PhysX/Code/Source/Joint/PhysXJoint.cpp

@@ -0,0 +1,200 @@
+/*
+* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
+* its licensors.
+*
+* For complete copyright and license terms please see the LICENSE at the root of this
+* distribution (the "License"). All use of this software is governed by the License,
+* or, if provided, by the license below or the license accompanying this file. Do not
+* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+*
+*/
+
+#include <PhysX_precompiled.h>
+#include <Joint/PhysXJoint.h>
+#include <AzCore/Serialization/EditContext.h>
+#include <Include/PhysX/NativeTypeIdentifiers.h>
+#include <AzCore/Math/MathUtils.h>
+#include <AzFramework/Physics/Common/PhysicsSimulatedBody.h>
+#include <Source/Joint/PhysXJointUtils.h>
+
+namespace PhysX
+{
+    AzPhysics::SimulatedBodyHandle PhysXJoint::GetParentBodyHandle() const
+    {
+        return m_parentBodyHandle;
+    }
+
+    AzPhysics::SimulatedBodyHandle PhysXJoint::GetChildBodyHandle() const
+    {
+        return m_childBodyHandle;
+    }
+
+    PhysXJoint::PhysXJoint(
+        AzPhysics::SceneHandle sceneHandle,
+        AzPhysics::SimulatedBodyHandle parentBodyHandle,
+        AzPhysics::SimulatedBodyHandle childBodyHandle)
+        : m_sceneHandle(sceneHandle)
+        , m_parentBodyHandle(parentBodyHandle)
+        , m_childBodyHandle(childBodyHandle)
+    {
+
+    }
+
+    bool PhysXJoint::SetPxActors()
+    {
+        physx::PxRigidActor* parentActor = Utils::GetPxRigidActor(m_sceneHandle, m_parentBodyHandle);
+        physx::PxRigidActor* childActor = Utils::GetPxRigidActor(m_sceneHandle, m_childBodyHandle);
+        if (!parentActor && !childActor)
+        {
+            AZ_Error("PhysX Joint", false, "Invalid PhysX actors in joint - at least one must be a PxRigidActor.");
+            return false;
+        }
+
+        m_pxJoint->setActors(parentActor, childActor);
+        return true;
+    }
+
+    void PhysXJoint::SetParentBody(AzPhysics::SimulatedBodyHandle parentBodyHandle)
+    {
+        auto* parentBody = Utils::GetSimulatedBodyFromHandle(m_sceneHandle, parentBodyHandle);
+        auto* childBody = Utils::GetSimulatedBodyFromHandle(m_sceneHandle, m_childBodyHandle);
+
+        if (Utils::IsAtLeastOneDynamic(parentBody, childBody))
+        {
+            m_parentBodyHandle = parentBodyHandle;
+            SetPxActors();
+        }
+        else
+        {
+            AZ_Warning("PhysX Joint", false, "Call to SetParentBody would result in invalid joint - at least one "
+                "body in a joint must be dynamic.");
+        }
+    }
+
+    void PhysXJoint::SetChildBody(AzPhysics::SimulatedBodyHandle childBodyHandle)
+    {
+        auto* parentBody = Utils::GetSimulatedBodyFromHandle(m_sceneHandle, m_parentBodyHandle);
+        auto* childBody = Utils::GetSimulatedBodyFromHandle(m_sceneHandle, childBodyHandle);
+
+        if (Utils::IsAtLeastOneDynamic(parentBody, childBody))
+        {
+            m_childBodyHandle = childBodyHandle;
+            SetPxActors();
+        }
+        else
+        {
+            AZ_Warning("PhysX Joint", false, "Call to SetChildBody would result in invalid joint - at least one "
+                "body in a joint must be dynamic.");
+        }
+    }
+
+    void* PhysXJoint::GetNativePointer() const
+    {
+        return m_pxJoint.get();
+    }
+
+    PhysXD6Joint::PhysXD6Joint(const D6JointLimitConfiguration& configuration,
+        AzPhysics::SceneHandle sceneHandle,
+        AzPhysics::SimulatedBodyHandle parentBodyHandle,
+        AzPhysics::SimulatedBodyHandle childBodyHandle)
+        : PhysXJoint(sceneHandle, parentBodyHandle, childBodyHandle)
+    {
+        m_pxJoint = Utils::PxJointFactories::CreatePxD6Joint(configuration, sceneHandle, parentBodyHandle, childBodyHandle);
+    }
+
+    PhysXFixedJoint::PhysXFixedJoint(const FixedJointConfiguration& configuration,
+        AzPhysics::SceneHandle sceneHandle,
+        AzPhysics::SimulatedBodyHandle parentBodyHandle,
+        AzPhysics::SimulatedBodyHandle childBodyHandle)
+        : PhysXJoint(sceneHandle, parentBodyHandle, childBodyHandle)
+    {
+        m_pxJoint = Utils::PxJointFactories::CreatePxFixedJoint(configuration, sceneHandle, parentBodyHandle, childBodyHandle);
+    }
+
+    PhysXBallJoint::PhysXBallJoint(const BallJointConfiguration& configuration,
+        AzPhysics::SceneHandle sceneHandle,
+        AzPhysics::SimulatedBodyHandle parentBodyHandle,
+        AzPhysics::SimulatedBodyHandle childBodyHandle)
+        : PhysXJoint(sceneHandle, parentBodyHandle, childBodyHandle)
+    {
+        m_pxJoint = Utils::PxJointFactories::CreatePxBallJoint(configuration, sceneHandle, parentBodyHandle, childBodyHandle);
+    }
+
+    PhysXHingeJoint::PhysXHingeJoint(const HingeJointConfiguration& configuration,
+        AzPhysics::SceneHandle sceneHandle,
+        AzPhysics::SimulatedBodyHandle parentBodyHandle,
+        AzPhysics::SimulatedBodyHandle childBodyHandle)
+        : PhysXJoint(sceneHandle, parentBodyHandle, childBodyHandle)
+    {
+        m_pxJoint = Utils::PxJointFactories::CreatePxHingeJoint(configuration, sceneHandle, parentBodyHandle, childBodyHandle);
+    }
+
+    AZ::Crc32 PhysXD6Joint::GetNativeType() const
+    {
+        return NativeTypeIdentifiers::D6Joint;
+    }
+
+    AZ::Crc32 PhysXFixedJoint::GetNativeType() const
+    {
+        return NativeTypeIdentifiers::FixedJoint;
+    }
+
+    AZ::Crc32 PhysXBallJoint::GetNativeType() const
+    {
+        return NativeTypeIdentifiers::BallJoint;
+    }
+
+    AZ::Crc32 PhysXHingeJoint::GetNativeType() const
+    {
+        return NativeTypeIdentifiers::HingeJoint;
+    }
+
+    void PhysXD6Joint::GenerateJointLimitVisualizationData(
+        float scale,
+        AZ::u32 angularSubdivisions,
+        AZ::u32 radialSubdivisions,
+        [[maybe_unused]] AZStd::vector<AZ::Vector3>& vertexBufferOut,
+        [[maybe_unused]] AZStd::vector<AZ::u32>& indexBufferOut,
+        AZStd::vector<AZ::Vector3>& lineBufferOut,
+        AZStd::vector<bool>& lineValidityBufferOut)
+    {
+        auto* parentBody = Utils::GetSimulatedBodyFromHandle(m_sceneHandle, m_parentBodyHandle);
+        auto* childBody = Utils::GetSimulatedBodyFromHandle(m_sceneHandle, m_childBodyHandle);
+
+        const AZ::u32 angularSubdivisionsClamped = AZ::GetClamp(angularSubdivisions, 4u, 32u);
+        const AZ::u32 radialSubdivisionsClamped = AZ::GetClamp(radialSubdivisions, 1u, 4u);
+
+        const physx::PxD6Joint* joint = static_cast<physx::PxD6Joint*>(m_pxJoint.get());
+        const AZ::Quaternion parentLocalRotation = PxMathConvert(joint->getLocalPose(physx::PxJointActorIndex::eACTOR0).q);
+        const AZ::Quaternion parentWorldRotation = parentBody ? parentBody->GetOrientation() : AZ::Quaternion::CreateIdentity();
+        const AZ::Quaternion childLocalRotation = PxMathConvert(joint->getLocalPose(physx::PxJointActorIndex::eACTOR1).q);
+        const AZ::Quaternion childWorldRotation = childBody ? childBody->GetOrientation() : AZ::Quaternion::CreateIdentity();
+
+        const float swingAngleY = joint->getSwingYAngle();
+        const float swingAngleZ = joint->getSwingZAngle();
+        const float swingLimitY = joint->getSwingLimit().yAngle;
+        const float swingLimitZ = joint->getSwingLimit().zAngle;
+        const float twistAngle = joint->getTwist();
+        const float twistLimitLower = joint->getTwistLimit().lower;
+        const float twistLimitUpper = joint->getTwistLimit().upper;
+
+        Utils::Joints::AppendD6SwingConeToLineBuffer(
+            parentLocalRotation, swingAngleY, swingAngleZ, swingLimitY, swingLimitZ,
+            scale, angularSubdivisionsClamped, radialSubdivisionsClamped, lineBufferOut, lineValidityBufferOut);
+        Utils::Joints::AppendD6TwistArcToLineBuffer(
+            parentLocalRotation, twistAngle, twistLimitLower, twistLimitUpper,
+            scale, angularSubdivisionsClamped, radialSubdivisionsClamped, lineBufferOut, lineValidityBufferOut);
+        Utils::Joints::AppendD6CurrentTwistToLineBuffer(
+            parentLocalRotation, twistAngle, twistLimitLower, twistLimitUpper,
+            scale, lineBufferOut, lineValidityBufferOut);
+
+        // draw the X-axis of the child joint frame
+        // make the axis slightly longer than the radius of the twist arc so that it is easy to see
+        float axisLength = 1.25f * scale;
+        AZ::Vector3 childAxis = (parentWorldRotation.GetConjugate() * childWorldRotation * childLocalRotation).TransformVector(
+            AZ::Vector3::CreateAxisX(axisLength));
+        lineBufferOut.push_back(AZ::Vector3::CreateZero());
+        lineBufferOut.push_back(childAxis);
+    }
+} // namespace PhysX

+ 126 - 0
Gems/PhysX/Code/Source/Joint/PhysXJoint.h

@@ -0,0 +1,126 @@
+/*
+* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
+* its licensors.
+*
+* For complete copyright and license terms please see the LICENSE at the root of this
+* distribution (the "License"). All use of this software is governed by the License,
+* or, if provided, by the license below or the license accompanying this file. Do not
+* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+*
+*/
+
+#pragma once
+
+#include <AzFramework/Physics/Common/PhysicsJoint.h>
+#include <PhysX/Joint/Configuration/PhysXJointConfiguration.h>
+#include <Joint/PhysXJointUtils.h>
+
+namespace PhysX
+{
+    class PhysXJoint
+        : public AzPhysics::Joint
+    {
+    public:
+        AZ_CLASS_ALLOCATOR(PhysXJoint, AZ::SystemAllocator, 0);
+        AZ_RTTI(PhysXJoint, "{DBE1D185-E318-407D-A5A1-AC1DE7F4A62D}", AzPhysics::Joint);
+
+        PhysXJoint(
+            AzPhysics::SceneHandle sceneHandle,
+            AzPhysics::SimulatedBodyHandle parentBodyHandle,
+            AzPhysics::SimulatedBodyHandle childBodyHandle);
+
+        virtual ~PhysXJoint() = default;
+
+        AzPhysics::SimulatedBodyHandle GetParentBodyHandle() const override;
+        AzPhysics::SimulatedBodyHandle GetChildBodyHandle() const override;
+        void SetParentBody(AzPhysics::SimulatedBodyHandle parentBody) override;
+        void SetChildBody(AzPhysics::SimulatedBodyHandle childBody) override;
+        void* GetNativePointer() const override;
+
+    protected:
+        bool SetPxActors();
+
+        Utils::PxJointUniquePtr m_pxJoint;
+        AzPhysics::SceneHandle m_sceneHandle;
+        AzPhysics::SimulatedBodyHandle m_parentBodyHandle;
+        AzPhysics::SimulatedBodyHandle m_childBodyHandle;
+        AZStd::string m_name;
+    };
+
+    class PhysXD6Joint
+        : public PhysXJoint
+    {
+    public:
+        AZ_CLASS_ALLOCATOR(PhysXD6Joint, AZ::SystemAllocator, 0);
+        AZ_RTTI(PhysXD6Joint, "{144B2FAF-A3EE-4FE1-9328-2C44FE1E3676}", PhysX::PhysXJoint);
+
+        PhysXD6Joint(const D6JointLimitConfiguration& configuration,
+            AzPhysics::SceneHandle sceneHandle,
+            AzPhysics::SimulatedBodyHandle parentBodyHandle,
+            AzPhysics::SimulatedBodyHandle childBodyHandle);
+
+        virtual ~PhysXD6Joint() = default;
+
+        AZ::Crc32 GetNativeType() const override;
+        void GenerateJointLimitVisualizationData(
+            float scale,
+            AZ::u32 angularSubdivisions,
+            AZ::u32 radialSubdivisions,
+            AZStd::vector<AZ::Vector3>& vertexBufferOut,
+            AZStd::vector<AZ::u32>& indexBufferOut,
+            AZStd::vector<AZ::Vector3>& lineBufferOut,
+            AZStd::vector<bool>& lineValidityBufferOut) override;
+    };
+
+    //! A fixed joint locks 2 bodies relative to one another on all axes of freedom.
+    class PhysXFixedJoint : public PhysXJoint
+    {
+    public:
+        AZ_CLASS_ALLOCATOR(PhysXFixedJoint, AZ::SystemAllocator, 0);
+        AZ_RTTI(PhysXFixedJoint, "{B821D6D8-7B41-479D-9325-F9BC9754C5F8}", PhysX::PhysXJoint);
+
+        PhysXFixedJoint(const FixedJointConfiguration& configuration,
+            AzPhysics::SceneHandle sceneHandle,
+            AzPhysics::SimulatedBodyHandle parentBodyHandle,
+            AzPhysics::SimulatedBodyHandle childBodyHandle);
+
+        virtual ~PhysXFixedJoint() = default;
+
+        AZ::Crc32 GetNativeType() const override;
+    };
+
+    //! A ball joint locks 2 bodies relative to one another except about the y and z axes of the joint between them.
+    class PhysXBallJoint : public PhysXJoint
+    {
+    public:
+        AZ_CLASS_ALLOCATOR(PhysXBallJoint, AZ::SystemAllocator, 0);
+        AZ_RTTI(PhysXBallJoint, "{9494CE43-3AE2-40AB-ADF7-FDC5F8B0F15A}", PhysX::PhysXJoint);
+
+        PhysXBallJoint(const BallJointConfiguration& configuration,
+            AzPhysics::SceneHandle sceneHandle,
+            AzPhysics::SimulatedBodyHandle parentBodyHandle,
+            AzPhysics::SimulatedBodyHandle childBodyHandle);
+
+        virtual ~PhysXBallJoint() = default;
+
+        AZ::Crc32 GetNativeType() const override;
+    };
+
+    //! A hinge joint locks 2 bodies relative to one another except about the x-axis of the joint between them.
+    class PhysXHingeJoint : public PhysXJoint
+    {
+    public:
+        AZ_CLASS_ALLOCATOR(PhysXHingeJoint, AZ::SystemAllocator, 0);
+        AZ_RTTI(PhysXHingeJoint, "{9C5B955C-6C80-45FA-855D-DDA449C85313}", PhysX::PhysXJoint);
+
+        PhysXHingeJoint(const HingeJointConfiguration& configuration,
+            AzPhysics::SceneHandle sceneHandle,
+            AzPhysics::SimulatedBodyHandle parentBodyHandle,
+            AzPhysics::SimulatedBodyHandle childBodyHandle);
+
+        virtual ~PhysXHingeJoint() = default;
+
+        AZ::Crc32 GetNativeType() const override;
+    };
+} // namespace PhysX

+ 470 - 0
Gems/PhysX/Code/Source/Joint/PhysXJointUtils.cpp

@@ -0,0 +1,470 @@
+/*
+* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
+* its licensors.
+*
+* For complete copyright and license terms please see the LICENSE at the root of this
+* distribution (the "License"). All use of this software is governed by the License,
+* or, if provided, by the license below or the license accompanying this file. Do not
+* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+*
+*/
+
+#include <PhysX_precompiled.h>
+
+#include <AzFramework/Physics/PhysicsScene.h>
+#include <AzFramework/Physics/PhysicsSystem.h>
+#include <AzFramework/Physics/Common/PhysicsSimulatedBody.h>
+#include <AzCore/EBus/EBus.h>
+
+#include <PhysX/Joint/Configuration/PhysXJointConfiguration.h>
+#include <PhysX/PhysXLocks.h>
+#include <Source/Joint/PhysXJointUtils.h>
+#include <Include/PhysX/NativeTypeIdentifiers.h>
+
+namespace PhysX {
+    namespace Utils 
+    {
+        struct PxJointActorData 
+        {
+            static PxJointActorData InvalidPxJointActorData;
+
+            physx::PxRigidActor* parentActor = nullptr;
+            physx::PxRigidActor* childActor = nullptr;
+        };
+        PxJointActorData PxJointActorData::InvalidPxJointActorData;
+
+        PxJointActorData GetJointPxActors(
+            AzPhysics::SceneHandle sceneHandle,
+            AzPhysics::SimulatedBodyHandle parentBodyHandle,
+            AzPhysics::SimulatedBodyHandle childBodyHandle)
+        {
+            auto* parentBody = GetSimulatedBodyFromHandle(sceneHandle, parentBodyHandle);
+            auto* childBody = GetSimulatedBodyFromHandle(sceneHandle, childBodyHandle);
+
+            if (!IsAtLeastOneDynamic(parentBody, childBody))
+            {
+                AZ_Warning("PhysX Joint", false, "CreateJoint failed - at least one body must be dynamic.");
+                return PxJointActorData::InvalidPxJointActorData;
+            }
+
+            physx::PxRigidActor* parentActor = GetPxRigidActor(sceneHandle, parentBodyHandle);
+            physx::PxRigidActor* childActor = GetPxRigidActor(sceneHandle, childBodyHandle);
+
+            if (!parentActor && !childActor)
+            {
+                AZ_Warning("PhysX Joint", false, "CreateJoint failed - at least one body must be a PxRigidActor.");
+                return PxJointActorData::InvalidPxJointActorData;
+            }
+
+            return PxJointActorData{
+                parentActor,
+                childActor
+            };
+        }
+
+        bool IsAtLeastOneDynamic(AzPhysics::SimulatedBody* body0,
+            AzPhysics::SimulatedBody* body1)
+        {
+            for (const AzPhysics::SimulatedBody* body : { body0, body1 })
+            {
+                if (body)
+                {
+                    if (body->GetNativeType() == NativeTypeIdentifiers::RigidBody ||
+                        body->GetNativeType() == NativeTypeIdentifiers::ArticulationLink)
+                    {
+                        return true;
+                    }
+                }
+            }
+            return false;
+        }
+        
+        physx::PxRigidActor* GetPxRigidActor(AzPhysics::SceneHandle sceneHandle, AzPhysics::SimulatedBodyHandle worldBodyHandle)
+        {
+            auto* worldBody = GetSimulatedBodyFromHandle(sceneHandle, worldBodyHandle);
+            if (worldBody != nullptr 
+                && static_cast<physx::PxBase*>(worldBody->GetNativePointer())->is<physx::PxRigidActor>())
+            {
+                return static_cast<physx::PxRigidActor*>(worldBody->GetNativePointer());
+            }
+
+            return nullptr;
+        }
+
+        void ReleasePxJoint(physx::PxJoint* joint)
+        {
+            PHYSX_SCENE_WRITE_LOCK(joint->getScene());
+            joint->userData = nullptr;
+            joint->release();
+        }
+
+        AzPhysics::SimulatedBody* GetSimulatedBodyFromHandle(AzPhysics::SceneHandle sceneHandle,
+            AzPhysics::SimulatedBodyHandle bodyHandle) 
+        {
+            if (auto* sceneInterface = AZ::Interface<AzPhysics::SceneInterface>::Get())
+            {
+                return sceneInterface->GetSimulatedBodyFromHandle(sceneHandle, bodyHandle);
+            }
+            return nullptr;
+        }
+
+        void InitializeGenericProperties(const JointGenericProperties& properties, physx::PxJoint* nativeJoint) 
+        {
+            if (!nativeJoint)
+            {
+                return;
+            }
+            PHYSX_SCENE_WRITE_LOCK(nativeJoint->getScene());
+            nativeJoint->setConstraintFlag(
+                physx::PxConstraintFlag::eCOLLISION_ENABLED,
+                properties.IsFlagSet(JointGenericProperties::GenericJointFlag::SelfCollide));
+
+            if (properties.IsFlagSet(JointGenericProperties::GenericJointFlag::Breakable))
+            {
+                nativeJoint->setBreakForce(properties.m_forceMax, properties.m_torqueMax);
+            }
+        }
+
+        void InitializeSphericalLimitProperties(const JointLimitProperties& properties, physx::PxSphericalJoint* nativeJoint)
+        {
+            if (!nativeJoint)
+            {
+                return;
+            }
+
+            if (!properties.m_isLimited)
+            {
+                nativeJoint->setSphericalJointFlag(physx::PxSphericalJointFlag::eLIMIT_ENABLED, false);
+                return;
+            }
+
+            // Hard limit uses a tolerance value (distance to limit at which limit becomes active).
+            // Soft limit allows angle to exceed limit but springs back with configurable spring stiffness and damping.
+            physx::PxJointLimitCone swingLimit(
+                AZ::DegToRad(properties.m_limitFirst), 
+                AZ::DegToRad(properties.m_limitSecond), 
+                properties.m_tolerance);
+            
+            if (properties.m_isSoftLimit)
+            {
+                swingLimit.stiffness = properties.m_stiffness;
+                swingLimit.damping = properties.m_damping;
+            }
+
+            nativeJoint->setLimitCone(swingLimit);
+            nativeJoint->setSphericalJointFlag(physx::PxSphericalJointFlag::eLIMIT_ENABLED, true);
+        }
+
+        void InitializeRevoluteLimitProperties(const JointLimitProperties& properties, physx::PxRevoluteJoint* nativeJoint)
+        {
+            if (!nativeJoint)
+            {
+                return;
+            }
+
+            if (!properties.m_isLimited)
+            {
+                nativeJoint->setRevoluteJointFlag(physx::PxRevoluteJointFlag::eLIMIT_ENABLED, false);
+                return;
+            }
+
+            physx::PxJointAngularLimitPair limitPair(
+                AZ::DegToRad(properties.m_limitSecond), 
+                AZ::DegToRad(properties.m_limitFirst), 
+                properties.m_tolerance);
+
+            if (properties.m_isSoftLimit)
+            {
+                limitPair.stiffness = properties.m_stiffness;
+                limitPair.damping = properties.m_damping;
+            }
+
+            nativeJoint->setLimit(limitPair);
+            nativeJoint->setRevoluteJointFlag(physx::PxRevoluteJointFlag::eLIMIT_ENABLED, true);
+        }
+
+        namespace PxJointFactories
+        {   
+            PxJointUniquePtr CreatePxD6Joint(
+                const PhysX::D6JointLimitConfiguration& configuration,
+                AzPhysics::SceneHandle sceneHandle,
+                AzPhysics::SimulatedBodyHandle parentBodyHandle,
+                AzPhysics::SimulatedBodyHandle childBodyHandle) 
+            {
+                PxJointActorData actorData = GetJointPxActors(sceneHandle, parentBodyHandle, childBodyHandle);
+
+                if (!actorData.parentActor || !actorData.childActor)
+                {
+                    return nullptr;
+                }
+
+                const physx::PxTransform parentWorldTransform =
+                    actorData.parentActor ? actorData.parentActor->getGlobalPose() : physx::PxTransform(physx::PxIdentity);
+                const physx::PxTransform childWorldTransform =
+                    actorData.childActor ? actorData.childActor->getGlobalPose() : physx::PxTransform(physx::PxIdentity);
+                const physx::PxVec3 childOffset = childWorldTransform.p - parentWorldTransform.p;
+                physx::PxTransform parentLocalTransform(PxMathConvert(configuration.m_parentLocalRotation).getNormalized());
+                const physx::PxTransform childLocalTransform(PxMathConvert(configuration.m_childLocalRotation).getNormalized());
+                parentLocalTransform.p = parentWorldTransform.q.rotateInv(childOffset);
+
+                physx::PxD6Joint* joint = PxD6JointCreate(PxGetPhysics(), 
+                    actorData.parentActor, parentLocalTransform, actorData.childActor, childLocalTransform);
+
+                joint->setMotion(physx::PxD6Axis::eTWIST, physx::PxD6Motion::eLIMITED);
+                joint->setMotion(physx::PxD6Axis::eSWING1, physx::PxD6Motion::eLIMITED);
+                joint->setMotion(physx::PxD6Axis::eSWING2, physx::PxD6Motion::eLIMITED);
+
+                AZ_Warning("PhysX Joint",
+                    configuration.m_swingLimitY >= JointConstants::MinSwingLimitDegrees && configuration.m_swingLimitZ >= JointConstants::MinSwingLimitDegrees,
+                    "Very small swing limit requested for joint between \"%s\" and \"%s\", increasing to %f degrees to improve stability",
+                    actorData.parentActor ? actorData.parentActor->getName() : "world", 
+                    actorData.childActor ? actorData.childActor->getName() : "world",
+                    JointConstants::MinSwingLimitDegrees);
+                
+                const float swingLimitY = AZ::DegToRad(AZ::GetMax(JointConstants::MinSwingLimitDegrees, configuration.m_swingLimitY));
+                const float swingLimitZ = AZ::DegToRad(AZ::GetMax(JointConstants::MinSwingLimitDegrees, configuration.m_swingLimitZ));
+                physx::PxJointLimitCone limitCone(swingLimitY, swingLimitZ);
+                joint->setSwingLimit(limitCone);
+
+                const float twistLower = AZ::DegToRad(AZStd::GetMin(configuration.m_twistLimitLower, configuration.m_twistLimitUpper));
+                const float twistUpper = AZ::DegToRad(AZStd::GetMax(configuration.m_twistLimitLower, configuration.m_twistLimitUpper));
+                physx::PxJointAngularLimitPair twistLimitPair(twistLower, twistUpper);
+                joint->setTwistLimit(twistLimitPair);
+
+                return Utils::PxJointUniquePtr(joint, ReleasePxJoint);
+            }
+
+            PxJointUniquePtr CreatePxFixedJoint(
+                const PhysX::FixedJointConfiguration& configuration,
+                AzPhysics::SceneHandle sceneHandle,
+                AzPhysics::SimulatedBodyHandle parentBodyHandle,
+                AzPhysics::SimulatedBodyHandle childBodyHandle) 
+            {
+                PxJointActorData actorData = GetJointPxActors(sceneHandle, parentBodyHandle, childBodyHandle);
+
+                if (!actorData.parentActor || !actorData.childActor)
+                {
+                    return nullptr;
+                }
+
+                physx::PxFixedJoint* joint;
+                const AZ::Transform parentLocalTM = AZ::Transform::CreateFromQuaternionAndTranslation(
+                    configuration.m_parentLocalRotation, configuration.m_parentLocalPosition);
+                const AZ::Transform childLocalTM = AZ::Transform::CreateFromQuaternionAndTranslation(
+                    configuration.m_childLocalRotation, configuration.m_childLocalPosition);
+
+                {
+                    PHYSX_SCENE_READ_LOCK(actorData.childActor->getScene());
+                    joint = physx::PxFixedJointCreate(PxGetPhysics(), 
+                        actorData.parentActor, PxMathConvert(parentLocalTM),
+                        actorData.childActor, PxMathConvert(childLocalTM));
+                }
+
+                InitializeGenericProperties(
+                    configuration.m_genericProperties, 
+                    static_cast<physx::PxJoint*>(joint));
+
+                return Utils::PxJointUniquePtr(joint, ReleasePxJoint);
+            }
+
+            PxJointUniquePtr CreatePxBallJoint(
+                const PhysX::BallJointConfiguration& configuration,
+                AzPhysics::SceneHandle sceneHandle,
+                AzPhysics::SimulatedBodyHandle parentBodyHandle,
+                AzPhysics::SimulatedBodyHandle childBodyHandle) 
+            {
+                PxJointActorData actorData = GetJointPxActors(sceneHandle, parentBodyHandle, childBodyHandle);
+
+                if (!actorData.parentActor || !actorData.childActor)
+                {
+                    return nullptr;
+                }
+
+                physx::PxSphericalJoint* joint;
+                const AZ::Transform parentLocalTM = AZ::Transform::CreateFromQuaternionAndTranslation(
+                    configuration.m_parentLocalRotation, configuration.m_parentLocalPosition);
+                const AZ::Transform childLocalTM = AZ::Transform::CreateFromQuaternionAndTranslation(
+                    configuration.m_childLocalRotation, configuration.m_childLocalPosition);
+
+                {
+                    PHYSX_SCENE_READ_LOCK(actorData.childActor->getScene());
+                    joint = physx::PxSphericalJointCreate(PxGetPhysics(), 
+                        actorData.parentActor, PxMathConvert(parentLocalTM),
+                        actorData.childActor, PxMathConvert(childLocalTM));
+                }
+
+                InitializeSphericalLimitProperties(configuration.m_limitProperties, joint);
+                InitializeGenericProperties(
+                    configuration.m_genericProperties, 
+                    static_cast<physx::PxJoint*>(joint));
+
+                return Utils::PxJointUniquePtr(joint, ReleasePxJoint);
+            }
+
+            PxJointUniquePtr CreatePxHingeJoint(
+                const PhysX::HingeJointConfiguration& configuration,
+                AzPhysics::SceneHandle sceneHandle,
+                AzPhysics::SimulatedBodyHandle parentBodyHandle,
+                AzPhysics::SimulatedBodyHandle childBodyHandle) 
+            {
+                PxJointActorData actorData = GetJointPxActors(sceneHandle, parentBodyHandle, childBodyHandle);
+
+                if (!actorData.parentActor || !actorData.childActor)
+                {
+                    return nullptr;
+                }
+
+                physx::PxRevoluteJoint* joint;
+                const AZ::Transform parentLocalTM = AZ::Transform::CreateFromQuaternionAndTranslation(
+                    configuration.m_parentLocalRotation, configuration.m_parentLocalPosition);
+                const AZ::Transform childLocalTM = AZ::Transform::CreateFromQuaternionAndTranslation(
+                    configuration.m_childLocalRotation, configuration.m_childLocalPosition);
+
+                {
+                    PHYSX_SCENE_READ_LOCK(actorData.childActor->getScene());
+                    joint = physx::PxRevoluteJointCreate(PxGetPhysics(), 
+                        actorData.parentActor, PxMathConvert(parentLocalTM),
+                        actorData.childActor, PxMathConvert(childLocalTM));
+                }
+
+                InitializeRevoluteLimitProperties(configuration.m_limitProperties, joint);
+                InitializeGenericProperties(
+                    configuration.m_genericProperties, 
+                    static_cast<physx::PxJoint*>(joint));
+
+                return Utils::PxJointUniquePtr(joint, ReleasePxJoint);
+            }
+        } // namespace PxJointFactories
+
+        namespace Joints
+        {
+            bool IsD6SwingValid(float swingAngleY, float swingAngleZ, float swingLimitY, float swingLimitZ)
+            {
+                const float epsilon = AZ::Constants::FloatEpsilon;
+                const float yFactor = AZStd::tan(0.25f * swingAngleY) / AZStd::GetMax(epsilon, AZStd::tan(0.25f * swingLimitY));
+                const float zFactor = AZStd::tan(0.25f * swingAngleZ) / AZStd::GetMax(epsilon, AZStd::tan(0.25f * swingLimitZ));
+
+                return (yFactor * yFactor + zFactor * zFactor <= 1.0f + epsilon);
+            }
+
+            void AppendD6SwingConeToLineBuffer(
+                const AZ::Quaternion& parentLocalRotation,
+                float swingAngleY,
+                float swingAngleZ,
+                float swingLimitY,
+                float swingLimitZ,
+                float scale,
+                AZ::u32 angularSubdivisions,
+                AZ::u32 radialSubdivisions,
+                AZStd::vector<AZ::Vector3>& lineBufferOut,
+                AZStd::vector<bool>& lineValidityBufferOut)
+            {
+                const AZ::u32 numLinesSwingCone = angularSubdivisions * (1u + radialSubdivisions);
+                lineBufferOut.reserve(lineBufferOut.size() + 2u * numLinesSwingCone);
+                lineValidityBufferOut.reserve(lineValidityBufferOut.size() + numLinesSwingCone);
+
+                // the orientation quat for a radial line in the cone can be represented in terms of sin and cos half angles
+                // these expressions can be efficiently calculated using tan quarter angles as follows:
+                // writing t = tan(x / 4)
+                // sin(x / 2) = 2 * t / (1 + t * t)
+                // cos(x / 2) = (1 - t * t) / (1 + t * t)
+                const float tanQuarterSwingZ = AZStd::tan(0.25f * swingLimitZ);
+                const float tanQuarterSwingY = AZStd::tan(0.25f * swingLimitY);
+
+                AZ::Vector3 previousRadialVector = AZ::Vector3::CreateZero();
+                for (AZ::u32 angularIndex = 0; angularIndex <= angularSubdivisions; angularIndex++)
+                {
+                    const float angle = AZ::Constants::TwoPi / angularSubdivisions * angularIndex;
+                    // the axis about which to rotate the x-axis to get the radial vector for this segment of the cone
+                    const AZ::Vector3 rotationAxis(0, -tanQuarterSwingY * sinf(angle), tanQuarterSwingZ * cosf(angle));
+                    const float normalizationFactor = rotationAxis.GetLengthSq();
+                    const AZ::Quaternion radialVectorRotation = 1.0f / (1.0f + normalizationFactor) *
+                        AZ::Quaternion::CreateFromVector3AndValue(2.0f * rotationAxis, 1.0f - normalizationFactor);
+                    const AZ::Vector3 radialVector =
+                        (parentLocalRotation * radialVectorRotation).TransformVector(AZ::Vector3::CreateAxisX(scale));
+
+                    if (angularIndex > 0)
+                    {
+                        for (AZ::u32 radialIndex = 1; radialIndex <= radialSubdivisions; radialIndex++)
+                        {
+                            float radiusFraction = 1.0f / radialSubdivisions * radialIndex;
+                            lineBufferOut.push_back(radiusFraction * radialVector);
+                            lineBufferOut.push_back(radiusFraction * previousRadialVector);
+                        }
+                    }
+
+                    if (angularIndex < angularSubdivisions)
+                    {
+                        lineBufferOut.push_back(AZ::Vector3::CreateZero());
+                        lineBufferOut.push_back(radialVector);
+                    }
+
+                    previousRadialVector = radialVector;
+                }
+
+                const bool swingValid = IsD6SwingValid(swingAngleY, swingAngleZ, swingLimitY, swingLimitZ);
+                lineValidityBufferOut.insert(lineValidityBufferOut.end(), numLinesSwingCone, swingValid);
+            }
+
+            void AppendD6TwistArcToLineBuffer(
+                const AZ::Quaternion& parentLocalRotation,
+                float twistAngle,
+                float twistLimitLower,
+                float twistLimitUpper,
+                float scale,
+                AZ::u32 angularSubdivisions,
+                AZ::u32 radialSubdivisions,
+                AZStd::vector<AZ::Vector3>& lineBufferOut,
+                AZStd::vector<bool>& lineValidityBufferOut)
+            {
+                const AZ::u32 numLinesTwistArc = angularSubdivisions * (1u + radialSubdivisions) + 1u;
+                lineBufferOut.reserve(lineBufferOut.size() + 2u * numLinesTwistArc);
+
+                AZ::Vector3 previousRadialVector = AZ::Vector3::CreateZero();
+                const float twistRange = twistLimitUpper - twistLimitLower;
+
+                for (AZ::u32 angularIndex = 0; angularIndex <= angularSubdivisions; angularIndex++)
+                {
+                    const float angle = twistLimitLower + twistRange / angularSubdivisions * angularIndex;
+                    const AZ::Vector3 radialVector =
+                        parentLocalRotation.TransformVector(scale * AZ::Vector3(0.0f, cosf(angle), sinf(angle)));
+
+                    if (angularIndex > 0)
+                    {
+                        for (AZ::u32 radialIndex = 1; radialIndex <= radialSubdivisions; radialIndex++)
+                        {
+                            const float radiusFraction = 1.0f / radialSubdivisions * radialIndex;
+                            lineBufferOut.push_back(radiusFraction * radialVector);
+                            lineBufferOut.push_back(radiusFraction * previousRadialVector);
+                        }
+                    }
+
+                    lineBufferOut.push_back(AZ::Vector3::CreateZero());
+                    lineBufferOut.push_back(radialVector);
+
+                    previousRadialVector = radialVector;
+                }
+
+                const bool twistValid = (twistAngle >= twistLimitLower && twistAngle <= twistLimitUpper);
+                lineValidityBufferOut.insert(lineValidityBufferOut.end(), numLinesTwistArc, twistValid);
+            }
+
+            void AppendD6CurrentTwistToLineBuffer(
+                const AZ::Quaternion& parentLocalRotation,
+                float twistAngle,
+                [[maybe_unused]] float twistLimitLower,
+                [[maybe_unused]] float twistLimitUpper,
+                float scale,
+                AZStd::vector<AZ::Vector3>& lineBufferOut,
+                AZStd::vector<bool>& lineValidityBufferOut)
+            {
+                const AZ::Vector3 twistVector =
+                    parentLocalRotation.TransformVector(1.25f * scale * AZ::Vector3(0.0f, cosf(twistAngle), sinf(twistAngle)));
+                lineBufferOut.push_back(AZ::Vector3::CreateZero());
+                lineBufferOut.push_back(twistVector);
+                lineValidityBufferOut.push_back(true);
+            }
+        } // namespace Joints
+    } // namespace Utils
+} // namespace PhysX

+ 101 - 0
Gems/PhysX/Code/Source/Joint/PhysXJointUtils.h

@@ -0,0 +1,101 @@
+/*
+* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
+* its licensors.
+*
+* For complete copyright and license terms please see the LICENSE at the root of this
+* distribution (the "License"). All use of this software is governed by the License,
+* or, if provided, by the license below or the license accompanying this file. Do not
+* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+*
+*/
+
+#pragma once
+
+#include <AzCore/Math/Quaternion.h>
+#include <AzCore/Math/Transform.h>
+#include <AzCore/Math/Vector3.h>
+
+#include <PxPhysicsAPI.h>
+
+namespace PhysX
+{
+    namespace JointConstants
+    {
+        // Setting swing limits to very small values can cause extreme stability problems, so clamp above a small
+        // threshold.
+        static const float MinSwingLimitDegrees = 1.0f;
+    } // namespace JointConstants
+
+    namespace Utils
+    {
+        using PxJointUniquePtr = AZStd::unique_ptr<physx::PxJoint, AZStd::function<void(physx::PxJoint*)>>;
+
+        bool IsAtLeastOneDynamic(AzPhysics::SimulatedBody* body0, AzPhysics::SimulatedBody* body1);
+
+        physx::PxRigidActor* GetPxRigidActor(AzPhysics::SceneHandle sceneHandle, AzPhysics::SimulatedBodyHandle worldBodyHandle);
+        AzPhysics::SimulatedBody* GetSimulatedBodyFromHandle(
+            AzPhysics::SceneHandle sceneHandle, AzPhysics::SimulatedBodyHandle bodyHandle);
+
+        namespace PxJointFactories
+        {
+            PxJointUniquePtr CreatePxD6Joint(const PhysX::D6JointLimitConfiguration& configuration,
+                AzPhysics::SceneHandle sceneHandle,
+                AzPhysics::SimulatedBodyHandle parentBodyHandle,
+                AzPhysics::SimulatedBodyHandle childBodyHandle);
+
+            PxJointUniquePtr CreatePxFixedJoint(const PhysX::FixedJointConfiguration& configuration,
+                AzPhysics::SceneHandle sceneHandle,
+                AzPhysics::SimulatedBodyHandle parentBodyHandle,
+                AzPhysics::SimulatedBodyHandle childBodyHandle);
+
+            PxJointUniquePtr CreatePxBallJoint(const PhysX::BallJointConfiguration& configuration,
+                AzPhysics::SceneHandle sceneHandle,
+                AzPhysics::SimulatedBodyHandle parentBodyHandle,
+                AzPhysics::SimulatedBodyHandle childBodyHandle);
+                
+            PxJointUniquePtr CreatePxHingeJoint(const PhysX::HingeJointConfiguration& configuration,
+                AzPhysics::SceneHandle sceneHandle,
+                AzPhysics::SimulatedBodyHandle parentBodyHandle,
+                AzPhysics::SimulatedBodyHandle childBodyHandle);
+        } // namespace PxActorFactories
+
+        namespace Joints
+        {
+            bool IsD6SwingValid(float swingAngleY, float swingAngleZ, float swingLimitY, float swingLimitZ);
+
+            void AppendD6SwingConeToLineBuffer(
+                const AZ::Quaternion& parentLocalRotation,
+                float swingAngleY,
+                float swingAngleZ,
+                float swingLimitY,
+                float swingLimitZ,
+                float scale,
+                AZ::u32 angularSubdivisions,
+                AZ::u32 radialSubdivisions,
+                AZStd::vector<AZ::Vector3>& lineBufferOut,
+                AZStd::vector<bool>& lineValidityBufferOut);
+
+            void AppendD6TwistArcToLineBuffer(
+                const AZ::Quaternion& parentLocalRotation,
+                float twistAngle,
+                float twistLimitLower,
+                float twistLimitUpper,
+                float scale,
+                AZ::u32 angularSubdivisions,
+                AZ::u32 radialSubdivisions,
+                AZStd::vector<AZ::Vector3>& lineBufferOut,
+                AZStd::vector<bool>& lineValidityBufferOut);
+
+            void AppendD6CurrentTwistToLineBuffer(
+                const AZ::Quaternion& parentLocalRotation,
+                float twistAngle,
+                float twistLimitLower,
+                float twistLimitUpper,
+                float scale,
+                AZStd::vector<AZ::Vector3>& lineBufferOut,
+                AZStd::vector<bool>& lineValidityBufferOut);
+        } // namespace Joints
+    } // namespace Utils
+} // namespace PhysX
+

+ 55 - 43
Gems/PhysX/Code/Source/JointComponent.cpp

@@ -18,33 +18,65 @@
 #include <AzFramework/Physics/SimulatedBodies/RigidBody.h>
 #include <PhysX/NativeTypeIdentifiers.h>
 #include <PhysX/PhysXLocks.h>
-#include <Source/Joint.h>
 #include <Source/JointComponent.h>
 #include <Source/Utils.h>
+#include <AzFramework/Physics/PhysicsSystem.h>
 
 namespace PhysX
 {
+    JointComponentConfiguration::JointComponentConfiguration(
+        AZ::Transform localTransformFromFollower,
+        AZ::EntityId leadEntity,
+        AZ::EntityId followerEntity)
+        : m_localTransformFromFollower(localTransformFromFollower)
+        , m_leadEntity(leadEntity)
+        , m_followerEntity(followerEntity)
+    {
+    }
+
+    void JointComponentConfiguration::Reflect(AZ::ReflectContext* context)
+    {
+        if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
+        {
+            serializeContext->Class<JointComponentConfiguration>()
+                ->Version(2)
+                ->Field("Follower Local Transform", &JointComponentConfiguration::m_localTransformFromFollower)
+                ->Field("Lead Entity", &JointComponentConfiguration::m_leadEntity)
+                ->Field("Follower Entity", &JointComponentConfiguration::m_followerEntity)
+                ;
+        }
+    }
+
     void JointComponent::Reflect(AZ::ReflectContext* context)
     {
+        JointComponentConfiguration::Reflect(context);
+
         if (auto* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
         {
             serializeContext->Class<JointComponent, AZ::Component>()
-                ->Version(1)
+                ->Version(2)
                 ->Field("Joint Configuration", &JointComponent::m_configuration)
+                ->Field("Joint Generic Properties", &JointComponent::m_genericProperties)
                 ->Field("Joint Limits", &JointComponent::m_limits)
                 ;
         }
     }
 
-    JointComponent::JointComponent(const GenericJointConfiguration& config)
-        : m_configuration(config)
+    JointComponent::JointComponent(
+        const JointComponentConfiguration& configuration, 
+        const JointGenericProperties& genericProperties)
+        : m_configuration(configuration)
+        , m_genericProperties(genericProperties)
     {
     }
 
-    JointComponent::JointComponent(const GenericJointConfiguration& config
-        , const GenericJointLimitsConfiguration& limits)
-            : m_configuration(AZStd::move(config))
-            , m_limits(limits)
+    JointComponent::JointComponent(
+        const JointComponentConfiguration& configuration, 
+        const JointGenericProperties& genericProperties,
+        const JointLimitProperties& limitProperties)
+        : m_configuration(configuration)
+        , m_genericProperties(genericProperties)
+        , m_limits(limitProperties)
     {
     }
 
@@ -67,16 +99,22 @@ namespace PhysX
     void JointComponent::Deactivate()
     {
         AZ::EntityBus::Handler::BusDisconnect();
-        m_joint.reset();
+        if (auto* physicsSystem = AZ::Interface<AzPhysics::SystemInterface>::Get())
+        {
+            if (auto* scene = physicsSystem->GetScene(m_jointSceneOwner))
+            {
+                scene->RemoveJoint(m_jointHandle);
+                m_jointSceneOwner = AzPhysics::InvalidSceneHandle;
+            }
+        }
     }
 
-    physx::PxTransform JointComponent::GetJointLocalPose(const physx::PxRigidActor* actor
-        , const physx::PxTransform& jointPose)
+    AZ::Transform JointComponent::GetJointLocalPose(const physx::PxRigidActor* actor, const AZ::Transform& jointPose)
     {
         if (!actor)
         {
             AZ_Error("JointComponent::GetJointLocalPose", false, "Can't get pose for invalid actor pointer.");
-            return physx::PxTransform();
+            return AZ::Transform::CreateIdentity();
         }
 
         PHYSX_SCENE_READ_LOCK(actor->getScene());
@@ -84,41 +122,17 @@ namespace PhysX
         physx::PxTransform actorTranslateInv(-actorPose.p);
         physx::PxTransform actorRotateInv(actorPose.q);
         actorRotateInv = actorRotateInv.getInverse();
-        return actorRotateInv * actorTranslateInv * jointPose;
+        return PxMathConvert(actorRotateInv * actorTranslateInv) * jointPose;
     }
 
     AZ::Transform JointComponent::GetJointTransform(AZ::EntityId entityId
-        , const GenericJointConfiguration& jointConfig)
+        , const JointComponentConfiguration& jointConfig)
     {
         AZ::Transform jointTransform = PhysX::Utils::GetEntityWorldTransformWithoutScale(entityId);
         jointTransform = jointTransform * jointConfig.m_localTransformFromFollower;
         return jointTransform;
     }
 
-    void JointComponent::InitGenericProperties()
-    {
-        if (!m_joint)
-        {
-            return;
-        }
-
-        physx::PxJoint* jointNative = static_cast<physx::PxJoint*>(m_joint->GetNativePointer());
-        if (!jointNative)
-        {
-            return;
-        }
-        PHYSX_SCENE_WRITE_LOCK(jointNative->getScene());
-        jointNative->setConstraintFlag(
-            physx::PxConstraintFlag::eCOLLISION_ENABLED,
-            m_configuration.GetFlag(GenericJointConfiguration::GenericJointFlag::SelfCollide));
-
-        if (m_configuration.GetFlag(GenericJointConfiguration::GenericJointFlag::Breakable))
-        {
-            jointNative->setBreakForce(m_configuration.m_forceMax
-                , m_configuration.m_torqueMax);
-        }
-    }
-
     void JointComponent::ObtainLeadFollowerInfo(JointComponent::LeadFollowerInfo& info)
     {
         info = LeadFollowerInfo();
@@ -160,16 +174,15 @@ namespace PhysX
 
         const AZ::Transform jointTransform = GetJointTransform(GetEntityId(), m_configuration);
 
-        physx::PxTransform jointPose = PxMathConvert(jointTransform);
         if (info.m_leadActor)
         {
-            info.m_leadLocal = GetJointLocalPose(info.m_leadActor, jointPose); // joint position & orientation in lead actor's frame.
+            info.m_leadLocal = GetJointLocalPose(info.m_leadActor, jointTransform); // joint position & orientation in lead actor's frame.
         }
         else
         {
-            info.m_leadLocal = jointPose; // lead is null, attaching follower to global position of joint.
+            info.m_leadLocal = jointTransform; // lead is null, attaching follower to global position of joint.
         }
-        info.m_followerLocal = PxMathConvert(m_configuration.m_localTransformFromFollower);// joint position & orientation in follower actor's frame.
+        info.m_followerLocal = m_configuration.m_localTransformFromFollower;// joint position & orientation in follower actor's frame.
     }
 
     void JointComponent::WarnInvalidJointSetup(AZ::EntityId entityId, const AZStd::string& message)
@@ -189,7 +202,6 @@ namespace PhysX
         if (!m_configuration.m_leadEntity.IsValid() || entityId == m_configuration.m_leadEntity)
         {
             InitNativeJoint(); // Invoke overriden specific joint type instantiation
-            InitGenericProperties();
         }
         // Else, follower entity is activated, subscribe to be notified that lead entity is activated.
         else

+ 36 - 15
Gems/PhysX/Code/Source/JointComponent.h

@@ -18,7 +18,7 @@
 #include <AzCore/Component/EntityId.h>
 #include <AzCore/Component/TransformBus.h>
 
-#include <Source/Joint.h>
+#include <PhysX/Joint/Configuration/PhysXJointConfiguration.h>
 
 namespace AzPhysics
 {
@@ -27,6 +27,24 @@ namespace AzPhysics
 
 namespace PhysX
 {
+    class JointComponentConfiguration
+    {
+    public:
+        AZ_CLASS_ALLOCATOR(JointComponentConfiguration, AZ::SystemAllocator, 0);
+        AZ_TYPE_INFO(JointComponentConfiguration, "{1454F33F-AA6E-424B-A70C-9E463FBDEA19}");
+        static void Reflect(AZ::ReflectContext* context);
+
+        JointComponentConfiguration() = default;
+        JointComponentConfiguration(
+            AZ::Transform localTransformFromFollower,
+            AZ::EntityId leadEntity,
+            AZ::EntityId followerEntity);
+
+        AZ::EntityId m_leadEntity; ///< EntityID for entity containing body that is lead to this joint constraint.
+        AZ::EntityId m_followerEntity; ///< EntityID for entity containing body that is follower to this joint constraint.
+        AZ::Transform m_localTransformFromFollower; ///< Joint's location and orientation in the frame (coordinate system) of the follower entity.
+    };
+
     /// Base class for game-time generic joint components.
     class JointComponent: public AZ::Component
         , protected AZ::EntityBus::Handler
@@ -36,9 +54,13 @@ namespace PhysX
         static void Reflect(AZ::ReflectContext* context);
 
         JointComponent() = default;
-        explicit JointComponent(const GenericJointConfiguration& config);
-        JointComponent(const GenericJointConfiguration& config
-            , const GenericJointLimitsConfiguration& limits);
+        JointComponent(
+            const JointComponentConfiguration& configuration, 
+            const JointGenericProperties& genericProperties);
+        JointComponent(
+            const JointComponentConfiguration& configuration, 
+            const JointGenericProperties& genericProperties,
+            const JointLimitProperties& limitProperties);
 
     protected:
         /// Struct to provide subclasses with native pointers during joint initialization.
@@ -47,8 +69,8 @@ namespace PhysX
         {
             physx::PxRigidActor* m_leadActor = nullptr;
             physx::PxRigidActor* m_followerActor = nullptr;
-            physx::PxTransform m_leadLocal = physx::PxTransform(physx::PxIdentity);
-            physx::PxTransform m_followerLocal = physx::PxTransform(physx::PxIdentity);
+            AZ::Transform m_leadLocal = AZ::Transform::CreateIdentity();
+            AZ::Transform m_followerLocal = AZ::Transform::CreateIdentity();
             AzPhysics::SimulatedBody* m_leadBody = nullptr;
             AzPhysics::SimulatedBody* m_followerBody = nullptr;
         };
@@ -63,14 +85,10 @@ namespace PhysX
         /// Invoked in JointComponent::OnEntityActivated for specific joint types to instantiate native joint pointer.
         virtual void InitNativeJoint() {};
 
-        physx::PxTransform GetJointLocalPose(const physx::PxRigidActor* actor,
-            const physx::PxTransform& jointPose);
+        AZ::Transform GetJointLocalPose(const physx::PxRigidActor* actor, const AZ::Transform& jointPose);
 
         AZ::Transform GetJointTransform(AZ::EntityId entityId,
-            const GenericJointConfiguration& jointConfig);
-
-        /// Initializes joint properties common to all native joint types after native joint creation.
-        void InitGenericProperties();
+            const JointComponentConfiguration& jointConfig);
 
         /// Used on initialization by sub-classes to get native pointers from entity IDs.
         /// This allows sub-classes to instantiate specific native types. This base class does not need knowledge of any specific joint type.
@@ -79,8 +97,11 @@ namespace PhysX
         /// Issues warnings for invalid scenarios when initializing a joint from entity IDs.
         void WarnInvalidJointSetup(AZ::EntityId entityId, const AZStd::string& message);
 
-        GenericJointConfiguration m_configuration;
-        GenericJointLimitsConfiguration m_limits;
-        AZStd::shared_ptr<PhysX::Joint> m_joint = nullptr;
+
+        JointComponentConfiguration m_configuration;
+        JointGenericProperties m_genericProperties;
+        JointLimitProperties m_limits;
+        AzPhysics::JointHandle m_jointHandle = AzPhysics::InvalidJointHandle;
+        AzPhysics::SceneHandle m_jointSceneOwner = AzPhysics::InvalidSceneHandle;
     };
 } // namespace PhysX

+ 16 - 13
Gems/PhysX/Code/Source/PhysXCharacters/API/CharacterUtils.cpp

@@ -18,10 +18,10 @@
 #include <AzFramework/Physics/MaterialBus.h>
 #include <cfloat>
 #include <PhysX/PhysXLocks.h>
+#include <PhysX/Joint/Configuration/PhysXJointConfiguration.h>
 #include <Source/RigidBody.h>
 #include <Source/Scene/PhysXScene.h>
 #include <Source/Shape.h>
-#include <Source/Joint.h>
 
 namespace PhysX
 {
@@ -262,21 +262,24 @@ namespace PhysX
                             physx::PxTransform parentTM(parentOffset);
                             physx::PxTransform childTM(physx::PxIdentity);
 
-                            AZStd::shared_ptr<Physics::JointLimitConfiguration> jointLimitConfig = configuration.m_nodes[nodeIndex].m_jointLimit;
-                            if (!jointLimitConfig)
+                            AZStd::shared_ptr<AzPhysics::JointConfiguration> jointConfig = configuration.m_nodes[nodeIndex].m_jointConfig;
+                            if (!jointConfig)
                             {
-                                AZStd::vector<AZ::TypeId> supportedJointLimitTypes = JointUtils::GetSupportedJointTypes();
-
-                                if (!supportedJointLimitTypes.empty())
-                                {
-                                    jointLimitConfig = JointUtils::CreateJointLimitConfiguration(supportedJointLimitTypes[0]);
-                                }
+                                jointConfig = AZStd::make_shared<D6JointLimitConfiguration>();
                             }
+                            
+                            AzPhysics::JointHandle jointHandle = sceneInterface->AddJoint(
+                                sceneHandle, jointConfig.get(), 
+                                ragdoll->GetNode(parentIndex)->GetRigidBody().m_bodyHandle,
+                                ragdoll->GetNode(nodeIndex)->GetRigidBody().m_bodyHandle);
+
+                            AzPhysics::Joint* joint = sceneInterface->GetJointFromHandle(sceneHandle, jointHandle);
 
-                            AZStd::shared_ptr<Physics::Joint> joint = JointUtils::CreateJoint(
-                                jointLimitConfig,
-                                &ragdoll->GetNode(parentIndex)->GetRigidBody(),
-                                &ragdoll->GetNode(nodeIndex)->GetRigidBody());
+                            if (!joint)
+                            {
+                                AZ_Error("PhysX Ragdoll", false, "Failed to create joint for node index %i.", nodeIndex);
+                                return nullptr;
+                            }
 
                             // Moving from PhysX 3.4 to 4.1, the allowed range of the twist angle was expanded from -pi..pi
                             // to -2*pi..2*pi.

+ 2 - 2
Gems/PhysX/Code/Source/PhysXCharacters/API/Ragdoll.cpp

@@ -378,8 +378,8 @@ namespace PhysX
         else
         {
             actor->setRigidBodyFlag(physx::PxRigidBodyFlag::eKINEMATIC, false);
-            const AZStd::shared_ptr<Physics::Joint>& joint = m_nodes[nodeIndex]->GetJoint();
-            if (joint)
+
+            if (AzPhysics::Joint* joint = m_nodes[nodeIndex]->GetJoint())
             {
                 if (physx::PxD6Joint* pxJoint = static_cast<physx::PxD6Joint*>(joint->GetNativePointer()))
                 {

+ 20 - 2
Gems/PhysX/Code/Source/PhysXCharacters/API/RagdollNode.cpp

@@ -38,11 +38,17 @@ namespace PhysX
 
     RagdollNode::~RagdollNode()
     {
+        DestroyJoint();
         DestroyPhysicsBody();
     }
 
-    void RagdollNode::SetJoint(const AZStd::shared_ptr<Physics::Joint>& joint)
+    void RagdollNode::SetJoint(AzPhysics::Joint* joint)
     {
+        if (m_joint)
+        {
+            return;
+        }
+        
         m_joint = joint;
     }
 
@@ -52,7 +58,7 @@ namespace PhysX
         return *m_rigidBody;
     }
 
-    const AZStd::shared_ptr<Physics::Joint>& RagdollNode::GetJoint() const
+    AzPhysics::Joint* RagdollNode::GetJoint()
     {
         return m_joint;
     }
@@ -167,4 +173,16 @@ namespace PhysX
         }
     }
 
+    void RagdollNode::DestroyJoint()
+    {
+        if (m_joint != nullptr && m_sceneOwner != AzPhysics::InvalidSceneHandle)
+        {
+            if (auto* sceneInterface = AZ::Interface<AzPhysics::SceneInterface>::Get())
+            {
+                sceneInterface->RemoveJoint(m_sceneOwner, m_joint->m_jointHandle);
+            }
+            m_joint = nullptr;
+        }
+    }
+
 } // namespace PhysX

+ 5 - 4
Gems/PhysX/Code/Source/PhysXCharacters/API/RagdollNode.h

@@ -32,11 +32,11 @@ namespace PhysX
         explicit RagdollNode(AzPhysics::SceneHandle sceneHandle, Physics::RagdollNodeConfiguration& nodeConfig);
         ~RagdollNode();
 
-        void SetJoint(const AZStd::shared_ptr<Physics::Joint>& joint);
+        void SetJoint(AzPhysics::Joint* joint);
 
         // Physics::RagdollNode
         AzPhysics::RigidBody& GetRigidBody() override;
-        const AZStd::shared_ptr<Physics::Joint>& GetJoint() const override;
+        AzPhysics::Joint* GetJoint() override;
         bool IsSimulating() const override;
 
         // AzPhysics::SimulatedBody
@@ -60,9 +60,10 @@ namespace PhysX
     private:
         void CreatePhysicsBody(AzPhysics::SceneHandle sceneHandle, Physics::RagdollNodeConfiguration& nodeConfig);
         void DestroyPhysicsBody();
+        void DestroyJoint();
 
-        AZStd::shared_ptr<Physics::Joint> m_joint;
-        AzPhysics::RigidBody* m_rigidBody;
+        AzPhysics::Joint* m_joint = nullptr;
+        AzPhysics::RigidBody* m_rigidBody = nullptr;
         AzPhysics::SimulatedBodyHandle m_rigidBodyHandle = AzPhysics::InvalidSimulatedBodyHandle;
         AzPhysics::SceneHandle m_sceneOwner = AzPhysics::InvalidSceneHandle;
         PhysX::ActorData m_actorUserData;

+ 1 - 1
Gems/PhysX/Code/Source/PhysXCharacters/Components/RagdollComponent.cpp

@@ -379,7 +379,7 @@ namespace PhysX
 
             for (size_t nodeIndex = 0; nodeIndex < numNodes; nodeIndex++)
             {
-                if (const AZStd::shared_ptr<Physics::Joint>& joint = ragdoll->GetNode(nodeIndex)->GetJoint())
+                if (const AzPhysics::Joint* joint = ragdoll->GetNode(nodeIndex)->GetJoint())
                 {
                     if (auto* pxJoint = static_cast<physx::PxD6Joint*>(joint->GetNativePointer()))
                     {

+ 2 - 0
Gems/PhysX/Code/Source/Platform/Android/PAL_android.cmake

@@ -10,3 +10,5 @@
 #
 
 set(PAL_TRAIT_PHYSX_SUPPORTED TRUE)
+set(PAL_TRAIT_JOINTS_TYPED_TEST_CASE FALSE)
+

+ 1 - 0
Gems/PhysX/Code/Source/Platform/Linux/PAL_linux.cmake

@@ -10,3 +10,4 @@
 #
 
 set(PAL_TRAIT_PHYSX_SUPPORTED TRUE)
+set(PAL_TRAIT_JOINTS_TYPED_TEST_CASE FALSE)

+ 1 - 0
Gems/PhysX/Code/Source/Platform/Mac/PAL_mac.cmake

@@ -10,3 +10,4 @@
 #
 
 set(PAL_TRAIT_PHYSX_SUPPORTED TRUE)
+set(PAL_TRAIT_JOINTS_TYPED_TEST_CASE TRUE)

+ 1 - 0
Gems/PhysX/Code/Source/Platform/Windows/PAL_windows.cmake

@@ -10,3 +10,4 @@
 #
 
 set(PAL_TRAIT_PHYSX_SUPPORTED TRUE)
+set(PAL_TRAIT_JOINTS_TYPED_TEST_CASE TRUE)

+ 1 - 0
Gems/PhysX/Code/Source/Platform/iOS/PAL_ios.cmake

@@ -10,3 +10,4 @@
 #
 
 set(PAL_TRAIT_PHYSX_SUPPORTED TRUE)
+set(PAL_TRAIT_JOINTS_TYPED_TEST_CASE TRUE)

+ 105 - 0
Gems/PhysX/Code/Source/Scene/PhysXScene.cpp

@@ -31,6 +31,8 @@
 #include <PhysXCharacters/API/CharacterController.h>
 #include <PhysXCharacters/API/CharacterUtils.h>
 #include <System/PhysXSystem.h>
+#include <PhysX/Joint/Configuration/PhysXJointConfiguration.h>
+#include <Joint/PhysXJoint.h>
 
 namespace PhysX
 {
@@ -232,6 +234,18 @@ namespace PhysX
                 scene->GetSceneHandle());
         }
 
+        template<class JointType, class ConfigurationType>
+        AzPhysics::Joint* CreateJoint(const ConfigurationType* configuration, 
+            AzPhysics::SceneHandle sceneHandle,
+            AzPhysics::SimulatedBodyHandle parentBodyHandle,
+            AzPhysics::SimulatedBodyHandle childBodyHandle, 
+            AZ::Crc32& crc)
+        {
+            JointType* newBody = aznew JointType(*configuration, sceneHandle, parentBodyHandle, childBodyHandle);
+            crc = AZ::Crc32(newBody, sizeof(*newBody));
+            return newBody;
+        }
+
         //helper to perform a ray cast
         AzPhysics::SceneQueryHits RayCast(const AzPhysics::RayCastRequest* raycastRequest,
             AZStd::vector<physx::PxRaycastHit>& raycastBuffer,
@@ -813,6 +827,90 @@ namespace PhysX
         }
     }
 
+    AzPhysics::JointHandle PhysXScene::AddJoint(const AzPhysics::JointConfiguration* jointConfig, 
+        AzPhysics::SimulatedBodyHandle parentBody, AzPhysics::SimulatedBodyHandle childBody) 
+    {
+        AzPhysics::Joint* newJoint = nullptr;
+        AZ::Crc32 newJointCrc;
+        if (azrtti_istypeof<PhysX::D6JointLimitConfiguration>(jointConfig))
+        {
+            newJoint = Internal::CreateJoint<PhysXD6Joint, D6JointLimitConfiguration>(
+                azdynamic_cast<const D6JointLimitConfiguration*>(jointConfig),
+                m_sceneHandle, parentBody, childBody, newJointCrc);
+        }
+        else if (azrtti_istypeof<PhysX::FixedJointConfiguration*>(jointConfig))
+        {
+            newJoint = Internal::CreateJoint<PhysXFixedJoint, FixedJointConfiguration>(
+                azdynamic_cast<const FixedJointConfiguration*>(jointConfig),
+                m_sceneHandle, parentBody, childBody, newJointCrc);
+        }
+        else if (azrtti_istypeof<PhysX::BallJointConfiguration*>(jointConfig))
+        {
+            newJoint = Internal::CreateJoint<PhysXBallJoint, BallJointConfiguration>(
+                azdynamic_cast<const BallJointConfiguration*>(jointConfig),
+                m_sceneHandle, parentBody, childBody, newJointCrc);
+        }
+        else if (azrtti_istypeof<PhysX::HingeJointConfiguration*>(jointConfig))
+        {
+            newJoint = Internal::CreateJoint<PhysXHingeJoint, HingeJointConfiguration>(
+                azdynamic_cast<const HingeJointConfiguration*>(jointConfig),
+                m_sceneHandle, parentBody, childBody, newJointCrc);
+        }
+        else
+        {
+            AZ_Warning("PhysXScene", false, "Unknown JointConfiguration.");
+            return AzPhysics::InvalidJointHandle;
+        }
+
+        if (newJoint != nullptr)
+        {
+            AzPhysics::JointIndex index = index = m_joints.size();
+            m_joints.emplace_back(newJointCrc, newJoint);
+
+            const AzPhysics::JointHandle newJointHandle(newJointCrc, index);
+            newJoint->m_sceneOwner = m_sceneHandle;
+            newJoint->m_jointHandle = newJointHandle;
+
+            return newJointHandle;
+        }
+
+        return AzPhysics::InvalidJointHandle;
+    }
+
+    AzPhysics::Joint* PhysXScene::GetJointFromHandle(AzPhysics::JointHandle jointHandle) 
+    {
+        if (jointHandle == AzPhysics::InvalidJointHandle)
+        {
+            return nullptr;
+        }
+
+        AzPhysics::JointIndex index = AZStd::get<AzPhysics::HandleTypeIndex::Index>(jointHandle);
+        if (index < m_joints.size()
+            && m_joints[index].first == AZStd::get<AzPhysics::HandleTypeIndex::Crc>(jointHandle))
+        {
+            return m_joints[index].second;
+        }
+        return nullptr;
+    }
+
+    void PhysXScene::RemoveJoint(AzPhysics::JointHandle jointHandle) 
+    {
+        if (jointHandle == AzPhysics::InvalidJointHandle)
+        {
+            return;
+        }
+        
+        AzPhysics::JointIndex index = AZStd::get<AzPhysics::HandleTypeIndex::Index>(jointHandle);
+        if (index < m_joints.size()
+            && m_joints[index].first == AZStd::get<AzPhysics::HandleTypeIndex::Crc>(jointHandle))
+        {
+            m_deferredDeletionsJoints.push_back(m_joints[index].second);
+            m_joints[index] = AZStd::make_pair(AZ::Crc32(), nullptr);
+            m_freeJointSlots.push(index);
+            jointHandle = AzPhysics::InvalidJointHandle;
+        }
+    }
+
     AzPhysics::SceneQueryHits PhysXScene::QueryScene(const AzPhysics::SceneQueryRequest* request)
     {
         if (request == nullptr)
@@ -996,6 +1094,13 @@ namespace PhysX
         {
             delete simulatedBody;
         }
+
+        AZStd::vector<AzPhysics::Joint*> jointDeletions;
+        jointDeletions.swap(m_deferredDeletionsJoints);
+        for (auto* joint : jointDeletions)
+        {
+            delete joint;
+        }
     }
 
     void PhysXScene::ProcessTriggerEvents()

+ 9 - 0
Gems/PhysX/Code/Source/Scene/PhysXScene.h

@@ -12,6 +12,7 @@
 #pragma once
 
 #include <AzFramework/Physics/PhysicsScene.h>
+#include <AzFramework/Physics/Common/PhysicsJoint.h>
 #include <AzFramework/Physics/Common/PhysicsEvents.h>
 #include <AzFramework/Physics/Common/PhysicsSimulatedBody.h>
 #include <AzFramework/Physics/Configuration/SceneConfiguration.h>
@@ -52,6 +53,10 @@ namespace PhysX
         void RemoveSimulatedBodies(AzPhysics::SimulatedBodyHandleList& bodyHandles) override;
         void EnableSimulationOfBody(AzPhysics::SimulatedBodyHandle bodyHandle) override;
         void DisableSimulationOfBody(AzPhysics::SimulatedBodyHandle bodyHandle) override;
+        AzPhysics::JointHandle AddJoint(const AzPhysics::JointConfiguration* jointConfig, 
+            AzPhysics::SimulatedBodyHandle parentBody, AzPhysics::SimulatedBodyHandle childBody) override;
+        AzPhysics::Joint* GetJointFromHandle(AzPhysics::JointHandle jointHandle) override;
+        void RemoveJoint(AzPhysics::JointHandle jointHandle) override;
         AzPhysics::SceneQueryHits QueryScene(const AzPhysics::SceneQueryRequest* request) override;
         AzPhysics::SceneQueryHitsList QuerySceneBatch(const AzPhysics::SceneQueryRequests& requests) override;
         [[nodiscard]] bool QuerySceneAsync(AzPhysics::SceneQuery::AsyncRequestId requestId,
@@ -94,6 +99,10 @@ namespace PhysX
         AZStd::vector<AzPhysics::SimulatedBody*> m_deferredDeletions;
         AZStd::queue<AzPhysics::SimulatedBodyIndex> m_freeSceneSlots;
 
+        AZStd::vector<AZStd::pair<AZ::Crc32, AzPhysics::Joint*>> m_joints;
+        AZStd::vector<AzPhysics::Joint*> m_deferredDeletionsJoints;
+        AZStd::queue<AzPhysics::JointIndex> m_freeJointSlots;
+
         AzPhysics::SystemEvents::OnConfigurationChangedEvent::Handler m_physicsSystemConfigChanged;
 
         static thread_local AZStd::vector<physx::PxRaycastHit> s_rayCastBuffer; //!< thread local structure to hold hits for a single raycast or shapecast.

+ 31 - 0
Gems/PhysX/Code/Source/Scene/PhysXSceneInterface.cpp

@@ -11,6 +11,7 @@
 */
 #include <Scene/PhysXSceneInterface.h>
 
+#include <AzFramework/Physics/Common/PhysicsJoint.h>
 #include <AzFramework/Physics/Common/PhysicsSimulatedBody.h>
 #include <AzFramework/Physics/Configuration/SceneConfiguration.h>
 #include <System/PhysXSystem.h>
@@ -144,6 +145,36 @@ namespace PhysX
         }
     }
 
+    AzPhysics::JointHandle PhysXSceneInterface::AddJoint(
+        AzPhysics::SceneHandle sceneHandle, const AzPhysics::JointConfiguration* jointConfig, 
+        AzPhysics::SimulatedBodyHandle parentBody, AzPhysics::SimulatedBodyHandle childBody) 
+    {
+        if (AzPhysics::Scene* scene = m_physxSystem->GetScene(sceneHandle))
+        {
+            return scene->AddJoint(jointConfig, parentBody, childBody);
+        }
+
+        return AzPhysics::InvalidJointHandle;
+    }
+
+    AzPhysics::Joint* PhysXSceneInterface::GetJointFromHandle(AzPhysics::SceneHandle sceneHandle, AzPhysics::JointHandle jointHandle) 
+    {
+        if (AzPhysics::Scene* scene = m_physxSystem->GetScene(sceneHandle))
+        {
+            return scene->GetJointFromHandle(jointHandle);
+        }
+
+        return nullptr;
+    }
+
+    void PhysXSceneInterface::RemoveJoint(AzPhysics::SceneHandle sceneHandle, AzPhysics::JointHandle jointHandle) 
+    {
+        if (AzPhysics::Scene* scene = m_physxSystem->GetScene(sceneHandle))
+        {
+            scene->RemoveJoint(jointHandle);
+        }
+    }
+
     AzPhysics::SceneQueryHits PhysXSceneInterface::QueryScene(
         AzPhysics::SceneHandle sceneHandle, const AzPhysics::SceneQueryRequest* request)
     {

+ 4 - 0
Gems/PhysX/Code/Source/Scene/PhysXSceneInterface.h

@@ -44,6 +44,10 @@ namespace PhysX
         void RemoveSimulatedBodies(AzPhysics::SceneHandle sceneHandle, AzPhysics::SimulatedBodyHandleList& bodyHandles) override;
         void EnableSimulationOfBody(AzPhysics::SceneHandle sceneHandle, AzPhysics::SimulatedBodyHandle bodyHandle) override;
         void DisableSimulationOfBody(AzPhysics::SceneHandle sceneHandle, AzPhysics::SimulatedBodyHandle bodyHandle) override;
+        AzPhysics::JointHandle AddJoint(AzPhysics::SceneHandle sceneHandle, const AzPhysics::JointConfiguration* jointConfig, 
+            AzPhysics::SimulatedBodyHandle parentBody, AzPhysics::SimulatedBodyHandle childBody) override;
+        AzPhysics::Joint* GetJointFromHandle(AzPhysics::SceneHandle sceneHandle, AzPhysics::JointHandle jointHandle) override;
+        void RemoveJoint(AzPhysics::SceneHandle sceneHandle, AzPhysics::JointHandle jointHandle) override;
         AzPhysics::SceneQueryHits QueryScene(AzPhysics::SceneHandle sceneHandle, const AzPhysics::SceneQueryRequest* request) override;
         AzPhysics::SceneQueryHitsList QuerySceneBatch(AzPhysics::SceneHandle sceneHandle, const AzPhysics::SceneQueryRequests& requests) override;
         [[nodiscard]] bool QuerySceneAsync(AzPhysics::SceneHandle sceneHandle, AzPhysics::SceneQuery::AsyncRequestId requestId,

+ 299 - 0
Gems/PhysX/Code/Source/System/PhysXJointInterface.cpp

@@ -0,0 +1,299 @@
+/*
+ * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates, or
+ * a third party where indicated.
+ *
+ * For complete copyright and license terms please see the LICENSE at the root of this
+ * distribution (the "License"). All use of this software is governed by the License,
+ * or, if provided, by the license below or the license accompanying this file. Do not
+ * remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *
+ */
+
+#include <System/PhysXJointInterface.h>
+#include <AzFramework/Physics/Configuration/JointConfiguration.h>
+
+#include <PhysX/Joint/Configuration/PhysXJointConfiguration.h>
+
+namespace PhysX
+{
+    namespace
+    {
+        struct D6JointState
+        {
+            float m_swingAngleY;
+            float m_swingAngleZ;
+            float m_twistAngle;
+        };
+
+        D6JointState CalculateD6JointState(
+            const AZ::Quaternion& parentWorldRotation,
+            const AZ::Quaternion& parentLocalRotation,
+            const AZ::Quaternion& childWorldRotation,
+            const AZ::Quaternion& childLocalRotation)
+        {
+            D6JointState result;
+
+            const AZ::Quaternion parentRotation = parentWorldRotation * parentLocalRotation;
+            const AZ::Quaternion childRotation = childWorldRotation * childLocalRotation;
+            const AZ::Quaternion relativeRotation = parentRotation.GetConjugate() * childRotation;
+            AZ::Quaternion twistQuat = AZ::IsClose(relativeRotation.GetX(), 0.0f, AZ::Constants::FloatEpsilon)
+                ? AZ::Quaternion::CreateIdentity()
+                : AZ::Quaternion(relativeRotation.GetX(), 0.0f, 0.0f, relativeRotation.GetW()).GetNormalized();
+            AZ::Quaternion swingQuat = relativeRotation * twistQuat.GetConjugate();
+
+            // make sure the twist angle has the correct sign for the rotation
+            twistQuat *= AZ::GetSign(twistQuat.GetX());
+            // make sure we get the shortest arcs for the swing degrees of freedom
+            swingQuat *= AZ::GetSign(swingQuat.GetW());
+            // the PhysX swing limits work in terms of tan quarter angles
+            result.m_swingAngleY = 4.0f * atan2f(swingQuat.GetY(), 1.0f + swingQuat.GetW());
+            result.m_swingAngleZ = 4.0f * atan2f(swingQuat.GetZ(), 1.0f + swingQuat.GetW());
+            const float twistAngle = twistQuat.GetAngle();
+            // GetAngle returns an angle in the range 0..2 pi, but the twist limits work in the range -pi..pi
+            const float wrappedTwistAngle = twistAngle > AZ::Constants::Pi ? twistAngle - AZ::Constants::TwoPi : twistAngle;
+            result.m_twistAngle = wrappedTwistAngle;
+
+            return result;
+        }
+
+        bool IsD6SwingValid(float swingAngleY, float swingAngleZ, float swingLimitY, float swingLimitZ)
+        {
+            const float epsilon = AZ::Constants::FloatEpsilon;
+            const float yFactor = tanf(0.25f * swingAngleY) / AZStd::GetMax(epsilon, tanf(0.25f * swingLimitY));
+            const float zFactor = tanf(0.25f * swingAngleZ) / AZStd::GetMax(epsilon, tanf(0.25f * swingLimitZ));
+
+            return (yFactor * yFactor + zFactor * zFactor <= 1.0f + epsilon);
+        }
+
+        void AppendD6SwingConeToLineBuffer(
+            const AZ::Quaternion& parentLocalRotation,
+            float swingAngleY,
+            float swingAngleZ,
+            float swingLimitY,
+            float swingLimitZ,
+            float scale,
+            AZ::u32 angularSubdivisions,
+            AZ::u32 radialSubdivisions,
+            AZStd::vector<AZ::Vector3>& lineBufferOut,
+            AZStd::vector<bool>& lineValidityBufferOut)
+        {
+            const AZ::u32 numLinesSwingCone = angularSubdivisions * (1u + radialSubdivisions);
+            lineBufferOut.reserve(lineBufferOut.size() + 2u * numLinesSwingCone);
+            lineValidityBufferOut.reserve(lineValidityBufferOut.size() + numLinesSwingCone);
+
+            // the orientation quat for a radial line in the cone can be represented in terms of sin and cos half angles
+            // these expressions can be efficiently calculated using tan quarter angles as follows:
+            // writing t = tan(x / 4)
+            // sin(x / 2) = 2 * t / (1 + t * t)
+            // cos(x / 2) = (1 - t * t) / (1 + t * t)
+            const float tanQuarterSwingZ = tanf(0.25f * swingLimitZ);
+            const float tanQuarterSwingY = tanf(0.25f * swingLimitY);
+
+            AZ::Vector3 previousRadialVector = AZ::Vector3::CreateZero();
+            for (AZ::u32 angularIndex = 0; angularIndex <= angularSubdivisions; angularIndex++)
+            {
+                const float angle = AZ::Constants::TwoPi / angularSubdivisions * angularIndex;
+                // the axis about which to rotate the x-axis to get the radial vector for this segment of the cone
+                const AZ::Vector3 rotationAxis(0, -tanQuarterSwingY * sinf(angle), tanQuarterSwingZ * cosf(angle));
+                const float normalizationFactor = rotationAxis.GetLengthSq();
+                const AZ::Quaternion radialVectorRotation = 1.0f / (1.0f + normalizationFactor) *
+                    AZ::Quaternion::CreateFromVector3AndValue(2.0f * rotationAxis, 1.0f - normalizationFactor);
+                const AZ::Vector3 radialVector =
+                    (parentLocalRotation * radialVectorRotation).TransformVector(AZ::Vector3::CreateAxisX(scale));
+
+                if (angularIndex > 0)
+                {
+                    for (AZ::u32 radialIndex = 1; radialIndex <= radialSubdivisions; radialIndex++)
+                    {
+                        float radiusFraction = 1.0f / radialSubdivisions * radialIndex;
+                        lineBufferOut.push_back(radiusFraction * radialVector);
+                        lineBufferOut.push_back(radiusFraction * previousRadialVector);
+                    }
+                }
+
+                if (angularIndex < angularSubdivisions)
+                {
+                    lineBufferOut.push_back(AZ::Vector3::CreateZero());
+                    lineBufferOut.push_back(radialVector);
+                }
+
+                previousRadialVector = radialVector;
+            }
+
+            const bool swingValid = IsD6SwingValid(swingAngleY, swingAngleZ, swingLimitY, swingLimitZ);
+            lineValidityBufferOut.insert(lineValidityBufferOut.end(), numLinesSwingCone, swingValid);
+        }
+
+        void AppendD6TwistArcToLineBuffer(
+            const AZ::Quaternion& parentLocalRotation,
+            float twistAngle,
+            float twistLimitLower,
+            float twistLimitUpper,
+            float scale,
+            AZ::u32 angularSubdivisions,
+            AZ::u32 radialSubdivisions,
+            AZStd::vector<AZ::Vector3>& lineBufferOut,
+            AZStd::vector<bool>& lineValidityBufferOut)
+        {
+            const AZ::u32 numLinesTwistArc = angularSubdivisions * (1u + radialSubdivisions) + 1u;
+            lineBufferOut.reserve(lineBufferOut.size() + 2u * numLinesTwistArc);
+
+            AZ::Vector3 previousRadialVector = AZ::Vector3::CreateZero();
+            const float twistRange = twistLimitUpper - twistLimitLower;
+
+            for (AZ::u32 angularIndex = 0; angularIndex <= angularSubdivisions; angularIndex++)
+            {
+                const float angle = twistLimitLower + twistRange / angularSubdivisions * angularIndex;
+                const AZ::Vector3 radialVector = parentLocalRotation.TransformVector(scale * AZ::Vector3(0.0f, cosf(angle), sinf(angle)));
+
+                if (angularIndex > 0)
+                {
+                    for (AZ::u32 radialIndex = 1; radialIndex <= radialSubdivisions; radialIndex++)
+                    {
+                        const float radiusFraction = 1.0f / radialSubdivisions * radialIndex;
+                        lineBufferOut.push_back(radiusFraction * radialVector);
+                        lineBufferOut.push_back(radiusFraction * previousRadialVector);
+                    }
+                }
+
+                lineBufferOut.push_back(AZ::Vector3::CreateZero());
+                lineBufferOut.push_back(radialVector);
+
+                previousRadialVector = radialVector;
+            }
+
+            const bool twistValid = (twistAngle >= twistLimitLower && twistAngle <= twistLimitUpper);
+            lineValidityBufferOut.insert(lineValidityBufferOut.end(), numLinesTwistArc, twistValid);
+        }
+
+        void AppendD6CurrentTwistToLineBuffer(
+            const AZ::Quaternion& parentLocalRotation,
+            float twistAngle,
+            [[maybe_unused]] float twistLimitLower,
+            [[maybe_unused]] float twistLimitUpper,
+            float scale,
+            AZStd::vector<AZ::Vector3>& lineBufferOut,
+            AZStd::vector<bool>& lineValidityBufferOut)
+        {
+            const AZ::Vector3 twistVector =
+                parentLocalRotation.TransformVector(1.25f * scale * AZ::Vector3(0.0f, cosf(twistAngle), sinf(twistAngle)));
+            lineBufferOut.push_back(AZ::Vector3::CreateZero());
+            lineBufferOut.push_back(twistVector);
+            lineValidityBufferOut.push_back(true);
+        }
+
+        template<typename Configuration>
+        AZStd::unique_ptr<AzPhysics::JointConfiguration> ConfigurationFactory(
+            const AZ::Quaternion& parentLocalRotation, const AZ::Quaternion& childLocalRotation)
+        {
+            auto jointConfig = AZStd::make_unique<Configuration>();
+            jointConfig->m_childLocalRotation = childLocalRotation;
+            jointConfig->m_parentLocalRotation = parentLocalRotation;
+
+            return jointConfig;
+        }
+
+    } // namespace
+
+    const AZStd::vector<AZ::TypeId> PhysXJointHelpersInterface::GetSupportedJointTypeIds() const
+    {
+        static AZStd::vector<AZ::TypeId> jointTypes = {
+            D6JointLimitConfiguration::RTTI_Type(),
+            FixedJointConfiguration::RTTI_Type(),
+            BallJointConfiguration::RTTI_Type(),
+            HingeJointConfiguration::RTTI_Type()
+        };
+        return jointTypes;
+    }
+
+    AZStd::optional<const AZ::TypeId> PhysXJointHelpersInterface::GetSupportedJointTypeId(AzPhysics::JointType typeEnum) const
+    {
+        switch (typeEnum)
+        {
+        case AzPhysics::JointType::D6Joint:
+            return azrtti_typeid<D6JointLimitConfiguration>();
+        case AzPhysics::JointType::FixedJoint:
+            return azrtti_typeid<FixedJointConfiguration>();
+        case AzPhysics::JointType::BallJoint:
+            return azrtti_typeid<BallJointConfiguration>();
+        case AzPhysics::JointType::HingeJoint:
+            return azrtti_typeid<HingeJointConfiguration>();
+        default:
+            AZ_Warning("PhysX Joint Utils", false, "Unsupported joint type in GetSupportedJointTypeId");
+        }
+        return AZStd::nullopt;
+    }
+
+    AZStd::unique_ptr<AzPhysics::JointConfiguration> PhysXJointHelpersInterface::ComputeInitialJointLimitConfiguration(
+        const AZ::TypeId& jointLimitTypeId,
+        const AZ::Quaternion& parentWorldRotation,
+        const AZ::Quaternion& childWorldRotation,
+        const AZ::Vector3& axis,
+        [[maybe_unused]] const AZStd::vector<AZ::Quaternion>& exampleLocalRotations)
+    {
+        const AZ::Vector3& normalizedAxis = axis.IsZero() ? AZ::Vector3::CreateAxisX() : axis.GetNormalized();
+        const AZ::Quaternion childLocalRotation = AZ::Quaternion::CreateShortestArc(
+            AZ::Vector3::CreateAxisX(), childWorldRotation.GetConjugate().TransformVector(normalizedAxis));
+        const AZ::Quaternion parentLocalRotation = parentWorldRotation.GetConjugate() * childWorldRotation * childLocalRotation;
+
+        if (jointLimitTypeId == azrtti_typeid<D6JointLimitConfiguration>())
+        {
+            return ConfigurationFactory<D6JointLimitConfiguration>(parentLocalRotation, childLocalRotation);
+        }
+        else if (jointLimitTypeId == azrtti_typeid<FixedJointConfiguration>())
+        {
+            return ConfigurationFactory<FixedJointConfiguration>(parentLocalRotation, childLocalRotation);
+        }
+        else if (jointLimitTypeId == azrtti_typeid<BallJointConfiguration>())
+        {
+            return ConfigurationFactory<BallJointConfiguration>(parentLocalRotation, childLocalRotation);
+        }
+        else if (jointLimitTypeId == azrtti_typeid<HingeJointConfiguration>())
+        {
+            return ConfigurationFactory<HingeJointConfiguration>(parentLocalRotation, childLocalRotation);
+        }
+
+        AZ_Warning("PhysX Joint Utils", false, "Unsupported joint type in ComputeInitialJointLimitConfiguration");
+        return nullptr;
+    }
+
+    void PhysXJointHelpersInterface::GenerateJointLimitVisualizationData(
+        const AzPhysics::JointConfiguration& configuration,
+        const AZ::Quaternion& parentRotation,
+        const AZ::Quaternion& childRotation,
+        float scale,
+        AZ::u32 angularSubdivisions,
+        AZ::u32 radialSubdivisions,
+        [[maybe_unused]] AZStd::vector<AZ::Vector3>& vertexBufferOut,
+        [[maybe_unused]] AZStd::vector<AZ::u32>& indexBufferOut,
+        AZStd::vector<AZ::Vector3>& lineBufferOut,
+        AZStd::vector<bool>& lineValidityBufferOut)
+    {
+        if (const auto d6JointConfiguration = azrtti_cast<const D6JointLimitConfiguration*>(&configuration))
+        {
+            const AZ::u32 angularSubdivisionsClamped = AZ::GetClamp(angularSubdivisions, 4u, 32u);
+            const AZ::u32 radialSubdivisionsClamped = AZ::GetClamp(radialSubdivisions, 1u, 4u);
+
+            const D6JointState jointState = CalculateD6JointState(
+                parentRotation, d6JointConfiguration->m_parentLocalRotation, childRotation, d6JointConfiguration->m_childLocalRotation);
+            const float swingAngleY = jointState.m_swingAngleY;
+            const float swingAngleZ = jointState.m_swingAngleZ;
+            const float twistAngle = jointState.m_twistAngle;
+            const float swingLimitY = AZ::DegToRad(d6JointConfiguration->m_swingLimitY);
+            const float swingLimitZ = AZ::DegToRad(d6JointConfiguration->m_swingLimitZ);
+            const float twistLimitLower = AZ::DegToRad(d6JointConfiguration->m_twistLimitLower);
+            const float twistLimitUpper = AZ::DegToRad(d6JointConfiguration->m_twistLimitUpper);
+
+            AppendD6SwingConeToLineBuffer(
+                d6JointConfiguration->m_parentLocalRotation, swingAngleY, swingAngleZ, swingLimitY, swingLimitZ, scale,
+                angularSubdivisionsClamped, radialSubdivisionsClamped, lineBufferOut, lineValidityBufferOut);
+            AppendD6TwistArcToLineBuffer(
+                d6JointConfiguration->m_parentLocalRotation, twistAngle, twistLimitLower, twistLimitUpper, scale,
+                angularSubdivisionsClamped, radialSubdivisionsClamped, lineBufferOut, lineValidityBufferOut);
+            AppendD6CurrentTwistToLineBuffer(
+                d6JointConfiguration->m_parentLocalRotation, twistAngle, twistLimitLower, twistLimitUpper, scale, lineBufferOut,
+                lineValidityBufferOut);
+        }
+    }
+}

+ 52 - 0
Gems/PhysX/Code/Source/System/PhysXJointInterface.h

@@ -0,0 +1,52 @@
+/*
+ * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates, or
+ * a third party where indicated.
+ *
+ * For complete copyright and license terms please see the LICENSE at the root of this
+ * distribution (the "License"). All use of this software is governed by the License,
+ * or, if provided, by the license below or the license accompanying this file. Do not
+ * remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *
+ */
+#pragma once
+
+#include <AzCore/Interface/Interface.h>
+#include <AzFramework/Physics/Common/PhysicsJoint.h>
+
+namespace AzPhysics
+{
+    struct JointConfiguration;
+}
+
+namespace PhysX
+{
+    class PhysXJointHelpersInterface
+        : public AZ::Interface<AzPhysics::JointHelpersInterface>::Registrar
+    {
+    public:
+        AZ_RTTI(PhysX::PhysXJointHelpersInterface, "{48AC5137-2226-4C57-8E4C-FCF3C1965252}", AzPhysics::JointHelpersInterface);
+
+        const AZStd::vector<AZ::TypeId> GetSupportedJointTypeIds() const override;
+        AZStd::optional<const AZ::TypeId> GetSupportedJointTypeId(AzPhysics::JointType typeEnum) const override;
+
+        AZStd::unique_ptr<AzPhysics::JointConfiguration> ComputeInitialJointLimitConfiguration(
+            const AZ::TypeId& jointLimitTypeId,
+            const AZ::Quaternion& parentWorldRotation,
+            const AZ::Quaternion& childWorldRotation,
+            const AZ::Vector3& axis,
+            const AZStd::vector<AZ::Quaternion>& exampleLocalRotations) override;
+
+        void GenerateJointLimitVisualizationData(
+            const AzPhysics::JointConfiguration& configuration,
+            const AZ::Quaternion& parentRotation,
+            const AZ::Quaternion& childRotation,
+            float scale,
+            AZ::u32 angularSubdivisions,
+            AZ::u32 radialSubdivisions,
+            AZStd::vector<AZ::Vector3>& vertexBufferOut,
+            AZStd::vector<AZ::u32>& indexBufferOut,
+            AZStd::vector<AZ::Vector3>& lineBufferOut,
+            AZStd::vector<bool>& lineValidityBufferOut) override;
+    };
+}

+ 2 - 0
Gems/PhysX/Code/Source/System/PhysXSystem.h

@@ -25,6 +25,7 @@
 #include <System/PhysXSdkCallbacks.h>
 
 #include <PhysX/Configuration/PhysXConfiguration.h>
+#include <System/PhysXJointInterface.h>
 
 namespace physx
 {
@@ -128,6 +129,7 @@ namespace PhysX
         Debug::PhysXDebug m_physXDebug; //! Handler for the PhysXDebug Interface.
         PhysXSettingsRegistryManager& m_registryManager; //! Handles all settings registry interactions.
         PhysXSceneInterface m_sceneInterface; //! Implemented the Scene Az::Interface.
+        PhysXJointHelpersInterface m_jointHelperInterface; //! Implementation of the JointHelpersInterface.
 
         class MaterialLibraryAssetHelper
             : private AZ::Data::AssetBus::Handler

+ 0 - 45
Gems/PhysX/Code/Source/SystemComponent.cpp

@@ -20,7 +20,6 @@
 #include <Source/Utils.h>
 #include <Source/Collision.h>
 #include <Source/Shape.h>
-#include <Source/Joint.h>
 #include <Source/Pipeline/MeshAssetHandler.h>
 #include <Source/Pipeline/HeightFieldAssetHandler.h>
 #include <Source/PhysXCharacters/API/CharacterUtils.h>
@@ -93,7 +92,6 @@ namespace PhysX
 
     void SystemComponent::Reflect(AZ::ReflectContext* context)
     {
-        D6JointLimitConfiguration::Reflect(context);
         Pipeline::MeshAsset::Reflect(context);
 
         PhysX::ReflectionUtils::ReflectPhysXOnlyApi(context);
@@ -347,49 +345,6 @@ namespace PhysX
         return AZStd::make_shared<PhysX::Material>(materialConfiguration);
     }
 
-    AZStd::vector<AZ::TypeId> SystemComponent::GetSupportedJointTypes()
-    {
-        return JointUtils::GetSupportedJointTypes();
-    }
-
-    AZStd::shared_ptr<Physics::JointLimitConfiguration> SystemComponent::CreateJointLimitConfiguration(AZ::TypeId jointType)
-    {
-        return JointUtils::CreateJointLimitConfiguration(jointType);
-    }
-
-    AZStd::shared_ptr<Physics::Joint> SystemComponent::CreateJoint(const AZStd::shared_ptr<Physics::JointLimitConfiguration>& configuration,
-        AzPhysics::SimulatedBody* parentBody, AzPhysics::SimulatedBody* childBody)
-    {
-        return JointUtils::CreateJoint(configuration, parentBody, childBody);
-    }
-
-    void SystemComponent::GenerateJointLimitVisualizationData(
-        const Physics::JointLimitConfiguration& configuration,
-        const AZ::Quaternion& parentRotation,
-        const AZ::Quaternion& childRotation,
-        float scale,
-        AZ::u32 angularSubdivisions,
-        AZ::u32 radialSubdivisions,
-        AZStd::vector<AZ::Vector3>& vertexBufferOut,
-        AZStd::vector<AZ::u32>& indexBufferOut,
-        AZStd::vector<AZ::Vector3>& lineBufferOut,
-        AZStd::vector<bool>& lineValidityBufferOut)
-    {
-        JointUtils::GenerateJointLimitVisualizationData(configuration, parentRotation, childRotation, scale,
-            angularSubdivisions, radialSubdivisions, vertexBufferOut, indexBufferOut, lineBufferOut, lineValidityBufferOut);
-    }
-
-    AZStd::unique_ptr<Physics::JointLimitConfiguration> SystemComponent::ComputeInitialJointLimitConfiguration(
-        const AZ::TypeId& jointLimitTypeId,
-        const AZ::Quaternion& parentWorldRotation,
-        const AZ::Quaternion& childWorldRotation,
-        const AZ::Vector3& axis,
-        const AZStd::vector<AZ::Quaternion>& exampleLocalRotations)
-    {
-        return JointUtils::ComputeInitialJointLimitConfiguration(jointLimitTypeId, parentWorldRotation,
-            childWorldRotation, axis, exampleLocalRotations);
-    }
-
     void SystemComponent::ReleaseNativeMeshObject(void* nativeMeshObject)
     {
         if (nativeMeshObject)

+ 0 - 22
Gems/PhysX/Code/Source/SystemComponent.h

@@ -115,28 +115,6 @@ namespace PhysX
         AZStd::shared_ptr<Physics::Shape> CreateShape(const Physics::ColliderConfiguration& colliderConfiguration, const Physics::ShapeConfiguration& configuration) override;
         AZStd::shared_ptr<Physics::Material> CreateMaterial(const Physics::MaterialConfiguration& materialConfiguration) override;
 
-        AZStd::vector<AZ::TypeId> GetSupportedJointTypes() override;
-        AZStd::shared_ptr<Physics::JointLimitConfiguration> CreateJointLimitConfiguration(AZ::TypeId jointType) override;
-        AZStd::shared_ptr<Physics::Joint> CreateJoint(const AZStd::shared_ptr<Physics::JointLimitConfiguration>& configuration,
-            AzPhysics::SimulatedBody* parentBody, AzPhysics::SimulatedBody* childBody) override;
-        void GenerateJointLimitVisualizationData(
-            const Physics::JointLimitConfiguration& configuration,
-            const AZ::Quaternion& parentRotation,
-            const AZ::Quaternion& childRotation,
-            float scale,
-            AZ::u32 angularSubdivisions,
-            AZ::u32 radialSubdivisions,
-            AZStd::vector<AZ::Vector3>& vertexBufferOut,
-            AZStd::vector<AZ::u32>& indexBufferOut,
-            AZStd::vector<AZ::Vector3>& lineBufferOut,
-            AZStd::vector<bool>& lineValidityBufferOut) override;
-        AZStd::unique_ptr<Physics::JointLimitConfiguration> ComputeInitialJointLimitConfiguration(
-            const AZ::TypeId& jointLimitTypeId,
-            const AZ::Quaternion& parentWorldRotation,
-            const AZ::Quaternion& childWorldRotation,
-            const AZ::Vector3& axis,
-            const AZStd::vector<AZ::Quaternion>& exampleLocalRotations) override;
-
         void ReleaseNativeMeshObject(void* nativeMeshObject) override;
 
         // Assets related data

+ 7 - 3
Gems/PhysX/Code/Source/Utils.cpp

@@ -40,9 +40,9 @@
 #include <Source/Shape.h>
 #include <Source/StaticRigidBodyComponent.h>
 #include <Source/RigidBodyStatic.h>
-#include <Source/Joint.h>
 #include <Source/Utils.h>
 #include <PhysX/PhysXLocks.h>
+#include <PhysX/Joint/Configuration/PhysXJointConfiguration.h>
 
 namespace PhysX
 {
@@ -1394,8 +1394,12 @@ namespace PhysX
 
             ForceRegionBusBehaviorHandler::Reflect(context);
 
-            GenericJointConfiguration::Reflect(context);
-            GenericJointLimitsConfiguration::Reflect(context);
+            D6JointLimitConfiguration::Reflect(context);
+            JointGenericProperties::Reflect(context);
+            JointLimitProperties::Reflect(context);
+            FixedJointConfiguration::Reflect(context);
+            BallJointConfiguration::Reflect(context);
+            HingeJointConfiguration::Reflect(context);
         }
 
         void ForceRegionBusBehaviorHandler::Reflect(AZ::ReflectContext* context)

+ 1 - 0
Gems/PhysX/Code/Tests/Benchmarks/PhysXBenchmarksUtilities.h

@@ -24,6 +24,7 @@
 namespace AzPhysics
 {
     class Scene;
+    struct RigidBody;
 }
 
 namespace PhysX::Benchmarks

+ 33 - 24
Gems/PhysX/Code/Tests/Benchmarks/PhysXJointBenchmarks.cpp

@@ -26,7 +26,7 @@
 #include <Benchmarks/PhysXBenchmarksCommon.h>
 
 #include <PhysXTestCommon.h>
-#include <Joint.h>
+#include <PhysX/Joint/Configuration/PhysXJointConfiguration.h>
 
 namespace PhysX::Benchmarks
 {
@@ -102,7 +102,7 @@ namespace PhysX::Benchmarks
         {
             AzPhysics::RigidBody* m_parent;
             AzPhysics::SimulatedBody* m_child;
-            AZStd::shared_ptr<Physics::Joint> m_joint;
+            AzPhysics::JointHandle m_jointHandle;
         };
         //! Structure to hold the upper and lower twist limits
         //! used with Utils::CreateJoints GenerateTwistLimitsFuncPtr
@@ -122,13 +122,11 @@ namespace PhysX::Benchmarks
 
         //! Helper function to add the required number of joints to the provided world
         //! @param numJoints, the requested number of joints to spawn
-        //! @param system, current active physics system
         //! @param scene, the physics scene to spawn the joints and their world bodies into
         //! @param parentPositionGenerator, [optional] function pointer to allow caller to pick the spawn position of the parent body
         //! @param childPositionGenerator, [optional] function pointer to allow caller to pick the spawn position of the child body
         //! @param GenerateTwistLimitsFuncPtr, [optional] function pointer to allow caller to pick the twist limits of the joint
         AZStd::vector<JointGroup> CreateJoints(int numJoints,
-            Physics::System* system,
             AzPhysics::Scene* scene,
             GenerateSpawnPositionFuncPtr* parentPositionGenerator = nullptr,
             GenerateSpawnPositionFuncPtr* childPositionGenerator = nullptr,
@@ -173,18 +171,18 @@ namespace PhysX::Benchmarks
                 AzPhysics::SimulatedBodyHandle staticRigidBodyHandle = scene->AddSimulatedBody(&staticRigidBodyConfig);
                 newJoint.m_child = scene->GetSimulatedBodyFromHandle(staticRigidBodyHandle);
 
-                AZStd::shared_ptr <PhysX::D6JointLimitConfiguration> config = AZStd::make_shared<D6JointLimitConfiguration>();
+                PhysX::D6JointLimitConfiguration config;
 
                 TwistLimits limits(JointConstants::CreateJointDefaults::UpperLimit, JointConstants::CreateJointDefaults::LowerLimit);
                 if (twistLimitsGenerator)
                 {
                     limits = (*twistLimitsGenerator)(i);
                 }
-                config->m_twistLimitUpper = limits.m_upperLimit;
-                config->m_twistLimitLower = limits.m_lowerLimit;
-                config->m_swingLimitY = 1.0f;
-                config->m_swingLimitZ = 1.0f;
-                newJoint.m_joint = system->CreateJoint(config, newJoint.m_parent, newJoint.m_child);
+                config.m_twistLimitUpper = limits.m_upperLimit;
+                config.m_twistLimitLower = limits.m_lowerLimit;
+                config.m_swingLimitY = 1.0f;
+                config.m_swingLimitZ = 1.0f;
+                newJoint.m_jointHandle = scene->AddJoint(&config, newJoint.m_parent->m_bodyHandle, newJoint.m_child->m_bodyHandle);
                 joints.emplace_back(AZStd::move(newJoint));
             }
 
@@ -201,8 +199,6 @@ namespace PhysX::Benchmarks
         virtual void SetUp([[maybe_unused]] const ::benchmark::State &state) override
         {
             PhysXBaseBenchmarkFixture::SetUpInternal();
-            //need to get the Physics::System to be able to spawn the rigid bodies
-            m_system = AZ::Interface<Physics::System>::Get();
         }
 
         virtual void TearDown([[maybe_unused]] const ::benchmark::State &state) override
@@ -218,8 +214,6 @@ namespace PhysX::Benchmarks
             return sceneConfig;
         }
         // PhysXBaseBenchmarkFixture Interface ---------
-
-        Physics::System *m_system;
     };
 
     //! BM_Joints_AtRest - This test will spawn the requested number of joints
@@ -237,7 +231,7 @@ namespace PhysX::Benchmarks
             return position;
         };
 
-        AZStd::vector<Utils::JointGroup> joinGroups = Utils::CreateJoints(aznumeric_cast<int>(state.range(0)), m_system, m_defaultScene,
+        AZStd::vector<Utils::JointGroup> joinGroups = Utils::CreateJoints(aznumeric_cast<int>(state.range(0)), m_defaultScene,
             &parentPosGenerator, &childPosGenerator);
 
         //setup the sub tick tracker
@@ -260,6 +254,11 @@ namespace PhysX::Benchmarks
         }
         subTickTracker.Stop();
 
+        for (const auto& jointGroup : joinGroups)
+        {
+            m_defaultScene->RemoveJoint(jointGroup.m_jointHandle);
+        }
+
         //sort the frame times and get the P50, P90, P99 percentiles
         Utils::ReportFramePercentileCounters(state, tickTimes, subTickTracker.GetSubTickTimes());
         Utils::ReportFrameStandardDeviationAndMeanCounters(state, tickTimes, subTickTracker.GetSubTickTimes());
@@ -287,7 +286,7 @@ namespace PhysX::Benchmarks
             return Utils::TwistLimits(JointConstants::JointSettings::SwingingJointUpperLimit, JointConstants::JointSettings::SwingingJointLowerLimit);
         };
 
-        AZStd::vector<Utils::JointGroup> joinGroups = Utils::CreateJoints(aznumeric_cast<int>(state.range(0)), m_system, m_defaultScene,
+        AZStd::vector<Utils::JointGroup> joinGroups = Utils::CreateJoints(aznumeric_cast<int>(state.range(0)), m_defaultScene,
             &parentPosGenerator, &childPosGenerator, &twistGenerator);
 
         //setup the sub tick tracker
@@ -326,6 +325,11 @@ namespace PhysX::Benchmarks
         }
         subTickTracker.Stop();
 
+        for (const auto& jointGroup : joinGroups)
+        {
+            m_defaultScene->RemoveJoint(jointGroup.m_jointHandle);
+        }
+
         //sort the frame times and get the P50, P90, P99 percentiles
         Utils::ReportFramePercentileCounters(state, tickTimes, subTickTracker.GetSubTickTimes());
         Utils::ReportFrameStandardDeviationAndMeanCounters(state, tickTimes, subTickTracker.GetSubTickTimes());
@@ -370,17 +374,18 @@ namespace PhysX::Benchmarks
         snakeRigidBodies = Utils::GetRigidBodiesFromHandles(m_defaultScene, snakeRigidBodyHandles);
 
         //build the snake
-        AZStd::vector<AZStd::shared_ptr<Physics::Joint>> joints;
-        AZStd::shared_ptr <PhysX::D6JointLimitConfiguration> config = AZStd::make_shared<D6JointLimitConfiguration>();
-        config->m_twistLimitUpper = JointConstants::CreateJointDefaults::UpperLimit;
-        config->m_twistLimitLower = JointConstants::CreateJointDefaults::LowerLimit;
-        config->m_swingLimitY = 1.0f;
-        config->m_swingLimitZ = 1.0f;
+        AZStd::vector<AzPhysics::JointHandle> jointHandles;
+        PhysX::D6JointLimitConfiguration config;
+        config.m_twistLimitUpper = JointConstants::CreateJointDefaults::UpperLimit;
+        config.m_twistLimitLower = JointConstants::CreateJointDefaults::LowerLimit;
+        config.m_swingLimitY = 1.0f;
+        config.m_swingLimitZ = 1.0f;
         //build the head
-        joints.emplace_back(m_system->CreateJoint(config, snakeRigidBodies[0], snakeHead));
+        jointHandles.emplace_back(m_defaultScene->AddJoint(&config, snakeRigidBodies[0]->m_bodyHandle, snakeHead->m_bodyHandle));
         for (size_t i = 0; (i+1) < snakeRigidBodies.size(); i++)
         {
-            joints.emplace_back(m_system->CreateJoint(config, snakeRigidBodies[i + 1], snakeRigidBodies[i]));
+            jointHandles.emplace_back(
+                m_defaultScene->AddJoint(&config, snakeRigidBodies[i + 1]->m_bodyHandle, snakeRigidBodies[i]->m_bodyHandle));
         }
 
         //setup the sub tick tracker
@@ -404,6 +409,10 @@ namespace PhysX::Benchmarks
         subTickTracker.Stop();
 
         m_defaultScene->RemoveSimulatedBodies(snakeRigidBodyHandles);
+        for (const auto& jointHandle : jointHandles)
+        {
+            m_defaultScene->RemoveJoint(jointHandle);
+        }
         snakeRigidBodyHandles.clear();
 
         //sort the frame times and get the P50, P90, P99 percentiles

+ 1 - 1
Gems/PhysX/Code/Tests/PhysXGenericTestFixture.h

@@ -111,7 +111,7 @@ namespace PhysX
     };
 
     class GenericPhysicsInterfaceTest
-        : protected GenericPhysicsFixture
+        : public GenericPhysicsFixture
         , public testing::Test
     {
     public:

+ 98 - 10
Gems/PhysX/Code/Tests/PhysXJointsTest.cpp

@@ -18,17 +18,18 @@
 
 #include <BoxColliderComponent.h>
 #include <RigidBodyComponent.h>
-#include <Joint.h>
 #include <JointComponent.h>
 #include <BallJointComponent.h>
 #include <FixedJointComponent.h>
 #include <HingeJointComponent.h>
+#include <PhysX/Joint/Configuration/PhysXJointConfiguration.h>
 
 #include <AzCore/Component/TransformBus.h>
 #include <AzFramework/Components/TransformComponent.h>
 #include <AzFramework/Physics/ShapeConfiguration.h>
 #include <AzFramework/Physics/SystemBus.h>
 #include <AzFramework/Physics/Configuration/RigidBodyConfiguration.h>
+#include <AzFramework/Physics/PhysicsSystem.h>
 
 namespace PhysX
 {
@@ -38,8 +39,9 @@ namespace PhysX
     AZStd::unique_ptr<AZ::Entity> AddBodyColliderEntity( AzPhysics::SceneHandle sceneHandle,
         const AZ::Vector3& position, 
         const AZ::Vector3& initialLinearVelocity,
-        AZStd::shared_ptr<GenericJointConfiguration> jointConfig = nullptr,
-        AZStd::shared_ptr<GenericJointLimitsConfiguration> jointLimitsConfig = nullptr)
+        AZStd::shared_ptr<JointComponentConfiguration> jointConfig = nullptr,
+        AZStd::shared_ptr<JointGenericProperties> jointGenericProperties = nullptr,
+        AZStd::shared_ptr<JointLimitProperties> jointLimitProperties = nullptr)
     {
         const char* entityName = "testEntity";
         auto entity = AZStd::make_unique<AZ::Entity>(entityName);
@@ -68,10 +70,12 @@ namespace PhysX
         {
             jointConfig->m_followerEntity = entity->GetId();
 
-            GenericJointLimitsConfiguration defaultJointLimitsConfig;
+            JointGenericProperties defaultJointGenericProperties;
+            JointLimitProperties defaultJointLimitProperties;
             entity->CreateComponent<JointComponentType>(
                     *jointConfig,
-                    (jointLimitsConfig)? *jointLimitsConfig : defaultJointLimitsConfig);
+                    (jointGenericProperties)? *jointGenericProperties : defaultJointGenericProperties,
+                    (jointLimitProperties)? *jointLimitProperties : defaultJointLimitProperties);
         }
 
         entity->Init();
@@ -114,7 +118,7 @@ namespace PhysX
             leadPosition,
             leadInitialLinearVelocity);
 
-        auto jointConfig = AZStd::make_shared<GenericJointConfiguration>();
+        auto jointConfig = AZStd::make_shared<JointComponentConfiguration>();
         jointConfig->m_leadEntity = leadEntity->GetId();
         jointConfig->m_localTransformFromFollower = jointLocalTransform;
 
@@ -150,17 +154,18 @@ namespace PhysX
             leadPosition, 
             leadInitialLinearVelocity);
 
-        auto jointConfig = AZStd::make_shared<GenericJointConfiguration>();
+        auto jointConfig = AZStd::make_shared<JointComponentConfiguration>();
         jointConfig->m_leadEntity = leadEntity->GetId();
         jointConfig->m_localTransformFromFollower = jointLocalTransform;
 
-        auto jointLimits = AZStd::make_shared <GenericJointLimitsConfiguration>();
+        auto jointLimits = AZStd::make_shared <JointLimitProperties>();
         jointLimits->m_isLimited = false;
 
         auto followerEntity = AddBodyColliderEntity<HingeJointComponent>(m_testSceneHandle,
             followerPosition,
             followerInitialLinearVelocity,
             jointConfig,
+            nullptr,
             jointLimits);
 
         const AZ::Vector3 followerEndPosition = RunJointTest(m_defaultScene, followerEntity->GetId());
@@ -191,21 +196,104 @@ namespace PhysX
             leadPosition,
             leadInitialLinearVelocity);
 
-        auto jointConfig = AZStd::make_shared<GenericJointConfiguration>();
+        auto jointConfig = AZStd::make_shared<JointComponentConfiguration>();
         jointConfig->m_leadEntity = leadEntity->GetId();
         jointConfig->m_localTransformFromFollower = jointLocalTransform;
 
-        auto jointLimits = AZStd::make_shared <GenericJointLimitsConfiguration>();
+        auto jointLimits = AZStd::make_shared <JointLimitProperties>();
         jointLimits->m_isLimited = false;
 
         auto followerEntity = AddBodyColliderEntity<BallJointComponent>(m_testSceneHandle,
             followerPosition,
             followerInitialLinearVelocity,
             jointConfig,
+            nullptr,
             jointLimits);
 
         const AZ::Vector3 followerEndPosition = RunJointTest(m_defaultScene, followerEntity->GetId());
 
         EXPECT_TRUE(followerEndPosition.GetZ() > followerPosition.GetZ());
     }
+
+// for some reason TYPED_TEST_CASE with the fixture is not working on Android + Linux
+#ifdef ENABLE_JOINTS_TYPED_TEST_CASE
+    template<class JointConfigurationType>
+    class PhysXJointsApiTest : public PhysX::GenericPhysicsInterfaceTest
+    {
+    public:
+        
+        void SetUp() override
+        {
+            PhysX::GenericPhysicsInterfaceTest::SetUp();
+
+            if (auto* sceneInterface = AZ::Interface<AzPhysics::SceneInterface>::Get())
+            {
+                AzPhysics::RigidBodyConfiguration parentConfiguration;
+                AzPhysics::RigidBodyConfiguration childConfiguration;
+
+                auto colliderConfig = AZStd::make_shared<Physics::ColliderConfiguration>();
+                auto shapeConfiguration = AZStd::make_shared<Physics::BoxShapeConfiguration>(AZ::Vector3(1.0f, 1.0f, 1.0f));
+
+                parentConfiguration.m_colliderAndShapeData = AzPhysics::ShapeColliderPair(colliderConfig, shapeConfiguration);
+                childConfiguration.m_colliderAndShapeData = AzPhysics::ShapeColliderPair(colliderConfig, shapeConfiguration);
+                
+                // Put the child body a bit to the lower side of X to avoid it colliding with parent
+                childConfiguration.m_position.SetX(childConfiguration.m_position.GetX() - 2.0f);
+                m_childInitialPos = childConfiguration.m_position;
+                parentConfiguration.m_initialLinearVelocity.SetX(10.0f);
+
+                m_parentBodyHandle = sceneInterface->AddSimulatedBody(m_testSceneHandle, &parentConfiguration);
+                m_childBodyHandle = sceneInterface->AddSimulatedBody(m_testSceneHandle, &childConfiguration);
+            }
+        }
+
+        void TearDown() override
+        {
+            if (auto* sceneInterface = AZ::Interface<AzPhysics::SceneInterface>::Get())
+            {
+                sceneInterface->RemoveSimulatedBody(m_testSceneHandle, m_parentBodyHandle);
+                sceneInterface->RemoveSimulatedBody(m_testSceneHandle, m_childBodyHandle);    
+            }
+
+            PhysX::GenericPhysicsInterfaceTest::TearDown();
+        }
+
+        AzPhysics::SimulatedBodyHandle m_parentBodyHandle = AzPhysics::InvalidJointHandle;
+        AzPhysics::SimulatedBodyHandle m_childBodyHandle = AzPhysics::InvalidJointHandle;
+        AZ::Vector3 m_childInitialPos;
+    };
+
+    using JointTypes = testing::Types<
+        D6JointLimitConfiguration, 
+        FixedJointConfiguration, 
+        BallJointConfiguration, 
+        HingeJointConfiguration>;
+    TYPED_TEST_CASE(PhysXJointsApiTest, JointTypes);
+
+    TYPED_TEST(PhysXJointsApiTest, Joint_ChildFollowsParent)
+    {
+        TypeParam jointConfiguration;
+        AzPhysics::JointHandle jointHandle = AzPhysics::InvalidJointHandle;
+
+        if (auto* sceneInterface = AZ::Interface<AzPhysics::SceneInterface>::Get())
+        {
+            jointHandle = sceneInterface->AddJoint(m_testSceneHandle, &jointConfiguration, m_parentBodyHandle, m_childBodyHandle);    
+        }
+
+        EXPECT_NE(jointHandle, AzPhysics::InvalidJointHandle);
+
+        // run physics to trigger the the move of parent body
+        TestUtils::UpdateScene(m_testSceneHandle, AzPhysics::SystemConfiguration::DefaultFixedTimestep, 1);
+
+        AZ::Vector3 childCurrentPos;
+
+        if (auto* sceneInterface = AZ::Interface<AzPhysics::SceneInterface>::Get())
+        {
+            auto* childBody = sceneInterface->GetSimulatedBodyFromHandle(m_testSceneHandle, m_childBodyHandle);
+            childCurrentPos = childBody->GetPosition();
+        }
+
+        EXPECT_GT(childCurrentPos.GetX(), m_childInitialPos.GetX());
+    }
+#endif // ENABLE_JOINTS_TYPED_TEST_CASE
 }

+ 88 - 88
Gems/PhysX/Code/Tests/RagdollConfiguration.xml

@@ -4,7 +4,7 @@
 			<Class name="AZStd::string" field="name" value="" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
 		</Class>
 		<Class name="AZStd::vector" field="nodes" type="{023260FD-3D32-570B-A75E-4099359BE960}">
-			<Class name="RagdollNodeConfiguration" field="element" version="4" type="{A1796586-85AB-496E-93C9-C5841F03B1AD}">
+			<Class name="RagdollNodeConfiguration" field="element" version="5" type="{A1796586-85AB-496E-93C9-C5841F03B1AD}">
 				<Class name="RigidBodyConfiguration" field="BaseClass1" version="2" type="{ACFA8900-8530-4744-AF00-AA533C868A8E}">
 					<Class name="WorldBodyConfiguration" field="BaseClass1" version="1" type="{6EEB377C-DC60-4E10-AF12-9626C0763B2D}">
 						<Class name="AZStd::string" field="name" value="C_pelvis_JNT" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
@@ -27,9 +27,9 @@
 					<Class name="Matrix3x3" field="Inertia tensor" value="1.0000000 0.0000000 0.0000000 0.0000000 1.0000000 0.0000000 0.0000000 0.0000000 1.0000000" type="{15A4332F-7C3F-4A58-AC35-50E1CE53FB9C}"/>
 					<Class name="float" field="Maximum Angular Velocity" value="100.0000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
 				</Class>
-				<Class name="AZStd::shared_ptr" field="JointLimit" type="{F5C4DCF0-277D-5536-921B-A6A4AEEF7226}">
-					<Class name="D6JointLimitConfiguration" field="element" version="1" type="{90C5C23D-16C0-4F23-AD50-A190E402388E}">
-						<Class name="JointLimitConfiguration" field="BaseClass1" version="1" type="{C9B70C4D-22D7-45AB-9B0A-30A4ED5E42DB}">
+				<Class name="AZStd::shared_ptr" field="JointConfig" type="{B7A3D04D-1ECC-5D84-8C93-B4C9E4F8F6B8}">
+					<Class name="D6JointLimitConfiguration" field="element" version="1" type="{88E067B4-21E8-4FFA-9142-6C52605B704C}">
+						<Class name="JointConfiguration" field="BaseClass1" version="1" type="{DF91D39A-4901-48C4-9159-93FD2ACA5252}">
 							<Class name="Quaternion" field="ParentLocalRotation" value="-0.0391054 -0.7143280 0.0391054 0.6976625" type="{73103120-3DD3-4873-BAB3-9713FA2804FB}"/>
 							<Class name="Vector3" field="ParentLocalPosition" value="0.0000000 0.0000000 0.0000000" type="{8379EB7D-01FA-4538-B64B-A6543B4BE73D}"/>
 							<Class name="Quaternion" field="ChildLocalRotation" value="0.0000000 -0.0117843 0.0553034 0.9984280" type="{73103120-3DD3-4873-BAB3-9713FA2804FB}"/>
@@ -42,7 +42,7 @@
 					</Class>
 				</Class>
 			</Class>
-			<Class name="RagdollNodeConfiguration" field="element" version="4" type="{A1796586-85AB-496E-93C9-C5841F03B1AD}">
+			<Class name="RagdollNodeConfiguration" field="element" version="5" type="{A1796586-85AB-496E-93C9-C5841F03B1AD}">
 				<Class name="RigidBodyConfiguration" field="BaseClass1" version="2" type="{ACFA8900-8530-4744-AF00-AA533C868A8E}">
 					<Class name="WorldBodyConfiguration" field="BaseClass1" version="1" type="{6EEB377C-DC60-4E10-AF12-9626C0763B2D}">
 						<Class name="AZStd::string" field="name" value="L_arm_JNT" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
@@ -65,9 +65,9 @@
 					<Class name="Matrix3x3" field="Inertia tensor" value="1.0000000 0.0000000 0.0000000 0.0000000 1.0000000 0.0000000 0.0000000 0.0000000 1.0000000" type="{15A4332F-7C3F-4A58-AC35-50E1CE53FB9C}"/>
 					<Class name="float" field="Maximum Angular Velocity" value="100.0000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
 				</Class>
-				<Class name="AZStd::shared_ptr" field="JointLimit" type="{F5C4DCF0-277D-5536-921B-A6A4AEEF7226}">
-					<Class name="D6JointLimitConfiguration" field="element" version="1" type="{90C5C23D-16C0-4F23-AD50-A190E402388E}">
-						<Class name="JointLimitConfiguration" field="BaseClass1" version="1" type="{C9B70C4D-22D7-45AB-9B0A-30A4ED5E42DB}">
+				<Class name="AZStd::shared_ptr" field="JointConfig" type="{B7A3D04D-1ECC-5D84-8C93-B4C9E4F8F6B8}">
+					<Class name="D6JointLimitConfiguration" field="element" version="1" type="{88E067B4-21E8-4FFA-9142-6C52605B704C}">
+						<Class name="JointConfiguration" field="BaseClass1" version="1" type="{DF91D39A-4901-48C4-9159-93FD2ACA5252}">
 							<Class name="Quaternion" field="ParentLocalRotation" value="0.0288827 -0.3743261 -0.0537340 0.9254376" type="{73103120-3DD3-4873-BAB3-9713FA2804FB}"/>
 							<Class name="Vector3" field="ParentLocalPosition" value="0.0000000 0.0000000 0.0000000" type="{8379EB7D-01FA-4538-B64B-A6543B4BE73D}"/>
 							<Class name="Quaternion" field="ChildLocalRotation" value="0.0000000 -0.0054825 -0.0006252 1.0001229" type="{73103120-3DD3-4873-BAB3-9713FA2804FB}"/>
@@ -80,7 +80,7 @@
 					</Class>
 				</Class>
 			</Class>
-			<Class name="RagdollNodeConfiguration" field="element" version="4" type="{A1796586-85AB-496E-93C9-C5841F03B1AD}">
+			<Class name="RagdollNodeConfiguration" field="element" version="5" type="{A1796586-85AB-496E-93C9-C5841F03B1AD}">
 				<Class name="RigidBodyConfiguration" field="BaseClass1" version="2" type="{ACFA8900-8530-4744-AF00-AA533C868A8E}">
 					<Class name="WorldBodyConfiguration" field="BaseClass1" version="1" type="{6EEB377C-DC60-4E10-AF12-9626C0763B2D}">
 						<Class name="AZStd::string" field="name" value="C_spine_01_JNT" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
@@ -103,9 +103,9 @@
 					<Class name="Matrix3x3" field="Inertia tensor" value="1.0000000 0.0000000 0.0000000 0.0000000 1.0000000 0.0000000 0.0000000 0.0000000 1.0000000" type="{15A4332F-7C3F-4A58-AC35-50E1CE53FB9C}"/>
 					<Class name="float" field="Maximum Angular Velocity" value="100.0000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
 				</Class>
-				<Class name="AZStd::shared_ptr" field="JointLimit" type="{F5C4DCF0-277D-5536-921B-A6A4AEEF7226}">
-					<Class name="D6JointLimitConfiguration" field="element" version="1" type="{90C5C23D-16C0-4F23-AD50-A190E402388E}">
-						<Class name="JointLimitConfiguration" field="BaseClass1" version="1" type="{C9B70C4D-22D7-45AB-9B0A-30A4ED5E42DB}">
+				<Class name="AZStd::shared_ptr" field="JointConfig" type="{B7A3D04D-1ECC-5D84-8C93-B4C9E4F8F6B8}">
+					<Class name="D6JointLimitConfiguration" field="element" version="1" type="{88E067B4-21E8-4FFA-9142-6C52605B704C}">
+						<Class name="JointConfiguration" field="BaseClass1" version="1" type="{DF91D39A-4901-48C4-9159-93FD2ACA5252}">
 							<Class name="Quaternion" field="ParentLocalRotation" value="0.0000000 -0.0045330 0.0049514 1.0000752" type="{73103120-3DD3-4873-BAB3-9713FA2804FB}"/>
 							<Class name="Vector3" field="ParentLocalPosition" value="0.0000000 0.0000000 0.0000000" type="{8379EB7D-01FA-4538-B64B-A6543B4BE73D}"/>
 							<Class name="Quaternion" field="ChildLocalRotation" value="0.0000000 -0.0045330 0.0049514 1.0000751" type="{73103120-3DD3-4873-BAB3-9713FA2804FB}"/>
@@ -118,7 +118,7 @@
 					</Class>
 				</Class>
 			</Class>
-			<Class name="RagdollNodeConfiguration" field="element" version="4" type="{A1796586-85AB-496E-93C9-C5841F03B1AD}">
+			<Class name="RagdollNodeConfiguration" field="element" version="5" type="{A1796586-85AB-496E-93C9-C5841F03B1AD}">
 				<Class name="RigidBodyConfiguration" field="BaseClass1" version="2" type="{ACFA8900-8530-4744-AF00-AA533C868A8E}">
 					<Class name="WorldBodyConfiguration" field="BaseClass1" version="1" type="{6EEB377C-DC60-4E10-AF12-9626C0763B2D}">
 						<Class name="AZStd::string" field="name" value="C_spine_02_JNT" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
@@ -141,9 +141,9 @@
 					<Class name="Matrix3x3" field="Inertia tensor" value="1.0000000 0.0000000 0.0000000 0.0000000 1.0000000 0.0000000 0.0000000 0.0000000 1.0000000" type="{15A4332F-7C3F-4A58-AC35-50E1CE53FB9C}"/>
 					<Class name="float" field="Maximum Angular Velocity" value="100.0000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
 				</Class>
-				<Class name="AZStd::shared_ptr" field="JointLimit" type="{F5C4DCF0-277D-5536-921B-A6A4AEEF7226}">
-					<Class name="D6JointLimitConfiguration" field="element" version="1" type="{90C5C23D-16C0-4F23-AD50-A190E402388E}">
-						<Class name="JointLimitConfiguration" field="BaseClass1" version="1" type="{C9B70C4D-22D7-45AB-9B0A-30A4ED5E42DB}">
+				<Class name="AZStd::shared_ptr" field="JointConfig" type="{B7A3D04D-1ECC-5D84-8C93-B4C9E4F8F6B8}">
+					<Class name="D6JointLimitConfiguration" field="element" version="1" type="{88E067B4-21E8-4FFA-9142-6C52605B704C}">
+						<Class name="JointConfiguration" field="BaseClass1" version="1" type="{DF91D39A-4901-48C4-9159-93FD2ACA5252}">
 							<Class name="Quaternion" field="ParentLocalRotation" value="0.0000000 0.0000000 -0.0000000 1.0001725" type="{73103120-3DD3-4873-BAB3-9713FA2804FB}"/>
 							<Class name="Vector3" field="ParentLocalPosition" value="0.0000000 0.0000000 0.0000000" type="{8379EB7D-01FA-4538-B64B-A6543B4BE73D}"/>
 							<Class name="Quaternion" field="ChildLocalRotation" value="0.0000000 0.0000000 -0.0000000 1.0001724" type="{73103120-3DD3-4873-BAB3-9713FA2804FB}"/>
@@ -156,7 +156,7 @@
 					</Class>
 				</Class>
 			</Class>
-			<Class name="RagdollNodeConfiguration" field="element" version="4" type="{A1796586-85AB-496E-93C9-C5841F03B1AD}">
+			<Class name="RagdollNodeConfiguration" field="element" version="5" type="{A1796586-85AB-496E-93C9-C5841F03B1AD}">
 				<Class name="RigidBodyConfiguration" field="BaseClass1" version="2" type="{ACFA8900-8530-4744-AF00-AA533C868A8E}">
 					<Class name="WorldBodyConfiguration" field="BaseClass1" version="1" type="{6EEB377C-DC60-4E10-AF12-9626C0763B2D}">
 						<Class name="AZStd::string" field="name" value="C_spine_03_JNT" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
@@ -179,9 +179,9 @@
 					<Class name="Matrix3x3" field="Inertia tensor" value="1.0000000 0.0000000 0.0000000 0.0000000 1.0000000 0.0000000 0.0000000 0.0000000 1.0000000" type="{15A4332F-7C3F-4A58-AC35-50E1CE53FB9C}"/>
 					<Class name="float" field="Maximum Angular Velocity" value="100.0000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
 				</Class>
-				<Class name="AZStd::shared_ptr" field="JointLimit" type="{F5C4DCF0-277D-5536-921B-A6A4AEEF7226}">
-					<Class name="D6JointLimitConfiguration" field="element" version="1" type="{90C5C23D-16C0-4F23-AD50-A190E402388E}">
-						<Class name="JointLimitConfiguration" field="BaseClass1" version="1" type="{C9B70C4D-22D7-45AB-9B0A-30A4ED5E42DB}">
+				<Class name="AZStd::shared_ptr" field="JointConfig" type="{B7A3D04D-1ECC-5D84-8C93-B4C9E4F8F6B8}">
+					<Class name="D6JointLimitConfiguration" field="element" version="1" type="{88E067B4-21E8-4FFA-9142-6C52605B704C}">
+						<Class name="JointConfiguration" field="BaseClass1" version="1" type="{DF91D39A-4901-48C4-9159-93FD2ACA5252}">
 							<Class name="Quaternion" field="ParentLocalRotation" value="0.0000000 0.0000000 0.0000000 1.0002152" type="{73103120-3DD3-4873-BAB3-9713FA2804FB}"/>
 							<Class name="Vector3" field="ParentLocalPosition" value="0.0000000 0.0000000 0.0000000" type="{8379EB7D-01FA-4538-B64B-A6543B4BE73D}"/>
 							<Class name="Quaternion" field="ChildLocalRotation" value="0.0000000 0.0000000 0.0000000 1.0002151" type="{73103120-3DD3-4873-BAB3-9713FA2804FB}"/>
@@ -194,7 +194,7 @@
 					</Class>
 				</Class>
 			</Class>
-			<Class name="RagdollNodeConfiguration" field="element" version="4" type="{A1796586-85AB-496E-93C9-C5841F03B1AD}">
+			<Class name="RagdollNodeConfiguration" field="element" version="5" type="{A1796586-85AB-496E-93C9-C5841F03B1AD}">
 				<Class name="RigidBodyConfiguration" field="BaseClass1" version="2" type="{ACFA8900-8530-4744-AF00-AA533C868A8E}">
 					<Class name="WorldBodyConfiguration" field="BaseClass1" version="1" type="{6EEB377C-DC60-4E10-AF12-9626C0763B2D}">
 						<Class name="AZStd::string" field="name" value="C_spine_04_JNT" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
@@ -217,9 +217,9 @@
 					<Class name="Matrix3x3" field="Inertia tensor" value="1.0000000 0.0000000 0.0000000 0.0000000 1.0000000 0.0000000 0.0000000 0.0000000 1.0000000" type="{15A4332F-7C3F-4A58-AC35-50E1CE53FB9C}"/>
 					<Class name="float" field="Maximum Angular Velocity" value="100.0000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
 				</Class>
-				<Class name="AZStd::shared_ptr" field="JointLimit" type="{F5C4DCF0-277D-5536-921B-A6A4AEEF7226}">
-					<Class name="D6JointLimitConfiguration" field="element" version="1" type="{90C5C23D-16C0-4F23-AD50-A190E402388E}">
-						<Class name="JointLimitConfiguration" field="BaseClass1" version="1" type="{C9B70C4D-22D7-45AB-9B0A-30A4ED5E42DB}">
+				<Class name="AZStd::shared_ptr" field="JointConfig" type="{B7A3D04D-1ECC-5D84-8C93-B4C9E4F8F6B8}">
+					<Class name="D6JointLimitConfiguration" field="element" version="1" type="{88E067B4-21E8-4FFA-9142-6C52605B704C}">
+						<Class name="JointConfiguration" field="BaseClass1" version="1" type="{DF91D39A-4901-48C4-9159-93FD2ACA5252}">
 							<Class name="Quaternion" field="ParentLocalRotation" value="0.0000000 -0.0004615 -0.1719919 0.9851767" type="{73103120-3DD3-4873-BAB3-9713FA2804FB}"/>
 							<Class name="Vector3" field="ParentLocalPosition" value="0.0000000 0.0000000 0.0000000" type="{8379EB7D-01FA-4538-B64B-A6543B4BE73D}"/>
 							<Class name="Quaternion" field="ChildLocalRotation" value="0.0000000 -0.0004615 -0.1719919 0.9851766" type="{73103120-3DD3-4873-BAB3-9713FA2804FB}"/>
@@ -232,7 +232,7 @@
 					</Class>
 				</Class>
 			</Class>
-			<Class name="RagdollNodeConfiguration" field="element" version="4" type="{A1796586-85AB-496E-93C9-C5841F03B1AD}">
+			<Class name="RagdollNodeConfiguration" field="element" version="5" type="{A1796586-85AB-496E-93C9-C5841F03B1AD}">
 				<Class name="RigidBodyConfiguration" field="BaseClass1" version="2" type="{ACFA8900-8530-4744-AF00-AA533C868A8E}">
 					<Class name="WorldBodyConfiguration" field="BaseClass1" version="1" type="{6EEB377C-DC60-4E10-AF12-9626C0763B2D}">
 						<Class name="AZStd::string" field="name" value="L_clavicle_JNT" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
@@ -255,9 +255,9 @@
 					<Class name="Matrix3x3" field="Inertia tensor" value="1.0000000 0.0000000 0.0000000 0.0000000 1.0000000 0.0000000 0.0000000 0.0000000 1.0000000" type="{15A4332F-7C3F-4A58-AC35-50E1CE53FB9C}"/>
 					<Class name="float" field="Maximum Angular Velocity" value="100.0000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
 				</Class>
-				<Class name="AZStd::shared_ptr" field="JointLimit" type="{F5C4DCF0-277D-5536-921B-A6A4AEEF7226}">
-					<Class name="D6JointLimitConfiguration" field="element" version="1" type="{90C5C23D-16C0-4F23-AD50-A190E402388E}">
-						<Class name="JointLimitConfiguration" field="BaseClass1" version="1" type="{C9B70C4D-22D7-45AB-9B0A-30A4ED5E42DB}">
+				<Class name="AZStd::shared_ptr" field="JointConfig" type="{B7A3D04D-1ECC-5D84-8C93-B4C9E4F8F6B8}">
+					<Class name="D6JointLimitConfiguration" field="element" version="1" type="{88E067B4-21E8-4FFA-9142-6C52605B704C}">
+						<Class name="JointConfiguration" field="BaseClass1" version="1" type="{DF91D39A-4901-48C4-9159-93FD2ACA5252}">
 							<Class name="Quaternion" field="ParentLocalRotation" value="-0.0195780 -0.7155011 0.0194289 0.6982983" type="{73103120-3DD3-4873-BAB3-9713FA2804FB}"/>
 							<Class name="Vector3" field="ParentLocalPosition" value="0.0000000 0.0000000 0.0000000" type="{8379EB7D-01FA-4538-B64B-A6543B4BE73D}"/>
 							<Class name="Quaternion" field="ChildLocalRotation" value="0.0000000 -0.0135283 0.0381485 0.9993421" type="{73103120-3DD3-4873-BAB3-9713FA2804FB}"/>
@@ -270,7 +270,7 @@
 					</Class>
 				</Class>
 			</Class>
-			<Class name="RagdollNodeConfiguration" field="element" version="4" type="{A1796586-85AB-496E-93C9-C5841F03B1AD}">
+			<Class name="RagdollNodeConfiguration" field="element" version="5" type="{A1796586-85AB-496E-93C9-C5841F03B1AD}">
 				<Class name="RigidBodyConfiguration" field="BaseClass1" version="2" type="{ACFA8900-8530-4744-AF00-AA533C868A8E}">
 					<Class name="WorldBodyConfiguration" field="BaseClass1" version="1" type="{6EEB377C-DC60-4E10-AF12-9626C0763B2D}">
 						<Class name="AZStd::string" field="name" value="L_wrist_JNT" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
@@ -293,9 +293,9 @@
 					<Class name="Matrix3x3" field="Inertia tensor" value="1.0000000 0.0000000 0.0000000 0.0000000 1.0000000 0.0000000 0.0000000 0.0000000 1.0000000" type="{15A4332F-7C3F-4A58-AC35-50E1CE53FB9C}"/>
 					<Class name="float" field="Maximum Angular Velocity" value="100.0000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
 				</Class>
-				<Class name="AZStd::shared_ptr" field="JointLimit" type="{F5C4DCF0-277D-5536-921B-A6A4AEEF7226}">
-					<Class name="D6JointLimitConfiguration" field="element" version="1" type="{90C5C23D-16C0-4F23-AD50-A190E402388E}">
-						<Class name="JointLimitConfiguration" field="BaseClass1" version="1" type="{C9B70C4D-22D7-45AB-9B0A-30A4ED5E42DB}">
+				<Class name="AZStd::shared_ptr" field="JointConfig" type="{B7A3D04D-1ECC-5D84-8C93-B4C9E4F8F6B8}">
+					<Class name="D6JointLimitConfiguration" field="element" version="1" type="{88E067B4-21E8-4FFA-9142-6C52605B704C}">
+						<Class name="JointConfiguration" field="BaseClass1" version="1" type="{DF91D39A-4901-48C4-9159-93FD2ACA5252}">
 							<Class name="Quaternion" field="ParentLocalRotation" value="0.0000000 -0.0883498 0.0613066 0.9941655" type="{73103120-3DD3-4873-BAB3-9713FA2804FB}"/>
 							<Class name="Vector3" field="ParentLocalPosition" value="0.0000000 0.0000000 0.0000000" type="{8379EB7D-01FA-4538-B64B-A6543B4BE73D}"/>
 							<Class name="Quaternion" field="ChildLocalRotation" value="0.0000000 -0.0883498 0.0613066 0.9941654" type="{73103120-3DD3-4873-BAB3-9713FA2804FB}"/>
@@ -308,7 +308,7 @@
 					</Class>
 				</Class>
 			</Class>
-			<Class name="RagdollNodeConfiguration" field="element" version="4" type="{A1796586-85AB-496E-93C9-C5841F03B1AD}">
+			<Class name="RagdollNodeConfiguration" field="element" version="5" type="{A1796586-85AB-496E-93C9-C5841F03B1AD}">
 				<Class name="RigidBodyConfiguration" field="BaseClass1" version="2" type="{ACFA8900-8530-4744-AF00-AA533C868A8E}">
 					<Class name="WorldBodyConfiguration" field="BaseClass1" version="1" type="{6EEB377C-DC60-4E10-AF12-9626C0763B2D}">
 						<Class name="AZStd::string" field="name" value="L_elbow_JNT" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
@@ -331,9 +331,9 @@
 					<Class name="Matrix3x3" field="Inertia tensor" value="1.0000000 0.0000000 0.0000000 0.0000000 1.0000000 0.0000000 0.0000000 0.0000000 1.0000000" type="{15A4332F-7C3F-4A58-AC35-50E1CE53FB9C}"/>
 					<Class name="float" field="Maximum Angular Velocity" value="100.0000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
 				</Class>
-				<Class name="AZStd::shared_ptr" field="JointLimit" type="{F5C4DCF0-277D-5536-921B-A6A4AEEF7226}">
-					<Class name="D6JointLimitConfiguration" field="element" version="1" type="{90C5C23D-16C0-4F23-AD50-A190E402388E}">
-						<Class name="JointLimitConfiguration" field="BaseClass1" version="1" type="{C9B70C4D-22D7-45AB-9B0A-30A4ED5E42DB}">
+				<Class name="AZStd::shared_ptr" field="JointConfig" type="{B7A3D04D-1ECC-5D84-8C93-B4C9E4F8F6B8}">
+					<Class name="D6JointLimitConfiguration" field="element" version="1" type="{88E067B4-21E8-4FFA-9142-6C52605B704C}">
+						<Class name="JointConfiguration" field="BaseClass1" version="1" type="{DF91D39A-4901-48C4-9159-93FD2ACA5252}">
 							<Class name="Quaternion" field="ParentLocalRotation" value="0.0000000 0.0000000 0.6087880 0.7948525" type="{73103120-3DD3-4873-BAB3-9713FA2804FB}"/>
 							<Class name="Vector3" field="ParentLocalPosition" value="0.0000000 0.0000000 0.0000000" type="{8379EB7D-01FA-4538-B64B-A6543B4BE73D}"/>
 							<Class name="Quaternion" field="ChildLocalRotation" value="0.0000000 -0.0000001 0.0000000 1.0002161" type="{73103120-3DD3-4873-BAB3-9713FA2804FB}"/>
@@ -346,7 +346,7 @@
 					</Class>
 				</Class>
 			</Class>
-			<Class name="RagdollNodeConfiguration" field="element" version="4" type="{A1796586-85AB-496E-93C9-C5841F03B1AD}">
+			<Class name="RagdollNodeConfiguration" field="element" version="5" type="{A1796586-85AB-496E-93C9-C5841F03B1AD}">
 				<Class name="RigidBodyConfiguration" field="BaseClass1" version="2" type="{ACFA8900-8530-4744-AF00-AA533C868A8E}">
 					<Class name="WorldBodyConfiguration" field="BaseClass1" version="1" type="{6EEB377C-DC60-4E10-AF12-9626C0763B2D}">
 						<Class name="AZStd::string" field="name" value="C_neck_01_JNT" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
@@ -369,9 +369,9 @@
 					<Class name="Matrix3x3" field="Inertia tensor" value="1.0000000 0.0000000 0.0000000 0.0000000 1.0000000 0.0000000 0.0000000 0.0000000 1.0000000" type="{15A4332F-7C3F-4A58-AC35-50E1CE53FB9C}"/>
 					<Class name="float" field="Maximum Angular Velocity" value="100.0000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
 				</Class>
-				<Class name="AZStd::shared_ptr" field="JointLimit" type="{F5C4DCF0-277D-5536-921B-A6A4AEEF7226}">
-					<Class name="D6JointLimitConfiguration" field="element" version="1" type="{90C5C23D-16C0-4F23-AD50-A190E402388E}">
-						<Class name="JointLimitConfiguration" field="BaseClass1" version="1" type="{C9B70C4D-22D7-45AB-9B0A-30A4ED5E42DB}">
+				<Class name="AZStd::shared_ptr" field="JointConfig" type="{B7A3D04D-1ECC-5D84-8C93-B4C9E4F8F6B8}">
+					<Class name="D6JointLimitConfiguration" field="element" version="1" type="{88E067B4-21E8-4FFA-9142-6C52605B704C}">
+						<Class name="JointConfiguration" field="BaseClass1" version="1" type="{DF91D39A-4901-48C4-9159-93FD2ACA5252}">
 							<Class name="Quaternion" field="ParentLocalRotation" value="0.0000000 -0.0000000 0.0967176 0.9951238" type="{73103120-3DD3-4873-BAB3-9713FA2804FB}"/>
 							<Class name="Vector3" field="ParentLocalPosition" value="0.0000000 0.0000000 0.0000000" type="{8379EB7D-01FA-4538-B64B-A6543B4BE73D}"/>
 							<Class name="Quaternion" field="ChildLocalRotation" value="0.0000000 -0.0000000 0.0000000 0.9998127" type="{73103120-3DD3-4873-BAB3-9713FA2804FB}"/>
@@ -384,7 +384,7 @@
 					</Class>
 				</Class>
 			</Class>
-			<Class name="RagdollNodeConfiguration" field="element" version="4" type="{A1796586-85AB-496E-93C9-C5841F03B1AD}">
+			<Class name="RagdollNodeConfiguration" field="element" version="5" type="{A1796586-85AB-496E-93C9-C5841F03B1AD}">
 				<Class name="RigidBodyConfiguration" field="BaseClass1" version="2" type="{ACFA8900-8530-4744-AF00-AA533C868A8E}">
 					<Class name="WorldBodyConfiguration" field="BaseClass1" version="1" type="{6EEB377C-DC60-4E10-AF12-9626C0763B2D}">
 						<Class name="AZStd::string" field="name" value="C_head_JNT" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
@@ -407,9 +407,9 @@
 					<Class name="Matrix3x3" field="Inertia tensor" value="1.0000000 0.0000000 0.0000000 0.0000000 1.0000000 0.0000000 0.0000000 0.0000000 1.0000000" type="{15A4332F-7C3F-4A58-AC35-50E1CE53FB9C}"/>
 					<Class name="float" field="Maximum Angular Velocity" value="100.0000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
 				</Class>
-				<Class name="AZStd::shared_ptr" field="JointLimit" type="{F5C4DCF0-277D-5536-921B-A6A4AEEF7226}">
-					<Class name="D6JointLimitConfiguration" field="element" version="1" type="{90C5C23D-16C0-4F23-AD50-A190E402388E}">
-						<Class name="JointLimitConfiguration" field="BaseClass1" version="1" type="{C9B70C4D-22D7-45AB-9B0A-30A4ED5E42DB}">
+				<Class name="AZStd::shared_ptr" field="JointConfig" type="{B7A3D04D-1ECC-5D84-8C93-B4C9E4F8F6B8}">
+					<Class name="D6JointLimitConfiguration" field="element" version="1" type="{88E067B4-21E8-4FFA-9142-6C52605B704C}">
+						<Class name="JointConfiguration" field="BaseClass1" version="1" type="{DF91D39A-4901-48C4-9159-93FD2ACA5252}">
 							<Class name="Quaternion" field="ParentLocalRotation" value="0.0000000 -0.0029691 0.5012505 0.8653531" type="{73103120-3DD3-4873-BAB3-9713FA2804FB}"/>
 							<Class name="Vector3" field="ParentLocalPosition" value="0.0000000 0.0000000 0.0000000" type="{8379EB7D-01FA-4538-B64B-A6543B4BE73D}"/>
 							<Class name="Quaternion" field="ChildLocalRotation" value="0.0000000 -0.0029691 0.5012505 0.8653531" type="{73103120-3DD3-4873-BAB3-9713FA2804FB}"/>
@@ -422,7 +422,7 @@
 					</Class>
 				</Class>
 			</Class>
-			<Class name="RagdollNodeConfiguration" field="element" version="4" type="{A1796586-85AB-496E-93C9-C5841F03B1AD}">
+			<Class name="RagdollNodeConfiguration" field="element" version="5" type="{A1796586-85AB-496E-93C9-C5841F03B1AD}">
 				<Class name="RigidBodyConfiguration" field="BaseClass1" version="2" type="{ACFA8900-8530-4744-AF00-AA533C868A8E}">
 					<Class name="WorldBodyConfiguration" field="BaseClass1" version="1" type="{6EEB377C-DC60-4E10-AF12-9626C0763B2D}">
 						<Class name="AZStd::string" field="name" value="C_neck_02_JNT" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
@@ -445,9 +445,9 @@
 					<Class name="Matrix3x3" field="Inertia tensor" value="1.0000000 0.0000000 0.0000000 0.0000000 1.0000000 0.0000000 0.0000000 0.0000000 1.0000000" type="{15A4332F-7C3F-4A58-AC35-50E1CE53FB9C}"/>
 					<Class name="float" field="Maximum Angular Velocity" value="100.0000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
 				</Class>
-				<Class name="AZStd::shared_ptr" field="JointLimit" type="{F5C4DCF0-277D-5536-921B-A6A4AEEF7226}">
-					<Class name="D6JointLimitConfiguration" field="element" version="1" type="{90C5C23D-16C0-4F23-AD50-A190E402388E}">
-						<Class name="JointLimitConfiguration" field="BaseClass1" version="1" type="{C9B70C4D-22D7-45AB-9B0A-30A4ED5E42DB}">
+				<Class name="AZStd::shared_ptr" field="JointConfig" type="{B7A3D04D-1ECC-5D84-8C93-B4C9E4F8F6B8}">
+					<Class name="D6JointLimitConfiguration" field="element" version="1" type="{88E067B4-21E8-4FFA-9142-6C52605B704C}">
+						<Class name="JointConfiguration" field="BaseClass1" version="1" type="{DF91D39A-4901-48C4-9159-93FD2ACA5252}">
 							<Class name="Quaternion" field="ParentLocalRotation" value="0.0000000 0.0000000 0.0028993 1.0001502" type="{73103120-3DD3-4873-BAB3-9713FA2804FB}"/>
 							<Class name="Vector3" field="ParentLocalPosition" value="0.0000000 0.0000000 0.0000000" type="{8379EB7D-01FA-4538-B64B-A6543B4BE73D}"/>
 							<Class name="Quaternion" field="ChildLocalRotation" value="-0.0000000 0.0000000 0.0028993 1.0001502" type="{73103120-3DD3-4873-BAB3-9713FA2804FB}"/>
@@ -460,7 +460,7 @@
 					</Class>
 				</Class>
 			</Class>
-			<Class name="RagdollNodeConfiguration" field="element" version="4" type="{A1796586-85AB-496E-93C9-C5841F03B1AD}">
+			<Class name="RagdollNodeConfiguration" field="element" version="5" type="{A1796586-85AB-496E-93C9-C5841F03B1AD}">
 				<Class name="RigidBodyConfiguration" field="BaseClass1" version="2" type="{ACFA8900-8530-4744-AF00-AA533C868A8E}">
 					<Class name="WorldBodyConfiguration" field="BaseClass1" version="1" type="{6EEB377C-DC60-4E10-AF12-9626C0763B2D}">
 						<Class name="AZStd::string" field="name" value="R_clavicle_JNT" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
@@ -483,9 +483,9 @@
 					<Class name="Matrix3x3" field="Inertia tensor" value="1.0000000 0.0000000 0.0000000 0.0000000 1.0000000 0.0000000 0.0000000 0.0000000 1.0000000" type="{15A4332F-7C3F-4A58-AC35-50E1CE53FB9C}"/>
 					<Class name="float" field="Maximum Angular Velocity" value="100.0000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
 				</Class>
-				<Class name="AZStd::shared_ptr" field="JointLimit" type="{F5C4DCF0-277D-5536-921B-A6A4AEEF7226}">
-					<Class name="D6JointLimitConfiguration" field="element" version="1" type="{90C5C23D-16C0-4F23-AD50-A190E402388E}">
-						<Class name="JointLimitConfiguration" field="BaseClass1" version="1" type="{C9B70C4D-22D7-45AB-9B0A-30A4ED5E42DB}">
+				<Class name="AZStd::shared_ptr" field="JointConfig" type="{B7A3D04D-1ECC-5D84-8C93-B4C9E4F8F6B8}">
+					<Class name="D6JointLimitConfiguration" field="element" version="1" type="{88E067B4-21E8-4FFA-9142-6C52605B704C}">
+						<Class name="JointConfiguration" field="BaseClass1" version="1" type="{DF91D39A-4901-48C4-9159-93FD2ACA5252}">
 							<Class name="Quaternion" field="ParentLocalRotation" value="0.0195778 0.7154908 0.0194290 0.6983082" type="{73103120-3DD3-4873-BAB3-9713FA2804FB}"/>
 							<Class name="Vector3" field="ParentLocalPosition" value="0.0000000 0.0000000 0.0000000" type="{8379EB7D-01FA-4538-B64B-A6543B4BE73D}"/>
 							<Class name="Quaternion" field="ChildLocalRotation" value="0.0135140 -0.0000000 -0.9993417 0.0381484" type="{73103120-3DD3-4873-BAB3-9713FA2804FB}"/>
@@ -498,7 +498,7 @@
 					</Class>
 				</Class>
 			</Class>
-			<Class name="RagdollNodeConfiguration" field="element" version="4" type="{A1796586-85AB-496E-93C9-C5841F03B1AD}">
+			<Class name="RagdollNodeConfiguration" field="element" version="5" type="{A1796586-85AB-496E-93C9-C5841F03B1AD}">
 				<Class name="RigidBodyConfiguration" field="BaseClass1" version="2" type="{ACFA8900-8530-4744-AF00-AA533C868A8E}">
 					<Class name="WorldBodyConfiguration" field="BaseClass1" version="1" type="{6EEB377C-DC60-4E10-AF12-9626C0763B2D}">
 						<Class name="AZStd::string" field="name" value="R_wrist_JNT" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
@@ -521,9 +521,9 @@
 					<Class name="Matrix3x3" field="Inertia tensor" value="1.0000000 0.0000000 0.0000000 0.0000000 1.0000000 0.0000000 0.0000000 0.0000000 1.0000000" type="{15A4332F-7C3F-4A58-AC35-50E1CE53FB9C}"/>
 					<Class name="float" field="Maximum Angular Velocity" value="100.0000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
 				</Class>
-				<Class name="AZStd::shared_ptr" field="JointLimit" type="{F5C4DCF0-277D-5536-921B-A6A4AEEF7226}">
-					<Class name="D6JointLimitConfiguration" field="element" version="1" type="{90C5C23D-16C0-4F23-AD50-A190E402388E}">
-						<Class name="JointLimitConfiguration" field="BaseClass1" version="1" type="{C9B70C4D-22D7-45AB-9B0A-30A4ED5E42DB}">
+				<Class name="AZStd::shared_ptr" field="JointConfig" type="{B7A3D04D-1ECC-5D84-8C93-B4C9E4F8F6B8}">
+					<Class name="D6JointLimitConfiguration" field="element" version="1" type="{88E067B4-21E8-4FFA-9142-6C52605B704C}">
+						<Class name="JointConfiguration" field="BaseClass1" version="1" type="{DF91D39A-4901-48C4-9159-93FD2ACA5252}">
 							<Class name="Quaternion" field="ParentLocalRotation" value="0.0883390 0.0000000 -0.9943419 0.0613376" type="{73103120-3DD3-4873-BAB3-9713FA2804FB}"/>
 							<Class name="Vector3" field="ParentLocalPosition" value="0.0000000 0.0000000 0.0000000" type="{8379EB7D-01FA-4538-B64B-A6543B4BE73D}"/>
 							<Class name="Quaternion" field="ChildLocalRotation" value="0.0883390 -0.0000000 -0.9943417 0.0613376" type="{73103120-3DD3-4873-BAB3-9713FA2804FB}"/>
@@ -536,7 +536,7 @@
 					</Class>
 				</Class>
 			</Class>
-			<Class name="RagdollNodeConfiguration" field="element" version="4" type="{A1796586-85AB-496E-93C9-C5841F03B1AD}">
+			<Class name="RagdollNodeConfiguration" field="element" version="5" type="{A1796586-85AB-496E-93C9-C5841F03B1AD}">
 				<Class name="RigidBodyConfiguration" field="BaseClass1" version="2" type="{ACFA8900-8530-4744-AF00-AA533C868A8E}">
 					<Class name="WorldBodyConfiguration" field="BaseClass1" version="1" type="{6EEB377C-DC60-4E10-AF12-9626C0763B2D}">
 						<Class name="AZStd::string" field="name" value="R_arm_JNT" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
@@ -559,9 +559,9 @@
 					<Class name="Matrix3x3" field="Inertia tensor" value="1.0000000 0.0000000 0.0000000 0.0000000 1.0000000 0.0000000 0.0000000 0.0000000 1.0000000" type="{15A4332F-7C3F-4A58-AC35-50E1CE53FB9C}"/>
 					<Class name="float" field="Maximum Angular Velocity" value="100.0000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
 				</Class>
-				<Class name="AZStd::shared_ptr" field="JointLimit" type="{F5C4DCF0-277D-5536-921B-A6A4AEEF7226}">
-					<Class name="D6JointLimitConfiguration" field="element" version="1" type="{90C5C23D-16C0-4F23-AD50-A190E402388E}">
-						<Class name="JointLimitConfiguration" field="BaseClass1" version="1" type="{C9B70C4D-22D7-45AB-9B0A-30A4ED5E42DB}">
+				<Class name="AZStd::shared_ptr" field="JointConfig" type="{B7A3D04D-1ECC-5D84-8C93-B4C9E4F8F6B8}">
+					<Class name="D6JointLimitConfiguration" field="element" version="1" type="{88E067B4-21E8-4FFA-9142-6C52605B704C}">
+						<Class name="JointConfiguration" field="BaseClass1" version="1" type="{DF91D39A-4901-48C4-9159-93FD2ACA5252}">
 							<Class name="Quaternion" field="ParentLocalRotation" value="0.3743524 0.0288702 -0.9254135 -0.0538333" type="{73103120-3DD3-4873-BAB3-9713FA2804FB}"/>
 							<Class name="Vector3" field="ParentLocalPosition" value="0.0000000 0.0000000 0.0000000" type="{8379EB7D-01FA-4538-B64B-A6543B4BE73D}"/>
 							<Class name="Quaternion" field="ChildLocalRotation" value="0.0024797 -0.0000000 -1.0001022 0.0071253" type="{73103120-3DD3-4873-BAB3-9713FA2804FB}"/>
@@ -574,7 +574,7 @@
 					</Class>
 				</Class>
 			</Class>
-			<Class name="RagdollNodeConfiguration" field="element" version="4" type="{A1796586-85AB-496E-93C9-C5841F03B1AD}">
+			<Class name="RagdollNodeConfiguration" field="element" version="5" type="{A1796586-85AB-496E-93C9-C5841F03B1AD}">
 				<Class name="RigidBodyConfiguration" field="BaseClass1" version="2" type="{ACFA8900-8530-4744-AF00-AA533C868A8E}">
 					<Class name="WorldBodyConfiguration" field="BaseClass1" version="1" type="{6EEB377C-DC60-4E10-AF12-9626C0763B2D}">
 						<Class name="AZStd::string" field="name" value="R_elbow_JNT" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
@@ -597,9 +597,9 @@
 					<Class name="Matrix3x3" field="Inertia tensor" value="1.0000000 0.0000000 0.0000000 0.0000000 1.0000000 0.0000000 0.0000000 0.0000000 1.0000000" type="{15A4332F-7C3F-4A58-AC35-50E1CE53FB9C}"/>
 					<Class name="float" field="Maximum Angular Velocity" value="100.0000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
 				</Class>
-				<Class name="AZStd::shared_ptr" field="JointLimit" type="{F5C4DCF0-277D-5536-921B-A6A4AEEF7226}">
-					<Class name="D6JointLimitConfiguration" field="element" version="1" type="{90C5C23D-16C0-4F23-AD50-A190E402388E}">
-						<Class name="JointLimitConfiguration" field="BaseClass1" version="1" type="{C9B70C4D-22D7-45AB-9B0A-30A4ED5E42DB}">
+				<Class name="AZStd::shared_ptr" field="JointConfig" type="{B7A3D04D-1ECC-5D84-8C93-B4C9E4F8F6B8}">
+					<Class name="D6JointLimitConfiguration" field="element" version="1" type="{88E067B4-21E8-4FFA-9142-6C52605B704C}">
+						<Class name="JointConfiguration" field="BaseClass1" version="1" type="{DF91D39A-4901-48C4-9159-93FD2ACA5252}">
 							<Class name="Quaternion" field="ParentLocalRotation" value="0.0000000 -0.0000000 0.8197944 -0.5787863" type="{73103120-3DD3-4873-BAB3-9713FA2804FB}"/>
 							<Class name="Vector3" field="ParentLocalPosition" value="0.0000000 0.0000000 0.0000000" type="{8379EB7D-01FA-4538-B64B-A6543B4BE73D}"/>
 							<Class name="Quaternion" field="ChildLocalRotation" value="-0.0000031 0.0000000 1.0002232 0.0000017" type="{73103120-3DD3-4873-BAB3-9713FA2804FB}"/>
@@ -612,7 +612,7 @@
 					</Class>
 				</Class>
 			</Class>
-			<Class name="RagdollNodeConfiguration" field="element" version="4" type="{A1796586-85AB-496E-93C9-C5841F03B1AD}">
+			<Class name="RagdollNodeConfiguration" field="element" version="5" type="{A1796586-85AB-496E-93C9-C5841F03B1AD}">
 				<Class name="RigidBodyConfiguration" field="BaseClass1" version="2" type="{ACFA8900-8530-4744-AF00-AA533C868A8E}">
 					<Class name="WorldBodyConfiguration" field="BaseClass1" version="1" type="{6EEB377C-DC60-4E10-AF12-9626C0763B2D}">
 						<Class name="AZStd::string" field="name" value="L_leg_JNT" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
@@ -635,9 +635,9 @@
 					<Class name="Matrix3x3" field="Inertia tensor" value="1.0000000 0.0000000 0.0000000 0.0000000 1.0000000 0.0000000 0.0000000 0.0000000 1.0000000" type="{15A4332F-7C3F-4A58-AC35-50E1CE53FB9C}"/>
 					<Class name="float" field="Maximum Angular Velocity" value="100.0000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
 				</Class>
-				<Class name="AZStd::shared_ptr" field="JointLimit" type="{F5C4DCF0-277D-5536-921B-A6A4AEEF7226}">
-					<Class name="D6JointLimitConfiguration" field="element" version="1" type="{90C5C23D-16C0-4F23-AD50-A190E402388E}">
-						<Class name="JointLimitConfiguration" field="BaseClass1" version="1" type="{C9B70C4D-22D7-45AB-9B0A-30A4ED5E42DB}">
+				<Class name="AZStd::shared_ptr" field="JointConfig" type="{B7A3D04D-1ECC-5D84-8C93-B4C9E4F8F6B8}">
+					<Class name="D6JointLimitConfiguration" field="element" version="1" type="{88E067B4-21E8-4FFA-9142-6C52605B704C}">
+						<Class name="JointConfiguration" field="BaseClass1" version="1" type="{DF91D39A-4901-48C4-9159-93FD2ACA5252}">
 							<Class name="Quaternion" field="ParentLocalRotation" value="0.2611587 0.9659281 -0.0034238 -0.0126628" type="{73103120-3DD3-4873-BAB3-9713FA2804FB}"/>
 							<Class name="Vector3" field="ParentLocalPosition" value="0.0000000 0.0000000 0.0000000" type="{8379EB7D-01FA-4538-B64B-A6543B4BE73D}"/>
 							<Class name="Quaternion" field="ChildLocalRotation" value="0.0000000 0.0000000 -0.0000000 0.9998225" type="{73103120-3DD3-4873-BAB3-9713FA2804FB}"/>
@@ -650,7 +650,7 @@
 					</Class>
 				</Class>
 			</Class>
-			<Class name="RagdollNodeConfiguration" field="element" version="4" type="{A1796586-85AB-496E-93C9-C5841F03B1AD}">
+			<Class name="RagdollNodeConfiguration" field="element" version="5" type="{A1796586-85AB-496E-93C9-C5841F03B1AD}">
 				<Class name="RigidBodyConfiguration" field="BaseClass1" version="2" type="{ACFA8900-8530-4744-AF00-AA533C868A8E}">
 					<Class name="WorldBodyConfiguration" field="BaseClass1" version="1" type="{6EEB377C-DC60-4E10-AF12-9626C0763B2D}">
 						<Class name="AZStd::string" field="name" value="L_foot_JNT" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
@@ -673,9 +673,9 @@
 					<Class name="Matrix3x3" field="Inertia tensor" value="1.0000000 0.0000000 0.0000000 0.0000000 1.0000000 0.0000000 0.0000000 0.0000000 1.0000000" type="{15A4332F-7C3F-4A58-AC35-50E1CE53FB9C}"/>
 					<Class name="float" field="Maximum Angular Velocity" value="100.0000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
 				</Class>
-				<Class name="AZStd::shared_ptr" field="JointLimit" type="{F5C4DCF0-277D-5536-921B-A6A4AEEF7226}">
-					<Class name="D6JointLimitConfiguration" field="element" version="1" type="{90C5C23D-16C0-4F23-AD50-A190E402388E}">
-						<Class name="JointLimitConfiguration" field="BaseClass1" version="1" type="{C9B70C4D-22D7-45AB-9B0A-30A4ED5E42DB}">
+				<Class name="AZStd::shared_ptr" field="JointConfig" type="{B7A3D04D-1ECC-5D84-8C93-B4C9E4F8F6B8}">
+					<Class name="D6JointLimitConfiguration" field="element" version="1" type="{88E067B4-21E8-4FFA-9142-6C52605B704C}">
+						<Class name="JointConfiguration" field="BaseClass1" version="1" type="{DF91D39A-4901-48C4-9159-93FD2ACA5252}">
 							<Class name="Quaternion" field="ParentLocalRotation" value="-0.0073839 -0.0140681 0.5175676 0.8556790" type="{73103120-3DD3-4873-BAB3-9713FA2804FB}"/>
 							<Class name="Vector3" field="ParentLocalPosition" value="0.0000000 0.0000000 0.0000000" type="{8379EB7D-01FA-4538-B64B-A6543B4BE73D}"/>
 							<Class name="Quaternion" field="ChildLocalRotation" value="0.0000000 0.0000000 -0.0000000 1.0001576" type="{73103120-3DD3-4873-BAB3-9713FA2804FB}"/>
@@ -688,7 +688,7 @@
 					</Class>
 				</Class>
 			</Class>
-			<Class name="RagdollNodeConfiguration" field="element" version="4" type="{A1796586-85AB-496E-93C9-C5841F03B1AD}">
+			<Class name="RagdollNodeConfiguration" field="element" version="5" type="{A1796586-85AB-496E-93C9-C5841F03B1AD}">
 				<Class name="RigidBodyConfiguration" field="BaseClass1" version="2" type="{ACFA8900-8530-4744-AF00-AA533C868A8E}">
 					<Class name="WorldBodyConfiguration" field="BaseClass1" version="1" type="{6EEB377C-DC60-4E10-AF12-9626C0763B2D}">
 						<Class name="AZStd::string" field="name" value="L_knee_JNT" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
@@ -711,9 +711,9 @@
 					<Class name="Matrix3x3" field="Inertia tensor" value="1.0000000 0.0000000 0.0000000 0.0000000 1.0000000 0.0000000 0.0000000 0.0000000 1.0000000" type="{15A4332F-7C3F-4A58-AC35-50E1CE53FB9C}"/>
 					<Class name="float" field="Maximum Angular Velocity" value="100.0000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
 				</Class>
-				<Class name="AZStd::shared_ptr" field="JointLimit" type="{F5C4DCF0-277D-5536-921B-A6A4AEEF7226}">
-					<Class name="D6JointLimitConfiguration" field="element" version="1" type="{90C5C23D-16C0-4F23-AD50-A190E402388E}">
-						<Class name="JointLimitConfiguration" field="BaseClass1" version="1" type="{C9B70C4D-22D7-45AB-9B0A-30A4ED5E42DB}">
+				<Class name="AZStd::shared_ptr" field="JointConfig" type="{B7A3D04D-1ECC-5D84-8C93-B4C9E4F8F6B8}">
+					<Class name="D6JointLimitConfiguration" field="element" version="1" type="{88E067B4-21E8-4FFA-9142-6C52605B704C}">
+						<Class name="JointConfiguration" field="BaseClass1" version="1" type="{DF91D39A-4901-48C4-9159-93FD2ACA5252}">
 							<Class name="Quaternion" field="ParentLocalRotation" value="0.0000000 -0.0000000 0.3640593 -0.9358648" type="{73103120-3DD3-4873-BAB3-9713FA2804FB}"/>
 							<Class name="Vector3" field="ParentLocalPosition" value="0.0000000 0.0000000 0.0000000" type="{8379EB7D-01FA-4538-B64B-A6543B4BE73D}"/>
 							<Class name="Quaternion" field="ChildLocalRotation" value="0.0000000 -0.0000000 -0.0000000 0.9999046" type="{73103120-3DD3-4873-BAB3-9713FA2804FB}"/>
@@ -726,7 +726,7 @@
 					</Class>
 				</Class>
 			</Class>
-			<Class name="RagdollNodeConfiguration" field="element" version="4" type="{A1796586-85AB-496E-93C9-C5841F03B1AD}">
+			<Class name="RagdollNodeConfiguration" field="element" version="5" type="{A1796586-85AB-496E-93C9-C5841F03B1AD}">
 				<Class name="RigidBodyConfiguration" field="BaseClass1" version="2" type="{ACFA8900-8530-4744-AF00-AA533C868A8E}">
 					<Class name="WorldBodyConfiguration" field="BaseClass1" version="1" type="{6EEB377C-DC60-4E10-AF12-9626C0763B2D}">
 						<Class name="AZStd::string" field="name" value="R_leg_JNT" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
@@ -749,9 +749,9 @@
 					<Class name="Matrix3x3" field="Inertia tensor" value="1.0000000 0.0000000 0.0000000 0.0000000 1.0000000 0.0000000 0.0000000 0.0000000 1.0000000" type="{15A4332F-7C3F-4A58-AC35-50E1CE53FB9C}"/>
 					<Class name="float" field="Maximum Angular Velocity" value="100.0000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
 				</Class>
-				<Class name="AZStd::shared_ptr" field="JointLimit" type="{F5C4DCF0-277D-5536-921B-A6A4AEEF7226}">
-					<Class name="D6JointLimitConfiguration" field="element" version="1" type="{90C5C23D-16C0-4F23-AD50-A190E402388E}">
-						<Class name="JointLimitConfiguration" field="BaseClass1" version="1" type="{C9B70C4D-22D7-45AB-9B0A-30A4ED5E42DB}">
+				<Class name="AZStd::shared_ptr" field="JointConfig" type="{B7A3D04D-1ECC-5D84-8C93-B4C9E4F8F6B8}">
+					<Class name="D6JointLimitConfiguration" field="element" version="1" type="{88E067B4-21E8-4FFA-9142-6C52605B704C}">
+						<Class name="JointConfiguration" field="BaseClass1" version="1" type="{DF91D39A-4901-48C4-9159-93FD2ACA5252}">
 							<Class name="Quaternion" field="ParentLocalRotation" value="-0.2770883 -0.9615672 -0.0035520 -0.0123266" type="{73103120-3DD3-4873-BAB3-9713FA2804FB}"/>
 							<Class name="Vector3" field="ParentLocalPosition" value="0.0000000 0.0000000 0.0000000" type="{8379EB7D-01FA-4538-B64B-A6543B4BE73D}"/>
 							<Class name="Quaternion" field="ChildLocalRotation" value="0.0000005 0.0000000 0.9998229 0.0000000" type="{73103120-3DD3-4873-BAB3-9713FA2804FB}"/>
@@ -764,7 +764,7 @@
 					</Class>
 				</Class>
 			</Class>
-			<Class name="RagdollNodeConfiguration" field="element" version="4" type="{A1796586-85AB-496E-93C9-C5841F03B1AD}">
+			<Class name="RagdollNodeConfiguration" field="element" version="5" type="{A1796586-85AB-496E-93C9-C5841F03B1AD}">
 				<Class name="RigidBodyConfiguration" field="BaseClass1" version="2" type="{ACFA8900-8530-4744-AF00-AA533C868A8E}">
 					<Class name="WorldBodyConfiguration" field="BaseClass1" version="1" type="{6EEB377C-DC60-4E10-AF12-9626C0763B2D}">
 						<Class name="AZStd::string" field="name" value="R_foot_JNT" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
@@ -787,9 +787,9 @@
 					<Class name="Matrix3x3" field="Inertia tensor" value="1.0000000 0.0000000 0.0000000 0.0000000 1.0000000 0.0000000 0.0000000 0.0000000 1.0000000" type="{15A4332F-7C3F-4A58-AC35-50E1CE53FB9C}"/>
 					<Class name="float" field="Maximum Angular Velocity" value="100.0000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
 				</Class>
-				<Class name="AZStd::shared_ptr" field="JointLimit" type="{F5C4DCF0-277D-5536-921B-A6A4AEEF7226}">
-					<Class name="D6JointLimitConfiguration" field="element" version="1" type="{90C5C23D-16C0-4F23-AD50-A190E402388E}">
-						<Class name="JointLimitConfiguration" field="BaseClass1" version="1" type="{C9B70C4D-22D7-45AB-9B0A-30A4ED5E42DB}">
+				<Class name="AZStd::shared_ptr" field="JointConfig" type="{B7A3D04D-1ECC-5D84-8C93-B4C9E4F8F6B8}">
+					<Class name="D6JointLimitConfiguration" field="element" version="1" type="{88E067B4-21E8-4FFA-9142-6C52605B704C}">
+						<Class name="JointConfiguration" field="BaseClass1" version="1" type="{DF91D39A-4901-48C4-9159-93FD2ACA5252}">
 							<Class name="Quaternion" field="ParentLocalRotation" value="0.0140685 -0.0073837 -0.8556802 0.5175687" type="{73103120-3DD3-4873-BAB3-9713FA2804FB}"/>
 							<Class name="Vector3" field="ParentLocalPosition" value="0.0000000 0.0000000 0.0000000" type="{8379EB7D-01FA-4538-B64B-A6543B4BE73D}"/>
 							<Class name="Quaternion" field="ChildLocalRotation" value="0.0000004 -0.0000000 -1.0001591 0.0000003" type="{73103120-3DD3-4873-BAB3-9713FA2804FB}"/>
@@ -802,7 +802,7 @@
 					</Class>
 				</Class>
 			</Class>
-			<Class name="RagdollNodeConfiguration" field="element" version="4" type="{A1796586-85AB-496E-93C9-C5841F03B1AD}">
+			<Class name="RagdollNodeConfiguration" field="element" version="5" type="{A1796586-85AB-496E-93C9-C5841F03B1AD}">
 				<Class name="RigidBodyConfiguration" field="BaseClass1" version="2" type="{ACFA8900-8530-4744-AF00-AA533C868A8E}">
 					<Class name="WorldBodyConfiguration" field="BaseClass1" version="1" type="{6EEB377C-DC60-4E10-AF12-9626C0763B2D}">
 						<Class name="AZStd::string" field="name" value="R_knee_JNT" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
@@ -825,9 +825,9 @@
 					<Class name="Matrix3x3" field="Inertia tensor" value="1.0000000 0.0000000 0.0000000 0.0000000 1.0000000 0.0000000 0.0000000 0.0000000 1.0000000" type="{15A4332F-7C3F-4A58-AC35-50E1CE53FB9C}"/>
 					<Class name="float" field="Maximum Angular Velocity" value="100.0000000" type="{EA2C3E90-AFBE-44D4-A90D-FAAF79BAF93D}"/>
 				</Class>
-				<Class name="AZStd::shared_ptr" field="JointLimit" type="{F5C4DCF0-277D-5536-921B-A6A4AEEF7226}">
-					<Class name="D6JointLimitConfiguration" field="element" version="1" type="{90C5C23D-16C0-4F23-AD50-A190E402388E}">
-						<Class name="JointLimitConfiguration" field="BaseClass1" version="1" type="{C9B70C4D-22D7-45AB-9B0A-30A4ED5E42DB}">
+				<Class name="AZStd::shared_ptr" field="JointConfig" type="{B7A3D04D-1ECC-5D84-8C93-B4C9E4F8F6B8}">
+					<Class name="D6JointLimitConfiguration" field="element" version="1" type="{88E067B4-21E8-4FFA-9142-6C52605B704C}">
+						<Class name="JointConfiguration" field="BaseClass1" version="1" type="{DF91D39A-4901-48C4-9159-93FD2ACA5252}">
 							<Class name="Quaternion" field="ParentLocalRotation" value="-0.0000033 0.0000092 -0.9416476 -0.3386196" type="{73103120-3DD3-4873-BAB3-9713FA2804FB}"/>
 							<Class name="Vector3" field="ParentLocalPosition" value="0.0000000 0.0000000 0.0000000" type="{8379EB7D-01FA-4538-B64B-A6543B4BE73D}"/>
 							<Class name="Quaternion" field="ChildLocalRotation" value="0.0000000 -0.0000000 -0.9999050 0.0000000" type="{73103120-3DD3-4873-BAB3-9713FA2804FB}"/>

+ 2 - 2
Gems/PhysX/Code/Tests/RagdollTests.cpp

@@ -208,8 +208,8 @@ namespace PhysX
             }
             else
             {
-                EXPECT_EQ(joint->GetChildBody(), &node->GetRigidBody());
-                EXPECT_EQ(joint->GetParentBody(), &ragdoll->GetNode(parentIndex)->GetRigidBody());
+                EXPECT_EQ(joint->GetChildBodyHandle(), node->GetRigidBody().m_bodyHandle);
+                EXPECT_EQ(joint->GetParentBodyHandle(), ragdoll->GetNode(parentIndex)->GetRigidBody().m_bodyHandle);
             }
         }
     }

+ 8 - 2
Gems/PhysX/Code/physx_files.cmake

@@ -75,8 +75,6 @@ set(FILES
     Source/Shape.cpp
     Source/Material.cpp
     Source/Material.h
-    Source/Joint.cpp
-    Source/Joint.h
     Source/ForceRegionForces.cpp
     Source/ForceRegionForces.h
     Source/ForceRegion.cpp
@@ -104,6 +102,7 @@ set(FILES
     Include/PhysX/Debug/PhysXDebugConfiguration.h
     Include/PhysX/Debug/PhysXDebugInterface.h
     Include/PhysX/Configuration/PhysXConfiguration.h
+    Include/PhysX/Joint/Configuration/PhysXJointConfiguration.h
     Source/Common/PhysXSceneQueryHelpers.h
     Source/Common/PhysXSceneQueryHelpers.cpp
     Source/Configuration/PhysXConfiguration.cpp
@@ -112,6 +111,11 @@ set(FILES
     Source/Debug/PhysXDebug.h
     Source/Debug/PhysXDebug.cpp
     Source/Debug/Configuration/PhysXDebugConfiguration.cpp
+    Source/Joint/PhysXJoint.h
+    Source/Joint/PhysXJoint.cpp
+    Source/Joint/PhysXJointUtils.h
+    Source/Joint/PhysXJointUtils.cpp
+    Source/Joint/Configuration/PhysXJointConfiguration.cpp
     Source/Scene/PhysXScene.h
     Source/Scene/PhysXScene.cpp
     Source/Scene/PhysXSceneInterface.h
@@ -128,6 +132,8 @@ set(FILES
     Source/System/PhysXCpuDispatcher.h
     Source/System/PhysXJob.cpp
     Source/System/PhysXJob.h
+    Source/System/PhysXJointInterface.h
+    Source/System/PhysXJointInterface.cpp
     Source/System/PhysXSdkCallbacks.h
     Source/System/PhysXSdkCallbacks.cpp
     Source/System/PhysXSystem.h

+ 1 - 1
Gems/PhysXDebug/Code/Source/SystemComponent.cpp

@@ -783,7 +783,7 @@ namespace PhysXDebug
                     Physics::RagdollNode* ragdollNode = actorData->GetRagdollNode();
                     if (ragdollNode)
                     {
-                        const AZStd::shared_ptr<Physics::Joint>& joint = ragdollNode->GetJoint();
+                        AzPhysics::Joint* joint = ragdollNode->GetJoint();
                         physx::PxJoint* pxJoint = static_cast<physx::PxJoint*>(joint->GetNativePointer());
                         physx::PxTransform jointPose = actor1->getGlobalPose() * pxJoint->getLocalPose(physx::PxJointActorIndex::eACTOR1);
                         if (!m_culling.m_enabled || m_cullingBox.contains(jointPose.p))

+ 7 - 0
Gems/ScriptCanvasPhysics/Code/Tests/ScriptCanvasPhysicsTest.cpp

@@ -100,6 +100,9 @@ namespace ScriptCanvasPhysicsTests
         void DisableSimulationOfBody(
             [[maybe_unused]] AzPhysics::SceneHandle sceneHandle,
             [[maybe_unused]] AzPhysics::SimulatedBodyHandle bodyHandle) override {}
+        void RemoveJoint(
+            [[maybe_unused]]AzPhysics::SceneHandle sceneHandle,
+            [[maybe_unused]] AzPhysics::JointHandle jointHandle) override {}
         void SuppressCollisionEvents(
             [[maybe_unused]] AzPhysics::SceneHandle sceneHandle,
             [[maybe_unused]] const AzPhysics::SimulatedBodyHandle& bodyHandleA,
@@ -148,6 +151,10 @@ namespace ScriptCanvasPhysicsTests
         MOCK_METHOD2(AddSimulatedBodies, AzPhysics::SimulatedBodyHandleList(AzPhysics::SceneHandle sceneHandle, const AzPhysics::SimulatedBodyConfigurationList& simulatedBodyConfigs));
         MOCK_METHOD2(GetSimulatedBodyFromHandle, AzPhysics::SimulatedBody* (AzPhysics::SceneHandle sceneHandle, AzPhysics::SimulatedBodyHandle bodyHandle));
         MOCK_METHOD2(GetSimulatedBodiesFromHandle, AzPhysics::SimulatedBodyList(AzPhysics::SceneHandle sceneHandle, const AzPhysics::SimulatedBodyHandleList& bodyHandles));
+        MOCK_METHOD4(AddJoint, AzPhysics::JointHandle(AzPhysics::SceneHandle sceneHandle, const AzPhysics::JointConfiguration* jointConfig,
+            AzPhysics::SimulatedBodyHandle parentBody, AzPhysics::SimulatedBodyHandle childBody));
+        MOCK_METHOD2(
+            GetJointFromHandle, AzPhysics::Joint*(AzPhysics::SceneHandle sceneHandle, AzPhysics::JointHandle jointHandle));
         MOCK_CONST_METHOD1(GetGravity, AZ::Vector3(AzPhysics::SceneHandle sceneHandle));
         MOCK_METHOD2(RegisterSceneSimulationFinishHandler, void(AzPhysics::SceneHandle sceneHandle, AzPhysics::SceneEvents::OnSceneSimulationFinishHandler& handler));
         MOCK_CONST_METHOD2(GetLegacyBody, AzPhysics::SimulatedBody* (AzPhysics::SceneHandle sceneHandle, AzPhysics::SimulatedBodyHandle handle));