diff --git a/AutomatedTesting/Gem/PythonTests/physics/C18243584_Joints_HingeSoftLimitsConstrained.py b/AutomatedTesting/Gem/PythonTests/physics/C18243584_Joints_HingeSoftLimitsConstrained.py index 90027be2d7..73800a54be 100755 --- a/AutomatedTesting/Gem/PythonTests/physics/C18243584_Joints_HingeSoftLimitsConstrained.py +++ b/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 the follower to move above the lead or Timeout + normalizedStartPos = JointsHelper.getRelativeVector(lead.position, follower.position) + normalizedStartPos = normalizedStartPos.GetNormalizedSafe() - # 4) Wait for several seconds - general.idle_wait(4.0) # wait for lead and follower to move + 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) diff --git a/AutomatedTesting/Levels/Physics/C18243584_Joints_HingeSoftLimitsConstrained/C18243584_Joints_HingeSoftLimitsConstrained.ly b/AutomatedTesting/Levels/Physics/C18243584_Joints_HingeSoftLimitsConstrained/C18243584_Joints_HingeSoftLimitsConstrained.ly index 585ca394f1..2307843887 100644 --- a/AutomatedTesting/Levels/Physics/C18243584_Joints_HingeSoftLimitsConstrained/C18243584_Joints_HingeSoftLimitsConstrained.ly +++ b/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 diff --git a/AutomatedTesting/Levels/Physics/C18243586_Joints_HingeLeadFollowerCollide/C18243586_Joints_HingeLeadFollowerCollide.ly b/AutomatedTesting/Levels/Physics/C18243586_Joints_HingeLeadFollowerCollide/C18243586_Joints_HingeLeadFollowerCollide.ly index d5675c2542..83ba9e3831 100644 --- a/AutomatedTesting/Levels/Physics/C18243586_Joints_HingeLeadFollowerCollide/C18243586_Joints_HingeLeadFollowerCollide.ly +++ b/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 diff --git a/AutomatedTesting/Levels/Physics/C18243589_Joints_BallSoftLimitsConstrained/C18243589_Joints_BallSoftLimitsConstrained.ly b/AutomatedTesting/Levels/Physics/C18243589_Joints_BallSoftLimitsConstrained/C18243589_Joints_BallSoftLimitsConstrained.ly index 6091c9e137..ecd4b61cb0 100644 --- a/AutomatedTesting/Levels/Physics/C18243589_Joints_BallSoftLimitsConstrained/C18243589_Joints_BallSoftLimitsConstrained.ly +++ b/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 diff --git a/AutomatedTesting/Levels/Physics/C18243591_Joints_BallLeadFollowerCollide/C18243591_Joints_BallLeadFollowerCollide.ly b/AutomatedTesting/Levels/Physics/C18243591_Joints_BallLeadFollowerCollide/C18243591_Joints_BallLeadFollowerCollide.ly index e50ffc9dff..3991492730 100644 --- a/AutomatedTesting/Levels/Physics/C18243591_Joints_BallLeadFollowerCollide/C18243591_Joints_BallLeadFollowerCollide.ly +++ b/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 diff --git a/Code/Framework/AzFramework/AzFramework/Physics/Common/PhysicsJoint.cpp b/Code/Framework/AzFramework/AzFramework/Physics/Common/PhysicsJoint.cpp new file mode 100644 index 0000000000..c654038ead --- /dev/null +++ b/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 + +#include +#include + +#include +#include + +namespace AzPhysics +{ + AZ_CLASS_ALLOCATOR_IMPL(Joint, AZ::SystemAllocator, 0); + + void Joint::Reflect(AZ::ReflectContext* context) + { + if (auto* serializeContext = azdynamic_cast(context)) + { + serializeContext->Class() + ->Version(1) + ->Field("SceneOwner", &Joint::m_sceneOwner) + ->Field("JointHandle", &Joint::m_jointHandle) + ; + } + } +} diff --git a/Code/Framework/AzFramework/AzFramework/Physics/Common/PhysicsJoint.h b/Code/Framework/AzFramework/AzFramework/Physics/Common/PhysicsJoint.h new file mode 100644 index 0000000000..a73a9e5cd4 --- /dev/null +++ b/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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 + void SetUserData(T* userData) + { + m_customUserData = static_cast(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& vertexBufferOut, + [[ maybe_unused ]] AZStd::vector& indexBufferOut, + [[ maybe_unused ]] AZStd::vector& lineBufferOut, + [[ maybe_unused ]] AZStd::vector& lineValidityBufferOut) { } + + private: + void* m_customUserData = nullptr; + }; + + //! Alias for a list of non owning weak pointers to Joint objects. + using JointList = AZStd::vector; + + //! 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 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 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 ComputeInitialJointLimitConfiguration( + const AZ::TypeId& jointLimitTypeId, + const AZ::Quaternion& parentWorldRotation, + const AZ::Quaternion& childWorldRotation, + const AZ::Vector3& axis, + const AZStd::vector& 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& vertexBufferOut, + AZStd::vector& indexBufferOut, + AZStd::vector& lineBufferOut, + AZStd::vector& lineValidityBufferOut) = 0; + }; +} diff --git a/Code/Framework/AzFramework/AzFramework/Physics/Common/PhysicsTypes.h b/Code/Framework/AzFramework/AzFramework/Physics/Common/PhysicsTypes.h index b2b5fd1511..42aee35c2c 100644 --- a/Code/Framework/AzFramework/AzFramework/Physics/Common/PhysicsTypes.h +++ b/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::value - && std::is_signed::value, "SceneIndex and SimulatedBodyIndex must be signed integers."); + && std::is_signed::value + && std::is_signed::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; + //! 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; + 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, AZStd::shared_ptr>; using ShapeColliderPairList = AZStd::vector; + //! 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 { diff --git a/Code/Framework/AzFramework/AzFramework/Physics/Configuration/JointConfiguration.cpp b/Code/Framework/AzFramework/AzFramework/Physics/Configuration/JointConfiguration.cpp new file mode 100644 index 0000000000..aa58136944 --- /dev/null +++ b/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 + +#include +#include + +namespace AzPhysics +{ + AZ_CLASS_ALLOCATOR_IMPL(JointConfiguration, AZ::SystemAllocator, 0); + + void JointConfiguration::Reflect(AZ::ReflectContext* context) + { + if (auto* serializeContext = azrtti_cast(context)) + { + serializeContext->Class() + ->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) + ; + } + } +} diff --git a/Code/Framework/AzFramework/AzFramework/Physics/Configuration/JointConfiguration.h b/Code/Framework/AzFramework/AzFramework/Physics/Configuration/JointConfiguration.h new file mode 100644 index 0000000000..8ebe1199a8 --- /dev/null +++ b/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 +#include +#include +#include +#include +#include + +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; + }; +} diff --git a/Code/Framework/AzFramework/AzFramework/Physics/Joint.cpp b/Code/Framework/AzFramework/AzFramework/Physics/Joint.cpp deleted file mode 100644 index 965583c113..0000000000 --- a/Code/Framework/AzFramework/AzFramework/Physics/Joint.cpp +++ /dev/null @@ -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 -#include -#include -#include - -namespace Physics -{ - const char* JointLimitConfiguration::GetTypeName() - { - return "Base Joint"; - } - - void JointLimitConfiguration::Reflect(AZ::ReflectContext* context) - { - if (auto serializeContext = azrtti_cast(context)) - { - serializeContext->Class() - ->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( - "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 diff --git a/Code/Framework/AzFramework/AzFramework/Physics/Joint.h b/Code/Framework/AzFramework/AzFramework/Physics/Joint.h deleted file mode 100644 index d78ce74611..0000000000 --- a/Code/Framework/AzFramework/AzFramework/Physics/Joint.h +++ /dev/null @@ -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 -#include - -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& vertexBufferOut, - AZStd::vector& indexBufferOut, - AZStd::vector& lineBufferOut, - AZStd::vector& lineValidityBufferOut) = 0; - }; -} // namespace Physics diff --git a/Code/Framework/AzFramework/AzFramework/Physics/PhysicsScene.h b/Code/Framework/AzFramework/AzFramework/Physics/PhysicsScene.h index 58e53b0b0d..c93278e7bf 100644 --- a/Code/Framework/AzFramework/AzFramework/Physics/PhysicsScene.h +++ b/Code/Framework/AzFramework/AzFramework/Physics/PhysicsScene.h @@ -16,6 +16,8 @@ #include #include #include +#include +#include #include 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. diff --git a/Code/Framework/AzFramework/AzFramework/Physics/Ragdoll.cpp b/Code/Framework/AzFramework/AzFramework/Physics/Ragdoll.cpp index f634360445..f4b88fe087 100644 --- a/Code/Framework/AzFramework/AzFramework/Physics/Ragdoll.cpp +++ b/Code/Framework/AzFramework/AzFramework/Physics/Ragdoll.cpp @@ -36,8 +36,8 @@ namespace Physics if (serializeContext) { serializeContext->Class() - ->Version(4, &ClassConverters::RagdollNodeConfigConverter) - ->Field("JointLimit", &RagdollNodeConfiguration::m_jointLimit) + ->Version(5, &ClassConverters::RagdollNodeConfigConverter) + ->Field("JointConfig", &RagdollNodeConfiguration::m_jointConfig) ; AZ::EditContext* editContext = serializeContext->GetEditContext(); diff --git a/Code/Framework/AzFramework/AzFramework/Physics/Ragdoll.h b/Code/Framework/AzFramework/AzFramework/Physics/Ragdoll.h index 97c841e8f8..5dcc93c9da 100644 --- a/Code/Framework/AzFramework/AzFramework/Physics/Ragdoll.h +++ b/Code/Framework/AzFramework/AzFramework/Physics/Ragdoll.h @@ -17,10 +17,11 @@ #include #include #include -#include #include +#include #include #include +#include namespace Physics { @@ -37,7 +38,7 @@ namespace Physics RagdollNodeConfiguration(); RagdollNodeConfiguration(const RagdollNodeConfiguration& settings) = default; - AZStd::shared_ptr m_jointLimit; + AZStd::shared_ptr m_jointConfig; }; class RagdollConfiguration @@ -73,7 +74,7 @@ namespace Physics virtual AzPhysics::RigidBody& GetRigidBody() = 0; virtual ~RagdollNode() = default; - virtual const AZStd::shared_ptr& GetJoint() const = 0; + virtual AzPhysics::Joint* GetJoint() = 0; virtual bool IsSimulating() const = 0; }; diff --git a/Code/Framework/AzFramework/AzFramework/Physics/SystemBus.h b/Code/Framework/AzFramework/AzFramework/Physics/SystemBus.h index 8cdd0e0cf0..73e9e4197a 100644 --- a/Code/Framework/AzFramework/AzFramework/Physics/SystemBus.h +++ b/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 GetSupportedJointTypes() = 0; - virtual AZStd::shared_ptr CreateJointLimitConfiguration(AZ::TypeId jointType) = 0; - virtual AZStd::shared_ptr CreateJoint(const AZStd::shared_ptr& 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& vertexBufferOut, - AZStd::vector& indexBufferOut, - AZStd::vector& lineBufferOut, - AZStd::vector& 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 ComputeInitialJointLimitConfiguration( - const AZ::TypeId& jointLimitTypeId, - const AZ::Quaternion& parentWorldRotation, - const AZ::Quaternion& childWorldRotation, - const AZ::Vector3& axis, - const AZStd::vector& exampleLocalRotations) = 0; - ////////////////////////////////////////////////////////////////////////// //// Cooking diff --git a/Code/Framework/AzFramework/AzFramework/Physics/Utils.cpp b/Code/Framework/AzFramework/AzFramework/Physics/Utils.cpp index 17c09bb6ce..695fe1dcf5 100644 --- a/Code/Framework/AzFramework/AzFramework/Physics/Utils.cpp +++ b/Code/Framework/AzFramework/AzFramework/Physics/Utils.cpp @@ -34,6 +34,7 @@ #include #include #include +#include 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); diff --git a/Code/Framework/AzFramework/AzFramework/azframework_files.cmake b/Code/Framework/AzFramework/AzFramework/azframework_files.cmake index 13dff43f68..2a5d251400 100644 --- a/Code/Framework/AzFramework/AzFramework/azframework_files.cmake +++ b/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 diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/Viewport/ViewportMessages.h b/Code/Framework/AzToolsFramework/AzToolsFramework/Viewport/ViewportMessages.h index 85250f2a32..d202735522 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/Viewport/ViewportMessages.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/Viewport/ViewportMessages.h @@ -210,6 +210,15 @@ namespace AzToolsFramework //! Type to inherit to implement ViewportInteractionRequests. using ViewportInteractionRequestBus = AZ::EBus; + //! An interface to notify when changes to viewport settings have happened. + class ViewportSettingNotifications + { + public: + virtual void OnGridSnappingChanged(bool enabled) = 0; + }; + + using ViewportSettingsNotificationBus = AZ::EBus; + //! Requests to freeze the Viewport Input //! Added to prevent a bug with the legacy CryEngine Viewport code that would //! keep doing raycast tests even when no level is loaded, causing a crash. diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp index c4f1ae33da..3e16088f0e 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.cpp @@ -489,6 +489,16 @@ namespace AzToolsFramework return buttonId; } + void SnappingCluster::TrySetVisible(const bool visible) + { + bool snapping = false; + ViewportInteraction::ViewportInteractionRequestBus::EventResult( + snapping, ViewportUi::DefaultViewportId, &ViewportInteraction::ViewportInteractionRequestBus::Events::GridSnappingEnabled); + + // show snapping viewport ui only if there are entities selected and snapping is enabled + SetViewportUiClusterVisible(m_clusterId, visible && snapping); + } + // return either center or entity pivot static AZ::Vector3 CalculatePivotTranslation(const AZ::EntityId entityId, const EditorTransformComponentSelectionRequests::Pivot pivot) { @@ -1035,6 +1045,7 @@ namespace AzToolsFramework EditorEntityLockComponentNotificationBus::Router::BusRouterConnect(); EditorManipulatorCommandUndoRedoRequestBus::Handler::BusConnect(entityContextId); EditorContextMenuBus::Handler::BusConnect(); + ViewportInteraction::ViewportSettingsNotificationBus::Handler::BusConnect(ViewportUi::DefaultViewportId); CreateTransformModeSelectionCluster(); CreateSpaceSelectionCluster(); @@ -1058,6 +1069,7 @@ namespace AzToolsFramework m_pivotOverrideFrame.Reset(); + ViewportInteraction::ViewportSettingsNotificationBus::Handler::BusDisconnect(); EditorContextMenuBus::Handler::BusConnect(); EditorManipulatorCommandUndoRedoRequestBus::Handler::BusDisconnect(); EditorEntityLockComponentNotificationBus::Router::BusRouterDisconnect(); @@ -3253,7 +3265,7 @@ namespace AzToolsFramework m_didSetSelectedEntities = false; } - SetViewportUiClusterVisible(m_snappingCluster.m_clusterId, m_viewportUiVisible && !m_selectedEntityIds.empty()); + m_snappingCluster.TrySetVisible(m_viewportUiVisible && !m_selectedEntityIds.empty()); RegenerateManipulators(); } @@ -3717,6 +3729,11 @@ namespace AzToolsFramework SetAllViewportUiVisible(true); } + void EditorTransformComponentSelection::OnGridSnappingChanged([[maybe_unused]] const bool enabled) + { + m_snappingCluster.TrySetVisible(m_viewportUiVisible && !m_selectedEntityIds.empty()); + } + namespace ETCS { // little raii wrapper to switch a value from true to false and back diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.h b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.h index db96d91911..00c2cb4e50 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportSelection/EditorTransformComponentSelection.h @@ -131,6 +131,9 @@ namespace AzToolsFramework SnappingCluster(const SnappingCluster&) = delete; SnappingCluster& operator=(const SnappingCluster&) = delete; + //! Attempt to show the snapping cluster (will only succeed if snapping is enabled). + void TrySetVisible(bool visible); + ViewportUi::ClusterId m_clusterId; //!< The cluster id for all snapping buttons. ViewportUi::ButtonId m_snapToWorldButtonId; //!< The button id for snapping all axes to the world. AZ::Event::Handler m_snappingHandler; //!< Callback for when a snapping cluster button is pressed. @@ -151,6 +154,7 @@ namespace AzToolsFramework , private EditorEntityLockComponentNotificationBus::Router , private EditorManipulatorCommandUndoRedoRequestBus::Handler , private AZ::TransformNotificationBus::MultiHandler + , private ViewportInteraction::ViewportSettingsNotificationBus::Handler { public: AZ_CLASS_ALLOCATOR_DECL @@ -289,6 +293,9 @@ namespace AzToolsFramework void OnStartPlayInEditor() override; void OnStopPlayInEditor() override; + // ViewportSettingsNotificationBus overrides ... + void OnGridSnappingChanged(bool enabled) override; + // Helpers to safely interact with the TransformBus (requests). void SetEntityWorldTranslation(AZ::EntityId entityId, const AZ::Vector3& worldTranslation); void SetEntityLocalTranslation(AZ::EntityId entityId, const AZ::Vector3& localTranslation); diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportUi/ViewportUiDisplay.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportUi/ViewportUiDisplay.cpp index bb2aeed4d6..81be2bba90 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportUi/ViewportUiDisplay.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportUi/ViewportUiDisplay.cpp @@ -24,8 +24,6 @@ namespace AzToolsFramework::ViewportUi::Internal { - // margin for the Viewport UI Overlay in pixels - const static int ViewportUiOverlayMargin = 5; const static int HighlightBorderSize = 5; const static int TopHighlightBorderSize = 25; const static char* HighlightBorderColor = "#44B2F8"; @@ -387,7 +385,6 @@ namespace AzToolsFramework::ViewportUi::Internal m_fullScreenLayout.setSpacing(0); m_fullScreenLayout.setContentsMargins(0, 0, 0, 0); m_fullScreenLayout.addLayout(&m_uiOverlayLayout, 0, 0, 1, 1); - m_uiOverlayLayout.setMargin(ViewportUiOverlayMargin); // format the label which will appear on top of the highlight border AZStd::string styleSheet = AZStd::string::format( diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportUi/ViewportUiDisplayLayout.cpp b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportUi/ViewportUiDisplayLayout.cpp index bab664d832..92fad8cc93 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportUi/ViewportUiDisplayLayout.cpp +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportUi/ViewportUiDisplayLayout.cpp @@ -25,13 +25,15 @@ namespace AzToolsFramework::ViewportUi::Internal : QGridLayout(parent) { // set margins and spacing for internal contents - setContentsMargins(0, 0, 0, 0); + setContentsMargins( + ViewportUiOverlayMargin, ViewportUiOverlayMargin + ViewportUiOverlayTopMarginPadding, ViewportUiOverlayMargin, + ViewportUiOverlayMargin); setSpacing(ViewportUiDisplayLayoutSpacing); // create a 3x2 map of sub layouts which will stack widgets according to their mapped alignment m_internalLayouts = AZStd::unordered_map { CreateSubLayout(new QVBoxLayout(), 0, 0, Qt::AlignTop | Qt::AlignLeft), - CreateSubLayout(new QHBoxLayout(), 1, 0, Qt::AlignBottom | Qt::AlignLeft), + CreateSubLayout(new QVBoxLayout(), 1, 0, Qt::AlignBottom | Qt::AlignLeft), CreateSubLayout(new QVBoxLayout(), 0, 1, Qt::AlignTop), CreateSubLayout(new QHBoxLayout(), 1, 1, Qt::AlignBottom), CreateSubLayout(new QVBoxLayout(), 0, 2, Qt::AlignTop | Qt::AlignRight), @@ -50,9 +52,42 @@ namespace AzToolsFramework::ViewportUi::Internal if (auto layoutForAlignment = m_internalLayouts.find(alignment); layoutForAlignment != m_internalLayouts.end()) { - // place the widget before the invisible spacer - // spacer must be last item in layout to not interfere with positioning - int index = layoutForAlignment->second->count() - 1; + // place the widget before or after the invisible spacer + // depending on the layout alignment + int index = 0; + switch (alignment) + { + case Qt::AlignTop | Qt::AlignLeft: + case Qt::AlignTop: + index = layoutForAlignment->second->count() - 1; + break; + case Qt::AlignBottom | Qt::AlignRight: + case Qt::AlignBottom: + index = layoutForAlignment->second->count(); + break; + // TopRight and BottomLeft are special cases + // place the spacer differently according to whether it's a vertical or horizontal layout + case Qt::AlignTop | Qt::AlignRight: + if (QVBoxLayout* vLayout = qobject_cast(layoutForAlignment->second)) + { + index = layoutForAlignment->second->count() - 1; + } + else if (QHBoxLayout* hLayout = qobject_cast(layoutForAlignment->second)) + { + index = layoutForAlignment->second->count(); + } + break; + case Qt::AlignBottom | Qt::AlignLeft: + if (QVBoxLayout* vLayout = qobject_cast(layoutForAlignment->second)) + { + index = layoutForAlignment->second->count(); + } + else if (QHBoxLayout* hLayout = qobject_cast(layoutForAlignment->second)) + { + index = layoutForAlignment->second->count() - 1; + } + break; + } layoutForAlignment->second->insertWidget(index, widget); } } diff --git a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportUi/ViewportUiDisplayLayout.h b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportUi/ViewportUiDisplayLayout.h index 0beb0d5bd6..8d710264be 100644 --- a/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportUi/ViewportUiDisplayLayout.h +++ b/Code/Framework/AzToolsFramework/AzToolsFramework/ViewportUi/ViewportUiDisplayLayout.h @@ -19,6 +19,11 @@ namespace AzToolsFramework::ViewportUi::Internal { + // margin for the Viewport UI Overlay in pixels + constexpr int ViewportUiOverlayMargin = 5; + // padding to make space for ImGui + constexpr int ViewportUiOverlayTopMarginPadding = 20; + //! QGridLayout implementation that uses a grid of QVBox/QHBoxLayouts internally to stack widgets. class ViewportUiDisplayLayout : public QGridLayout { diff --git a/Code/Sandbox/Editor/EditorViewportSettings.cpp b/Code/Sandbox/Editor/EditorViewportSettings.cpp index dbfd3ea4ed..02e280b8d1 100644 --- a/Code/Sandbox/Editor/EditorViewportSettings.cpp +++ b/Code/Sandbox/Editor/EditorViewportSettings.cpp @@ -14,6 +14,7 @@ #include #include +#include #include namespace SandboxEditor @@ -56,6 +57,39 @@ namespace SandboxEditor return value; } + struct EditorViewportSettingsCallbacksImpl : public EditorViewportSettingsCallbacks + { + EditorViewportSettingsCallbacksImpl() + { + if (auto* registry = AZ::SettingsRegistry::Get()) + { + using AZ::SettingsRegistryMergeUtils::IsPathAncestorDescendantOrEqual; + + m_notifyEventHandler = registry->RegisterNotifier( + [this](const AZStd::string_view path, [[maybe_unused]] const AZ::SettingsRegistryInterface::Type type) + { + if (IsPathAncestorDescendantOrEqual(GridSnappingSetting, path)) + { + m_gridSnappingChanged.Signal(GridSnappingEnabled()); + } + }); + } + } + + void SetGridSnappingChangedEvent(GridSnappingChangedEvent::Handler& handler) override + { + handler.Connect(m_gridSnappingChanged); + } + + GridSnappingChangedEvent m_gridSnappingChanged; + AZ::SettingsRegistryInterface::NotifyEventHandler m_notifyEventHandler; + }; + + AZStd::unique_ptr CreateEditorViewportSettingsCallbacks() + { + return AZStd::make_unique(); + } + bool GridSnappingEnabled() { return GetRegistry(GridSnappingSetting, false); diff --git a/Code/Sandbox/Editor/EditorViewportSettings.h b/Code/Sandbox/Editor/EditorViewportSettings.h index a2f80d196e..d3a082c095 100644 --- a/Code/Sandbox/Editor/EditorViewportSettings.h +++ b/Code/Sandbox/Editor/EditorViewportSettings.h @@ -14,8 +14,27 @@ #include +#include +#include + namespace SandboxEditor { + using GridSnappingChangedEvent = AZ::Event; + + //! Set callbacks to listen for editor settings change events. + class EditorViewportSettingsCallbacks + { + public: + virtual ~EditorViewportSettingsCallbacks() = default; + + virtual void SetGridSnappingChangedEvent(GridSnappingChangedEvent::Handler& handler) = 0; + }; + + //! Create an instance of EditorViewportSettingsCallbacks + //! Note: EditorViewportSettingsCallbacks is implemented in EditorViewportSettings.cpp - a change + //! event will fire when a value in the settings registry (editorpreferences.setreg) is modified. + SANDBOX_API AZStd::unique_ptr CreateEditorViewportSettingsCallbacks(); + SANDBOX_API bool GridSnappingEnabled(); SANDBOX_API void SetGridSnapping(bool enabled); diff --git a/Code/Sandbox/Editor/EditorViewportWidget.cpp b/Code/Sandbox/Editor/EditorViewportWidget.cpp index 1a7c54967b..699cd7acb9 100644 --- a/Code/Sandbox/Editor/EditorViewportWidget.cpp +++ b/Code/Sandbox/Editor/EditorViewportWidget.cpp @@ -76,7 +76,6 @@ #include "EditorPreferencesPageGeneral.h" #include "ViewportManipulatorController.h" #include "LegacyViewportCameraController.h" -#include "EditorViewportSettings.h" #include "ViewPane.h" #include "CustomResolutionDlg.h" @@ -1450,6 +1449,17 @@ void EditorViewportWidget::SetViewportId(int id) { SetAsActiveViewport(); } + + m_editorViewportSettingsCallbacks = SandboxEditor::CreateEditorViewportSettingsCallbacks(); + + m_gridSnappingHandler = SandboxEditor::GridSnappingChangedEvent::Handler( + [id](const bool snapping) + { + AzToolsFramework::ViewportInteraction::ViewportSettingsNotificationBus::Event( + id, &AzToolsFramework::ViewportInteraction::ViewportSettingsNotificationBus::Events::OnGridSnappingChanged, snapping); + }); + + m_editorViewportSettingsCallbacks->SetGridSnappingChangedEvent(m_gridSnappingHandler); } void EditorViewportWidget::ConnectViewportInteractionRequestBus() diff --git a/Code/Sandbox/Editor/EditorViewportWidget.h b/Code/Sandbox/Editor/EditorViewportWidget.h index 511a7910c6..670694f91f 100644 --- a/Code/Sandbox/Editor/EditorViewportWidget.h +++ b/Code/Sandbox/Editor/EditorViewportWidget.h @@ -24,6 +24,7 @@ #include "Objects/DisplayContext.h" #include "Undo/Undo.h" #include "Util/PredefinedAspectRatios.h" +#include "EditorViewportSettings.h" #include #include @@ -571,6 +572,9 @@ private: AzFramework::EntityVisibilityQuery m_entityVisibilityQuery; + SandboxEditor::GridSnappingChangedEvent::Handler m_gridSnappingHandler; + AZStd::unique_ptr m_editorViewportSettingsCallbacks; + QSet m_keyDown; bool m_freezeViewportInput = false; diff --git a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Thumbnails/Rendering/ThumbnailRendererSteps/CaptureStep.cpp b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Thumbnails/Rendering/ThumbnailRendererSteps/CaptureStep.cpp index 16baf16886..f2f33c4236 100644 --- a/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Thumbnails/Rendering/ThumbnailRendererSteps/CaptureStep.cpp +++ b/Gems/AtomLyIntegration/CommonFeatures/Code/Source/Thumbnails/Rendering/ThumbnailRendererSteps/CaptureStep.cpp @@ -96,6 +96,13 @@ namespace AZ RPI::AttachmentReadback::CallbackFunction readbackCallback = [&](const RPI::AttachmentReadback::ReadbackResult& result) { + if (!result.m_dataBuffer) + { + AzToolsFramework::Thumbnailer::ThumbnailerRendererNotificationBus::Event( + m_context->GetData()->m_thumbnailKeyRendered, + &AzToolsFramework::Thumbnailer::ThumbnailerRendererNotifications::ThumbnailFailedToRender); + return; + } uchar* data = result.m_dataBuffer.get()->data(); QImage image( data, result.m_imageDescriptor.m_size.m_width, result.m_imageDescriptor.m_size.m_height, QImage::Format_RGBA8888); diff --git a/Gems/Blast/Code/Tests/Mocks/BlastMocks.h b/Gems/Blast/Code/Tests/Mocks/BlastMocks.h index ca78623a8a..b1fea21550 100644 --- a/Gems/Blast/Code/Tests/Mocks/BlastMocks.h +++ b/Gems/Blast/Code/Tests/Mocks/BlastMocks.h @@ -15,7 +15,6 @@ #include #include #include -#include #include #include #include @@ -221,23 +220,6 @@ namespace Blast AZStd::vector>(const Physics::MaterialSelection&)); MOCK_METHOD2( UpdateMaterialSelection, bool(const Physics::ShapeConfiguration&, Physics::ColliderConfiguration&)); - MOCK_METHOD0(GetSupportedJointTypes, AZStd::vector()); - MOCK_METHOD1(CreateJointLimitConfiguration, AZStd::shared_ptr(AZ::TypeId)); - MOCK_METHOD3( - CreateJoint, - AZStd::shared_ptr( - const AZStd::shared_ptr&, AzPhysics::SimulatedBody*, AzPhysics::SimulatedBody*)); - MOCK_METHOD10( - GenerateJointLimitVisualizationData, - void( - const Physics::JointLimitConfiguration&, const AZ::Quaternion&, const AZ::Quaternion&, float, AZ::u32, - AZ::u32, AZStd::vector&, AZStd::vector&, AZStd::vector&, - AZStd::vector&)); - MOCK_METHOD5( - ComputeInitialJointLimitConfiguration, - AZStd::unique_ptr( - const AZ::TypeId&, const AZ::Quaternion&, const AZ::Quaternion&, const AZ::Vector3&, - const AZStd::vector&)); MOCK_METHOD3(CookConvexMeshToFile, bool(const AZStd::string&, const AZ::Vector3*, AZ::u32)); MOCK_METHOD3(CookConvexMeshToMemory, bool(const AZ::Vector3*, AZ::u32, AZStd::vector&)); MOCK_METHOD5( diff --git a/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/RagdollCommands.cpp b/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/RagdollCommands.cpp index 22c875d8c3..864f80a2c5 100644 --- a/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/RagdollCommands.cpp +++ b/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 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 CommandRagdollHelpers::CreateJointLimitByType( - const AZ::TypeId& typeId, const Skeleton* skeleton, const Node* node) + AZStd::unique_ptr 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 exampleRotationsLocal; - AZStd::unique_ptr jointLimitConfig = - AZ::Interface::Get()->ComputeInitialJointLimitConfiguration( - typeId, parentBindRotationWorld, nodeBindRotationWorld, boneDirection, exampleRotationsLocal); + if (auto* jointHelpers = AZ::Interface::Get()) + { + if (AZStd::optional jointTypeId = jointHelpers->GetSupportedJointTypeId(jointType); + jointTypeId.has_value()) + { + AZStd::unique_ptr jointLimitConfig = jointHelpers->ComputeInitialJointLimitConfiguration( + *jointTypeId, parentBindRotationWorld, nodeBindRotationWorld, boneDirection, exampleRotationsLocal); - AZ_Assert(jointLimitConfig, "Could not create joint limit configuration with type '%s'.", typeId.ToString().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& jointNames, @@ -532,7 +535,7 @@ namespace EMotionFX if (m_serializedJointLimits) { AZ::Outcome 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 CommandAdjustRagdollJoint::SerializeJointLimits(const Physics::RagdollNodeConfiguration* ragdollNodeConfig) { return MCore::ReflectionSerializer::SerializeMembersExcept( - ragdollNodeConfig->m_jointLimit.get(), + ragdollNodeConfig->m_jointConfig.get(), {"ParentLocalRotation", "ParentLocalPosition", "ChildLocalRotation", "ChildLocalPosition", } ); } diff --git a/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/RagdollCommands.h b/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/RagdollCommands.h index 8e94f028db..36683c82b4 100644 --- a/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/RagdollCommands.h +++ b/Gems/EMotionFX/Code/EMotionFX/CommandSystem/Source/RagdollCommands.h @@ -45,8 +45,8 @@ namespace EMotionFX Physics::RagdollConfiguration& ragdollConfig, const AZStd::optional& index, AZStd::string& outResult); - static AZStd::unique_ptr CreateJointLimitByType(const AZ::TypeId& typeId, - const Skeleton* skeleton, const Node* node); + static AZStd::unique_ptr CreateJointLimitByType( + AzPhysics::JointType jointType, const Skeleton* skeleton, const Node* node); static void AddJointsToRagdoll(AZ::u32 actorId, const AZStd::vector& jointNames, MCore::CommandGroup* commandGroup = nullptr, bool executeInsideCommand = false, bool addDefaultCollider = true); diff --git a/Gems/EMotionFX/Code/Source/Editor/Plugins/Ragdoll/RagdollJointLimitWidget.cpp b/Gems/EMotionFX/Code/Source/Editor/Plugins/Ragdoll/RagdollJointLimitWidget.cpp index 934bb113d5..96464de230 100644 --- a/Gems/EMotionFX/Code/Source/Editor/Plugins/Ragdoll/RagdollJointLimitWidget.cpp +++ b/Gems/EMotionFX/Code/Source/Editor/Plugins/Ragdoll/RagdollJointLimitWidget.cpp @@ -90,12 +90,15 @@ namespace EMotionFX if (serializeContext) { - AZStd::vector 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::Get()) { - const char* jointLimitName = serializeContext->FindClassData(jointLimitType)->m_editData->m_name; - m_typeComboBox->addItem(jointLimitName, jointLimitType.ToString().c_str()); + if (AZStd::optional 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().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(); const Skeleton* skeleton = m_nodeIndex.data(SkeletonModel::ROLE_ACTOR_POINTER).value()->GetSkeleton(); - ragdollNodeConfig->m_jointLimit = CommandRagdollHelpers::CreateJointLimitByType(type, skeleton, node); + ragdollNodeConfig->m_jointConfig = + CommandRagdollHelpers::CreateJointLimitByType(AzPhysics::JointType::D6Joint, skeleton, node); } Update(); diff --git a/Gems/EMotionFX/Code/Source/Editor/Plugins/Ragdoll/RagdollJointLimitWidget.h b/Gems/EMotionFX/Code/Source/Editor/Plugins/Ragdoll/RagdollJointLimitWidget.h index 211c70e9bf..7861334239 100644 --- a/Gems/EMotionFX/Code/Source/Editor/Plugins/Ragdoll/RagdollJointLimitWidget.h +++ b/Gems/EMotionFX/Code/Source/Editor/Plugins/Ragdoll/RagdollJointLimitWidget.h @@ -13,7 +13,6 @@ #pragma once #if !defined(Q_MOC_RUN) -#include #include #include #include diff --git a/Gems/EMotionFX/Code/Source/Editor/Plugins/Ragdoll/RagdollNodeInspectorPlugin.cpp b/Gems/EMotionFX/Code/Source/Editor/Plugins/Ragdoll/RagdollNodeInspectorPlugin.cpp index e2fde04016..467c56f16a 100644 --- a/Gems/EMotionFX/Code/Source/Editor/Plugins/Ragdoll/RagdollNodeInspectorPlugin.cpp +++ b/Gems/EMotionFX/Code/Source/Editor/Plugins/Ragdoll/RagdollNodeInspectorPlugin.cpp @@ -514,7 +514,7 @@ namespace EMotionFX if (renderJointLimits && jointSelected) { - const AZStd::shared_ptr& jointLimitConfig = ragdollNode.m_jointLimit; + const AZStd::shared_ptr& 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::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, diff --git a/Gems/EMotionFX/Code/Source/Editor/Plugins/Ragdoll/RagdollNodeInspectorPlugin.h b/Gems/EMotionFX/Code/Source/Editor/Plugins/Ragdoll/RagdollNodeInspectorPlugin.h index f7aa1277b0..7bba1b0b4f 100644 --- a/Gems/EMotionFX/Code/Source/Editor/Plugins/Ragdoll/RagdollNodeInspectorPlugin.h +++ b/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, diff --git a/Gems/EMotionFX/Code/Tests/D6JointLimitConfiguration.cpp b/Gems/EMotionFX/Code/Tests/D6JointLimitConfiguration.cpp index 417946524d..73c0a9fdae 100644 --- a/Gems/EMotionFX/Code/Tests/D6JointLimitConfiguration.cpp +++ b/Gems/EMotionFX/Code/Tests/D6JointLimitConfiguration.cpp @@ -19,7 +19,7 @@ namespace EMotionFX { if (auto serializeContext = azrtti_cast(context)) { - serializeContext->Class() + serializeContext->Class() ->Version(1) ->Field("SwingLimitY", &D6JointLimitConfiguration::m_swingLimitY) ->Field("SwingLimitZ", &D6JointLimitConfiguration::m_swingLimitZ) diff --git a/Gems/EMotionFX/Code/Tests/D6JointLimitConfiguration.h b/Gems/EMotionFX/Code/Tests/D6JointLimitConfiguration.h index 47b6b4bef4..b41ba6435b 100644 --- a/Gems/EMotionFX/Code/Tests/D6JointLimitConfiguration.h +++ b/Gems/EMotionFX/Code/Tests/D6JointLimitConfiguration.h @@ -12,23 +12,22 @@ #pragma once -#include +#include 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. diff --git a/Gems/EMotionFX/Code/Tests/Mocks/PhysicsSystem.h b/Gems/EMotionFX/Code/Tests/Mocks/PhysicsSystem.h index 3aafdb4e2b..2ad3853edb 100644 --- a/Gems/EMotionFX/Code/Tests/Mocks/PhysicsSystem.h +++ b/Gems/EMotionFX/Code/Tests/Mocks/PhysicsSystem.h @@ -35,11 +35,6 @@ namespace Physics MOCK_METHOD2(CreateShape, AZStd::shared_ptr(const Physics::ColliderConfiguration& colliderConfiguration, const Physics::ShapeConfiguration& configuration)); MOCK_METHOD1(ReleaseNativeMeshObject, void(void* nativeMeshObject)); MOCK_METHOD1(CreateMaterial, AZStd::shared_ptr(const Physics::MaterialConfiguration& materialConfiguration)); - MOCK_METHOD0(GetSupportedJointTypes, AZStd::vector()); - MOCK_METHOD1(CreateJointLimitConfiguration, AZStd::shared_ptr(AZ::TypeId jointType)); - MOCK_METHOD3(CreateJoint, AZStd::shared_ptr(const AZStd::shared_ptr& 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& vertexBufferOut, AZStd::vector& indexBufferOut, AZStd::vector& lineBufferOut, AZStd::vector& lineValidityBufferOut)); - MOCK_METHOD5(ComputeInitialJointLimitConfiguration, AZStd::unique_ptr(const AZ::TypeId& jointLimitTypeId, const AZ::Quaternion& parentWorldRotation, const AZ::Quaternion& childWorldRotation, const AZ::Vector3& axis, const AZStd::vector& 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& 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::Registrar + { + public: + MOCK_CONST_METHOD0(GetSupportedJointTypeIds, const AZStd::vector()); + MOCK_CONST_METHOD1(GetSupportedJointTypeId, AZStd::optional(AzPhysics::JointType typeEnum)); + + MOCK_METHOD5( + ComputeInitialJointLimitConfiguration, + AZStd::unique_ptr( + const AZ::TypeId& jointLimitTypeId, + const AZ::Quaternion& parentWorldRotation, + const AZ::Quaternion& childWorldRotation, + const AZ::Vector3& axis, + const AZStd::vector& 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& vertexBufferOut, + AZStd::vector& indexBufferOut, + AZStd::vector& lineBufferOut, + AZStd::vector& 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::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)); diff --git a/Gems/EMotionFX/Code/Tests/ProvidesUI/Ragdoll/CanCopyPasteColliders.cpp b/Gems/EMotionFX/Code/Tests/ProvidesUI/Ragdoll/CanCopyPasteColliders.cpp index eaef6f31ee..7e5f481453 100644 --- a/Gems/EMotionFX/Code/Tests/ProvidesUI/Ragdoll/CanCopyPasteColliders.cpp +++ b/Gems/EMotionFX/Code/Tests/ProvidesUI/Ragdoll/CanCopyPasteColliders.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include @@ -44,10 +45,21 @@ namespace EMotionFX D6JointLimitConfiguration::Reflect(GetSerializeContext()); - const AZ::TypeId& jointLimitTypeId = azrtti_typeid(); - EXPECT_CALL(m_physicsSystem, GetSupportedJointTypes) - .WillRepeatedly(testing::Return(AZStd::vector{jointLimitTypeId})); - EXPECT_CALL(m_physicsSystem, ComputeInitialJointLimitConfiguration(jointLimitTypeId, _, _, _, _)) + EXPECT_CALL(m_jointHelpers, GetSupportedJointTypeIds) + .WillRepeatedly(testing::Return(AZStd::vector{ azrtti_typeid() })); + + EXPECT_CALL(m_jointHelpers, GetSupportedJointTypeId(_)) + .WillRepeatedly( + [](AzPhysics::JointType jointType) -> AZStd::optional + { + if (jointType == AzPhysics::JointType::D6Joint) + { + return azrtti_typeid(); + } + 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 diff --git a/Gems/EMotionFX/Code/Tests/ProvidesUI/Ragdoll/CanCopyPasteJointLimits.cpp b/Gems/EMotionFX/Code/Tests/ProvidesUI/Ragdoll/CanCopyPasteJointLimits.cpp index 6223af3599..4f65220400 100644 --- a/Gems/EMotionFX/Code/Tests/ProvidesUI/Ragdoll/CanCopyPasteJointLimits.cpp +++ b/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{azrtti_typeid()})); - EXPECT_CALL(physicsSystem, ComputeInitialJointLimitConfiguration(azrtti_typeid(), _, _, _, _)) - .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& exampleLocalRotations) { return AZStd::make_unique(); }); + Physics::MockJointHelpersInterface jointHelpers; + EXPECT_CALL(jointHelpers, GetSupportedJointTypeIds) + .WillRepeatedly(testing::Return(AZStd::vector{ azrtti_typeid() })); + EXPECT_CALL(jointHelpers, GetSupportedJointTypeId(_)) + .WillRepeatedly( + [](AzPhysics::JointType jointType) -> AZStd::optional + { + if (jointType == AzPhysics::JointType::D6Joint) + { + return azrtti_typeid(); + } + 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& exampleLocalRotations) + { + return AZStd::make_unique(); + }); AutoRegisteredActor actor {ActorFactory::CreateAndInit(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 rootJointLimit = AZStd::rtti_pointer_cast(ragdollConfig.FindNodeConfigByName("rootJoint")->m_jointLimit); + AZStd::shared_ptr rootJointLimit = AZStd::rtti_pointer_cast(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 joint1Limit = AZStd::rtti_pointer_cast(ragdollConfig.FindNodeConfigByName("joint1")->m_jointLimit); + const AZStd::shared_ptr joint1Limit = + AZStd::rtti_pointer_cast(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 joint2Limit = AZStd::rtti_pointer_cast(ragdollConfig.FindNodeConfigByName("joint2")->m_jointLimit); + const AZStd::shared_ptr joint2Limit = + AZStd::rtti_pointer_cast(ragdollConfig.FindNodeConfigByName("joint2")->m_jointConfig); EXPECT_EQ(joint2Limit->m_swingLimitY, 45.0f); EXPECT_EQ(joint2Limit->m_swingLimitZ, 45.0f); - const AZStd::shared_ptr joint3Limit = AZStd::rtti_pointer_cast(ragdollConfig.FindNodeConfigByName("joint3")->m_jointLimit); + const AZStd::shared_ptr joint3Limit = + AZStd::rtti_pointer_cast(ragdollConfig.FindNodeConfigByName("joint3")->m_jointConfig); EXPECT_EQ(joint3Limit->m_swingLimitY, 45.0f); EXPECT_EQ(joint3Limit->m_swingLimitZ, 45.0f); diff --git a/Gems/EMotionFX/Code/Tests/RagdollCommandTests.cpp b/Gems/EMotionFX/Code/Tests/RagdollCommandTests.cpp index 279d3ff38c..f78dc417ac 100644 --- a/Gems/EMotionFX/Code/Tests/RagdollCommandTests.cpp +++ b/Gems/EMotionFX/Code/Tests/RagdollCommandTests.cpp @@ -18,11 +18,47 @@ #include #include #include - +#include +#include 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{ azrtti_typeid() })); + + EXPECT_CALL(m_jointHelpers, GetSupportedJointTypeId(_)) + .WillRepeatedly( + [](AzPhysics::JointType jointType) -> AZStd::optional + { + if (jointType == AzPhysics::JointType::D6Joint) + { + return azrtti_typeid(); + } + 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& exampleLocalRotations) + { return AZStd::make_unique(); }); + } + protected: + Physics::MockJointHelpersInterface m_jointHelpers; + }; AZStd::vector GetRagdollJointNames(const Actor* actor) { diff --git a/Gems/EMotionFX/Code/Tests/UI/CanAddToSimulatedObject.cpp b/Gems/EMotionFX/Code/Tests/UI/CanAddToSimulatedObject.cpp index 50fb434a26..8b75cfbf37 100644 --- a/Gems/EMotionFX/Code/Tests/UI/CanAddToSimulatedObject.cpp +++ b/Gems/EMotionFX/Code/Tests/UI/CanAddToSimulatedObject.cpp @@ -10,6 +10,7 @@ * */ +#include #include #include @@ -29,6 +30,7 @@ #include #include #include +#include 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{ azrtti_typeid() })); + + EXPECT_CALL(jointHelpers, GetSupportedJointTypeId(_)) + .WillRepeatedly( + [](AzPhysics::JointType jointType) -> AZStd::optional + { + if (jointType == AzPhysics::JointType::D6Joint) + { + return azrtti_typeid(); + } + 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& exampleLocalRotations) + { + return AZStd::make_unique(); + }); + RecordProperty("test_case_id", "C13291807"); AutoRegisteredActor actor = ActorFactory::CreateAndInit(7, "CanAddToSimulatedObjectActor"); diff --git a/Gems/EMotionFX/Code/Tests/UI/RagdollEditTests.cpp b/Gems/EMotionFX/Code/Tests/UI/RagdollEditTests.cpp index bd30ac378d..9cc2a3add2 100644 --- a/Gems/EMotionFX/Code/Tests/UI/RagdollEditTests.cpp +++ b/Gems/EMotionFX/Code/Tests/UI/RagdollEditTests.cpp @@ -10,6 +10,7 @@ * */ +#include #include #include @@ -27,6 +28,7 @@ #include #include +#include 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{ azrtti_typeid() })); + + EXPECT_CALL(m_jointHelpers, GetSupportedJointTypeId(_)) + .WillRepeatedly( + [](AzPhysics::JointType jointType) -> AZStd::optional + { + if (jointType == AzPhysics::JointType::D6Joint) + { + return azrtti_typeid(); + } + 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& exampleLocalRotations) + { + return AZStd::make_unique(); + }); + SetupPluginWindows(); } @@ -73,6 +100,7 @@ namespace EMotionFX QModelIndexList m_indexList; ReselectingTreeView* m_treeView; EMotionFX::SkeletonOutlinerPlugin* m_skeletonOutliner; + Physics::MockJointHelpersInterface m_jointHelpers; }; diff --git a/Gems/EMotionFX/Code/emotionfx_editor_tests_files.cmake b/Gems/EMotionFX/Code/emotionfx_editor_tests_files.cmake index e2670c20f7..58366f8ffb 100644 --- a/Gems/EMotionFX/Code/emotionfx_editor_tests_files.cmake +++ b/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 diff --git a/Gems/EMotionFX/Code/emotionfx_shared_tests_files.cmake b/Gems/EMotionFX/Code/emotionfx_shared_tests_files.cmake index 968dbd0f52..f1a0be0b8d 100644 --- a/Gems/EMotionFX/Code/emotionfx_shared_tests_files.cmake +++ b/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 ) diff --git a/Gems/PhysX/Code/CMakeLists.txt b/Gems/PhysX/Code/CMakeLists.txt index d281270ce4..caeaf884bf 100644 --- a/Gems/PhysX/Code/CMakeLists.txt +++ b/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 ) diff --git a/Gems/PhysX/Code/Editor/EditorJointConfiguration.cpp b/Gems/PhysX/Code/Editor/EditorJointConfiguration.cpp index 7b4ec15880..0ceec25918 100644 --- a/Gems/PhysX/Code/Editor/EditorJointConfiguration.cpp +++ b/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 diff --git a/Gems/PhysX/Code/Editor/EditorJointConfiguration.h b/Gems/PhysX/Code/Editor/EditorJointConfiguration.h index a7146124f0..aa0062b3ac 100644 --- a/Gems/PhysX/Code/Editor/EditorJointConfiguration.h +++ b/Gems/PhysX/Code/Editor/EditorJointConfiguration.h @@ -15,7 +15,7 @@ #include #include -#include +#include 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; diff --git a/Gems/PhysX/Code/Include/PhysX/Joint/Configuration/PhysXJointConfiguration.h b/Gems/PhysX/Code/Include/PhysX/Joint/Configuration/PhysXJointConfiguration.h new file mode 100644 index 0000000000..873a939192 --- /dev/null +++ b/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 + +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 diff --git a/Gems/PhysX/Code/Source/BallJointComponent.cpp b/Gems/PhysX/Code/Source/BallJointComponent.cpp index b37efd5a33..0f494c6714 100644 --- a/Gems/PhysX/Code/Source/BallJointComponent.cpp +++ b/Gems/PhysX/Code/Source/BallJointComponent.cpp @@ -16,8 +16,10 @@ #include #include #include +#include #include #include +#include #include @@ -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(physx::PxSphericalJointCreate( - PxGetPhysics(), - leadFollowerInfo.m_leadActor, - leadFollowerInfo.m_leadLocal, - leadFollowerInfo.m_followerActor, - leadFollowerInfo.m_followerLocal), - leadFollowerInfo.m_leadBody, - leadFollowerInfo.m_followerBody); - InitSwingLimits(); - } + 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(); - void BallJointComponent::InitSwingLimits() - { - if (!m_joint) + configuration.m_genericProperties = m_genericProperties; + configuration.m_limitProperties = m_limits; + + if (auto* sceneInterface = AZ::Interface::Get()) { - return; + 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; } - - physx::PxSphericalJoint* ballJointNative = static_cast(m_joint->GetNativePointer()); - if (!ballJointNative) - { - return; - } - - if (!m_limits.m_isLimited) - { - ballJointNative->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(m_limits.m_limitFirst) - , AZ::DegToRad(m_limits.m_limitSecond) - , m_limits.m_tolerance); - if (m_limits.m_isSoftLimit) - { - swingLimit.stiffness = m_limits.m_stiffness; - swingLimit.damping = m_limits.m_damping; - } - - ballJointNative->setLimitCone(swingLimit); - ballJointNative->setSphericalJointFlag(physx::PxSphericalJointFlag::eLIMIT_ENABLED, true); } } // namespace PhysX diff --git a/Gems/PhysX/Code/Source/BallJointComponent.h b/Gems/PhysX/Code/Source/BallJointComponent.h index 4ac489a5fb..9741193e81 100644 --- a/Gems/PhysX/Code/Source/BallJointComponent.h +++ b/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 diff --git a/Gems/PhysX/Code/Source/EditorBallJointComponent.cpp b/Gems/PhysX/Code/Source/EditorBallJointComponent.cpp index deb1d0d231..37203330b2 100644 --- a/Gems/PhysX/Code/Source/EditorBallJointComponent.cpp +++ b/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( m_config.ToGameTimeConfig(), + m_config.ToGenericProperties(), m_swingLimit.ToGameTimeConfig()); } diff --git a/Gems/PhysX/Code/Source/EditorBallJointComponent.h b/Gems/PhysX/Code/Source/EditorBallJointComponent.h index 44f96b3fa8..e06c026a97 100644 --- a/Gems/PhysX/Code/Source/EditorBallJointComponent.h +++ b/Gems/PhysX/Code/Source/EditorBallJointComponent.h @@ -13,7 +13,6 @@ #pragma once -#include #include #include #include diff --git a/Gems/PhysX/Code/Source/EditorFixedJointComponent.cpp b/Gems/PhysX/Code/Source/EditorFixedJointComponent.cpp index 1645d8c932..8af78b005f 100644 --- a/Gems/PhysX/Code/Source/EditorFixedJointComponent.cpp +++ b/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(m_config.ToGameTimeConfig()); + gameEntity->CreateComponent(m_config.ToGameTimeConfig(), m_config.ToGenericProperties()); } } diff --git a/Gems/PhysX/Code/Source/EditorFixedJointComponent.h b/Gems/PhysX/Code/Source/EditorFixedJointComponent.h index 8642f83472..b9275186e9 100644 --- a/Gems/PhysX/Code/Source/EditorFixedJointComponent.h +++ b/Gems/PhysX/Code/Source/EditorFixedJointComponent.h @@ -13,7 +13,6 @@ #pragma once -#include #include #include #include diff --git a/Gems/PhysX/Code/Source/EditorHingeJointComponent.cpp b/Gems/PhysX/Code/Source/EditorHingeJointComponent.cpp index 6d136e898b..3f263c11d4 100644 --- a/Gems/PhysX/Code/Source/EditorHingeJointComponent.cpp +++ b/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( m_config.ToGameTimeConfig(), + m_config.ToGenericProperties(), m_angularLimit.ToGameTimeConfig()); } diff --git a/Gems/PhysX/Code/Source/EditorHingeJointComponent.h b/Gems/PhysX/Code/Source/EditorHingeJointComponent.h index ef555d145e..abb6baf3ea 100644 --- a/Gems/PhysX/Code/Source/EditorHingeJointComponent.h +++ b/Gems/PhysX/Code/Source/EditorHingeJointComponent.h @@ -13,7 +13,6 @@ #pragma once -#include #include #include #include diff --git a/Gems/PhysX/Code/Source/EditorJointComponent.h b/Gems/PhysX/Code/Source/EditorJointComponent.h index 10f2a6fc72..9082f6bc33 100644 --- a/Gems/PhysX/Code/Source/EditorJointComponent.h +++ b/Gems/PhysX/Code/Source/EditorJointComponent.h @@ -13,7 +13,6 @@ #pragma once -#include #include #include diff --git a/Gems/PhysX/Code/Source/FixedJointComponent.cpp b/Gems/PhysX/Code/Source/FixedJointComponent.cpp index 8170477aeb..13a42ffbac 100644 --- a/Gems/PhysX/Code/Source/FixedJointComponent.cpp +++ b/Gems/PhysX/Code/Source/FixedJointComponent.cpp @@ -17,6 +17,9 @@ #include #include #include +#include +#include +#include #include @@ -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(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::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 diff --git a/Gems/PhysX/Code/Source/FixedJointComponent.h b/Gems/PhysX/Code/Source/FixedJointComponent.h index 0f64eea2bb..1fd7d4d999 100644 --- a/Gems/PhysX/Code/Source/FixedJointComponent.h +++ b/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: diff --git a/Gems/PhysX/Code/Source/HingeJointComponent.cpp b/Gems/PhysX/Code/Source/HingeJointComponent.cpp index 7b0702559c..41d948bd5d 100644 --- a/Gems/PhysX/Code/Source/HingeJointComponent.cpp +++ b/Gems/PhysX/Code/Source/HingeJointComponent.cpp @@ -16,8 +16,10 @@ #include #include #include +#include #include #include +#include #include @@ -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) - { - return; - } - PHYSX_SCENE_READ_LOCK(leadFollowerInfo.m_followerActor->getScene()); - m_joint = AZStd::make_shared(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) + if (leadFollowerInfo.m_followerActor == nullptr || + leadFollowerInfo.m_leadBody == nullptr || + leadFollowerInfo.m_followerBody == nullptr) { return; } - physx::PxRevoluteJoint* revoluteJointNative = static_cast(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::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 diff --git a/Gems/PhysX/Code/Source/HingeJointComponent.h b/Gems/PhysX/Code/Source/HingeJointComponent.h index 357fe61b1a..8bc11a0051 100644 --- a/Gems/PhysX/Code/Source/HingeJointComponent.h +++ b/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 diff --git a/Gems/PhysX/Code/Source/Joint.cpp b/Gems/PhysX/Code/Source/Joint.cpp deleted file mode 100644 index 80575607c4..0000000000 --- a/Gems/PhysX/Code/Source/Joint.cpp +++ /dev/null @@ -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 -#include -#include -#include -#include -#include -#include - -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(worldBody->GetNativePointer())->is()) - { - return static_cast(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& vertexBufferOut, - [[maybe_unused]] AZStd::vector& indexBufferOut, - AZStd::vector& lineBufferOut, - AZStd::vector& 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(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(context)) - { - serializeContext->Class() - ->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(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(m_flags & flag); - } - - void GenericJointLimitsConfiguration::Reflect(AZ::ReflectContext* context) - { - if (auto* serializeContext = azrtti_cast(context)) - { - serializeContext->Class() - ->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 JointUtils::GetSupportedJointTypes() - { - return AZStd::vector - { - D6JointLimitConfiguration::RTTI_Type() - }; - } - - AZStd::shared_ptr JointUtils::CreateJointLimitConfiguration([[maybe_unused]] AZ::TypeId jointType) - { - return AZStd::make_shared(); - } - - AZStd::shared_ptr JointUtils::CreateJoint(const AZStd::shared_ptr& 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(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(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& lineBufferOut, - AZStd::vector& 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& lineBufferOut, - AZStd::vector& 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& lineBufferOut, - AZStd::vector& 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& vertexBufferOut, - [[maybe_unused]] AZStd::vector& indexBufferOut, - AZStd::vector& lineBufferOut, - AZStd::vector& lineValidityBufferOut) - { - if (const auto d6JointConfiguration = azrtti_cast(&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 JointUtils::ComputeInitialJointLimitConfiguration( - const AZ::TypeId& jointLimitTypeId, - const AZ::Quaternion& parentWorldRotation, - const AZ::Quaternion& childWorldRotation, - const AZ::Vector3& axis, - const AZStd::vector& 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(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(context)) - { - serializeContext->Class() - ->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( - "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 diff --git a/Gems/PhysX/Code/Source/Joint.h b/Gems/PhysX/Code/Source/Joint.h deleted file mode 100644 index 01214a0fec..0000000000 --- a/Gems/PhysX/Code/Source/Joint.h +++ /dev/null @@ -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 - -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>; - 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& vertexBufferOut, - AZStd::vector& indexBufferOut, - AZStd::vector& lineBufferOut, - AZStd::vector& 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& /*vertexBufferOut*/, - AZStd::vector& /*indexBufferOut*/, - AZStd::vector& /*lineBufferOut*/, - AZStd::vector& /*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& /*vertexBufferOut*/, - AZStd::vector& /*indexBufferOut*/, - AZStd::vector& /*lineBufferOut*/, - AZStd::vector& /*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& /*vertexBufferOut*/, - AZStd::vector& /*indexBufferOut*/, - AZStd::vector& /*lineBufferOut*/, - AZStd::vector& /*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 GetSupportedJointTypes(); - - static AZStd::shared_ptr CreateJointLimitConfiguration(AZ::TypeId jointType); - - static AZStd::shared_ptr CreateJoint(const AZStd::shared_ptr& 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& lineBufferOut, - AZStd::vector& lineValidityBufferOut); - - static void AppendD6TwistArcToLineBuffer( - const AZ::Quaternion& parentLocalRotation, - float twistAngle, - float twistLimitLower, - float twistLimitUpper, - float scale, - AZ::u32 angularSubdivisions, - AZ::u32 radialSubdivisions, - AZStd::vector& lineBufferOut, - AZStd::vector& lineValidityBufferOut); - - static void AppendD6CurrentTwistToLineBuffer( - const AZ::Quaternion& parentLocalRotation, - float twistAngle, - float twistLimitLower, - float twistLimitUpper, - float scale, - AZStd::vector& lineBufferOut, - AZStd::vector& 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& vertexBufferOut, - AZStd::vector& indexBufferOut, - AZStd::vector& lineBufferOut, - AZStd::vector& lineValidityBufferOut); - - static AZStd::unique_ptr ComputeInitialJointLimitConfiguration( - const AZ::TypeId& jointLimitTypeId, - const AZ::Quaternion& parentWorldRotation, - const AZ::Quaternion& childWorldRotation, - const AZ::Vector3& axis, - const AZStd::vector& exampleLocalRotations); - }; -} // namespace PhysX diff --git a/Gems/PhysX/Code/Source/Joint/Configuration/PhysXJointConfiguration.cpp b/Gems/PhysX/Code/Source/Joint/Configuration/PhysXJointConfiguration.cpp new file mode 100644 index 0000000000..0d1fe9c988 --- /dev/null +++ b/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 +#include +#include +#include +#include + +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(m_flags & flag); + } + + void D6JointLimitConfiguration::Reflect(AZ::ReflectContext* context) + { + if (auto serializeContext = azrtti_cast(context)) + { + serializeContext->Class() + ->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( + "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(context)) + { + serializeContext->Class() + ->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(context)) + { + serializeContext->Class() + ->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(context)) + { + serializeContext->Class() + ->Version(1) + ->Field("Generic Properties", &FixedJointConfiguration::m_genericProperties) + ; + } + } + + void BallJointConfiguration::Reflect(AZ::ReflectContext* context) + { + if (auto* serializeContext = azrtti_cast(context)) + { + serializeContext->Class() + ->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(context)) + { + serializeContext->Class() + ->Version(1) + ->Field("Generic Properties", &HingeJointConfiguration::m_genericProperties) + ->Field("Limit Properties", &HingeJointConfiguration::m_limitProperties) + ; + } + } +} // namespace PhysX diff --git a/Gems/PhysX/Code/Source/Joint/PhysXJoint.cpp b/Gems/PhysX/Code/Source/Joint/PhysXJoint.cpp new file mode 100644 index 0000000000..53f054c1df --- /dev/null +++ b/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 +#include +#include +#include +#include +#include +#include + +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& vertexBufferOut, + [[maybe_unused]] AZStd::vector& indexBufferOut, + AZStd::vector& lineBufferOut, + AZStd::vector& 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(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 diff --git a/Gems/PhysX/Code/Source/Joint/PhysXJoint.h b/Gems/PhysX/Code/Source/Joint/PhysXJoint.h new file mode 100644 index 0000000000..549025d951 --- /dev/null +++ b/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 +#include +#include + +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& vertexBufferOut, + AZStd::vector& indexBufferOut, + AZStd::vector& lineBufferOut, + AZStd::vector& 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 diff --git a/Gems/PhysX/Code/Source/Joint/PhysXJointUtils.cpp b/Gems/PhysX/Code/Source/Joint/PhysXJointUtils.cpp new file mode 100644 index 0000000000..5ab4564e0e --- /dev/null +++ b/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 + +#include +#include +#include +#include + +#include +#include +#include +#include + +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(worldBody->GetNativePointer())->is()) + { + return static_cast(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::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(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(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(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& lineBufferOut, + AZStd::vector& 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& lineBufferOut, + AZStd::vector& 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& lineBufferOut, + AZStd::vector& 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 diff --git a/Gems/PhysX/Code/Source/Joint/PhysXJointUtils.h b/Gems/PhysX/Code/Source/Joint/PhysXJointUtils.h new file mode 100644 index 0000000000..a129c6862a --- /dev/null +++ b/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 +#include +#include + +#include + +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>; + + 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& lineBufferOut, + AZStd::vector& lineValidityBufferOut); + + void AppendD6TwistArcToLineBuffer( + const AZ::Quaternion& parentLocalRotation, + float twistAngle, + float twistLimitLower, + float twistLimitUpper, + float scale, + AZ::u32 angularSubdivisions, + AZ::u32 radialSubdivisions, + AZStd::vector& lineBufferOut, + AZStd::vector& lineValidityBufferOut); + + void AppendD6CurrentTwistToLineBuffer( + const AZ::Quaternion& parentLocalRotation, + float twistAngle, + float twistLimitLower, + float twistLimitUpper, + float scale, + AZStd::vector& lineBufferOut, + AZStd::vector& lineValidityBufferOut); + } // namespace Joints + } // namespace Utils +} // namespace PhysX + diff --git a/Gems/PhysX/Code/Source/JointComponent.cpp b/Gems/PhysX/Code/Source/JointComponent.cpp index 39fbe4a3fa..0d62cafa62 100644 --- a/Gems/PhysX/Code/Source/JointComponent.cpp +++ b/Gems/PhysX/Code/Source/JointComponent.cpp @@ -18,33 +18,65 @@ #include #include #include -#include #include #include +#include namespace PhysX { - void JointComponent::Reflect(AZ::ReflectContext* context) + 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(context)) + { + serializeContext->Class() + ->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(context)) { serializeContext->Class() - ->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::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(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 diff --git a/Gems/PhysX/Code/Source/JointComponent.h b/Gems/PhysX/Code/Source/JointComponent.h index 468470dd37..6c63cc8ac3 100644 --- a/Gems/PhysX/Code/Source/JointComponent.h +++ b/Gems/PhysX/Code/Source/JointComponent.h @@ -18,7 +18,7 @@ #include #include -#include +#include 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 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 diff --git a/Gems/PhysX/Code/Source/PhysXCharacters/API/CharacterUtils.cpp b/Gems/PhysX/Code/Source/PhysXCharacters/API/CharacterUtils.cpp index 87e3304a90..90c8d1ad77 100644 --- a/Gems/PhysX/Code/Source/PhysXCharacters/API/CharacterUtils.cpp +++ b/Gems/PhysX/Code/Source/PhysXCharacters/API/CharacterUtils.cpp @@ -18,10 +18,10 @@ #include #include #include +#include #include #include #include -#include namespace PhysX { @@ -262,21 +262,24 @@ namespace PhysX physx::PxTransform parentTM(parentOffset); physx::PxTransform childTM(physx::PxIdentity); - AZStd::shared_ptr jointLimitConfig = configuration.m_nodes[nodeIndex].m_jointLimit; - if (!jointLimitConfig) + AZStd::shared_ptr jointConfig = configuration.m_nodes[nodeIndex].m_jointConfig; + if (!jointConfig) { - AZStd::vector supportedJointLimitTypes = JointUtils::GetSupportedJointTypes(); - - if (!supportedJointLimitTypes.empty()) - { - jointLimitConfig = JointUtils::CreateJointLimitConfiguration(supportedJointLimitTypes[0]); - } + jointConfig = AZStd::make_shared(); } + + AzPhysics::JointHandle jointHandle = sceneInterface->AddJoint( + sceneHandle, jointConfig.get(), + ragdoll->GetNode(parentIndex)->GetRigidBody().m_bodyHandle, + ragdoll->GetNode(nodeIndex)->GetRigidBody().m_bodyHandle); - AZStd::shared_ptr joint = JointUtils::CreateJoint( - jointLimitConfig, - &ragdoll->GetNode(parentIndex)->GetRigidBody(), - &ragdoll->GetNode(nodeIndex)->GetRigidBody()); + AzPhysics::Joint* joint = sceneInterface->GetJointFromHandle(sceneHandle, jointHandle); + + 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. diff --git a/Gems/PhysX/Code/Source/PhysXCharacters/API/Ragdoll.cpp b/Gems/PhysX/Code/Source/PhysXCharacters/API/Ragdoll.cpp index 4725212a9d..a489135d34 100644 --- a/Gems/PhysX/Code/Source/PhysXCharacters/API/Ragdoll.cpp +++ b/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& joint = m_nodes[nodeIndex]->GetJoint(); - if (joint) + + if (AzPhysics::Joint* joint = m_nodes[nodeIndex]->GetJoint()) { if (physx::PxD6Joint* pxJoint = static_cast(joint->GetNativePointer())) { diff --git a/Gems/PhysX/Code/Source/PhysXCharacters/API/RagdollNode.cpp b/Gems/PhysX/Code/Source/PhysXCharacters/API/RagdollNode.cpp index 0f9c7644cd..a76fecb613 100644 --- a/Gems/PhysX/Code/Source/PhysXCharacters/API/RagdollNode.cpp +++ b/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& 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& 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::Get()) + { + sceneInterface->RemoveJoint(m_sceneOwner, m_joint->m_jointHandle); + } + m_joint = nullptr; + } + } + } // namespace PhysX diff --git a/Gems/PhysX/Code/Source/PhysXCharacters/API/RagdollNode.h b/Gems/PhysX/Code/Source/PhysXCharacters/API/RagdollNode.h index 0567723c01..2f1e508dd4 100644 --- a/Gems/PhysX/Code/Source/PhysXCharacters/API/RagdollNode.h +++ b/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& joint); + void SetJoint(AzPhysics::Joint* joint); // Physics::RagdollNode AzPhysics::RigidBody& GetRigidBody() override; - const AZStd::shared_ptr& 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 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; diff --git a/Gems/PhysX/Code/Source/PhysXCharacters/Components/RagdollComponent.cpp b/Gems/PhysX/Code/Source/PhysXCharacters/Components/RagdollComponent.cpp index 8da512647f..c341fbf348 100644 --- a/Gems/PhysX/Code/Source/PhysXCharacters/Components/RagdollComponent.cpp +++ b/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& joint = ragdoll->GetNode(nodeIndex)->GetJoint()) + if (const AzPhysics::Joint* joint = ragdoll->GetNode(nodeIndex)->GetJoint()) { if (auto* pxJoint = static_cast(joint->GetNativePointer())) { diff --git a/Gems/PhysX/Code/Source/Platform/Android/PAL_android.cmake b/Gems/PhysX/Code/Source/Platform/Android/PAL_android.cmake index 975225b8a4..4002fe5484 100644 --- a/Gems/PhysX/Code/Source/Platform/Android/PAL_android.cmake +++ b/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) + diff --git a/Gems/PhysX/Code/Source/Platform/Linux/PAL_linux.cmake b/Gems/PhysX/Code/Source/Platform/Linux/PAL_linux.cmake index 975225b8a4..d57b460d13 100644 --- a/Gems/PhysX/Code/Source/Platform/Linux/PAL_linux.cmake +++ b/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) diff --git a/Gems/PhysX/Code/Source/Platform/Mac/PAL_mac.cmake b/Gems/PhysX/Code/Source/Platform/Mac/PAL_mac.cmake index 975225b8a4..052ad091e4 100644 --- a/Gems/PhysX/Code/Source/Platform/Mac/PAL_mac.cmake +++ b/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) \ No newline at end of file diff --git a/Gems/PhysX/Code/Source/Platform/Windows/PAL_windows.cmake b/Gems/PhysX/Code/Source/Platform/Windows/PAL_windows.cmake index 975225b8a4..052ad091e4 100644 --- a/Gems/PhysX/Code/Source/Platform/Windows/PAL_windows.cmake +++ b/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) \ No newline at end of file diff --git a/Gems/PhysX/Code/Source/Platform/iOS/PAL_ios.cmake b/Gems/PhysX/Code/Source/Platform/iOS/PAL_ios.cmake index 975225b8a4..b7794acee0 100644 --- a/Gems/PhysX/Code/Source/Platform/iOS/PAL_ios.cmake +++ b/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) diff --git a/Gems/PhysX/Code/Source/Scene/PhysXScene.cpp b/Gems/PhysX/Code/Source/Scene/PhysXScene.cpp index 689ea47be7..a47e2a99ec 100644 --- a/Gems/PhysX/Code/Source/Scene/PhysXScene.cpp +++ b/Gems/PhysX/Code/Source/Scene/PhysXScene.cpp @@ -31,6 +31,8 @@ #include #include #include +#include +#include namespace PhysX { @@ -232,6 +234,18 @@ namespace PhysX scene->GetSceneHandle()); } + template + 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& 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(jointConfig)) + { + newJoint = Internal::CreateJoint( + azdynamic_cast(jointConfig), + m_sceneHandle, parentBody, childBody, newJointCrc); + } + else if (azrtti_istypeof(jointConfig)) + { + newJoint = Internal::CreateJoint( + azdynamic_cast(jointConfig), + m_sceneHandle, parentBody, childBody, newJointCrc); + } + else if (azrtti_istypeof(jointConfig)) + { + newJoint = Internal::CreateJoint( + azdynamic_cast(jointConfig), + m_sceneHandle, parentBody, childBody, newJointCrc); + } + else if (azrtti_istypeof(jointConfig)) + { + newJoint = Internal::CreateJoint( + azdynamic_cast(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(jointHandle); + if (index < m_joints.size() + && m_joints[index].first == AZStd::get(jointHandle)) + { + return m_joints[index].second; + } + return nullptr; + } + + void PhysXScene::RemoveJoint(AzPhysics::JointHandle jointHandle) + { + if (jointHandle == AzPhysics::InvalidJointHandle) + { + return; + } + + AzPhysics::JointIndex index = AZStd::get(jointHandle); + if (index < m_joints.size() + && m_joints[index].first == AZStd::get(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 jointDeletions; + jointDeletions.swap(m_deferredDeletionsJoints); + for (auto* joint : jointDeletions) + { + delete joint; + } } void PhysXScene::ProcessTriggerEvents() diff --git a/Gems/PhysX/Code/Source/Scene/PhysXScene.h b/Gems/PhysX/Code/Source/Scene/PhysXScene.h index 2e257283f0..c6bb045e65 100644 --- a/Gems/PhysX/Code/Source/Scene/PhysXScene.h +++ b/Gems/PhysX/Code/Source/Scene/PhysXScene.h @@ -12,6 +12,7 @@ #pragma once #include +#include #include #include #include @@ -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 m_deferredDeletions; AZStd::queue m_freeSceneSlots; + AZStd::vector> m_joints; + AZStd::vector m_deferredDeletionsJoints; + AZStd::queue m_freeJointSlots; + AzPhysics::SystemEvents::OnConfigurationChangedEvent::Handler m_physicsSystemConfigChanged; static thread_local AZStd::vector s_rayCastBuffer; //!< thread local structure to hold hits for a single raycast or shapecast. diff --git a/Gems/PhysX/Code/Source/Scene/PhysXSceneInterface.cpp b/Gems/PhysX/Code/Source/Scene/PhysXSceneInterface.cpp index 3b3ab2f0f8..9aeafb1931 100644 --- a/Gems/PhysX/Code/Source/Scene/PhysXSceneInterface.cpp +++ b/Gems/PhysX/Code/Source/Scene/PhysXSceneInterface.cpp @@ -11,6 +11,7 @@ */ #include +#include #include #include #include @@ -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) { diff --git a/Gems/PhysX/Code/Source/Scene/PhysXSceneInterface.h b/Gems/PhysX/Code/Source/Scene/PhysXSceneInterface.h index 2edfbd8457..b96f74a3f7 100644 --- a/Gems/PhysX/Code/Source/Scene/PhysXSceneInterface.h +++ b/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, diff --git a/Gems/PhysX/Code/Source/System/PhysXJointInterface.cpp b/Gems/PhysX/Code/Source/System/PhysXJointInterface.cpp new file mode 100644 index 0000000000..1fa13cffb4 --- /dev/null +++ b/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 +#include + +#include + +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& lineBufferOut, + AZStd::vector& 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& lineBufferOut, + AZStd::vector& 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& lineBufferOut, + AZStd::vector& 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 + AZStd::unique_ptr ConfigurationFactory( + const AZ::Quaternion& parentLocalRotation, const AZ::Quaternion& childLocalRotation) + { + auto jointConfig = AZStd::make_unique(); + jointConfig->m_childLocalRotation = childLocalRotation; + jointConfig->m_parentLocalRotation = parentLocalRotation; + + return jointConfig; + } + + } // namespace + + const AZStd::vector PhysXJointHelpersInterface::GetSupportedJointTypeIds() const + { + static AZStd::vector jointTypes = { + D6JointLimitConfiguration::RTTI_Type(), + FixedJointConfiguration::RTTI_Type(), + BallJointConfiguration::RTTI_Type(), + HingeJointConfiguration::RTTI_Type() + }; + return jointTypes; + } + + AZStd::optional PhysXJointHelpersInterface::GetSupportedJointTypeId(AzPhysics::JointType typeEnum) const + { + switch (typeEnum) + { + case AzPhysics::JointType::D6Joint: + return azrtti_typeid(); + case AzPhysics::JointType::FixedJoint: + return azrtti_typeid(); + case AzPhysics::JointType::BallJoint: + return azrtti_typeid(); + case AzPhysics::JointType::HingeJoint: + return azrtti_typeid(); + default: + AZ_Warning("PhysX Joint Utils", false, "Unsupported joint type in GetSupportedJointTypeId"); + } + return AZStd::nullopt; + } + + AZStd::unique_ptr PhysXJointHelpersInterface::ComputeInitialJointLimitConfiguration( + const AZ::TypeId& jointLimitTypeId, + const AZ::Quaternion& parentWorldRotation, + const AZ::Quaternion& childWorldRotation, + const AZ::Vector3& axis, + [[maybe_unused]] const AZStd::vector& 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()) + { + return ConfigurationFactory(parentLocalRotation, childLocalRotation); + } + else if (jointLimitTypeId == azrtti_typeid()) + { + return ConfigurationFactory(parentLocalRotation, childLocalRotation); + } + else if (jointLimitTypeId == azrtti_typeid()) + { + return ConfigurationFactory(parentLocalRotation, childLocalRotation); + } + else if (jointLimitTypeId == azrtti_typeid()) + { + return ConfigurationFactory(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& vertexBufferOut, + [[maybe_unused]] AZStd::vector& indexBufferOut, + AZStd::vector& lineBufferOut, + AZStd::vector& lineValidityBufferOut) + { + if (const auto d6JointConfiguration = azrtti_cast(&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); + } + } +} diff --git a/Gems/PhysX/Code/Source/System/PhysXJointInterface.h b/Gems/PhysX/Code/Source/System/PhysXJointInterface.h new file mode 100644 index 0000000000..1cee865e8b --- /dev/null +++ b/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 +#include + +namespace AzPhysics +{ + struct JointConfiguration; +} + +namespace PhysX +{ + class PhysXJointHelpersInterface + : public AZ::Interface::Registrar + { + public: + AZ_RTTI(PhysX::PhysXJointHelpersInterface, "{48AC5137-2226-4C57-8E4C-FCF3C1965252}", AzPhysics::JointHelpersInterface); + + const AZStd::vector GetSupportedJointTypeIds() const override; + AZStd::optional GetSupportedJointTypeId(AzPhysics::JointType typeEnum) const override; + + AZStd::unique_ptr ComputeInitialJointLimitConfiguration( + const AZ::TypeId& jointLimitTypeId, + const AZ::Quaternion& parentWorldRotation, + const AZ::Quaternion& childWorldRotation, + const AZ::Vector3& axis, + const AZStd::vector& 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& vertexBufferOut, + AZStd::vector& indexBufferOut, + AZStd::vector& lineBufferOut, + AZStd::vector& lineValidityBufferOut) override; + }; +} diff --git a/Gems/PhysX/Code/Source/System/PhysXSystem.h b/Gems/PhysX/Code/Source/System/PhysXSystem.h index 533685bbd1..60f72c6145 100644 --- a/Gems/PhysX/Code/Source/System/PhysXSystem.h +++ b/Gems/PhysX/Code/Source/System/PhysXSystem.h @@ -25,6 +25,7 @@ #include #include +#include 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 diff --git a/Gems/PhysX/Code/Source/SystemComponent.cpp b/Gems/PhysX/Code/Source/SystemComponent.cpp index eae56967b2..6dcf3951f4 100644 --- a/Gems/PhysX/Code/Source/SystemComponent.cpp +++ b/Gems/PhysX/Code/Source/SystemComponent.cpp @@ -20,7 +20,6 @@ #include #include #include -#include #include #include #include @@ -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(materialConfiguration); } - AZStd::vector SystemComponent::GetSupportedJointTypes() - { - return JointUtils::GetSupportedJointTypes(); - } - - AZStd::shared_ptr SystemComponent::CreateJointLimitConfiguration(AZ::TypeId jointType) - { - return JointUtils::CreateJointLimitConfiguration(jointType); - } - - AZStd::shared_ptr SystemComponent::CreateJoint(const AZStd::shared_ptr& 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& vertexBufferOut, - AZStd::vector& indexBufferOut, - AZStd::vector& lineBufferOut, - AZStd::vector& lineValidityBufferOut) - { - JointUtils::GenerateJointLimitVisualizationData(configuration, parentRotation, childRotation, scale, - angularSubdivisions, radialSubdivisions, vertexBufferOut, indexBufferOut, lineBufferOut, lineValidityBufferOut); - } - - AZStd::unique_ptr SystemComponent::ComputeInitialJointLimitConfiguration( - const AZ::TypeId& jointLimitTypeId, - const AZ::Quaternion& parentWorldRotation, - const AZ::Quaternion& childWorldRotation, - const AZ::Vector3& axis, - const AZStd::vector& exampleLocalRotations) - { - return JointUtils::ComputeInitialJointLimitConfiguration(jointLimitTypeId, parentWorldRotation, - childWorldRotation, axis, exampleLocalRotations); - } - void SystemComponent::ReleaseNativeMeshObject(void* nativeMeshObject) { if (nativeMeshObject) diff --git a/Gems/PhysX/Code/Source/SystemComponent.h b/Gems/PhysX/Code/Source/SystemComponent.h index 4609fde0ce..aab5c5a8e9 100644 --- a/Gems/PhysX/Code/Source/SystemComponent.h +++ b/Gems/PhysX/Code/Source/SystemComponent.h @@ -115,28 +115,6 @@ namespace PhysX AZStd::shared_ptr CreateShape(const Physics::ColliderConfiguration& colliderConfiguration, const Physics::ShapeConfiguration& configuration) override; AZStd::shared_ptr CreateMaterial(const Physics::MaterialConfiguration& materialConfiguration) override; - AZStd::vector GetSupportedJointTypes() override; - AZStd::shared_ptr CreateJointLimitConfiguration(AZ::TypeId jointType) override; - AZStd::shared_ptr CreateJoint(const AZStd::shared_ptr& 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& vertexBufferOut, - AZStd::vector& indexBufferOut, - AZStd::vector& lineBufferOut, - AZStd::vector& lineValidityBufferOut) override; - AZStd::unique_ptr ComputeInitialJointLimitConfiguration( - const AZ::TypeId& jointLimitTypeId, - const AZ::Quaternion& parentWorldRotation, - const AZ::Quaternion& childWorldRotation, - const AZ::Vector3& axis, - const AZStd::vector& exampleLocalRotations) override; - void ReleaseNativeMeshObject(void* nativeMeshObject) override; // Assets related data diff --git a/Gems/PhysX/Code/Source/Utils.cpp b/Gems/PhysX/Code/Source/Utils.cpp index 270a352fc0..cb28358213 100644 --- a/Gems/PhysX/Code/Source/Utils.cpp +++ b/Gems/PhysX/Code/Source/Utils.cpp @@ -40,9 +40,9 @@ #include #include #include -#include #include #include +#include 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) diff --git a/Gems/PhysX/Code/Tests/Benchmarks/PhysXBenchmarksUtilities.h b/Gems/PhysX/Code/Tests/Benchmarks/PhysXBenchmarksUtilities.h index 0971226644..5a54ab5833 100644 --- a/Gems/PhysX/Code/Tests/Benchmarks/PhysXBenchmarksUtilities.h +++ b/Gems/PhysX/Code/Tests/Benchmarks/PhysXBenchmarksUtilities.h @@ -24,6 +24,7 @@ namespace AzPhysics { class Scene; + struct RigidBody; } namespace PhysX::Benchmarks diff --git a/Gems/PhysX/Code/Tests/Benchmarks/PhysXJointBenchmarks.cpp b/Gems/PhysX/Code/Tests/Benchmarks/PhysXJointBenchmarks.cpp index 24b51f0085..e1ebed18ee 100644 --- a/Gems/PhysX/Code/Tests/Benchmarks/PhysXJointBenchmarks.cpp +++ b/Gems/PhysX/Code/Tests/Benchmarks/PhysXJointBenchmarks.cpp @@ -26,7 +26,7 @@ #include #include -#include +#include namespace PhysX::Benchmarks { @@ -102,7 +102,7 @@ namespace PhysX::Benchmarks { AzPhysics::RigidBody* m_parent; AzPhysics::SimulatedBody* m_child; - AZStd::shared_ptr 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 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 config = AZStd::make_shared(); + 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::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 joinGroups = Utils::CreateJoints(aznumeric_cast(state.range(0)), m_system, m_defaultScene, + AZStd::vector joinGroups = Utils::CreateJoints(aznumeric_cast(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 joinGroups = Utils::CreateJoints(aznumeric_cast(state.range(0)), m_system, m_defaultScene, + AZStd::vector joinGroups = Utils::CreateJoints(aznumeric_cast(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> joints; - AZStd::shared_ptr config = AZStd::make_shared(); - config->m_twistLimitUpper = JointConstants::CreateJointDefaults::UpperLimit; - config->m_twistLimitLower = JointConstants::CreateJointDefaults::LowerLimit; - config->m_swingLimitY = 1.0f; - config->m_swingLimitZ = 1.0f; + AZStd::vector 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 diff --git a/Gems/PhysX/Code/Tests/PhysXGenericTestFixture.h b/Gems/PhysX/Code/Tests/PhysXGenericTestFixture.h index 80b338150f..1a1eefab46 100644 --- a/Gems/PhysX/Code/Tests/PhysXGenericTestFixture.h +++ b/Gems/PhysX/Code/Tests/PhysXGenericTestFixture.h @@ -111,7 +111,7 @@ namespace PhysX }; class GenericPhysicsInterfaceTest - : protected GenericPhysicsFixture + : public GenericPhysicsFixture , public testing::Test { public: diff --git a/Gems/PhysX/Code/Tests/PhysXJointsTest.cpp b/Gems/PhysX/Code/Tests/PhysXJointsTest.cpp index b976bbe77a..7d691d02a6 100644 --- a/Gems/PhysX/Code/Tests/PhysXJointsTest.cpp +++ b/Gems/PhysX/Code/Tests/PhysXJointsTest.cpp @@ -18,17 +18,18 @@ #include #include -#include #include #include #include #include +#include #include #include #include #include #include +#include namespace PhysX { @@ -38,8 +39,9 @@ namespace PhysX AZStd::unique_ptr AddBodyColliderEntity( AzPhysics::SceneHandle sceneHandle, const AZ::Vector3& position, const AZ::Vector3& initialLinearVelocity, - AZStd::shared_ptr jointConfig = nullptr, - AZStd::shared_ptr jointLimitsConfig = nullptr) + AZStd::shared_ptr jointConfig = nullptr, + AZStd::shared_ptr jointGenericProperties = nullptr, + AZStd::shared_ptr jointLimitProperties = nullptr) { const char* entityName = "testEntity"; auto entity = AZStd::make_unique(entityName); @@ -68,10 +70,12 @@ namespace PhysX { jointConfig->m_followerEntity = entity->GetId(); - GenericJointLimitsConfiguration defaultJointLimitsConfig; + JointGenericProperties defaultJointGenericProperties; + JointLimitProperties defaultJointLimitProperties; entity->CreateComponent( *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(); + auto jointConfig = AZStd::make_shared(); jointConfig->m_leadEntity = leadEntity->GetId(); jointConfig->m_localTransformFromFollower = jointLocalTransform; @@ -150,17 +154,18 @@ namespace PhysX leadPosition, leadInitialLinearVelocity); - auto jointConfig = AZStd::make_shared(); + auto jointConfig = AZStd::make_shared(); jointConfig->m_leadEntity = leadEntity->GetId(); jointConfig->m_localTransformFromFollower = jointLocalTransform; - auto jointLimits = AZStd::make_shared (); + auto jointLimits = AZStd::make_shared (); jointLimits->m_isLimited = false; auto followerEntity = AddBodyColliderEntity(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(); + auto jointConfig = AZStd::make_shared(); jointConfig->m_leadEntity = leadEntity->GetId(); jointConfig->m_localTransformFromFollower = jointLocalTransform; - auto jointLimits = AZStd::make_shared (); + auto jointLimits = AZStd::make_shared (); jointLimits->m_isLimited = false; auto followerEntity = AddBodyColliderEntity(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 PhysXJointsApiTest : public PhysX::GenericPhysicsInterfaceTest + { + public: + + void SetUp() override + { + PhysX::GenericPhysicsInterfaceTest::SetUp(); + + if (auto* sceneInterface = AZ::Interface::Get()) + { + AzPhysics::RigidBodyConfiguration parentConfiguration; + AzPhysics::RigidBodyConfiguration childConfiguration; + + auto colliderConfig = AZStd::make_shared(); + auto shapeConfiguration = AZStd::make_shared(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::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::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::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 } diff --git a/Gems/PhysX/Code/Tests/RagdollConfiguration.xml b/Gems/PhysX/Code/Tests/RagdollConfiguration.xml index e1c2c5bed9..b3a268a14b 100644 --- a/Gems/PhysX/Code/Tests/RagdollConfiguration.xml +++ b/Gems/PhysX/Code/Tests/RagdollConfiguration.xml @@ -4,7 +4,7 @@ - + @@ -27,9 +27,9 @@ - - - + + + @@ -42,7 +42,7 @@ - + @@ -65,9 +65,9 @@ - - - + + + @@ -80,7 +80,7 @@ - + @@ -103,9 +103,9 @@ - - - + + + @@ -118,7 +118,7 @@ - + @@ -141,9 +141,9 @@ - - - + + + @@ -156,7 +156,7 @@ - + @@ -179,9 +179,9 @@ - - - + + + @@ -194,7 +194,7 @@ - + @@ -217,9 +217,9 @@ - - - + + + @@ -232,7 +232,7 @@ - + @@ -255,9 +255,9 @@ - - - + + + @@ -270,7 +270,7 @@ - + @@ -293,9 +293,9 @@ - - - + + + @@ -308,7 +308,7 @@ - + @@ -331,9 +331,9 @@ - - - + + + @@ -346,7 +346,7 @@ - + @@ -369,9 +369,9 @@ - - - + + + @@ -384,7 +384,7 @@ - + @@ -407,9 +407,9 @@ - - - + + + @@ -422,7 +422,7 @@ - + @@ -445,9 +445,9 @@ - - - + + + @@ -460,7 +460,7 @@ - + @@ -483,9 +483,9 @@ - - - + + + @@ -498,7 +498,7 @@ - + @@ -521,9 +521,9 @@ - - - + + + @@ -536,7 +536,7 @@ - + @@ -559,9 +559,9 @@ - - - + + + @@ -574,7 +574,7 @@ - + @@ -597,9 +597,9 @@ - - - + + + @@ -612,7 +612,7 @@ - + @@ -635,9 +635,9 @@ - - - + + + @@ -650,7 +650,7 @@ - + @@ -673,9 +673,9 @@ - - - + + + @@ -688,7 +688,7 @@ - + @@ -711,9 +711,9 @@ - - - + + + @@ -726,7 +726,7 @@ - + @@ -749,9 +749,9 @@ - - - + + + @@ -764,7 +764,7 @@ - + @@ -787,9 +787,9 @@ - - - + + + @@ -802,7 +802,7 @@ - + @@ -825,9 +825,9 @@ - - - + + + diff --git a/Gems/PhysX/Code/Tests/RagdollTests.cpp b/Gems/PhysX/Code/Tests/RagdollTests.cpp index 477e754d74..816e7e2884 100644 --- a/Gems/PhysX/Code/Tests/RagdollTests.cpp +++ b/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); } } } diff --git a/Gems/PhysX/Code/physx_files.cmake b/Gems/PhysX/Code/physx_files.cmake index 24aa42d62a..3f5589242a 100644 --- a/Gems/PhysX/Code/physx_files.cmake +++ b/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 diff --git a/Gems/PhysXDebug/Code/Source/SystemComponent.cpp b/Gems/PhysXDebug/Code/Source/SystemComponent.cpp index c693599a59..3a5499368b 100644 --- a/Gems/PhysXDebug/Code/Source/SystemComponent.cpp +++ b/Gems/PhysXDebug/Code/Source/SystemComponent.cpp @@ -783,7 +783,7 @@ namespace PhysXDebug Physics::RagdollNode* ragdollNode = actorData->GetRagdollNode(); if (ragdollNode) { - const AZStd::shared_ptr& joint = ragdollNode->GetJoint(); + AzPhysics::Joint* joint = ragdollNode->GetJoint(); physx::PxJoint* pxJoint = static_cast(joint->GetNativePointer()); physx::PxTransform jointPose = actor1->getGlobalPose() * pxJoint->getLocalPose(physx::PxJointActorIndex::eACTOR1); if (!m_culling.m_enabled || m_cullingBox.contains(jointPose.p)) diff --git a/Gems/ScriptCanvasPhysics/Code/Tests/ScriptCanvasPhysicsTest.cpp b/Gems/ScriptCanvasPhysics/Code/Tests/ScriptCanvasPhysicsTest.cpp index 07ccdc5011..63b43938e7 100644 --- a/Gems/ScriptCanvasPhysics/Code/Tests/ScriptCanvasPhysicsTest.cpp +++ b/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)); diff --git a/Tools/LyTestTools/ly_test_tools/__init__.py b/Tools/LyTestTools/ly_test_tools/__init__.py index d534f5e0d2..fe987deb67 100755 --- a/Tools/LyTestTools/ly_test_tools/__init__.py +++ b/Tools/LyTestTools/ly_test_tools/__init__.py @@ -28,6 +28,7 @@ WINDOWS = sys.platform.startswith('win') HOST_OS_PLATFORM = 'unknown' HOST_OS_EDITOR = 'unknown' HOST_OS_DEDICATED_SERVER = 'unknown' +HOST_OS_GENERIC_EXECUTABLE = 'unknown' LAUNCHERS = {} for launcher_option in ALL_LAUNCHER_OPTIONS: LAUNCHERS[launcher_option] = None diff --git a/cmake/LYTestWrappers.cmake b/cmake/LYTestWrappers.cmake index 1bacc4feb0..d925015c5e 100644 --- a/cmake/LYTestWrappers.cmake +++ b/cmake/LYTestWrappers.cmake @@ -319,72 +319,6 @@ function(ly_add_pytest) set_tests_properties(${LY_ADDED_TEST_NAME} PROPERTIES RUN_SERIAL "${ly_add_pytest_TEST_SERIAL}") endfunction() -#! ly_add_editor_python_test: registers target Editor Python Bindings test with CTest -# -# \arg:NAME name of the test-module to register with CTest -# \arg:PATH path to the file (or dir) containing Editor Python Bindings-based tests -# \arg:TEST_PROJECT Name of the project to be set before running the test -# \arg:TEST_SUITE name of the test suite to register with CTest -# \arg:TEST_SERIAL (bool) disable parallel execution alongside other test modules, important when this test depends on shared resources or environment state -# \arg:TEST_REQUIRES (optional) list of system resources needed by the tests in this module. Used to filter out execution when those system resources are not available. For example, 'gpu' -# \arg:RUNTIME_DEPENDENCIES (optional) - List of additional runtime dependencies required by this test. -# "Editor" and "EditorPythonBindings" gem are automatically included as dependencies. -# \arg:COMPONENT (optional) - Scope of the feature area that the test belongs to (eg. physics, graphics, etc.). -# \arg:TIMEOUT (optional) The timeout in seconds for the module. If not set, will have its timeout set by ly_add_test to the default timeout. -function(ly_add_editor_python_test) - if(NOT PAL_TRAIT_TEST_PYTEST_SUPPORTED) - return() - endif() - - set(options TEST_SERIAL) - set(oneValueArgs NAME PATH TEST_SUITE TEST_PROJECT TIMEOUT) - set(multiValueArgs TEST_REQUIRES RUNTIME_DEPENDENCIES COMPONENT) - - cmake_parse_arguments(ly_add_editor_python_test "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) - - set(executable_target $) - - if(NOT TARGET Legacy::Editor) - message(FATAL_ERROR "Legacy::Editor was not recognized as a valid target") - endif() - - if(NOT ly_add_editor_python_test_PATH) - message(FATAL_ERROR "Must supply a value for PATH to tests") - endif() - - if(NOT ly_add_editor_python_test_TEST_SUITE) - message(FATAL_ERROR "Must supply a value for TEST_SUITE") - endif() - - file(REAL_PATH ${ly_add_editor_python_test_TEST_PROJECT} project_real_path BASE_DIRECTORY ${LY_ROOT_FOLDER}) - - # Add the script path to the test target params - set(LY_TEST_PARAMS "${ly_add_editor_python_test_PATH}") - # Run test via the run_epbtest.cmake script. - # Parameters used are explained in run_epbtest.cmake. - ly_add_test( - NAME ${ly_add_editor_python_test_NAME} - PARENT_NAME ${ly_add_editor_python_test_NAME} - TEST_REQUIRES ${ly_add_editor_python_test_TEST_REQUIRES} - TEST_COMMAND ${CMAKE_COMMAND} - -DCMD_ARG_TEST_PROJECT=${project_real_path} - -DCMD_ARG_EDITOR=$ - -DCMD_ARG_PYTHON_SCRIPT=${ly_add_editor_python_test_PATH} - -DPLATFORM=${PAL_PLATFORM_NAME} - -P ${CMAKE_CURRENT_FUNCTION_LIST_DIR}/run_epbtest.cmake - RUNTIME_DEPENDENCIES - ${ly_add_editor_python_test_RUNTIME_DEPENDENCIES} - Gem::EditorPythonBindings.Editor - Legacy::Editor - TEST_SUITE ${ly_add_editor_python_test_TEST_SUITE} - LABELS FRAMEWORK_pytest - TEST_LIBRARY pytest_editor - TIMEOUT ${ly_add_editor_python_test_TIMEOUT} - COMPONENT ${ly_add_editor_python_test_COMPONENT} - ) - set_tests_properties(${LY_ADDED_TEST_NAME} PROPERTIES RUN_SERIAL "${ly_add_editor_python_test_TEST_SERIAL}") -endfunction() - #! ly_add_googletest: Adds a new RUN_TEST using for the specified target using the supplied command or fallback to running # googletest tests through AzTestRunner # \arg:NAME Name to for the test run target diff --git a/cmake/cmake_files.cmake b/cmake/cmake_files.cmake index a1fd66a06d..3d7ca9794f 100644 --- a/cmake/cmake_files.cmake +++ b/cmake/cmake_files.cmake @@ -35,7 +35,6 @@ set(FILES PAL.cmake PALTools.cmake Projects.cmake - run_epbtest.cmake RuntimeDependencies.cmake SettingsRegistry.cmake UnitTest.cmake diff --git a/cmake/run_epbtest.cmake b/cmake/run_epbtest.cmake deleted file mode 100644 index a0c5eb2b17..0000000000 --- a/cmake/run_epbtest.cmake +++ /dev/null @@ -1,42 +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. -# - -# Script for running a test that uses EditorPythonBindings. Takes care of: -# 1. Activating a project. -# 2. Enabling the EditorPythonBindings gem. -# 3. Invoking the Editor executable with the parameters to load the test script. -# 4. Kills the AssetProcessor process -# The following arguments are required: -# CMD_ARG_TEST_PROJECT - name of the project to enable via lmbr. -# CMD_ARG_EDITOR - full path to the Editor executable. -# CMD_ARG_PYTHON_SCRIPT - full path to the python script to be executed by the Editor. - -# EditorPythonBindings need to be enabled for the project we launch - -execute_process( - COMMAND ${CMD_ARG_EDITOR} -NullRenderer --skipWelcomeScreenDialog --autotest_mode --regset="/Amazon/AzCore/Bootstrap/project_path=${CMD_ARG_TEST_PROJECT}" --runpython ${CMD_ARG_PYTHON_SCRIPT} - TIMEOUT 1800 - RESULT_VARIABLE TEST_CMD_RESULT -) - -if(${PLATFORM} STREQUAL "Windows") - execute_process( - COMMAND taskkill /F /IM AssetProcessor.exe - ) -else() - execute_process( - COMMAND killall -I AssetProcessor - ) -endif() - -if(TEST_CMD_RESULT) - message(FATAL_ERROR "Error running EditorPythonBindings Test via CMake Wrapper, result ${TEST_CMD_RESULT}") -endif() \ No newline at end of file diff --git a/scripts/ctest/CMakeLists.txt b/scripts/ctest/CMakeLists.txt index c07aaf4bff..3a327592db 100644 --- a/scripts/ctest/CMakeLists.txt +++ b/scripts/ctest/CMakeLists.txt @@ -37,19 +37,6 @@ if(PAL_TRAIT_TEST_LYTESTTOOLS_SUPPORTED) endforeach() endif() -# EPB Sanity test is being registered here to validate that the ly_add_editor_python_test function works. -#if(PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_BUILD_TESTS_SUPPORTED AND AutomatedTesting IN_LIST LY_PROJECTS_TARGET_NAME) -# ly_add_editor_python_test( -# NAME epb_sanity_smoke_no_gpu -# TEST_PROJECT AutomatedTesting -# PATH ${CMAKE_CURRENT_LIST_DIR}/epb_sanity_test.py -# TEST_SUITE smoke -# TEST_SERIAL TRUE -# RUNTIME_DEPENDENCIES -# AutomatedTesting.Assets -# ) -#endif() - # add a custom test which makes sure that the test filtering works! ly_add_test( diff --git a/scripts/ctest/ctest_entrypoint.sh b/scripts/ctest/ctest_entrypoint.sh index 74e322630c..ea23ae271d 100755 --- a/scripts/ctest/ctest_entrypoint.sh +++ b/scripts/ctest/ctest_entrypoint.sh @@ -14,7 +14,7 @@ # CURRENT_SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE:-0}" )" >/dev/null 2>&1 && pwd )" -DEV_DIR="$( dirname "$CURRENT_SCRIPT_DIR" )" +DEV_DIR=$( dirname "$( dirname "$CURRENT_SCRIPT_DIR" )" ) PYTHON=$DEV_DIR/python/python.sh CTEST_SCRIPT=$CURRENT_SCRIPT_DIR/ctest_driver.py diff --git a/scripts/ctest/epb_sanity_test.py b/scripts/ctest/epb_sanity_test.py deleted file mode 100755 index 9a532536b4..0000000000 --- a/scripts/ctest/epb_sanity_test.py +++ /dev/null @@ -1,21 +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. -""" - -# Sanity test for EditorPythonBindings CTest wrapper - -import azlmbr.framework as framework - -print("EditorPythonBindings CTest Sanity Test") - -# A test should have logic to determine success (zero) or failure (non-zero) and -# return it to the caller. In this sanity test, always return success. -return_code = 0 -framework.Terminate(return_code)