You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
o3de/Gems/PhysX/Code/Source/Shape.cpp

467 lines
15 KiB
C++

/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <Source/Shape.h>
#include <AzFramework/Physics/Common/PhysicsSceneQueries.h>
#include <AzFramework/Physics/Material.h>
#include <Common/PhysXSceneQueryHelpers.h>
#include <PhysX/PhysXLocks.h>
#include <PhysX/Utils.h>
#include <Source/Collision.h>
#include <Source/Material.h>
#include <Source/Utils.h>
#include <PhysX/MathConversion.h>
namespace PhysX
{
namespace ShapeConstants
{
// 48 is the number of stacks/slices used when generating mesh geo for spheres in legacy physics
// we default to these values for consistency
constexpr size_t NumStacks = 48;
constexpr size_t NumSlices = 48;
}
Shape::Shape(Shape&& shape)
: m_pxShape(AZStd::move(shape.m_pxShape))
, m_materials(AZStd::move(shape.m_materials))
, m_collisionLayer(AZStd::move(shape.m_collisionLayer))
, m_collisionGroup(AZStd::move(shape.m_collisionGroup))
{
if (m_pxShape)
{
m_pxShape->userData = this;
}
}
Shape& Shape::operator=(Shape&& shape)
{
m_pxShape = AZStd::move(shape.m_pxShape);
m_materials = AZStd::move(shape.m_materials);
m_collisionLayer = AZStd::move(shape.m_collisionLayer);
m_collisionGroup = AZStd::move(shape.m_collisionGroup);
if (m_pxShape)
{
m_pxShape->userData = this;
}
return *this;
}
void Shape::ReleasePxShape(physx::PxShape* shape)
{
if (shape != nullptr)
{
PHYSX_SCENE_WRITE_LOCK(GetScene());
shape->userData = nullptr;
shape->release();
}
}
Shape::Shape(const Physics::ColliderConfiguration& colliderConfiguration, const Physics::ShapeConfiguration& shapeConfiguration)
: m_collisionLayer(colliderConfiguration.m_collisionLayer)
{
if (physx::PxShape* newShape = Utils::CreatePxShapeFromConfig(colliderConfiguration, shapeConfiguration, m_collisionGroup))
{
m_pxShape = PxShapeUniquePtr(newShape, AZStd::bind(&Shape::ReleasePxShape, this, newShape));
m_pxShape->userData = this;
ExtractMaterialsFromPxShape();
m_tag = AZ::Crc32(colliderConfiguration.m_tag);
}
}
Shape::Shape(physx::PxShape* nativeShape)
{
m_pxShape = PxShapeUniquePtr(nativeShape, AZStd::bind(&Shape::ReleasePxShape, this, nativeShape));
m_pxShape->acquireReference();
m_pxShape->userData = this;
ExtractMaterialsFromPxShape();
}
Shape::~Shape()
{
//release the shape here, so when Shape::ReleasePxShape is called to delete the physx::PxShape* we can still acquire the scene lock.
m_pxShape.reset();
m_pxShape = nullptr;
m_attachedActor = nullptr;
}
physx::PxShape* Shape::GetPxShape()
{
if (m_pxShape)
{
return m_pxShape.get();
}
return nullptr;
}
void Shape::SetMaterial(const AZStd::shared_ptr<Physics::Material>& material)
{
if (auto materialWrapper = AZStd::rtti_pointer_cast<PhysX::Material>(material))
{
m_materials.clear();
m_materials.emplace_back(materialWrapper);
BindMaterialsWithPxShape();
}
else
{
AZ_Warning("PhysX Shape", false, "Trying to assign material of unknown type");
}
}
AZStd::shared_ptr<Physics::Material> Shape::GetMaterial() const
{
if (!m_materials.empty())
{
return m_materials[0];
}
return nullptr;
}
void Shape::SetMaterials(const AZStd::vector<AZStd::shared_ptr<PhysX::Material>>& materials)
{
m_materials = materials;
BindMaterialsWithPxShape();
}
void Shape::BindMaterialsWithPxShape()
{
if (m_pxShape)
{
AZStd::vector<physx::PxMaterial*> pxMaterials;
pxMaterials.reserve(m_materials.size());
for (const auto& material : m_materials)
{
pxMaterials.emplace_back(material->GetPxMaterial());
}
AZ_Warning("PhysX Shape", m_materials.size() < std::numeric_limits<AZ::u16>::max(), "Trying to assign too many materials, cutting down");
size_t materialsCount = AZStd::GetMin(m_materials.size(), static_cast<size_t>(std::numeric_limits<AZ::u16>::max()));
m_pxShape->setMaterials(&pxMaterials[0], static_cast<physx::PxU16>(materialsCount));
}
}
void Shape::ExtractMaterialsFromPxShape()
{
if (m_pxShape == nullptr)
{
return;
}
const int BufferSize = 100;
AZ_Warning("PhysX Shape", m_pxShape->getNbMaterials() < BufferSize, "Shape has too many materials, consider increasing the buffer");
physx::PxMaterial* assignedMaterials[BufferSize];
int materialsCount = m_pxShape->getMaterials(assignedMaterials, BufferSize, 0);
m_materials.clear();
m_materials.reserve(materialsCount);
for (int i = 0; i < materialsCount; ++i)
{
if (assignedMaterials[i]->userData == nullptr)
{
AZ_Warning("PhysX Shape", false, "Trying to assign material with no user data. Make sure you are creating materials using MaterialManager");
continue;
}
m_materials.push_back(static_cast<PhysX::Material*>(PhysX::Utils::GetUserData(assignedMaterials[i]))->shared_from_this());
}
}
const AZStd::vector<AZStd::shared_ptr<PhysX::Material>>& Shape::GetMaterials()
{
return m_materials;
}
void Shape::SetCollisionLayer(const AzPhysics::CollisionLayer& layer)
{
m_collisionLayer = layer;
PHYSX_SCENE_WRITE_LOCK(GetScene());
physx::PxFilterData filterData = m_pxShape->getSimulationFilterData();
Collision::SetLayer(layer, filterData);
m_pxShape->setSimulationFilterData(filterData);
m_pxShape->setQueryFilterData(filterData);
}
AzPhysics::CollisionLayer Shape::GetCollisionLayer() const
{
return m_collisionLayer;
}
void Shape::SetCollisionGroup(const AzPhysics::CollisionGroup& group)
{
m_collisionGroup = group;
PHYSX_SCENE_WRITE_LOCK(GetScene());
physx::PxFilterData filterData = m_pxShape->getSimulationFilterData();
Collision::SetGroup(m_collisionGroup, filterData);
m_pxShape->setSimulationFilterData(filterData);
m_pxShape->setQueryFilterData(filterData);
}
AzPhysics::CollisionGroup Shape::GetCollisionGroup() const
{
return m_collisionGroup;
}
void Shape::SetName(const char* name)
{
if (m_pxShape)
{
m_pxShape->setName(name);
}
}
void Shape::SetLocalPose(const AZ::Vector3& offset, const AZ::Quaternion& rotation)
{
PHYSX_SCENE_WRITE_LOCK(GetScene());
physx::PxTransform pxShapeTransform = PxMathConvert(offset, rotation);
AZ_Warning("Physics::Shape", m_pxShape->isExclusive(), "Non-exclusive shapes are not mutable after they're attached to a body.");
if (m_pxShape->getGeometryType() == physx::PxGeometryType::eCAPSULE)
{
physx::PxQuat lyToPxRotation(AZ::Constants::HalfPi, physx::PxVec3(0.0f, 1.0f, 0.0f));
pxShapeTransform.q *= lyToPxRotation;
}
m_pxShape->setLocalPose(pxShapeTransform);
}
AZStd::pair<AZ::Vector3, AZ::Quaternion> Shape::GetLocalPose() const
{
PHYSX_SCENE_READ_LOCK(GetScene());
physx::PxTransform pose = m_pxShape->getLocalPose();
return { PxMathConvert(pose.p), PxMathConvert(pose.q) };
}
float Shape::GetRestOffset() const
{
return m_pxShape->getRestOffset();
}
float Shape::GetContactOffset() const
{
return m_pxShape->getContactOffset();
}
void Shape::SetRestOffset(float restOffset)
{
float contactOffset = GetContactOffset();
if (restOffset >= contactOffset)
{
AZ_Error("PhysX Shape", false, "Requested rest offset (%e) must be less than contact offset (%e).",
restOffset, contactOffset);
return;
}
m_pxShape->setRestOffset(restOffset);
}
void Shape::SetContactOffset(float contactOffset)
{
if (contactOffset <= 0.0f)
{
AZ_Error("PhysX Shape", false, "Requested contact offset (%e) must exceed 0.");
return;
}
float restOffset = GetRestOffset();
if (contactOffset <= restOffset)
{
AZ_Error("PhysX Shape", false, "Requested contact offset (%e) must exceed rest offset (%e).",
contactOffset, restOffset);
return;
}
m_pxShape->setContactOffset(contactOffset);
}
void* Shape::GetNativePointer()
{
return m_pxShape.get();
}
AZ::Crc32 Shape::GetTag() const
{
return m_tag;
}
bool Shape::IsTrigger() const
{
if (m_pxShape->getFlags() & physx::PxShapeFlag::eTRIGGER_SHAPE)
{
return true;
}
return false;
}
void Shape::AttachedToActor(void* actor)
{
physx::PxActor* pxActor = static_cast<physx::PxActor*>(actor);
if (pxActor != nullptr)
{
m_attachedActor = pxActor;
}
}
void Shape::DetachedFromActor()
{
m_attachedActor = nullptr;
}
AzPhysics::SceneQueryHit Shape::RayCastInternal(const AzPhysics::RayCastRequest& worldSpaceRequest, const physx::PxTransform& pose)
{
if (const bool shouldCollide = worldSpaceRequest.m_collisionGroup.GetMask() & m_collisionLayer.GetMask();
!shouldCollide)
{
return AzPhysics::SceneQueryHit();
}
const physx::PxVec3 start = PxMathConvert(worldSpaceRequest.m_start);
const physx::PxVec3 unitDir = PxMathConvert(worldSpaceRequest.m_direction);
const physx::PxU32 maxHits = 1;
const physx::PxHitFlags hitFlags = SceneQueryHelpers::GetPxHitFlags(worldSpaceRequest.m_hitFlags);
physx::PxRaycastHit hitInfo;
const bool hit = physx::PxGeometryQuery::raycast(start, unitDir, m_pxShape->getGeometry().any(), pose,
worldSpaceRequest.m_distance, hitFlags, maxHits, &hitInfo);
if (hit)
{
// Fill actor and shape, as they won't be filled from PxGeometryQuery
hitInfo.actor = static_cast<physx::PxRigidActor*>(m_attachedActor); // This cast is safe since GetHitFromPxHit() only uses PxActor:: functions
hitInfo.shape = GetPxShape();
return SceneQueryHelpers::GetHitFromPxHit(hitInfo);
}
return AzPhysics::SceneQueryHit();
}
AzPhysics::SceneQueryHit Shape::RayCast(const AzPhysics::RayCastRequest& worldSpaceRequest, const AZ::Transform& worldTransform)
{
physx::PxTransform localPose;
{
PHYSX_SCENE_READ_LOCK(GetScene());
localPose = m_pxShape->getLocalPose();
}
return RayCastInternal(worldSpaceRequest, PxMathConvert(worldTransform) * localPose);
}
AzPhysics::SceneQueryHit Shape::RayCastLocal(const AzPhysics::RayCastRequest& localSpaceRequest)
{
physx::PxTransform localPose;
{
PHYSX_SCENE_READ_LOCK(GetScene());
localPose = m_pxShape->getLocalPose();
}
return RayCastInternal(localSpaceRequest, localPose);
}
AZ::Aabb Shape::GetAabb(const AZ::Transform& worldTransform) const
{
physx::PxTransform localPose;
{
PHYSX_SCENE_READ_LOCK(GetScene());
localPose = m_pxShape->getLocalPose();
}
return PxMathConvert(physx::PxGeometryQuery::getWorldBounds(m_pxShape->getGeometry().any(), PxMathConvert(worldTransform) * localPose, 1.0f));
}
AZ::Aabb Shape::GetAabbLocal() const
{
physx::PxTransform localPose;
{
PHYSX_SCENE_READ_LOCK(GetScene());
localPose = m_pxShape->getLocalPose();
}
return PxMathConvert(physx::PxGeometryQuery::getWorldBounds(m_pxShape->getGeometry().any(), localPose, 1.0f));
}
physx::PxScene* Shape::GetScene() const
{
if (m_attachedActor != nullptr)
{
return m_attachedActor->getScene();
}
return nullptr;
}
void Shape::GetGeometry(AZStd::vector<AZ::Vector3>& vertices, AZStd::vector<AZ::u32>& indices, AZ::Aabb* optionalBounds)
{
if (!m_pxShape)
{
return;
}
PHYSX_SCENE_READ_LOCK(GetScene());
if (m_pxShape->getGeometryType() == physx::PxGeometryType::eTRIANGLEMESH)
{
physx::PxTriangleMeshGeometry geometry{};
if (m_pxShape->getTriangleMeshGeometry(geometry) && geometry.triangleMesh && geometry.isValid())
{
Utils::Geometry::GetTriangleMeshGeometry(geometry, vertices, indices);
}
}
else if (m_pxShape->getGeometryType() == physx::PxGeometryType::eCONVEXMESH)
{
physx::PxConvexMeshGeometry geometry{};
if (m_pxShape->getConvexMeshGeometry(geometry) && geometry.convexMesh && geometry.isValid())
{
Utils::Geometry::GetConvexMeshGeometry(geometry, vertices, indices);
}
}
else if (m_pxShape->getGeometryType() == physx::PxGeometryType::eHEIGHTFIELD)
{
physx::PxHeightFieldGeometry geometry{};
if (m_pxShape->getHeightFieldGeometry(geometry) && geometry.heightField && geometry.isValid())
{
Utils::Geometry::GetHeightFieldGeometry(geometry, vertices, indices, optionalBounds);
}
}
else if (m_pxShape->getGeometryType() == physx::PxGeometryType::eBOX)
{
physx::PxBoxGeometry geometry{};
if (m_pxShape->getBoxGeometry(geometry) && geometry.isValid())
{
Utils::Geometry::GetBoxGeometry(geometry, vertices, indices);
}
}
else if (m_pxShape->getGeometryType() == physx::PxGeometryType::eSPHERE)
{
physx::PxSphereGeometry geometry{};
if (m_pxShape->getSphereGeometry(geometry) && geometry.isValid())
{
Utils::Geometry::GetSphereGeometry(geometry, vertices, indices, ShapeConstants::NumStacks, ShapeConstants::NumSlices);
}
}
else if (m_pxShape->getGeometryType() == physx::PxGeometryType::eCAPSULE)
{
physx::PxCapsuleGeometry geometry{};
if (m_pxShape->getCapsuleGeometry(geometry) && geometry.isValid())
{
Utils::Geometry::GetCapsuleGeometry(geometry, vertices, indices, ShapeConstants::NumStacks, ShapeConstants::NumSlices);
}
}
else
{
AZ_TracePrintf("Shape", "GetGeometry for PxGeometryType %d is not supported", static_cast<int>(m_pxShape->getGeometryType()));
}
}
}