From 3b9762142a198c6d35ddc8b9fa1ea492e41beffe Mon Sep 17 00:00:00 2001 From: moraaar Date: Fri, 15 Oct 2021 08:58:18 +0100 Subject: [PATCH] Triangle Mesh with a Kinematic PhysX Rigid Body warns the user instead of error. (#4657) Using triangle mesh with a kinematic rigid body is allowed, but the options "Compute COM", "Compute Mass" and "Compute Inertia" are not supported by PhysX and an error in logged that default values for COM, Mass and Inertia will be used. Now this situation is captured and an explanatory warning is used instead. - Improved RigidBody::UpdateMassProperties function to apply the same logic in the treatment of shapes for all three parameters: COM, Mass and Inertia. - Improved UpdateMassProperties function by using references for the override parameters instead of pointers. - Improved function that computes the Center of Mass UpdateCenterOfMass (renamed from UpdateComputedCenterOfMass), to include the same shapes that the compute mass and inertia functions in physx updateMassAndInertia, which is to include all shapes if includeAllShapesInMassCalculation is true, else include only the shapes with eSIMULATION_SHAPE flag. - Removed unused private function RigidBody::ComputeInertia. - Added unit test to check when the warnings are fired correctly when COM, Mass or Inertia are asked to be computed on a rigid body with triangle mesh shapes. - Improved MassComputeFixture tests by not only using Box shape, but also sphere and capture, plus improved the PossibleMassComputeFlags parameters to include all possible variations of the MassComputeFlags flags. Fixes #3322 Fixes #3979 Signed-off-by: moraaar --- .../AzFramework/Physics/RigidBody.h | 234 --------------- .../Physics/SimulatedBodies/RigidBody.h | 6 +- Gems/Blast/Code/Tests/Mocks/BlastMocks.h | 6 +- Gems/PhysX/Code/Source/RigidBody.cpp | 277 ++++++++++-------- Gems/PhysX/Code/Source/RigidBody.h | 10 +- Gems/PhysX/Code/Source/Scene/PhysXScene.cpp | 4 +- Gems/PhysX/Code/Tests/PhysXSpecificTest.cpp | 223 ++++++++++---- Gems/PhysX/Code/Tests/PhysXTestCommon.cpp | 30 ++ Gems/PhysX/Code/Tests/PhysXTestCommon.h | 1 + .../Components/WhiteBoxColliderComponent.cpp | 5 + 10 files changed, 367 insertions(+), 429 deletions(-) delete mode 100644 Code/Framework/AzFramework/AzFramework/Physics/RigidBody.h diff --git a/Code/Framework/AzFramework/AzFramework/Physics/RigidBody.h b/Code/Framework/AzFramework/AzFramework/Physics/RigidBody.h deleted file mode 100644 index 13d29b6bb9..0000000000 --- a/Code/Framework/AzFramework/AzFramework/Physics/RigidBody.h +++ /dev/null @@ -1,234 +0,0 @@ -/* - * Copyright (c) Contributors to the Open 3D Engine Project. - * For complete copyright and license terms please see the LICENSE at the root of this distribution. - * - * SPDX-License-Identifier: Apache-2.0 OR MIT - * - */ - -#pragma once - -#include -#include -#include - -#include -#include - -namespace -{ - class ReflectContext; -} - -namespace Physics -{ - class ShapeConfiguration; - class World; - class Shape; - - /// Default values used for initializing RigidBodySettings. - /// These can be modified by Physics Implementation gems. // O3DE_DEPRECATED(LY-114472) - DefaultRigidBodyConfiguration values are not shared across modules. - // Use RigidBodyConfiguration default values. - struct DefaultRigidBodyConfiguration - { - static float m_mass; - static bool m_computeInertiaTensor; - static float m_linearDamping; - static float m_angularDamping; - static float m_sleepMinEnergy; - static float m_maxAngularVelocity; - }; - - enum class MassComputeFlags : AZ::u8 - { - NONE = 0, - - //! Flags indicating whether a certain mass property should be auto-computed or not. - COMPUTE_MASS = 1, - COMPUTE_INERTIA = 1 << 1, - COMPUTE_COM = 1 << 2, - - //! If set, non-simulated shapes will also be included in the mass properties calculation. - INCLUDE_ALL_SHAPES = 1 << 3, - - DEFAULT = COMPUTE_COM | COMPUTE_INERTIA | COMPUTE_MASS - }; - - class RigidBodyConfiguration - : public WorldBodyConfiguration - { - public: - AZ_CLASS_ALLOCATOR(RigidBodyConfiguration, AZ::SystemAllocator, 0); - AZ_RTTI(RigidBodyConfiguration, "{ACFA8900-8530-4744-AF00-AA533C868A8E}", WorldBodyConfiguration); - static void Reflect(AZ::ReflectContext* context); - - enum PropertyVisibility : AZ::u16 - { - InitialVelocities = 1 << 0, ///< Whether the initial linear and angular velocities are visible. - InertiaProperties = 1 << 1, ///< Whether the whole category of inertia properties (mass, compute inertia, - ///< inertia tensor etc) is visible. - Damping = 1 << 2, ///< Whether linear and angular damping are visible. - SleepOptions = 1 << 3, ///< Whether the sleep threshold and start asleep options are visible. - Interpolation = 1 << 4, ///< Whether the interpolation option is visible. - Gravity = 1 << 5, ///< Whether the effected by gravity option is visible. - Kinematic = 1 << 6, ///< Whether the option to make the body kinematic is visible. - ContinuousCollisionDetection = 1 << 7, ///< Whether the option to enable continuous collision detection is visible. - MaxVelocities = 1 << 8 ///< Whether upper limits on velocities are visible. - }; - - RigidBodyConfiguration() = default; - RigidBodyConfiguration(const RigidBodyConfiguration& settings) = default; - - // Visibility functions. - AZ::Crc32 GetPropertyVisibility(PropertyVisibility property) const; - void SetPropertyVisibility(PropertyVisibility property, bool isVisible); - - AZ::Crc32 GetInitialVelocitiesVisibility() const; - /// Returns whether the whole category of inertia settings (mass, inertia, center of mass offset etc) is visible. - AZ::Crc32 GetInertiaSettingsVisibility() const; - /// Returns whether the individual inertia tensor field is visible or is hidden because the compute inertia option is selected. - AZ::Crc32 GetInertiaVisibility() const; - /// Returns whether the mass field is visible or is hidden because compute mass option is selected. - AZ::Crc32 GetMassVisibility() const; - /// Returns whether the individual centre of mass offset field is visible or is hidden because compute CoM option is selected. - AZ::Crc32 GetCoMVisibility() const; - AZ::Crc32 GetDampingVisibility() const; - AZ::Crc32 GetSleepOptionsVisibility() const; - AZ::Crc32 GetInterpolationVisibility() const; - AZ::Crc32 GetGravityVisibility() const; - AZ::Crc32 GetKinematicVisibility() const; - AZ::Crc32 GetCCDVisibility() const; - AZ::Crc32 GetMaxVelocitiesVisibility() const; - MassComputeFlags GetMassComputeFlags() const; - void SetMassComputeFlags(MassComputeFlags flags); - - bool IsCCDEnabled() const; - - // Basic initial settings. - AZ::Vector3 m_initialLinearVelocity = AZ::Vector3::CreateZero(); - AZ::Vector3 m_initialAngularVelocity = AZ::Vector3::CreateZero(); - AZ::Vector3 m_centerOfMassOffset = AZ::Vector3::CreateZero(); - - // Simulation parameters. - float m_mass = DefaultRigidBodyConfiguration::m_mass; - AZ::Matrix3x3 m_inertiaTensor = AZ::Matrix3x3::CreateIdentity(); - float m_linearDamping = DefaultRigidBodyConfiguration::m_linearDamping; - float m_angularDamping = DefaultRigidBodyConfiguration::m_angularDamping; - float m_sleepMinEnergy = DefaultRigidBodyConfiguration::m_sleepMinEnergy; - float m_maxAngularVelocity = DefaultRigidBodyConfiguration::m_maxAngularVelocity; - - // Visibility settings. - AZ::u16 m_propertyVisibilityFlags = (std::numeric_limits::max)(); - - bool m_startAsleep = false; - bool m_interpolateMotion = false; - bool m_gravityEnabled = true; - bool m_simulated = true; - bool m_kinematic = false; - bool m_ccdEnabled = false; ///< Whether continuous collision detection is enabled. - float m_ccdMinAdvanceCoefficient = 0.15f; ///< Coefficient affecting how granularly time is subdivided in CCD. - bool m_ccdFrictionEnabled = false; ///< Whether friction is applied when resolving CCD collisions. - - bool m_computeCenterOfMass = true; - bool m_computeInertiaTensor = true; - bool m_computeMass = true; - - //! If set, non-simulated shapes will also be included in the mass properties calculation. - bool m_includeAllShapesInMassCalculation = false; - }; - - /// Dynamic rigid body. - class RigidBody - : public WorldBody - { - public: - - AZ_CLASS_ALLOCATOR(RigidBody, AZ::SystemAllocator, 0); - AZ_RTTI(RigidBody, "{156E459F-7BB7-4B4E-ADA0-2130D96B7E80}", WorldBody); - - public: - RigidBody() = default; - explicit RigidBody(const RigidBodyConfiguration& settings); - - - virtual void AddShape(AZStd::shared_ptr shape) = 0; - virtual void RemoveShape(AZStd::shared_ptr shape) = 0; - virtual AZ::u32 GetShapeCount() { return 0; } - virtual AZStd::shared_ptr GetShape(AZ::u32 /*index*/) { return nullptr; } - - virtual AZ::Vector3 GetCenterOfMassWorld() const = 0; - virtual AZ::Vector3 GetCenterOfMassLocal() const = 0; - - virtual AZ::Matrix3x3 GetInverseInertiaWorld() const = 0; - virtual AZ::Matrix3x3 GetInverseInertiaLocal() const = 0; - - virtual float GetMass() const = 0; - virtual float GetInverseMass() const = 0; - virtual void SetMass(float mass) = 0; - virtual void SetCenterOfMassOffset(const AZ::Vector3& comOffset) = 0; - - /// Retrieves the velocity at center of mass; only linear velocity, no rotational velocity contribution. - virtual AZ::Vector3 GetLinearVelocity() const = 0; - virtual void SetLinearVelocity(const AZ::Vector3& velocity) = 0; - virtual AZ::Vector3 GetAngularVelocity() const = 0; - virtual void SetAngularVelocity(const AZ::Vector3& angularVelocity) = 0; - virtual AZ::Vector3 GetLinearVelocityAtWorldPoint(const AZ::Vector3& worldPoint) = 0; - virtual void ApplyLinearImpulse(const AZ::Vector3& impulse) = 0; - virtual void ApplyLinearImpulseAtWorldPoint(const AZ::Vector3& impulse, const AZ::Vector3& worldPoint) = 0; - virtual void ApplyAngularImpulse(const AZ::Vector3& angularImpulse) = 0; - - virtual float GetLinearDamping() const = 0; - virtual void SetLinearDamping(float damping) = 0; - virtual float GetAngularDamping() const = 0; - virtual void SetAngularDamping(float damping) = 0; - - virtual bool IsAwake() const = 0; - virtual void ForceAsleep() = 0; - virtual void ForceAwake() = 0; - virtual float GetSleepThreshold() const = 0; - virtual void SetSleepThreshold(float threshold) = 0; - - virtual bool IsKinematic() const = 0; - virtual void SetKinematic(bool kinematic) = 0; - virtual void SetKinematicTarget(const AZ::Transform& targetPosition) = 0; - - virtual bool IsGravityEnabled() const = 0; - virtual void SetGravityEnabled(bool enabled) = 0; - virtual void SetSimulationEnabled(bool enabled) = 0; - virtual void SetCCDEnabled(bool enabled) = 0; - - //! Recalculates mass, inertia and center of mass based on the flags passed. - //! @param flags MassComputeFlags specifying which properties should be recomputed. - //! @param centerOfMassOffsetOverride Optional override of the center of mass. Note: This parameter will be ignored if COMPUTE_COM is passed in flags. - //! @param inertiaTensorOverride Optional override of the inertia. Note: This parameter will be ignored if COMPUTE_INERTIA is passed in flags. - //! @param massOverride Optional override of the mass. Note: This parameter will be ignored if COMPUTE_MASS is passed in flags. - virtual void UpdateMassProperties(MassComputeFlags flags = MassComputeFlags::DEFAULT, - const AZ::Vector3* centerOfMassOffsetOverride = nullptr, - const AZ::Matrix3x3* inertiaTensorOverride = nullptr, - const float* massOverride = nullptr) = 0; - }; - - /// Bitwise operators for MassComputeFlags - inline MassComputeFlags operator|(MassComputeFlags lhs, MassComputeFlags rhs) - { - return aznumeric_cast(aznumeric_cast(lhs) | aznumeric_cast(rhs)); - } - - inline MassComputeFlags operator&(MassComputeFlags lhs, MassComputeFlags rhs) - { - return aznumeric_cast(aznumeric_cast(lhs) & aznumeric_cast(rhs)); - } - - /// Static rigid body. - class RigidBodyStatic - : public WorldBody - { - public: - AZ_CLASS_ALLOCATOR(RigidBodyStatic, AZ::SystemAllocator, 0); - AZ_RTTI(RigidBodyStatic, "{13A677BB-7085-4EDB-BCC8-306548238692}", WorldBody); - - virtual void AddShape(const AZStd::shared_ptr& shape) = 0; - virtual AZ::u32 GetShapeCount() { return 0; } - virtual AZStd::shared_ptr GetShape(AZ::u32 /*index*/) { return nullptr; } - }; -} // namespace Physics diff --git a/Code/Framework/AzFramework/AzFramework/Physics/SimulatedBodies/RigidBody.h b/Code/Framework/AzFramework/AzFramework/Physics/SimulatedBodies/RigidBody.h index 3c0a1afa1d..e9bf8a7307 100644 --- a/Code/Framework/AzFramework/AzFramework/Physics/SimulatedBodies/RigidBody.h +++ b/Code/Framework/AzFramework/AzFramework/Physics/SimulatedBodies/RigidBody.h @@ -89,9 +89,9 @@ namespace AzPhysics //! @param inertiaTensorOverride Optional override of the inertia. Note: This parameter will be ignored if COMPUTE_INERTIA is passed in flags. //! @param massOverride Optional override of the mass. Note: This parameter will be ignored if COMPUTE_MASS is passed in flags. virtual void UpdateMassProperties(MassComputeFlags flags = MassComputeFlags::DEFAULT, - const AZ::Vector3* centerOfMassOffsetOverride = nullptr, - const AZ::Matrix3x3* inertiaTensorOverride = nullptr, - const float* massOverride = nullptr) = 0; + const AZ::Vector3& centerOfMassOffsetOverride = AZ::Vector3::CreateZero(), + const AZ::Matrix3x3& inertiaTensorOverride = AZ::Matrix3x3::CreateIdentity(), + const float massOverride = 1.0f) = 0; }; } // namespace AzPhysics diff --git a/Gems/Blast/Code/Tests/Mocks/BlastMocks.h b/Gems/Blast/Code/Tests/Mocks/BlastMocks.h index dddd1e275a..1fe6e35d75 100644 --- a/Gems/Blast/Code/Tests/Mocks/BlastMocks.h +++ b/Gems/Blast/Code/Tests/Mocks/BlastMocks.h @@ -344,9 +344,9 @@ namespace Blast void UpdateMassProperties( [[maybe_unused]] AzPhysics::MassComputeFlags flags, - [[maybe_unused]] const AZ::Vector3* centerOfMassOffsetOverride, - [[maybe_unused]] const AZ::Matrix3x3* inertiaTensorOverride, - [[maybe_unused]] const float* massOverride) override + [[maybe_unused]] const AZ::Vector3& centerOfMassOffsetOverride, + [[maybe_unused]] const AZ::Matrix3x3& inertiaTensorOverride, + [[maybe_unused]] const float massOverride) override { } diff --git a/Gems/PhysX/Code/Source/RigidBody.cpp b/Gems/PhysX/Code/Source/RigidBody.cpp index f8b22affe0..5b36376da5 100644 --- a/Gems/PhysX/Code/Source/RigidBody.cpp +++ b/Gems/PhysX/Code/Source/RigidBody.cpp @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -23,6 +24,28 @@ namespace PhysX { + namespace + { + const AZ::Vector3 DefaultCenterOfMass = AZ::Vector3::CreateZero(); + const float DefaultMass = 1.0f; + const AZ::Matrix3x3 DefaultInertiaTensor = AZ::Matrix3x3::CreateIdentity(); + + bool IsSimulationShape(const physx::PxShape& pxShape) + { + return (pxShape.getFlags() & physx::PxShapeFlag::eSIMULATION_SHAPE); + } + + bool CanShapeComputeMassProperties(const physx::PxShape& pxShape) + { + // Note: List based on computeMassAndInertia function in ExtRigidBodyExt.cpp file in PhysX. + const physx::PxGeometryType::Enum geometryType = pxShape.getGeometryType(); + return geometryType == physx::PxGeometryType::eSPHERE + || geometryType == physx::PxGeometryType::eBOX + || geometryType == physx::PxGeometryType::eCAPSULE + || geometryType == physx::PxGeometryType::eCONVEXMESH; + } + } + void RigidBody::Reflect(AZ::ReflectContext* context) { AZ::SerializeContext* serializeContext = azrtti_cast(context); @@ -152,104 +175,120 @@ namespace PhysX m_shapes.erase(found); } - void RigidBody::UpdateMassProperties(AzPhysics::MassComputeFlags flags, const AZ::Vector3* centerOfMassOffsetOverride, const AZ::Matrix3x3* inertiaTensorOverride, const float* massOverride) + void RigidBody::UpdateMassProperties(AzPhysics::MassComputeFlags flags, const AZ::Vector3& centerOfMassOffsetOverride, const AZ::Matrix3x3& inertiaTensorOverride, const float massOverride) { - // Input validation - bool computeCenterOfMass = AzPhysics::MassComputeFlags::COMPUTE_COM == (flags & AzPhysics::MassComputeFlags::COMPUTE_COM); - AZ_Assert(computeCenterOfMass || centerOfMassOffsetOverride, - "UpdateMassProperties: MassComputeFlags::COMPUTE_COM is not set but COM offset is not specified"); - computeCenterOfMass = computeCenterOfMass || !centerOfMassOffsetOverride; - - bool computeInertiaTensor = AzPhysics::MassComputeFlags::COMPUTE_INERTIA == (flags & AzPhysics::MassComputeFlags::COMPUTE_INERTIA); - AZ_Assert(computeInertiaTensor || inertiaTensorOverride, - "UpdateMassProperties: MassComputeFlags::COMPUTE_INERTIA is not set but inertia tensor is not specified"); - computeInertiaTensor = computeInertiaTensor || !inertiaTensorOverride; - - bool computeMass = AzPhysics::MassComputeFlags::COMPUTE_MASS == (flags & AzPhysics::MassComputeFlags::COMPUTE_MASS); - AZ_Assert(computeMass || massOverride, - "UpdateMassProperties: MassComputeFlags::COMPUTE_MASS is not set but mass is not specified"); - computeMass = computeMass || !massOverride; + const bool computeCenterOfMass = AzPhysics::MassComputeFlags::COMPUTE_COM == (flags & AzPhysics::MassComputeFlags::COMPUTE_COM); + const bool computeInertiaTensor = AzPhysics::MassComputeFlags::COMPUTE_INERTIA == (flags & AzPhysics::MassComputeFlags::COMPUTE_INERTIA); + const bool computeMass = AzPhysics::MassComputeFlags::COMPUTE_MASS == (flags & AzPhysics::MassComputeFlags::COMPUTE_MASS); + const bool needsCompute = computeCenterOfMass || computeInertiaTensor || computeMass; + const bool includeAllShapesInMassCalculation = AzPhysics::MassComputeFlags::INCLUDE_ALL_SHAPES == (flags & AzPhysics::MassComputeFlags::INCLUDE_ALL_SHAPES); - AZ::u32 shapesCount = GetShapeCount(); - - // Basic cases when we don't need to compute anything - if (shapesCount == 0 || flags == AzPhysics::MassComputeFlags::NONE) + // Basic case where all properties are set directly. + if (!needsCompute) { - if (massOverride) - { - SetMass(*massOverride); - } - - if (inertiaTensorOverride) - { - SetInertia(*inertiaTensorOverride); - } - - if (centerOfMassOffsetOverride) - { - SetCenterOfMassOffset(*centerOfMassOffsetOverride); - } + SetCenterOfMassOffset(centerOfMassOffsetOverride); + SetMass(massOverride); + SetInertia(inertiaTensorOverride); return; } - // Setup center of mass offset pointer for PxRigidBodyExt::updateMassAndInertia function - AZStd::optional optionalComOverride; - if (!computeCenterOfMass && centerOfMassOffsetOverride) + // If there are no shapes then set the properties directly without computing anything. + if (m_shapes.empty()) { - optionalComOverride = PxMathConvert(*centerOfMassOffsetOverride); + SetCenterOfMassOffset(computeCenterOfMass ? DefaultCenterOfMass : centerOfMassOffsetOverride); + SetMass(computeMass ? DefaultMass : massOverride); + SetInertia(computeInertiaTensor ? DefaultInertiaTensor : inertiaTensorOverride); + return; } - const physx::PxVec3* massLocalPose = optionalComOverride.has_value() ? &optionalComOverride.value() : nullptr; + auto cannotComputeMassProperties = [this, includeAllShapesInMassCalculation] + { + PHYSX_SCENE_READ_LOCK(m_pxRigidActor->getScene()); + return AZStd::any_of(m_shapes.cbegin(), m_shapes.cend(), + [includeAllShapesInMassCalculation](const AZStd::shared_ptr& shape) + { + const physx::PxShape& pxShape = *shape->GetPxShape(); + const bool includeShape = includeAllShapesInMassCalculation || IsSimulationShape(pxShape); + + return includeShape && !CanShapeComputeMassProperties(pxShape); + }); + }; + + // If contains shapes that cannot compute mass properties (triangle mesh, + // plane or heightfield) then default values will be used. + if (cannotComputeMassProperties()) + { + AZ_Warning("RigidBody", !computeCenterOfMass, + "Rigid body '%s' cannot compute COM because it contains triangle mesh, plane or heightfield shapes, it will default to %s.", + GetName().c_str(), AZ::ToString(DefaultCenterOfMass).c_str()); + AZ_Warning("RigidBody", !computeMass, + "Rigid body '%s' cannot compute Mass because it contains triangle mesh, plane or heightfield shapes, it will default to %0.1f.", + GetName().c_str(), DefaultMass); + AZ_Warning("RigidBody", !computeInertiaTensor, + "Rigid body '%s' cannot compute Inertia because it contains triangle mesh, plane or heightfield shapes, it will default to %s.", + GetName().c_str(), AZ::ToString(DefaultInertiaTensor.RetrieveScale()).c_str()); + + SetCenterOfMassOffset(computeCenterOfMass ? DefaultCenterOfMass : centerOfMassOffsetOverride); + SetMass(computeMass ? DefaultMass : massOverride); + SetInertia(computeInertiaTensor ? DefaultInertiaTensor : inertiaTensorOverride); + return; + } - bool includeAllShapesInMassCalculation = - AzPhysics::MassComputeFlags::INCLUDE_ALL_SHAPES == (flags & AzPhysics::MassComputeFlags::INCLUDE_ALL_SHAPES); + // Center of mass needs to be considered first since + // it's needed when computing mass and inertia. + if (computeCenterOfMass) + { + // Compute Center of Mass + UpdateCenterOfMass(includeAllShapesInMassCalculation); + } + else + { + SetCenterOfMassOffset(centerOfMassOffsetOverride); + } + const physx::PxVec3 pxCenterOfMass = PxMathConvert(GetCenterOfMassLocal()); - // Handle the case when we don't compute mass - if (!computeMass) + if (computeMass) { + // Gather material densities from all shapes, + // mass computation is based on them. + AZStd::vector densities; + densities.reserve(m_shapes.size()); + for (const auto& shape : m_shapes) + { + densities.emplace_back(shape->GetMaterial()->GetDensity()); + } + + // Compute Mass + Inertia { PHYSX_SCENE_WRITE_LOCK(m_pxRigidActor->getScene()); - physx::PxRigidBodyExt::setMassAndUpdateInertia(*m_pxRigidActor, *massOverride, massLocalPose, - includeAllShapesInMassCalculation); + physx::PxRigidBodyExt::updateMassAndInertia(*m_pxRigidActor, + densities.data(), static_cast(densities.size()), + &pxCenterOfMass, includeAllShapesInMassCalculation); } + // There is no physx function to only compute the mass without + // computing the inertia. So now that both have been computed + // we can override the inertia if it's suppose to use a + // specific value set by the user. if (!computeInertiaTensor) { - SetInertia(*inertiaTensorOverride); + SetInertia(inertiaTensorOverride); } - - return; - } - - // Handle the cases when mass should be computed from density - if (shapesCount == 1) - { - AZStd::shared_ptr shape = GetShape(0); - float density = shape->GetMaterial()->GetDensity(); - - PHYSX_SCENE_WRITE_LOCK(m_pxRigidActor->getScene()); - physx::PxRigidBodyExt::updateMassAndInertia(*m_pxRigidActor, density, massLocalPose, - includeAllShapesInMassCalculation); } else { - AZStd::vector densities(shapesCount); - for (AZ::u32 i = 0; i < shapesCount; ++i) + if (computeInertiaTensor) { - densities[i] = GetShape(i)->GetMaterial()->GetDensity(); + // Set Mass + Compute Inertia + PHYSX_SCENE_WRITE_LOCK(m_pxRigidActor->getScene()); + physx::PxRigidBodyExt::setMassAndUpdateInertia(*m_pxRigidActor, massOverride, + &pxCenterOfMass, includeAllShapesInMassCalculation); + } + else + { + SetMass(massOverride); + SetInertia(inertiaTensorOverride); } - - PHYSX_SCENE_WRITE_LOCK(m_pxRigidActor->getScene()); - physx::PxRigidBodyExt::updateMassAndInertia(*m_pxRigidActor, densities.data(), - shapesCount, massLocalPose, includeAllShapesInMassCalculation); - } - - // Set the overrides if provided. - // Note: We don't set the center of mass here because it was already provided - // to PxRigidBodyExt::updateMassAndInertia above - if (!computeInertiaTensor) - { - SetInertia(*inertiaTensorOverride); } } @@ -344,70 +383,57 @@ namespace PhysX } } - void RigidBody::UpdateComputedCenterOfMass() + void RigidBody::UpdateCenterOfMass(bool includeAllShapesInMassCalculation) { - if (m_pxRigidActor) + if (m_shapes.empty()) { - physx::PxU32 shapeCount = 0; - { - PHYSX_SCENE_READ_LOCK(m_pxRigidActor->getScene()); - shapeCount = m_pxRigidActor->getNbShapes(); - } - if (shapeCount > 0) - { - AZStd::vector shapes; - shapes.resize(shapeCount); - - { - PHYSX_SCENE_READ_LOCK(m_pxRigidActor->getScene()); - m_pxRigidActor->getShapes(&shapes[0], shapeCount); - } - - shapes.erase(AZStd::remove_if(shapes.begin() - , shapes.end() - , [](const physx::PxShape* shape) - { - return shape->getFlags() & physx::PxShapeFlag::eTRIGGER_SHAPE; - }) - , shapes.end()); - shapeCount = static_cast(shapes.size()); + SetCenterOfMassOffset(DefaultCenterOfMass); + return; + } - if (shapeCount == 0) - { - SetZeroCenterOfMass(); - return; - } + AZStd::vector pxShapes; + pxShapes.reserve(m_shapes.size()); + { + // Filter shapes in the same way that updateMassAndInertia function does. + PHYSX_SCENE_READ_LOCK(m_pxRigidActor->getScene()); + for (const auto& shape : m_shapes) + { + const physx::PxShape& pxShape = *shape->GetPxShape(); + const bool includeShape = includeAllShapesInMassCalculation || IsSimulationShape(pxShape); - const auto properties = physx::PxRigidBodyExt::computeMassPropertiesFromShapes(&shapes[0], shapeCount); - const physx::PxTransform computedCenterOfMass(properties.centerOfMass); + if (includeShape && CanShapeComputeMassProperties(pxShape)) { - PHYSX_SCENE_WRITE_LOCK(m_pxRigidActor->getScene()); - m_pxRigidActor->setCMassLocalPose(computedCenterOfMass); + pxShapes.emplace_back(&pxShape); } } - else - { - SetZeroCenterOfMass(); - } } - } - void RigidBody::SetInertia(const AZ::Matrix3x3& inertia) - { - if (m_pxRigidActor) + if (pxShapes.empty()) { - PHYSX_SCENE_WRITE_LOCK(m_pxRigidActor->getScene()); - m_pxRigidActor->setMassSpaceInertiaTensor(PxMathConvert(inertia.RetrieveScale())); + SetCenterOfMassOffset(DefaultCenterOfMass); + return; } + + const physx::PxMassProperties pxMassProperties = [this, &pxShapes] + { + // Note: PhysX computeMassPropertiesFromShapes function does not use densities + // to compute the shape's masses, which are needed to calculate the center of mass. + // This differs from updateMassAndInertia function, which uses material density values. + // So the masses used during center of mass calculation do not match the masses + // used during mass/inertia calculation. This is an inconsistency in PhysX. + PHYSX_SCENE_READ_LOCK(m_pxRigidActor->getScene()); + return physx::PxRigidBodyExt::computeMassPropertiesFromShapes(pxShapes.data(), static_cast(pxShapes.size())); + }(); + + SetCenterOfMassOffset(PxMathConvert(pxMassProperties.centerOfMass)); } - void RigidBody::ComputeInertia() + void RigidBody::SetInertia(const AZ::Matrix3x3& inertia) { if (m_pxRigidActor) { PHYSX_SCENE_WRITE_LOCK(m_pxRigidActor->getScene()); - auto localPose = m_pxRigidActor->getCMassLocalPose().p; - physx::PxRigidBodyExt::setMassAndUpdateInertia(*m_pxRigidActor, m_pxRigidActor->getMass(), &localPose); + m_pxRigidActor->setMassSpaceInertiaTensor(PxMathConvert(inertia.RetrieveScale())); } } @@ -783,13 +809,4 @@ namespace PhysX { return m_name; } - - void RigidBody::SetZeroCenterOfMass() - { - if (m_pxRigidActor) - { - PHYSX_SCENE_WRITE_LOCK(m_pxRigidActor->getScene()); - m_pxRigidActor->setCMassLocalPose(physx::PxTransform(PxMathConvert(AZ::Vector3::CreateZero()))); - } - } } diff --git a/Gems/PhysX/Code/Source/RigidBody.h b/Gems/PhysX/Code/Source/RigidBody.h index 10f2ebb556..a20f79b161 100644 --- a/Gems/PhysX/Code/Source/RigidBody.h +++ b/Gems/PhysX/Code/Source/RigidBody.h @@ -109,17 +109,15 @@ namespace PhysX void RemoveShape(AZStd::shared_ptr shape) override; void UpdateMassProperties(AzPhysics::MassComputeFlags flags = AzPhysics::MassComputeFlags::DEFAULT, - const AZ::Vector3* centerOfMassOffsetOverride = nullptr, - const AZ::Matrix3x3* inertiaTensorOverride = nullptr, - const float* massOverride = nullptr) override; + const AZ::Vector3& centerOfMassOffsetOverride = AZ::Vector3::CreateZero(), + const AZ::Matrix3x3& inertiaTensorOverride = AZ::Matrix3x3::CreateIdentity(), + const float massOverride = 1.0f) override; private: void CreatePhysXActor(const AzPhysics::RigidBodyConfiguration& configuration); - void UpdateComputedCenterOfMass(); - void ComputeInertia(); + void UpdateCenterOfMass(bool includeAllShapesInMassCalculation); void SetInertia(const AZ::Matrix3x3& inertia); - void SetZeroCenterOfMass(); AZStd::shared_ptr m_pxRigidActor; AZStd::vector> m_shapes; diff --git a/Gems/PhysX/Code/Source/Scene/PhysXScene.cpp b/Gems/PhysX/Code/Source/Scene/PhysXScene.cpp index 3b5c98ba2d..86e3ceb98f 100644 --- a/Gems/PhysX/Code/Source/Scene/PhysXScene.cpp +++ b/Gems/PhysX/Code/Source/Scene/PhysXScene.cpp @@ -198,8 +198,8 @@ namespace PhysX AZ_Warning("PhysXScene", shapeAdded, "No Collider or Shape information found when creating Rigid body [%s]", configuration->m_debugName.c_str()); } const AzPhysics::MassComputeFlags& flags = configuration->GetMassComputeFlags(); - newBody->UpdateMassProperties(flags, &configuration->m_centerOfMassOffset, - &configuration->m_inertiaTensor, &configuration->m_mass); + newBody->UpdateMassProperties(flags, configuration->m_centerOfMassOffset, + configuration->m_inertiaTensor, configuration->m_mass); crc = AZ::Crc32(newBody, sizeof(*newBody)); return newBody; diff --git a/Gems/PhysX/Code/Tests/PhysXSpecificTest.cpp b/Gems/PhysX/Code/Tests/PhysXSpecificTest.cpp index 0c0eee3eb0..9e3a738370 100644 --- a/Gems/PhysX/Code/Tests/PhysXSpecificTest.cpp +++ b/Gems/PhysX/Code/Tests/PhysXSpecificTest.cpp @@ -12,6 +12,8 @@ #include #include #include +#include +#include #include #include @@ -1283,11 +1285,13 @@ namespace PhysX EXPECT_TRUE(AZ::IsClose(expectedMass, mass, 0.001f)); } + // Valid material density values: [0.01f, 1e5f] INSTANTIATE_TEST_CASE_P(PhysX, MultiShapesDensityTestFixture, ::testing::Values( - AZStd::make_pair(std::numeric_limits::min(), std::numeric_limits::max()), - AZStd::make_pair(-std::numeric_limits::max(), 0.0f), - AZStd::make_pair(1.0f, 1e9f) + AZStd::make_pair(0.01f, 0.01f), + AZStd::make_pair(1e5f, 1e5f), + AZStd::make_pair(0.01f, 1e5f), + AZStd::make_pair(2364.0f, 10.0f) )); // Fixture for testing extreme density values @@ -1311,6 +1315,7 @@ namespace PhysX && resultingDensity <= Physics::MaterialConfiguration::MaxDensityLimit); } + // Valid material density values: [0.01f, 1e5f] INSTANTIATE_TEST_CASE_P(PhysX, DensityBoundariesTestFixture, ::testing::Values( std::numeric_limits::min(), @@ -1318,7 +1323,9 @@ namespace PhysX -std::numeric_limits::max(), 0.0f, 1.0f, - 1e9f + 1e9f, + 0.01f, + 1e5f )); enum class SimulatedShapesMode @@ -1329,7 +1336,7 @@ namespace PhysX }; class MassComputeFixture - : public ::testing::TestWithParam<::testing::tuple> + : public ::testing::TestWithParam<::testing::tuple> { public: void SetUp() override final @@ -1349,6 +1356,8 @@ namespace PhysX AzPhysics::SimulatedBodyHandle simBodyHandle = sceneInterface->AddSimulatedBody(m_testSceneHandle, &m_rigidBodyConfig); m_rigidBody = azdynamic_cast(sceneInterface->GetSimulatedBodyFromHandle(m_testSceneHandle, simBodyHandle)); } + + ASSERT_TRUE(m_rigidBody != nullptr); } void TearDown() override final @@ -1363,130 +1372,242 @@ namespace PhysX m_rigidBody = nullptr; } - SimulatedShapesMode GetShapesMode() const + Physics::ShapeType GetShapeType() const { return ::testing::get<0>(GetParam()); } - AzPhysics::MassComputeFlags GetMassComputeFlags() const + SimulatedShapesMode GetShapesMode() const { return ::testing::get<1>(GetParam()); } + AzPhysics::MassComputeFlags GetMassComputeFlags() const + { + const AzPhysics::MassComputeFlags massComputeFlags = ::testing::get<2>(GetParam()); + if (IncludeAllShapes()) + { + return massComputeFlags | AzPhysics::MassComputeFlags::INCLUDE_ALL_SHAPES; + } + else + { + return massComputeFlags; + } + } + + bool IncludeAllShapes() const + { + return ::testing::get<3>(GetParam()); + } + bool IsMultiShapeTest() const { - return ::testing::get<2>(GetParam()); + return ::testing::get<4>(GetParam()); } bool IsMassExpectedToChange() const { return m_rigidBodyConfig.m_computeMass && - (!(GetShapesMode() == SimulatedShapesMode::NONE) || m_rigidBodyConfig.m_includeAllShapesInMassCalculation); + (GetShapesMode() != SimulatedShapesMode::NONE || m_rigidBodyConfig.m_includeAllShapesInMassCalculation); } bool IsComExpectedToChange() const { return m_rigidBodyConfig.m_computeCenterOfMass && - (!(GetShapesMode() == SimulatedShapesMode::NONE) || m_rigidBodyConfig.m_includeAllShapesInMassCalculation); + (GetShapesMode() != SimulatedShapesMode::NONE || m_rigidBodyConfig.m_includeAllShapesInMassCalculation); } bool IsInertiaExpectedToChange() const { return m_rigidBodyConfig.m_computeInertiaTensor && - (!(GetShapesMode() == SimulatedShapesMode::NONE) || m_rigidBodyConfig.m_includeAllShapesInMassCalculation); + (GetShapesMode() != SimulatedShapesMode::NONE || m_rigidBodyConfig.m_includeAllShapesInMassCalculation); } + AZStd::shared_ptr CreateShape(const Physics::ColliderConfiguration& colliderConfiguration, Physics::ShapeType shapeType) + { + AZStd::shared_ptr shape; + Physics::System* physics = AZ::Interface::Get(); + switch (shapeType) + { + case Physics::ShapeType::Sphere: + shape = physics->CreateShape(colliderConfiguration, Physics::SphereShapeConfiguration()); + break; + case Physics::ShapeType::Box: + shape = physics->CreateShape(colliderConfiguration, Physics::BoxShapeConfiguration()); + break; + case Physics::ShapeType::Capsule: + shape = physics->CreateShape(colliderConfiguration, Physics::CapsuleShapeConfiguration()); + break; + } + return shape; + }; + AzPhysics::RigidBodyConfiguration m_rigidBodyConfig; - AzPhysics::RigidBody* m_rigidBody; + AzPhysics::RigidBody* m_rigidBody = nullptr; AzPhysics::SceneHandle m_testSceneHandle = AzPhysics::InvalidSceneHandle; }; TEST_P(MassComputeFixture, RigidBody_ComputeMassFlagsCombinationsTwoShapes_MassPropertiesCalculatedAccordingly) { - SimulatedShapesMode shapeMode = GetShapesMode(); - AzPhysics::MassComputeFlags massComputeFlags = GetMassComputeFlags(); - bool multiShapeTest = IsMultiShapeTest(); - Physics::System* physics = AZ::Interface::Get(); + const Physics::ShapeType shapeType = GetShapeType(); + const SimulatedShapesMode shapeMode = GetShapesMode(); + const AzPhysics::MassComputeFlags massComputeFlags = GetMassComputeFlags(); + const bool multiShapeTest = IsMultiShapeTest(); // Save initial values - AZ::Vector3 comBefore = m_rigidBody->GetCenterOfMassWorld(); - AZ::Matrix3x3 inertiaBefore = m_rigidBody->GetInverseInertiaWorld(); - float massBefore = m_rigidBody->GetMass(); + const AZ::Vector3 comBefore = m_rigidBody->GetCenterOfMassWorld(); + const AZ::Matrix3x3 inertiaBefore = m_rigidBody->GetInverseInertiaWorld(); + const float massBefore = m_rigidBody->GetMass(); - // Box shape will be simulated for ALL and MIXED shape modes - Physics::ColliderConfiguration boxColliderConfig; - boxColliderConfig.m_isSimulated = + // Shape will be simulated for ALL and MIXED shape modes + Physics::ColliderConfiguration colliderConfig; + colliderConfig.m_isSimulated = (shapeMode == SimulatedShapesMode::ALL || shapeMode == SimulatedShapesMode::MIXED); - boxColliderConfig.m_position = AZ::Vector3(1.0f, 0.0f, 0.0f); + colliderConfig.m_position = AZ::Vector3(1.0f, 0.0f, 0.0f); - AZStd::shared_ptr boxShape = - physics->CreateShape(boxColliderConfig, Physics::BoxShapeConfiguration()); - m_rigidBody->AddShape(boxShape); + AZStd::shared_ptr shape = CreateShape(colliderConfig, shapeType); + m_rigidBody->AddShape(shape); if (multiShapeTest) { // Sphere shape will be simulated only for the ALL shape mode Physics::ColliderConfiguration sphereColliderConfig; sphereColliderConfig.m_isSimulated = (shapeMode == SimulatedShapesMode::ALL); - sphereColliderConfig.m_position = AZ::Vector3(-1.0f, 0.0f, 0.0f); - AZStd::shared_ptr sphereShape = - physics->CreateShape(sphereColliderConfig, Physics::SphereShapeConfiguration()); + sphereColliderConfig.m_position = AZ::Vector3(-2.0f, 0.0f, 0.0f); + AZStd::shared_ptr sphereShape = CreateShape(sphereColliderConfig, Physics::ShapeType::Sphere); m_rigidBody->AddShape(sphereShape); } // Verify swapping materials results in changes in the mass. - m_rigidBody->UpdateMassProperties(massComputeFlags, &m_rigidBodyConfig.m_centerOfMassOffset, - &m_rigidBodyConfig.m_inertiaTensor, &m_rigidBodyConfig.m_mass); + m_rigidBody->UpdateMassProperties(massComputeFlags, m_rigidBodyConfig.m_centerOfMassOffset, + m_rigidBodyConfig.m_inertiaTensor, m_rigidBodyConfig.m_mass); - float massAfter = m_rigidBody->GetMass(); - AZ::Vector3 comAfter = m_rigidBody->GetCenterOfMassWorld(); - AZ::Matrix3x3 inertiaAfter = m_rigidBody->GetInverseInertiaWorld(); + const float massAfter = m_rigidBody->GetMass(); + const AZ::Vector3 comAfter = m_rigidBody->GetCenterOfMassWorld(); + const AZ::Matrix3x3 inertiaAfter = m_rigidBody->GetInverseInertiaWorld(); + using ::testing::Not; + using ::testing::FloatNear; + using ::UnitTest::IsClose; if (IsMassExpectedToChange()) { - EXPECT_FALSE(AZ::IsClose(massBefore, massAfter, FLT_EPSILON)); + EXPECT_THAT(massBefore, Not(FloatNear(massAfter, FLT_EPSILON))); } else { - EXPECT_TRUE(AZ::IsClose(massBefore, massAfter, FLT_EPSILON)); + EXPECT_THAT(massBefore, FloatNear(massAfter, FLT_EPSILON)); } if (IsComExpectedToChange()) { - EXPECT_FALSE(comBefore.IsClose(comAfter)); + EXPECT_THAT(comBefore, Not(IsClose(comAfter))); } else { - EXPECT_TRUE(comBefore.IsClose(comAfter)); + EXPECT_THAT(comBefore, IsClose(comAfter)); } if (IsInertiaExpectedToChange()) { - EXPECT_FALSE(inertiaBefore.IsClose(inertiaAfter)); + EXPECT_THAT(inertiaBefore, Not(IsClose(inertiaAfter))); } else { - EXPECT_TRUE(inertiaBefore.IsClose(inertiaAfter)); + EXPECT_THAT(inertiaBefore, IsClose(inertiaAfter)); } } - AzPhysics::MassComputeFlags possibleMassComputeFlags[] = { - AzPhysics::MassComputeFlags::NONE, AzPhysics::MassComputeFlags::DEFAULT, AzPhysics::MassComputeFlags::COMPUTE_MASS, - AzPhysics::MassComputeFlags::COMPUTE_COM, AzPhysics::MassComputeFlags::COMPUTE_INERTIA, - AzPhysics::MassComputeFlags::DEFAULT | AzPhysics::MassComputeFlags::INCLUDE_ALL_SHAPES, - AzPhysics::MassComputeFlags::COMPUTE_COM, AzPhysics::MassComputeFlags::COMPUTE_INERTIA, AzPhysics::MassComputeFlags::INCLUDE_ALL_SHAPES, + static const AzPhysics::MassComputeFlags PossibleMassComputeFlags[] = + { + // No compute + AzPhysics::MassComputeFlags::NONE, + + // Compute Mass only + AzPhysics::MassComputeFlags::COMPUTE_MASS, + + // Compute Inertia only + AzPhysics::MassComputeFlags::COMPUTE_INERTIA, + + // Compute COM only + AzPhysics::MassComputeFlags::COMPUTE_COM, + + // Compute combinations of 2 AzPhysics::MassComputeFlags::COMPUTE_MASS | AzPhysics::MassComputeFlags::COMPUTE_COM, - AzPhysics::MassComputeFlags::COMPUTE_MASS | AzPhysics::MassComputeFlags::COMPUTE_COM | AzPhysics::MassComputeFlags::INCLUDE_ALL_SHAPES, AzPhysics::MassComputeFlags::COMPUTE_MASS | AzPhysics::MassComputeFlags::COMPUTE_INERTIA, - AzPhysics::MassComputeFlags::COMPUTE_MASS | AzPhysics::MassComputeFlags::COMPUTE_INERTIA | AzPhysics::MassComputeFlags::INCLUDE_ALL_SHAPES, AzPhysics::MassComputeFlags::COMPUTE_COM | AzPhysics::MassComputeFlags::COMPUTE_INERTIA, - AzPhysics::MassComputeFlags::COMPUTE_COM | AzPhysics::MassComputeFlags::COMPUTE_INERTIA | AzPhysics::MassComputeFlags::INCLUDE_ALL_SHAPES + + // Compute all + AzPhysics::MassComputeFlags::DEFAULT, // COMPUTE_COM | COMPUTE_INERTIA | COMPUTE_MASS }; INSTANTIATE_TEST_CASE_P(PhysX, MassComputeFixture, ::testing::Combine( - ::testing::ValuesIn({ SimulatedShapesMode::NONE, SimulatedShapesMode::MIXED, SimulatedShapesMode::ALL }), - ::testing::ValuesIn(possibleMassComputeFlags), - ::testing::Bool())); + ::testing::ValuesIn({ Physics::ShapeType::Sphere, Physics::ShapeType::Box, Physics::ShapeType::Capsule }), // Values for GetShapeType() + ::testing::ValuesIn({ SimulatedShapesMode::NONE, SimulatedShapesMode::MIXED, SimulatedShapesMode::ALL }), // Values for GetShapesMode() + ::testing::ValuesIn(PossibleMassComputeFlags), // Values for GetMassComputeFlags() + ::testing::Bool(), // Values for IncludeAllShapes() + ::testing::Bool())); // Values for IsMultiShapeTest() + + class MassPropertiesWithTriangleMesh + : public ::testing::TestWithParam + { + public: + void SetUp() override + { + if (auto* physicsSystem = AZ::Interface::Get()) + { + AzPhysics::SceneConfiguration sceneConfiguration = physicsSystem->GetDefaultSceneConfiguration(); + sceneConfiguration.m_sceneName = AzPhysics::DefaultPhysicsSceneName; + m_testSceneHandle = physicsSystem->AddScene(sceneConfiguration); + } + } + + void TearDown() override + { + // Clean up the Test scene + if (auto* physicsSystem = AZ::Interface::Get()) + { + physicsSystem->RemoveScene(m_testSceneHandle); + } + m_testSceneHandle = AzPhysics::InvalidSceneHandle; + } + + AzPhysics::MassComputeFlags GetMassComputeFlags() const + { + return GetParam(); + } + + AzPhysics::SceneHandle m_testSceneHandle = AzPhysics::InvalidSceneHandle; + }; + + TEST_P(MassPropertiesWithTriangleMesh, KinematicRigidBody_ComputeMassProperties_TriggersWarnings) + { + const AzPhysics::MassComputeFlags flags = GetMassComputeFlags(); + + const bool doesComputeCenterOfMass = AzPhysics::MassComputeFlags::COMPUTE_COM == (flags & AzPhysics::MassComputeFlags::COMPUTE_COM); + const bool doesComputeMass = AzPhysics::MassComputeFlags::COMPUTE_MASS == (flags & AzPhysics::MassComputeFlags::COMPUTE_MASS); + const bool doesComputeInertia = AzPhysics::MassComputeFlags::COMPUTE_INERTIA == (flags & AzPhysics::MassComputeFlags::COMPUTE_INERTIA); + + UnitTest::ErrorHandler computeCenterOfMassWarningHandler( + "cannot compute COM"); + UnitTest::ErrorHandler computeMassWarningHandler( + "cannot compute Mass"); + UnitTest::ErrorHandler computeIneriaWarningHandler( + "cannot compute Inertia"); + + AzPhysics::SimulatedBodyHandle rigidBodyhandle = TestUtils::AddKinematicTriangleMeshCubeToScene(m_testSceneHandle, 3.0f, flags); + + EXPECT_TRUE(rigidBodyhandle != AzPhysics::InvalidSimulatedBodyHandle); + EXPECT_EQ(computeCenterOfMassWarningHandler.GetExpectedWarningCount(), doesComputeCenterOfMass ? 1 : 0); + EXPECT_EQ(computeMassWarningHandler.GetExpectedWarningCount(), doesComputeMass ? 1 : 0); + EXPECT_EQ(computeIneriaWarningHandler.GetExpectedWarningCount(), doesComputeInertia ? 1 : 0); + + if (auto* sceneInterface = AZ::Interface::Get()) + { + sceneInterface->RemoveSimulatedBody(m_testSceneHandle, rigidBodyhandle); + } + } + INSTANTIATE_TEST_CASE_P(PhysX, MassPropertiesWithTriangleMesh, + ::testing::ValuesIn(PossibleMassComputeFlags)); // Values for GetMassComputeFlags() } // namespace PhysX diff --git a/Gems/PhysX/Code/Tests/PhysXTestCommon.cpp b/Gems/PhysX/Code/Tests/PhysXTestCommon.cpp index 6186db6f1e..67fe67bc23 100644 --- a/Gems/PhysX/Code/Tests/PhysXTestCommon.cpp +++ b/Gems/PhysX/Code/Tests/PhysXTestCommon.cpp @@ -253,6 +253,36 @@ namespace PhysX return AzPhysics::InvalidSimulatedBodyHandle; } + AzPhysics::SimulatedBodyHandle AddKinematicTriangleMeshCubeToScene(AzPhysics::SceneHandle scene, float halfExtent, AzPhysics::MassComputeFlags massComputeFlags) + { + // Generate input data + VertexIndexData cubeMeshData = GenerateCubeMeshData(halfExtent); + AZStd::vector cookedData; + bool cookingResult = false; + Physics::SystemRequestBus::BroadcastResult(cookingResult, &Physics::SystemRequests::CookTriangleMeshToMemory, + cubeMeshData.first.data(), static_cast(cubeMeshData.first.size()), + cubeMeshData.second.data(), static_cast(cubeMeshData.second.size()), + cookedData); + AZ_Assert(cookingResult, "Failed to cook the cube mesh."); + + // Setup shape & collider configurations + auto shapeConfig = AZStd::make_shared(); + shapeConfig->SetCookedMeshData(cookedData.data(), cookedData.size(), + Physics::CookedMeshShapeConfiguration::MeshType::TriangleMesh); + + AzPhysics::RigidBodyConfiguration rigidBodyConfiguration; + rigidBodyConfiguration.m_kinematic = true; + rigidBodyConfiguration.SetMassComputeFlags(massComputeFlags); + rigidBodyConfiguration.m_colliderAndShapeData = AzPhysics::ShapeColliderPair( + AZStd::make_shared(), shapeConfig); + + if (auto* sceneInterface = AZ::Interface::Get()) + { + return sceneInterface->AddSimulatedBody(scene, &rigidBodyConfiguration); + } + return AzPhysics::InvalidSimulatedBodyHandle; + } + void SetCollisionLayer(EntityPtr& entity, const AZStd::string& layerName, const AZStd::string& colliderTag) { Physics::CollisionFilteringRequestBus::Event(entity->GetId(), &Physics::CollisionFilteringRequests::SetCollisionLayer, layerName, AZ::Crc32(colliderTag.c_str())); diff --git a/Gems/PhysX/Code/Tests/PhysXTestCommon.h b/Gems/PhysX/Code/Tests/PhysXTestCommon.h index ef12ca8350..95859a3f86 100644 --- a/Gems/PhysX/Code/Tests/PhysXTestCommon.h +++ b/Gems/PhysX/Code/Tests/PhysXTestCommon.h @@ -89,6 +89,7 @@ namespace PhysX const AzPhysics::CollisionLayer& layer = AzPhysics::CollisionLayer::Default); AzPhysics::SimulatedBodyHandle AddStaticTriangleMeshCubeToScene(AzPhysics::SceneHandle scene, float halfExtent); + AzPhysics::SimulatedBodyHandle AddKinematicTriangleMeshCubeToScene(AzPhysics::SceneHandle scene, float halfExtent, AzPhysics::MassComputeFlags massComputeFlags); // Collision Filtering void SetCollisionLayer(EntityPtr& entity, const AZStd::string& layerName, const AZStd::string& colliderTag = ""); diff --git a/Gems/WhiteBox/Code/Source/Components/WhiteBoxColliderComponent.cpp b/Gems/WhiteBox/Code/Source/Components/WhiteBoxColliderComponent.cpp index a7e6b6c707..18bddfa26f 100644 --- a/Gems/WhiteBox/Code/Source/Components/WhiteBoxColliderComponent.cpp +++ b/Gems/WhiteBox/Code/Source/Components/WhiteBoxColliderComponent.cpp @@ -91,6 +91,11 @@ namespace WhiteBox bodyConfiguration.m_position = worldTransform.GetTranslation(); bodyConfiguration.m_kinematic = true; // note: this field is ignored in the WhiteBoxBodyType::Static case bodyConfiguration.m_colliderAndShapeData = shape; + // Since the shape used is a triangle mesh the COM, Mass and Inertia + // cannot be computed. Disable them to use default values. + bodyConfiguration.m_computeCenterOfMass = false; + bodyConfiguration.m_computeMass = false; + bodyConfiguration.m_computeInertiaTensor = false; m_simulatedBodyHandle = sceneInterface->AddSimulatedBody(defaultScene, &bodyConfiguration); } break;