Files
o3de/Gems/PhysX/Code/Source/Utils.cpp
T
Aaron Ruiz Mora 00e860f326 Physics material system for spectra launch
- Invalidate 'Physics Materials From Mesh' boolean from collider component
- Removed material library from material selector. Default material library will always be used instead.
- Marking failing automated test as xfail
- Added default material to physics configuration.
- Moved material library asset from physx configuration to physics configuration, as it doesn't need to be physx specific.
- Refactor physics material system having into account that there is only one material library in the project.
- Renaming code from DefaultMaterialLibrary to MaterialLibrary.
- All queries about physics materials unified under PhysicsMaterialRequests bus.
- PhysXSystem only manages the material library asset.
- Saving and reloading the same physics material asset with different content didn't trigger a events that the material library has changed.
- Changing Physics Material Request interface to use shared_ptr instead of weak_ptr to be simpler to handle the returned materials and having a more consistent code.
- Refactored Material Manager to improve its implementation. Still following the same approach of "creating materials on the fly as they are requested", but now it's doing it consistently across the interface, with private helpers functions FindOrCreateMaterial that simplify vastly the implementation.
- Material Manager now listens to change event of material library asset and default material configuration so it updates its materials accordingly.
- Complete Material move constructor and operator.
2021-05-28 20:16:25 +01:00

1539 lines
72 KiB
C++

/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <PhysX_precompiled.h>
#include <AzCore/Component/TransformBus.h>
#include <AzCore/Component/NonUniformScaleBus.h>
#include <AzCore/EBus/Results.h>
#include <AzCore/Interface/Interface.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/Serialization/Utils.h>
#include <AzCore/Component/TransformBus.h>
#include <AzCore/Math/SimdMath.h>
#include <AzFramework/Physics/ShapeConfiguration.h>
#include <AzFramework/Physics/SystemBus.h>
#include <AzFramework/Physics/Collision/CollisionGroups.h>
#include <AzFramework/Physics/Collision/CollisionLayers.h>
#include <AzFramework/Physics/Configuration/RigidBodyConfiguration.h>
#include <AzFramework/Physics/Configuration/StaticRigidBodyConfiguration.h>
#include <AzFramework/Physics/PhysicsScene.h>
#include <AzFramework/Physics/PhysicsSystem.h>
#include <AzFramework/Physics/SimulatedBodies/StaticRigidBody.h>
#include <PhysX/ColliderShapeBus.h>
#include <PhysX/SystemComponentBus.h>
#include <PhysX/MeshAsset.h>
#include <PhysX/Utils.h>
#include <Source/SystemComponent.h>
#include <Source/Collision.h>
#include <Source/Pipeline/MeshAssetHandler.h>
#include <Source/Shape.h>
#include <Source/StaticRigidBodyComponent.h>
#include <Source/RigidBodyStatic.h>
#include <Source/Joint.h>
#include <Source/Utils.h>
#include <PhysX/PhysXLocks.h>
namespace PhysX
{
namespace Utils
{
physx::PxBase* CreateNativeMeshObjectFromCookedData(const AZStd::vector<AZ::u8>& cookedData,
Physics::CookedMeshShapeConfiguration::MeshType meshType)
{
// PxDefaultMemoryInputData only accepts a non-const U8* pointer however keeps it as const U8* inside.
// Hence we do const_cast here but it's safe to assume the data won't be modifed.
physx::PxDefaultMemoryInputData inpStream(
const_cast<physx::PxU8*>(cookedData.data()),
static_cast<physx::PxU32>(cookedData.size()));
if (meshType == Physics::CookedMeshShapeConfiguration::MeshType::Convex)
{
return PxGetPhysics().createConvexMesh(inpStream);
}
else
{
return PxGetPhysics().createTriangleMesh(inpStream);
}
}
bool CreatePxGeometryFromConfig(const Physics::ShapeConfiguration& shapeConfiguration, physx::PxGeometryHolder& pxGeometry)
{
if (!shapeConfiguration.m_scale.IsGreaterThan(AZ::Vector3::CreateZero()))
{
AZ_Error("PhysX Utils", false, "Negative or zero values are invalid for shape configuration scale values %s",
ToString(shapeConfiguration.m_scale).c_str());
return false;
}
auto shapeType = shapeConfiguration.GetShapeType();
switch (shapeType)
{
case Physics::ShapeType::Sphere:
{
const Physics::SphereShapeConfiguration& sphereConfig = static_cast<const Physics::SphereShapeConfiguration&>(shapeConfiguration);
if (sphereConfig.m_radius <= 0.0f)
{
AZ_Error("PhysX Utils", false, "Invalid radius value: %f", sphereConfig.m_radius);
return false;
}
pxGeometry.storeAny(physx::PxSphereGeometry(sphereConfig.m_radius * shapeConfiguration.m_scale.GetMaxElement()));
break;
}
case Physics::ShapeType::Box:
{
const Physics::BoxShapeConfiguration& boxConfig = static_cast<const Physics::BoxShapeConfiguration&>(shapeConfiguration);
if (!boxConfig.m_dimensions.IsGreaterThan(AZ::Vector3::CreateZero()))
{
AZ_Error("PhysX Utils", false, "Negative or zero values are invalid for box dimensions %s",
ToString(boxConfig.m_dimensions).c_str());
return false;
}
pxGeometry.storeAny(physx::PxBoxGeometry(PxMathConvert(boxConfig.m_dimensions * 0.5f * shapeConfiguration.m_scale)));
break;
}
case Physics::ShapeType::Capsule:
{
const Physics::CapsuleShapeConfiguration& capsuleConfig = static_cast<const Physics::CapsuleShapeConfiguration&>(shapeConfiguration);
float height = capsuleConfig.m_height * capsuleConfig.m_scale.GetZ();
float radius = capsuleConfig.m_radius * AZ::GetMax(capsuleConfig.m_scale.GetX(), capsuleConfig.m_scale.GetY());
if (height <= 0.0f || radius <= 0.0f)
{
AZ_Error("PhysX Utils", false, "Negative or zero values are invalid for capsule dimensions (height: %f, radius: %f)",
capsuleConfig.m_height, capsuleConfig.m_radius);
return false;
}
float halfHeight = 0.5f * height - radius;
if (halfHeight <= 0.0f)
{
AZ_Warning("PhysX", halfHeight < 0.0f, "Height must exceed twice the radius in capsule configuration (height: %f, radius: %f)",
capsuleConfig.m_height, capsuleConfig.m_radius);
halfHeight = std::numeric_limits<float>::epsilon();
}
pxGeometry.storeAny(physx::PxCapsuleGeometry(radius, halfHeight));
break;
}
case Physics::ShapeType::Native:
{
const Physics::NativeShapeConfiguration& nativeShapeConfig = static_cast<const Physics::NativeShapeConfiguration&>(shapeConfiguration);
AZ::Vector3 scale = nativeShapeConfig.m_nativeShapeScale * nativeShapeConfig.m_scale;
physx::PxBase* meshData = reinterpret_cast<physx::PxBase*>(nativeShapeConfig.m_nativeShapePtr);
return MeshDataToPxGeometry(meshData, pxGeometry, scale);
}
case Physics::ShapeType::CookedMesh:
{
const Physics::CookedMeshShapeConfiguration& cookedMeshShapeConfig =
static_cast<const Physics::CookedMeshShapeConfiguration&>(shapeConfiguration);
physx::PxBase* nativeMeshObject = nullptr;
// Use the cached mesh object if it is there, otherwise create one and save in the shape configuration
if (cookedMeshShapeConfig.GetCachedNativeMesh())
{
nativeMeshObject = static_cast<physx::PxBase*>(cookedMeshShapeConfig.GetCachedNativeMesh());
}
else
{
nativeMeshObject = CreateNativeMeshObjectFromCookedData(
cookedMeshShapeConfig.GetCookedMeshData(),
cookedMeshShapeConfig.GetMeshType());
if (nativeMeshObject)
{
cookedMeshShapeConfig.SetCachedNativeMesh(nativeMeshObject);
}
else
{
AZ_Warning("PhysX Rigid Body", false,
"Unable to create a mesh object from the CookedMeshShapeConfiguration buffer. "
"Please check if the data was cooked correctly.");
return false;
}
}
return MeshDataToPxGeometry(nativeMeshObject, pxGeometry, cookedMeshShapeConfig.m_scale);
}
case Physics::ShapeType::PhysicsAsset:
{
AZ_Assert(false,
"CreatePxGeometryFromConfig: Cannot pass PhysicsAsset configuration since it is a collection of shapes. "
"Please iterate over m_colliderShapes in the asset and call this function for each of them.");
return false;
}
default:
AZ_Warning("PhysX Rigid Body", false, "Shape not supported in PhysX. Shape Type: %d", shapeType);
return false;
}
return true;
}
physx::PxShape* CreatePxShapeFromConfig(const Physics::ColliderConfiguration& colliderConfiguration,
const Physics::ShapeConfiguration& shapeConfiguration, AzPhysics::CollisionGroup& assignedCollisionGroup)
{
AZStd::vector<physx::PxMaterial*> materials;
MaterialManagerRequestsBus::Broadcast(&MaterialManagerRequestsBus::Events::GetPxMaterials, colliderConfiguration.m_materialSelection, materials);
if (materials.empty())
{
AZStd::shared_ptr<Material> defaultMaterial = nullptr;
MaterialManagerRequestsBus::BroadcastResult(defaultMaterial, &MaterialManagerRequestsBus::Events::GetDefaultMaterial);
if (!defaultMaterial)
{
AZ_Error("PhysX", false, "Material array can't be empty!");
return nullptr;
}
materials.push_back(defaultMaterial->GetPxMaterial());
}
physx::PxGeometryHolder pxGeomHolder;
if (Utils::CreatePxGeometryFromConfig(shapeConfiguration, pxGeomHolder))
{
auto materialsCount = static_cast<physx::PxU16>(materials.size());
physx::PxShape* shape = PxGetPhysics().createShape(pxGeomHolder.any(), materials.begin(), materialsCount, colliderConfiguration.m_isExclusive);
if (shape)
{
AzPhysics::CollisionGroup collisionGroup;
Physics::CollisionRequestBus::BroadcastResult(collisionGroup, &Physics::CollisionRequests::GetCollisionGroupById, colliderConfiguration.m_collisionGroupId);
physx::PxFilterData filterData = PhysX::Collision::CreateFilterData(colliderConfiguration.m_collisionLayer, collisionGroup);
shape->setSimulationFilterData(filterData);
shape->setQueryFilterData(filterData);
// Do custom logic for specific shape types
if (pxGeomHolder.getType() == physx::PxGeometryType::eCAPSULE)
{
// PhysX capsules are oriented around x by default.
physx::PxQuat pxQuat(AZ::Constants::HalfPi, physx::PxVec3(0.0f, 1.0f, 0.0f));
shape->setLocalPose(physx::PxTransform(pxQuat));
}
// Handle a possible misconfiguration when a shape is set to be both simulated & trigger. This is illegal in PhysX.
shape->setFlag(physx::PxShapeFlag::eSIMULATION_SHAPE, colliderConfiguration.m_isSimulated && !colliderConfiguration.m_isTrigger);
shape->setFlag(physx::PxShapeFlag::eSCENE_QUERY_SHAPE, colliderConfiguration.m_isInSceneQueries);
shape->setFlag(physx::PxShapeFlag::eTRIGGER_SHAPE, colliderConfiguration.m_isTrigger);
shape->setRestOffset(colliderConfiguration.m_restOffset);
shape->setContactOffset(colliderConfiguration.m_contactOffset);
physx::PxTransform pxShapeTransform = PxMathConvert(colliderConfiguration.m_position, colliderConfiguration.m_rotation);
shape->setLocalPose(pxShapeTransform * shape->getLocalPose());
assignedCollisionGroup = collisionGroup;
return shape;
}
else
{
AZ_Error("PhysX Rigid Body", false, "Failed to create shape.");
return nullptr;
}
}
return nullptr;
}
AzPhysics::Scene* GetDefaultScene()
{
AzPhysics::SceneHandle sceneHandle;
Physics::DefaultWorldBus::BroadcastResult(sceneHandle, &Physics::DefaultWorldRequests::GetDefaultSceneHandle);
if (auto* physicsSystem = AZ::Interface<AzPhysics::SystemInterface>::Get())
{
if (auto* scene = physicsSystem->GetScene(sceneHandle))
{
return scene;
}
}
return nullptr;
}
AZStd::optional<Physics::CookedMeshShapeConfiguration> CreatePxCookedMeshConfiguration(const AZStd::vector<AZ::Vector3>& points, const AZ::Vector3& scale)
{
Physics::CookedMeshShapeConfiguration shapeConfig;
AZStd::vector<AZ::u8> cookedData;
bool cookingResult = false;
Physics::SystemRequestBus::BroadcastResult(cookingResult, &Physics::SystemRequests::CookConvexMeshToMemory,
points.data(), aznumeric_cast<AZ::u32>(points.size()), cookedData);
shapeConfig.SetCookedMeshData(cookedData.data(), cookedData.size(),
Physics::CookedMeshShapeConfiguration::MeshType::Convex);
shapeConfig.m_scale = scale;
if (!cookingResult)
{
AZ_Error("PhysX", false, "PhysX cooking of mesh data failed");
return {};
}
return shapeConfig;
}
bool IsPrimitiveShape(const Physics::ShapeConfiguration& shapeConfig)
{
const Physics::ShapeType shapeType = shapeConfig.GetShapeType();
return
shapeType == Physics::ShapeType::Box ||
shapeType == Physics::ShapeType::Capsule ||
shapeType == Physics::ShapeType::Sphere;
}
AZStd::optional<Physics::CookedMeshShapeConfiguration> CreateConvexFromPrimitive(
const Physics::ColliderConfiguration& colliderConfig,
const Physics::ShapeConfiguration& primitiveShapeConfig, AZ::u8 subdivisionLevel,
const AZ::Vector3& scale)
{
AZ::u8 subdivisionLevelClamped = AZ::GetClamp(subdivisionLevel, MinCapsuleSubdivisionLevel, MaxCapsuleSubdivisionLevel);
auto applyColliderOffset = [&colliderConfig](const AZ::Vector3 point) {
return colliderConfig.m_rotation.TransformVector(point) + colliderConfig.m_position;
};
auto shapeType = primitiveShapeConfig.GetShapeType();
switch (shapeType)
{
case Physics::ShapeType::Box:
{
auto boxConfig = static_cast<const Physics::BoxShapeConfiguration&>(primitiveShapeConfig);
AZStd::vector<AZ::Vector3> points;
points.reserve(8);
const float x = 0.5f * boxConfig.m_dimensions.GetX();
const float y = 0.5f * boxConfig.m_dimensions.GetY();
const float z = 0.5f * boxConfig.m_dimensions.GetZ();
points.push_back(applyColliderOffset(AZ::Vector3(-x, -y, -z)));
points.push_back(applyColliderOffset(AZ::Vector3(-x, -y, +z)));
points.push_back(applyColliderOffset(AZ::Vector3(-x, +y, -z)));
points.push_back(applyColliderOffset(AZ::Vector3(-x, +y, +z)));
points.push_back(applyColliderOffset(AZ::Vector3(+x, -y, -z)));
points.push_back(applyColliderOffset(AZ::Vector3(+x, -y, +z)));
points.push_back(applyColliderOffset(AZ::Vector3(+x, +y, -z)));
points.push_back(applyColliderOffset(AZ::Vector3(+x, +y, +z)));
return CreatePxCookedMeshConfiguration(points, scale);
}
break;
case Physics::ShapeType::Capsule:
{
auto capsuleConfig = static_cast<const Physics::CapsuleShapeConfiguration&>(primitiveShapeConfig);
const AZ::u8 numLayers = subdivisionLevelClamped;
const AZ::u8 numPerLayer = 4 * subdivisionLevelClamped;
AZStd::vector<AZ::Vector3> points;
points.reserve(2 * numLayers * numPerLayer + 2);
points.push_back(applyColliderOffset(AZ::Vector3::CreateAxisZ(0.5f * capsuleConfig.m_height)));
points.push_back(applyColliderOffset(AZ::Vector3::CreateAxisZ(-0.5f * capsuleConfig.m_height)));
for (AZ::u8 layerIndex = 0; layerIndex < numLayers; layerIndex++)
{
const float theta = (layerIndex + 1) * AZ::Constants::HalfPi / aznumeric_cast<float>(numLayers);
const float layerRadius = capsuleConfig.m_radius * AZ::Sin(theta);
const float layerHeight = 0.5f * capsuleConfig.m_height + capsuleConfig.m_radius * (AZ::Cos(theta) - 1.0f);
for (AZ::u8 radialIndex = 0; radialIndex < numPerLayer; radialIndex++)
{
const float phi = radialIndex * AZ::Constants::TwoPi / aznumeric_cast<float>(numPerLayer);
points.push_back(applyColliderOffset(AZ::Vector3(
layerRadius * AZ::Cos(phi), layerRadius * AZ::Sin(phi), layerHeight)));
points.push_back(applyColliderOffset(AZ::Vector3(
layerRadius * AZ::Cos(phi), layerRadius * AZ::Sin(phi), -layerHeight)));
}
}
return CreatePxCookedMeshConfiguration(points, scale);
}
break;
case Physics::ShapeType::Sphere:
{
auto sphereConfig = static_cast<const Physics::SphereShapeConfiguration&>(primitiveShapeConfig);
const AZ::u8 numLayers = 2 * subdivisionLevelClamped;
const AZ::u8 numPerLayer = 4 * subdivisionLevelClamped;
AZStd::vector<AZ::Vector3> points;
points.reserve((numLayers - 1) * numPerLayer + 2);
points.push_back(applyColliderOffset(AZ::Vector3::CreateAxisZ(sphereConfig.m_radius)));
points.push_back(applyColliderOffset(AZ::Vector3::CreateAxisZ(-sphereConfig.m_radius)));
for (AZ::u8 layerIndex = 1; layerIndex < numLayers; layerIndex++)
{
const float theta = layerIndex * AZ::Constants::Pi / aznumeric_cast<float>(numLayers);
const float layerRadius = sphereConfig.m_radius * AZ::Sin(theta);
const float layerHeight = sphereConfig.m_radius * AZ::Cos(theta);
for (AZ::u8 radialIndex = 0; radialIndex < numPerLayer; radialIndex++)
{
const float phi = radialIndex * AZ::Constants::TwoPi / aznumeric_cast<float>(numPerLayer);
points.push_back(applyColliderOffset(AZ::Vector3(
layerRadius * AZ::Cos(phi), layerRadius * AZ::Sin(phi), layerHeight)));
}
}
return CreatePxCookedMeshConfiguration(points, scale);
}
break;
default:
AZ_Error("PhysX Utils", false, "CreateConvexFromPrimitive was called with a non-primitive shape configuration.");
return {};
}
}
// Returns a point list of the frustum extents based on the supplied frustum parameters.
AZStd::optional<AZStd::vector<AZ::Vector3>> CreatePointsAtFrustumExtents(float height, float bottomRadius, float topRadius, AZ::u8 subdivisions)
{
AZStd::vector<AZ::Vector3> points;
if (height <= 0.0f)
{
AZ_Error("PhysX", false, "Frustum height %f must be greater than 0.", height);
return {};
}
if (bottomRadius < 0.0f)
{
AZ_Error("PhysX", false, "Frustum bottom radius %f must be greater or equal to 0.", bottomRadius);
return {};
}
else if (topRadius < 0.0f)
{
AZ_Error("PhysX", false, "Frustum top radius %f must be greater or equal to 0.", topRadius);
return {};
}
else if (bottomRadius == 0.0f && topRadius == 0.0f)
{
AZ_Error("PhysX", false, "Either frustum bottom radius or top radius must be greater than to 0.");
return {};
}
if (subdivisions < MinFrustumSubdivisions || subdivisions > MaxFrustumSubdivisions)
{
AZ_Error("PhysX", false, "Frustum subdivision count %u is not in [%u, %u] range", subdivisions, MinFrustumSubdivisions, MaxFrustumSubdivisions);
return {};
}
points.reserve(subdivisions * 2);
const float halfHeight = height * 0.5f;
const double step = AZ::Constants::TwoPi / aznumeric_cast<double>(subdivisions);
for (double rad = 0; rad < AZ::Constants::TwoPi; rad += step)
{
float x = aznumeric_cast<float>(std::cos(rad));
float y = aznumeric_cast<float>(std::sin(rad));
points.emplace_back(x * topRadius, y * topRadius, +halfHeight);
points.emplace_back(x * bottomRadius, y * bottomRadius, -halfHeight);
}
return points;
}
AZStd::string ConvexCookingResultToString(physx::PxConvexMeshCookingResult::Enum convexCookingResultCode)
{
static const AZStd::string resultToString[] = { "eSUCCESS", "eZERO_AREA_TEST_FAILED", "ePOLYGONS_LIMIT_REACHED", "eFAILURE" };
AZ_PUSH_DISABLE_WARNING(, "-Wtautological-constant-out-of-range-compare")
if (AZ_ARRAY_SIZE(resultToString) > convexCookingResultCode)
AZ_POP_DISABLE_WARNING
{
return resultToString[convexCookingResultCode];
}
else
{
AZ_Error("PhysX", false, "Unknown convex cooking result code: %i", convexCookingResultCode);
return "";
}
}
AZStd::string TriMeshCookingResultToString(physx::PxTriangleMeshCookingResult::Enum triangleCookingResultCode)
{
static const AZStd::string resultToString[] = { "eSUCCESS", "eLARGE_TRIANGLE", "eFAILURE" };
AZ_PUSH_DISABLE_WARNING(, "-Wtautological-constant-out-of-range-compare")
if (AZ_ARRAY_SIZE(resultToString) > triangleCookingResultCode)
AZ_POP_DISABLE_WARNING
{
return resultToString[triangleCookingResultCode];
}
else
{
AZ_Error("PhysX", false, "Unknown trimesh cooking result code: %i", triangleCookingResultCode);
return "";
}
}
bool WriteCookedMeshToFile(const AZStd::string& filePath, const AZStd::vector<AZ::u8>& physxData,
Physics::CookedMeshShapeConfiguration::MeshType meshType)
{
Pipeline::MeshAssetData assetData;
AZStd::shared_ptr<Pipeline::AssetColliderConfiguration> colliderConfig;
AZStd::shared_ptr<Physics::CookedMeshShapeConfiguration> shapeConfig = AZStd::make_shared<Physics::CookedMeshShapeConfiguration>();
shapeConfig->SetCookedMeshData(physxData.data(), physxData.size(), meshType);
assetData.m_colliderShapes.emplace_back(colliderConfig, shapeConfig);
return Utils::WriteCookedMeshToFile(filePath, assetData);
}
bool WriteCookedMeshToFile(const AZStd::string& filePath, const Pipeline::MeshAssetData& assetData)
{
AZ::SerializeContext* serializeContext = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationRequests::GetSerializeContext);
return AZ::Utils::SaveObjectToFile(filePath, AZ::DataStream::ST_BINARY, &assetData, serializeContext);
}
bool CookConvexToPxOutputStream(const AZ::Vector3* vertices, AZ::u32 vertexCount, physx::PxOutputStream& stream)
{
physx::PxCooking* cooking = nullptr;
SystemRequestsBus::BroadcastResult(cooking, &SystemRequests::GetCooking);
physx::PxConvexMeshDesc convexDesc;
convexDesc.points.count = vertexCount;
convexDesc.points.stride = sizeof(AZ::Vector3);
convexDesc.points.data = vertices;
convexDesc.flags = physx::PxConvexFlag::eCOMPUTE_CONVEX;
physx::PxConvexMeshCookingResult::Enum resultCode = physx::PxConvexMeshCookingResult::eSUCCESS;
bool result = cooking->cookConvexMesh(convexDesc, stream, &resultCode);
AZ_Error("PhysX", result,
"CookConvexToPxOutputStream: Failed to cook convex mesh. Please check the data is correct. Error: %s",
Utils::ConvexCookingResultToString(resultCode).c_str());
return result;
}
bool CookTriangleMeshToToPxOutputStream(const AZ::Vector3* vertices, AZ::u32 vertexCount,
const AZ::u32* indices, AZ::u32 indexCount, physx::PxOutputStream& stream)
{
physx::PxCooking* cooking = nullptr;
SystemRequestsBus::BroadcastResult(cooking, &SystemRequests::GetCooking);
// Validate indices size
AZ_Error("PhysX", indexCount % 3 == 0, "Number of indices must be a multiple of 3.");
physx::PxTriangleMeshDesc meshDesc;
meshDesc.points.count = vertexCount;
meshDesc.points.stride = sizeof(AZ::Vector3);
meshDesc.points.data = vertices;
meshDesc.triangles.count = indexCount / 3;
meshDesc.triangles.stride = sizeof(AZ::u32) * 3;
meshDesc.triangles.data = indices;
physx::PxTriangleMeshCookingResult::Enum resultCode = physx::PxTriangleMeshCookingResult::eSUCCESS;
bool result = cooking->cookTriangleMesh(meshDesc, stream, &resultCode);
AZ_Error("PhysX", result,
"CookTriangleMeshToToPxOutputStream: Failed to cook triangle mesh. Please check the data is correct. Error: %s.",
Utils::TriMeshCookingResultToString(resultCode).c_str());
return result;
}
bool MeshDataToPxGeometry(physx::PxBase* meshData, physx::PxGeometryHolder& pxGeometry, const AZ::Vector3& scale)
{
if (meshData)
{
if (meshData->is<physx::PxTriangleMesh>())
{
pxGeometry.storeAny(physx::PxTriangleMeshGeometry(reinterpret_cast<physx::PxTriangleMesh*>(meshData), physx::PxMeshScale(PxMathConvert(scale))));
}
else
{
pxGeometry.storeAny(physx::PxConvexMeshGeometry(reinterpret_cast<physx::PxConvexMesh*>(meshData), physx::PxMeshScale(PxMathConvert(scale))));
}
return true;
}
else
{
AZ_Error("PhysXUtils::MeshDataToPxGeometry", false, "Mesh data is null.");
return false;
}
}
bool ReadFile(const AZStd::string& path, AZStd::vector<uint8_t>& buffer)
{
AZ::IO::FileIOBase* fileIO = AZ::IO::FileIOBase::GetInstance();
if (!fileIO)
{
AZ_Warning("PhysXUtils::ReadFile", false, "No File System");
return false;
}
// Open file
AZ::IO::HandleType file;
if (!fileIO->Open(path.c_str(), AZ::IO::OpenMode::ModeRead, file))
{
AZ_Warning("PhysXUtils::ReadFile", false, "Failed to open file:%s", path.c_str());
return false;
}
// Get file size, we want to read the whole thing in one go
AZ::u64 fileSize;
if (!fileIO->Size(file, fileSize))
{
AZ_Warning("PhysXUtils::ReadFile", false, "Failed to read file size:%s", path.c_str());
fileIO->Close(file);
return false;
}
if (fileSize <= 0)
{
AZ_Warning("PhysXUtils::ReadFile", false, "File is empty:%s", path.c_str());
fileIO->Close(file);
return false;
}
buffer.resize(fileSize);
AZ::u64 bytesRead = 0;
bool failOnFewerThanSizeBytesRead = false;
if (!fileIO->Read(file, &buffer[0], fileSize, failOnFewerThanSizeBytesRead, &bytesRead))
{
AZ_Warning("PhysXUtils::ReadFile", false, "Failed to read file:%s", path.c_str());
fileIO->Close(file);
return false;
}
fileIO->Close(file);
return true;
}
AZStd::string ReplaceAll(AZStd::string str, const AZStd::string& fromString, const AZStd::string& toString) {
size_t positionBegin = 0;
while ((positionBegin = str.find(fromString, positionBegin)) != AZStd::string::npos)
{
str.replace(positionBegin, fromString.length(), toString);
positionBegin += toString.length();
}
return str;
}
void WarnEntityNames(const AZStd::vector<AZ::EntityId>& entityIds, [[maybe_unused]] const char* category, const char* message)
{
AZStd::string messageOutput = message;
messageOutput += "\n";
for (const auto& entityId : entityIds)
{
AZ::Entity* entity = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(entity, &AZ::ComponentApplicationRequests::FindEntity, entityId);
if (entity)
{
messageOutput += entity->GetName() + "\n";
}
}
AZStd::string percentageSymbol("%");
AZStd::string percentageReplace("%%"); //Replacing % with %% serves to escape the % character when printing out the entity names in printf style.
messageOutput = ReplaceAll(messageOutput, percentageSymbol, percentageReplace);
AZ_Warning(category, false, messageOutput.c_str());
}
AZ::Transform GetColliderLocalTransform(const AZ::Vector3& colliderRelativePosition,
const AZ::Quaternion& colliderRelativeRotation)
{
return AZ::Transform::CreateFromQuaternionAndTranslation(colliderRelativeRotation, colliderRelativePosition);
}
AZ::Transform GetColliderWorldTransform(const AZ::Transform& worldTransform,
const AZ::Vector3& colliderRelativePosition,
const AZ::Quaternion& colliderRelativeRotation)
{
return worldTransform * GetColliderLocalTransform(colliderRelativePosition, colliderRelativeRotation);
}
void ColliderPointsLocalToWorld(AZStd::vector<AZ::Vector3>& pointsInOut,
const AZ::Transform& worldTransform,
const AZ::Vector3& colliderRelativePosition,
const AZ::Quaternion& colliderRelativeRotation,
const AZ::Vector3& nonUniformScale)
{
AZ::Transform transform = GetColliderWorldTransform(worldTransform,
colliderRelativePosition,
colliderRelativeRotation);
for (AZ::Vector3& point : pointsInOut)
{
point = worldTransform.TransformPoint(nonUniformScale *
GetColliderLocalTransform(colliderRelativePosition, colliderRelativeRotation).TransformPoint(point));
}
}
AZ::Aabb GetPxGeometryAabb(const physx::PxGeometryHolder& geometryHolder,
const AZ::Transform& worldTransform,
const ::Physics::ColliderConfiguration& colliderConfiguration
)
{
const float boundsInflationFactor = 1.0f;
AZ::Transform overallTransformNoScale = GetColliderWorldTransform(worldTransform,
colliderConfiguration.m_position, colliderConfiguration.m_rotation);
overallTransformNoScale.ExtractUniformScale();
const physx::PxBounds3 bounds = physx::PxGeometryQuery::getWorldBounds(geometryHolder.any(),
PxMathConvert(overallTransformNoScale),
boundsInflationFactor);
return PxMathConvert(bounds);
}
AZ::Aabb GetColliderAabb(const AZ::Transform& worldTransform,
bool hasNonUniformScale,
AZ::u8 subdivisionLevel,
const ::Physics::ShapeConfiguration& shapeConfiguration,
const ::Physics::ColliderConfiguration& colliderConfiguration)
{
const AZ::Aabb worldPosAabb = AZ::Aabb::CreateFromPoint(worldTransform.GetTranslation());
physx::PxGeometryHolder geometryHolder;
bool isAssetShape = shapeConfiguration.GetShapeType() == Physics::ShapeType::PhysicsAsset;
if (!isAssetShape)
{
if (!hasNonUniformScale)
{
if (CreatePxGeometryFromConfig(shapeConfiguration, geometryHolder))
{
return GetPxGeometryAabb(geometryHolder, worldTransform, colliderConfiguration);
}
}
else
{
auto convexPrimitive = Utils::CreateConvexFromPrimitive(colliderConfiguration, shapeConfiguration, subdivisionLevel, shapeConfiguration.m_scale);
if (convexPrimitive.has_value())
{
if (CreatePxGeometryFromConfig(convexPrimitive.value(), geometryHolder))
{
Physics::ColliderConfiguration colliderConfigurationNoOffset = colliderConfiguration;
colliderConfigurationNoOffset.m_rotation = AZ::Quaternion::CreateIdentity();
colliderConfigurationNoOffset.m_position = AZ::Vector3::CreateZero();
return GetPxGeometryAabb(geometryHolder, worldTransform, colliderConfigurationNoOffset);
}
}
}
return worldPosAabb;
}
else
{
const Physics::PhysicsAssetShapeConfiguration& physicsAssetConfig =
static_cast<const Physics::PhysicsAssetShapeConfiguration&>(shapeConfiguration);
if (!physicsAssetConfig.m_asset.IsReady())
{
return worldPosAabb;
}
AzPhysics::ShapeColliderPairList colliderShapes;
GetColliderShapeConfigsFromAsset(physicsAssetConfig,
colliderConfiguration,
hasNonUniformScale,
subdivisionLevel,
colliderShapes);
if (colliderShapes.empty())
{
return worldPosAabb;
}
AZ::Aabb aabb = AZ::Aabb::CreateNull();
for (const auto& colliderShape : colliderShapes)
{
if (colliderShape.second &&
CreatePxGeometryFromConfig(*colliderShape.second, geometryHolder))
{
aabb.AddAabb(
GetPxGeometryAabb(geometryHolder, worldTransform, *colliderShape.first)
);
}
else
{
return worldPosAabb;
}
}
return aabb;
}
}
bool TriggerColliderExists(AZ::EntityId entityId)
{
AZ::EBusLogicalResult<bool, AZStd::logical_or<bool>> response(false);
PhysX::ColliderShapeRequestBus::EventResult(response,
entityId,
&PhysX::ColliderShapeRequestBus::Events::IsTrigger);
return response.value;
}
void GetColliderShapeConfigsFromAsset(const Physics::PhysicsAssetShapeConfiguration& assetConfiguration,
const Physics::ColliderConfiguration& originalColliderConfiguration, bool hasNonUniformScale,
AZ::u8 subdivisionLevel, AzPhysics::ShapeColliderPairList& resultingColliderShapes)
{
if (!assetConfiguration.m_asset.IsReady())
{
AZ_Error("PhysX", false, "GetColliderShapesFromAsset: Asset %s is not ready."
"Please make sure the calling code connects to the AssetBus and "
"creates the collider shapes only when OnAssetReady or OnAssetReload is invoked.",
assetConfiguration.m_asset.GetHint().c_str());
return;
}
const Pipeline::MeshAsset* asset = assetConfiguration.m_asset.GetAs<Pipeline::MeshAsset>();
if (!asset)
{
AZ_Error("PhysX", false, "GetColliderShapesFromAsset: Mesh Asset %s is null."
"Please check the file is in the correct format. Try to delete it and get AssetProcessor re-create it. "
"The data is loaded in Pipeline::MeshAssetHandler::LoadAssetData()",
assetConfiguration.m_asset.GetHint().c_str());
return;
}
const Pipeline::MeshAssetData& assetData = asset->m_assetData;
const Pipeline::MeshAssetData::ShapeConfigurationList& shapeConfigList = assetData.m_colliderShapes;
resultingColliderShapes.reserve(resultingColliderShapes.size() + shapeConfigList.size());
for (size_t shapeIndex = 0; shapeIndex < shapeConfigList.size(); shapeIndex++)
{
const Pipeline::MeshAssetData::ShapeConfigurationPair& shapeConfigPair = shapeConfigList[shapeIndex];
AZStd::shared_ptr<Physics::ColliderConfiguration> thisColliderConfiguration =
AZStd::make_shared<Physics::ColliderConfiguration>(originalColliderConfiguration);
AZ::u16 shapeMaterialIndex = assetData.m_materialIndexPerShape[shapeIndex];
// Triangle meshes have material indices cooked in the data.
if (shapeMaterialIndex != Pipeline::MeshAssetData::TriangleMeshMaterialIndex)
{
// Clear the materials that came in from the component collider configuration
thisColliderConfiguration->m_materialSelection.SetMaterialSlots({});
// Set the material that is relevant for this specific shape
Physics::MaterialId assignedMaterialForShape =
originalColliderConfiguration.m_materialSelection.GetMaterialId(shapeMaterialIndex);
thisColliderConfiguration->m_materialSelection.SetMaterialId(assignedMaterialForShape);
}
// Here we use the collider configuration data saved in the asset to update the one coming from the component
if (const Pipeline::AssetColliderConfiguration* optionalColliderData = shapeConfigPair.first.get())
{
optionalColliderData->UpdateColliderConfiguration(*thisColliderConfiguration);
}
// Update the scale with the data from the asset configuration
AZStd::shared_ptr<Physics::ShapeConfiguration> thisShapeConfiguration = shapeConfigPair.second;
thisShapeConfiguration->m_scale = assetConfiguration.m_scale * assetConfiguration.m_assetScale;
// If the shape is a primitive and there is non-uniform scale, replace it with a convex approximation
if (hasNonUniformScale && Utils::IsPrimitiveShape(*thisShapeConfiguration))
{
auto scaledPrimitive = Utils::CreateConvexFromPrimitive(*thisColliderConfiguration,
*thisShapeConfiguration, subdivisionLevel, thisShapeConfiguration->m_scale);
if (scaledPrimitive.has_value())
{
thisShapeConfiguration = AZStd::make_shared<Physics::CookedMeshShapeConfiguration>(scaledPrimitive.value());
physx::PxGeometryHolder pxGeometryHolder;
CreatePxGeometryFromConfig(*thisShapeConfiguration, pxGeometryHolder);
thisColliderConfiguration->m_rotation = AZ::Quaternion::CreateIdentity();
thisColliderConfiguration->m_position = AZ::Vector3::CreateZero();
resultingColliderShapes.emplace_back(thisColliderConfiguration, thisShapeConfiguration);
}
}
else
{
resultingColliderShapes.emplace_back(thisColliderConfiguration, thisShapeConfiguration);
}
}
}
void GetShapesFromAsset(const Physics::PhysicsAssetShapeConfiguration& assetConfiguration,
const Physics::ColliderConfiguration& originalColliderConfiguration, bool hasNonUniformScale,
AZ::u8 subdivisionLevel, AZStd::vector<AZStd::shared_ptr<Physics::Shape>>& resultingShapes)
{
AzPhysics::ShapeColliderPairList resultingColliderShapeConfigs;
GetColliderShapeConfigsFromAsset(assetConfiguration, originalColliderConfiguration,
hasNonUniformScale, subdivisionLevel, resultingColliderShapeConfigs);
resultingShapes.reserve(resultingShapes.size() + resultingColliderShapeConfigs.size());
for (const AzPhysics::ShapeColliderPair& shapeConfigPair : resultingColliderShapeConfigs)
{
// Scale the collider offset
shapeConfigPair.first->m_position *= shapeConfigPair.second->m_scale;
AZStd::shared_ptr<Physics::Shape> shape;
Physics::SystemRequestBus::BroadcastResult(shape, &Physics::SystemRequests::CreateShape,
*shapeConfigPair.first, *shapeConfigPair.second);
if (shape)
{
resultingShapes.emplace_back(shape);
}
}
}
AZ::Vector3 GetTransformScale(AZ::EntityId entityId)
{
AZ::Vector3 worldScale = AZ::Vector3::CreateOne();
AZ::TransformBus::EventResult(worldScale, entityId, &AZ::TransformBus::Events::GetWorldScale);
return worldScale;
}
AZ::Vector3 GetUniformScale(AZ::EntityId entityId)
{
const float uniformScale = GetTransformScale(entityId).GetMaxElement();
return AZ::Vector3(uniformScale);
}
AZ::Vector3 GetNonUniformScale(AZ::EntityId entityId)
{
AZ::Vector3 nonUniformScale = AZ::Vector3::CreateOne();
AZ::NonUniformScaleRequestBus::EventResult(nonUniformScale, entityId, &AZ::NonUniformScaleRequests::GetScale);
return nonUniformScale;
}
AZ::Vector3 GetOverallScale(AZ::EntityId entityId)
{
return GetUniformScale(entityId) * GetNonUniformScale(entityId);
}
const AZ::Vector3& Sanitize(const AZ::Vector3& input, const AZ::Vector3& defaultValue)
{
if (!input.IsFinite())
{
AZ_Error("PhysX", false, "Invalid Vector3 was passed to PhysX.");
return defaultValue;
}
return input;
}
namespace Geometry
{
PointList GenerateBoxPoints(const AZ::Vector3& min, const AZ::Vector3& max)
{
PointList pointList;
auto size = max - min;
const auto minSamples = 2.f;
const auto maxSamples = 8.f;
const auto desiredSampleDelta = 2.f;
// How many sample in each axis
int numSamples[] =
{
static_cast<int>(AZ::GetClamp(size.GetX() / desiredSampleDelta, minSamples, maxSamples)),
static_cast<int>(AZ::GetClamp(size.GetY() / desiredSampleDelta, minSamples, maxSamples)),
static_cast<int>(AZ::GetClamp(size.GetZ() / desiredSampleDelta, minSamples, maxSamples))
};
float sampleDelta[] =
{
size.GetX() / static_cast<float>(numSamples[0] - 1),
size.GetY() / static_cast<float>(numSamples[1] - 1),
size.GetZ() / static_cast<float>(numSamples[2] - 1),
};
for (auto i = 0; i < numSamples[0]; ++i)
{
for (auto j = 0; j < numSamples[1]; ++j)
{
for (auto k = 0; k < numSamples[2]; ++k)
{
pointList.emplace_back(
min.GetX() + i * sampleDelta[0],
min.GetY() + j * sampleDelta[1],
min.GetZ() + k * sampleDelta[2]
);
}
}
}
return pointList;
}
PointList GenerateSpherePoints(float radius)
{
PointList points;
int nSamples = static_cast<int>(radius * 5);
nSamples = AZ::GetClamp(nSamples, 5, 512);
// Draw arrows using Fibonacci sphere
float offset = 2.f / nSamples;
float increment = AZ::Constants::Pi * (3.f - sqrt(5.f));
for (int i = 0; i < nSamples; ++i)
{
float phi = ((i + 1) % nSamples) * increment;
float y = ((i * offset) - 1) + (offset / 2.f);
float r = aznumeric_cast<float>(sqrt(1 - pow(y, 2)));
float x = cos(phi) * r;
float z = sin(phi) * r;
points.emplace_back(x * radius, y * radius, z * radius);
}
return points;
}
PointList GenerateCylinderPoints(float height, float radius)
{
PointList points;
AZ::Vector3 base(0.f, 0.f, -height * 0.5f);
AZ::Vector3 radiusVector(radius, 0.f, 0.f);
const auto sides = AZ::GetClamp(radius, 3.f, 8.f);
const auto segments = AZ::GetClamp(height * 0.5f, 2.f, 8.f);
const auto angleDelta = AZ::Quaternion::CreateRotationZ(AZ::Constants::TwoPi / sides);
const auto segmentDelta = height / (segments - 1);
for (auto segment = 0; segment < segments; ++segment)
{
for (auto side = 0; side < sides; ++side)
{
auto point = base + radiusVector;
points.emplace_back(point);
radiusVector = angleDelta.TransformVector(radiusVector);
}
base += AZ::Vector3(0, 0, segmentDelta);
}
return points;
}
void GetBoxGeometry(const physx::PxBoxGeometry& geometry, AZStd::vector<AZ::Vector3>& vertices, AZStd::vector<AZ::u32>& indices)
{
constexpr size_t numVertices = 8;
vertices.reserve(numVertices);
vertices.push_back(AZ::Vector3(-geometry.halfExtents.x, -geometry.halfExtents.y, -geometry.halfExtents.z));
vertices.push_back(AZ::Vector3(geometry.halfExtents.x, -geometry.halfExtents.y, -geometry.halfExtents.z));
vertices.push_back(AZ::Vector3(geometry.halfExtents.x, geometry.halfExtents.y, -geometry.halfExtents.z));
vertices.push_back(AZ::Vector3(-geometry.halfExtents.x, geometry.halfExtents.y, -geometry.halfExtents.z));
vertices.push_back(AZ::Vector3(-geometry.halfExtents.x, -geometry.halfExtents.y, geometry.halfExtents.z));
vertices.push_back(AZ::Vector3(geometry.halfExtents.x, -geometry.halfExtents.y, geometry.halfExtents.z));
vertices.push_back(AZ::Vector3(geometry.halfExtents.x, geometry.halfExtents.y, geometry.halfExtents.z));
vertices.push_back(AZ::Vector3(-geometry.halfExtents.x, geometry.halfExtents.y, geometry.halfExtents.z));
constexpr size_t numIndices = 36;
static const AZ::u32 boxIndices[numIndices] =
{
2, 1, 0,
0, 3, 2,
3, 0, 7,
0, 4, 7,
0, 1, 5,
0, 5, 4,
1, 2, 5,
6, 5, 2,
7, 2, 3,
7, 6, 2,
7, 4, 5,
7, 5, 6
};
indices.reserve(numIndices);
for (int i = 0; i < numIndices; ++i)
{
indices.push_back(boxIndices[i]);
}
}
void GetCapsuleGeometry(const physx::PxCapsuleGeometry& geometry, AZStd::vector<AZ::Vector3>& vertices, AZStd::vector<AZ::u32>& indices, const AZ::u32 stacks, const AZ::u32 slices)
{
const AZ::Vector3 base(0.0, 0.0, -geometry.halfHeight);
const AZ::Vector3 top(0.0, 0.0, geometry.halfHeight);
const float radius = geometry.radius;
// topStack refers to the top row of vertices starting at 0
// get an even number so our caps reach all the way out to sphere radius
const AZ::u32 topStack = stacks % 2 ? stacks + 1 : stacks;
const AZ::u32 midStack = topStack / 2;
vertices.reserve(slices * topStack + 2);
indices.reserve((slices - 1) * topStack * 6);
const float thetaFactor = 1.f / aznumeric_cast<float>(topStack) * AZ::Constants::Pi;
const float phiFactor = 1.f / aznumeric_cast<float>(slices - 1) * AZ::Constants::TwoPi;
// bottom cap
vertices.push_back(base + AZ::Vector3(0.f, 0.f, -radius));
for (size_t stack = 1; stack <= midStack; ++stack)
{
for (size_t i = 0; i < slices; ++i)
{
float theta(aznumeric_cast<float>(stack) * thetaFactor);
float phi(aznumeric_cast<float>(i) * phiFactor);
float sinTheta, cosTheta;
AZ::SinCos(theta, sinTheta, cosTheta);
float sinPhi, cosPhi;
AZ::SinCos(phi, sinPhi, cosPhi);
vertices.push_back(base + AZ::Vector3(sinTheta * cosPhi * radius, sinTheta * sinPhi * radius, -cosTheta * radius));
}
}
// top cap
for (size_t stack = midStack; stack < topStack; ++stack)
{
for (size_t i = 0; i < slices; ++i)
{
float theta(aznumeric_cast<float>(stack) * thetaFactor);
float phi(aznumeric_cast<float>(i) * phiFactor);
float sinTheta, cosTheta;
AZ::SinCos(theta, sinTheta, cosTheta);
float sinPhi, cosPhi;
AZ::SinCos(phi, sinPhi, cosPhi);
vertices.push_back(top + AZ::Vector3(sinTheta * cosPhi * radius, sinTheta * sinPhi * radius, -cosTheta * radius));
}
}
vertices.push_back(top + AZ::Vector3(0.f, 0.f, radius));
const AZ::u32 lastVertex = aznumeric_cast<AZ::u32>(vertices.size()) - 1;
const AZ::u32 topRow = aznumeric_cast<AZ::u32>(vertices.size()) - slices - 1;
// top and bottom segment indices
for (AZ::u32 i = 0; i < slices - 1; ++i)
{
// bottom (add one to account for single bottom vertex)
indices.push_back(0);
indices.push_back(i + 2);
indices.push_back(i + 1);
//top (topRow accounts for the added bottom vertex)
indices.push_back(topRow + i + 0);
indices.push_back(topRow + i + 1);
indices.push_back(lastVertex);
}
// there are stacks + 1 stacks because we stretched the middle for the cylinder section,
// but we already built the top and bottom stack so there are stacks + 1 - 2 to build
// add 1 to each vertex index because there is a single bottom vertex for the bottom cap
for (AZ::u32 j = 0; j < stacks - 1; ++j)
{
for (AZ::u32 i = 0; i < slices - 1; ++i)
{
indices.push_back(j * slices + i + 2);
indices.push_back((j + 1) * slices + i + 2);
indices.push_back((j + 1) * slices + i + 1);
indices.push_back(j * slices + i + 1);
indices.push_back(j * slices + i + 2);
indices.push_back((j + 1) * slices + i + 1);
}
}
}
void GetConvexMeshGeometry(const physx::PxConvexMeshGeometry& geometry, AZStd::vector<AZ::Vector3>& vertices, [[maybe_unused]] AZStd::vector<AZ::u32>& indices)
{
const physx::PxConvexMesh* convexMesh = geometry.convexMesh;
const physx::PxU8* pxIndices = convexMesh->getIndexBuffer();
const physx::PxVec3* pxVertices = convexMesh->getVertices();
const AZ::u32 numPolys = convexMesh->getNbPolygons();
physx::PxHullPolygon poly;
for (AZ::u32 polygonIndex = 0; polygonIndex < numPolys; ++polygonIndex)
{
if (convexMesh->getPolygonData(polygonIndex, poly))
{
constexpr AZ::u32 index1 = 0;
AZ::u32 index2 = 1;
AZ::u32 index3 = 2;
const AZ::Vector3 a = PxMathConvert(geometry.scale.transform(pxVertices[pxIndices[poly.mIndexBase + index1]]));
const AZ::u32 triangleCount = poly.mNbVerts - 2;
for (AZ::u32 triangleIndex = 0; triangleIndex < triangleCount; ++triangleIndex)
{
AZ_Assert(index3 < poly.mNbVerts, "Implementation error: attempted to index outside range of polygon vertices.");
const AZ::Vector3 b = PxMathConvert(geometry.scale.transform(pxVertices[pxIndices[poly.mIndexBase + index2]]));
const AZ::Vector3 c = PxMathConvert(geometry.scale.transform(pxVertices[pxIndices[poly.mIndexBase + index3]]));
vertices.push_back(a);
vertices.push_back(b);
vertices.push_back(c);
index2 = index3++;
}
}
}
}
void GetHeightFieldGeometry(const physx::PxHeightFieldGeometry& geometry, AZStd::vector<AZ::Vector3>& vertices, [[maybe_unused]] AZStd::vector<AZ::u32>& indices, AZ::Aabb* optionalBounds)
{
int minX = 0;
int minY = 0;
// rows map to y and columns to x see EditorTerrainComponent
int maxX = geometry.heightField->getNbColumns() - 1;
int maxY = geometry.heightField->getNbRows() - 1;
if (optionalBounds)
{
// convert the provided bounds to heightfield sample grid positions
const AZ::Aabb bounds = *optionalBounds;
const float inverseRowScale = 1.f / geometry.rowScale;
const float inverseColumnScale = 1.f / geometry.columnScale;
minX = AZStd::max(minX, static_cast<int>(floor(bounds.GetMin().GetX() * inverseColumnScale)));
minY = AZStd::max(minY, static_cast<int>(floor(bounds.GetMin().GetY() * inverseRowScale)));
maxX = AZStd::min(maxX, static_cast<int>(ceil(bounds.GetMax().GetX() * inverseColumnScale)));
maxY = AZStd::min(maxY, static_cast<int>(ceil(bounds.GetMax().GetY() * inverseRowScale)));
}
// num quads * 2 triangles per quad * 3 vertices per triangle
const size_t numVertices = (maxY - minY) * (maxX - minX) * 2 * 3;
vertices.reserve(numVertices);
for (int y = minY; y < maxY; ++y)
{
for (int x = minX; x < maxX; ++x)
{
const physx::PxHeightFieldSample& pxSample = geometry.heightField->getSample(y, x);
if (pxSample.materialIndex0 == physx::PxHeightFieldMaterial::eHOLE ||
pxSample.materialIndex1 == physx::PxHeightFieldMaterial::eHOLE)
{
// skip terrain geometry marked as eHOLE, this feature is often used for tunnels
continue;
}
float height = aznumeric_cast<float>(pxSample.height) * geometry.heightScale;
const AZ::Vector3 v0(aznumeric_cast<float>(x) * geometry.rowScale, aznumeric_cast<float>(y) * geometry.columnScale, height);
height = aznumeric_cast<float>(geometry.heightField->getSample(y + 1, x).height) * geometry.heightScale;
const AZ::Vector3 v1(aznumeric_cast<float>(x) * geometry.rowScale, aznumeric_cast<float>(y + 1) * geometry.columnScale, height);
height = aznumeric_cast<float>(geometry.heightField->getSample(y, x + 1).height) * geometry.heightScale;
const AZ::Vector3 v2(aznumeric_cast<float>(x + 1) * geometry.rowScale, aznumeric_cast<float>(y) * geometry.columnScale, height);
height = aznumeric_cast<float>(geometry.heightField->getSample(y + 1, x + 1).height) * geometry.heightScale;
const AZ::Vector3 v3(aznumeric_cast<float>(x + 1) * geometry.rowScale, aznumeric_cast<float>(y + 1) * geometry.columnScale, height);
vertices.push_back(v0);
vertices.push_back(v2);
vertices.push_back(v1);
vertices.push_back(v1);
vertices.push_back(v2);
vertices.push_back(v3);
}
}
}
void GetSphereGeometry(const physx::PxSphereGeometry& geometry, AZStd::vector<AZ::Vector3>& vertices, AZStd::vector<AZ::u32>& indices, const AZ::u32 stacks, const AZ::u32 slices)
{
const float radius = geometry.radius;
const size_t vertexCount = slices * (stacks - 2) + 2;
vertices.reserve(vertexCount);
vertices.push_back(AZ::Vector3(0.f, radius, 0.f));
vertices.push_back(AZ::Vector3(0.f, -radius, 0.f));
for (size_t j = 1; j < stacks - 1; ++j)
{
for (size_t i = 0; i < slices; ++i)
{
float theta = (j / (float)(stacks - 1)) * AZ::Constants::Pi;
float phi = (i / (float)(slices - 1)) * AZ::Constants::TwoPi;
float sinTheta, cosTheta;
AZ::SinCos(theta, sinTheta, cosTheta);
float sinPhi, cosPhi;
AZ::SinCos(phi, sinPhi, cosPhi);
vertices.push_back(AZ::Vector3(sinTheta * cosPhi * radius, cosTheta * radius, -sinTheta * sinPhi * radius));
}
}
const size_t indexCount = (slices - 1) * (stacks - 2) * 6;
indices.reserve(indexCount);
for (AZ::u32 i = 0; i < slices - 1; ++i)
{
indices.push_back(0);
indices.push_back(i + 2);
indices.push_back(i + 3);
indices.push_back((stacks - 3) * slices + i + 3);
indices.push_back((stacks - 3) * slices + i + 2);
indices.push_back(1);
}
for (AZ::u32 j = 0; j < stacks - 3; ++j)
{
for (AZ::u32 i = 0; i < slices - 1; ++i)
{
indices.push_back((j + 1) * slices + i + 3);
indices.push_back(j * slices + i + 3);
indices.push_back((j + 1) * slices + i + 2);
indices.push_back(j * slices + i + 3);
indices.push_back(j * slices + i + 2);
indices.push_back((j + 1) * slices + i + 2);
}
}
}
void GetTriangleMeshGeometry(const physx::PxTriangleMeshGeometry& geometry, AZStd::vector<AZ::Vector3>& vertices, AZStd::vector<AZ::u32>& indices)
{
const physx::PxTriangleMesh* triangleMesh = geometry.triangleMesh;
const physx::PxMeshScale scale = geometry.scale;
const physx::PxVec3* meshVertices = triangleMesh->getVertices();
const AZ::u32 vertCount = triangleMesh->getNbVertices();
const AZ::u32 triangleCount = triangleMesh->getNbTriangles();
vertices.reserve(vertCount);
indices.reserve(triangleCount * 3);
for (AZ::u32 vertIndex = 0; vertIndex < vertCount; ++vertIndex)
{
vertices.push_back(PxMathConvert(geometry.scale.transform(meshVertices[vertIndex])));
}
physx::PxTriangleMeshFlags triangleMeshFlags = triangleMesh->getTriangleMeshFlags();
if (triangleMeshFlags.isSet(physx::PxTriangleMeshFlag::Enum::e16_BIT_INDICES))
{
const physx::PxU16* triangles = static_cast<const physx::PxU16*>(triangleMesh->getTriangles());
for (AZ::u32 triangleIndex = 0; triangleIndex < triangleCount * 3; triangleIndex += 3)
{
indices.push_back(triangles[triangleIndex]);
indices.push_back(triangles[triangleIndex + 1]);
indices.push_back(triangles[triangleIndex + 2]);
}
}
else
{
const physx::PxU32* triangles = static_cast<const physx::PxU32*>(triangleMesh->getTriangles());
for (AZ::u32 triangleIndex = 0; triangleIndex < triangleCount * 3; triangleIndex += 3)
{
indices.push_back(triangles[triangleIndex]);
indices.push_back(triangles[triangleIndex + 1]);
indices.push_back(triangles[triangleIndex + 2]);
}
}
}
} // namespace Geometry
AZ::Transform GetEntityWorldTransformWithScale(AZ::EntityId entityId)
{
AZ::Transform worldTransformWithoutScale = AZ::Transform::CreateIdentity();
AZ::TransformBus::EventResult(worldTransformWithoutScale
, entityId
, &AZ::TransformInterface::GetWorldTM);
return worldTransformWithoutScale;
}
AZ::Transform GetEntityWorldTransformWithoutScale(AZ::EntityId entityId)
{
AZ::Transform worldTransformWithoutScale = AZ::Transform::CreateIdentity();
AZ::TransformBus::EventResult(worldTransformWithoutScale
, entityId
, &AZ::TransformInterface::GetWorldTM);
worldTransformWithoutScale.ExtractUniformScale();
return worldTransformWithoutScale;
}
AZ::Transform ComputeJointLocalTransform(const AZ::Transform& jointWorldTransform,
const AZ::Transform& entityWorldTransform)
{
AZ::Transform jointWorldTransformWithoutScale = jointWorldTransform;
jointWorldTransformWithoutScale.ExtractUniformScale();
AZ::Transform entityWorldTransformWithoutScale = entityWorldTransform;
entityWorldTransformWithoutScale.ExtractUniformScale();
AZ::Transform entityWorldTransformInverse = entityWorldTransformWithoutScale.GetInverse();
return entityWorldTransformInverse * jointWorldTransformWithoutScale;
}
AZ::Transform ComputeJointWorldTransform(const AZ::Transform& jointLocalTransform,
const AZ::Transform& entityWorldTransform)
{
AZ::Transform jointLocalTransformWithoutScale = jointLocalTransform;
jointLocalTransformWithoutScale.ExtractUniformScale();
AZ::Transform entityWorldTransformWithoutScale = entityWorldTransform;
entityWorldTransformWithoutScale.ExtractUniformScale();
return entityWorldTransformWithoutScale * jointLocalTransformWithoutScale;
}
} // namespace Utils
namespace ReflectionUtils
{
// Forwards invocation of CalculateNetForce in a force region to script canvas.
class ForceRegionBusBehaviorHandler
: public ForceRegionNotificationBus::Handler
, public AZ::BehaviorEBusHandler
{
public:
AZ_EBUS_BEHAVIOR_BINDER(ForceRegionBusBehaviorHandler, "{EB6C0F7A-0BDA-4052-84C0-33C05E3FF739}", AZ::SystemAllocator
, OnCalculateNetForce
);
static void Reflect(AZ::ReflectContext* context);
/// Callback invoked when net force exerted on object is computed by a force region.
void OnCalculateNetForce(AZ::EntityId forceRegionEntityId
, AZ::EntityId targetEntityId
, const AZ::Vector3& netForceDirection
, float netForceMagnitude) override;
};
void ReflectPhysXOnlyApi(AZ::ReflectContext* context)
{
PhysXSystemConfiguration::Reflect(context);
Debug::DebugConfiguration::Reflect(context);
ForceRegionBusBehaviorHandler::Reflect(context);
GenericJointConfiguration::Reflect(context);
GenericJointLimitsConfiguration::Reflect(context);
}
void ForceRegionBusBehaviorHandler::Reflect(AZ::ReflectContext* context)
{
if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->EBus<PhysX::ForceRegionNotificationBus>("ForceRegionNotificationBus")
->Attribute(AZ::Script::Attributes::Module, "physics")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
->Handler<ForceRegionBusBehaviorHandler>()
;
}
}
void ForceRegionBusBehaviorHandler::OnCalculateNetForce(AZ::EntityId forceRegionEntityId
, AZ::EntityId targetEntityId
, const AZ::Vector3& netForceDirection
, float netForceMagnitude)
{
Call(FN_OnCalculateNetForce
, forceRegionEntityId
, targetEntityId
, netForceDirection
, netForceMagnitude);
}
} // namespace ReflectionUtils
namespace PxActorFactories
{
constexpr auto PxActorDestructor = [](physx::PxActor* actor)
{
if (!actor)
{
return;
}
if (auto* userData = Utils::GetUserData(actor))
{
userData->Invalidate();
}
actor->release();
};
AZStd::shared_ptr<physx::PxRigidDynamic> CreatePxRigidBody(const AzPhysics::RigidBodyConfiguration& configuration)
{
physx::PxTransform pxTransform(PxMathConvert(configuration.m_position),
PxMathConvert(configuration.m_orientation).getNormalized());
auto rigidDynamic = AZStd::shared_ptr<physx::PxRigidDynamic>(
PxGetPhysics().createRigidDynamic(pxTransform),
PxActorDestructor);
if (!rigidDynamic)
{
AZ_Error("PhysX Rigid Body", false, "Failed to create PhysX rigid actor. Name: %s", configuration.m_debugName.c_str());
return nullptr;
}
rigidDynamic->setMass(configuration.m_mass);
rigidDynamic->setSleepThreshold(configuration.m_sleepMinEnergy);
rigidDynamic->setLinearVelocity(PxMathConvert(configuration.m_initialLinearVelocity));
rigidDynamic->setAngularVelocity(PxMathConvert(configuration.m_initialAngularVelocity));
rigidDynamic->setLinearDamping(configuration.m_linearDamping);
rigidDynamic->setAngularDamping(configuration.m_angularDamping);
rigidDynamic->setCMassLocalPose(physx::PxTransform(PxMathConvert(configuration.m_centerOfMassOffset)));
rigidDynamic->setRigidBodyFlag(physx::PxRigidBodyFlag::eKINEMATIC, configuration.m_kinematic);
rigidDynamic->setMaxAngularVelocity(configuration.m_maxAngularVelocity);
return rigidDynamic;
}
AZStd::shared_ptr<physx::PxRigidStatic> CreatePxStaticRigidBody(const AzPhysics::StaticRigidBodyConfiguration& configuration)
{
physx::PxTransform pxTransform(PxMathConvert(configuration.m_position),
PxMathConvert(configuration.m_orientation).getNormalized());
auto rigidStatic = AZStd::shared_ptr<physx::PxRigidStatic>(
PxGetPhysics().createRigidStatic(pxTransform),
PxActorDestructor);
if (!rigidStatic)
{
AZ_Error("PhysX Static Rigid Body", false, "Failed to create PhysX static rigid actor. Name: %s", configuration.m_debugName.c_str());
return nullptr;
}
return rigidStatic;
}
} // namespace PxActorFactories
namespace StaticRigidBodyUtils
{
bool EntityHasComponentsUsingService(const AZ::Entity& entity, AZ::Crc32 service)
{
const AZ::Entity::ComponentArrayType& components = entity.GetComponents();
return AZStd::any_of(components.begin(), components.end(),
[service](const AZ::Component* component) -> bool
{
AZ::ComponentDescriptor* componentDescriptor = nullptr;
AZ::ComponentDescriptorBus::EventResult(
componentDescriptor, azrtti_typeid(component), &AZ::ComponentDescriptorBus::Events::GetDescriptor);
AZ::ComponentDescriptor::DependencyArrayType services;
componentDescriptor->GetDependentServices(services, nullptr);
return AZStd::find(services.begin(), services.end(), service) != services.end();
}
);
}
bool CanCreateRuntimeComponent(const AZ::Entity& editorEntity)
{
// Allow to create runtime StaticRigidBodyComponent if there are no components
// using 'PhysXColliderService' attached to entity.
const AZ::Crc32 physxColliderServiceId = AZ_CRC("PhysXColliderService", 0x4ff43f7c);
return !EntityHasComponentsUsingService(editorEntity, physxColliderServiceId);
}
bool TryCreateRuntimeComponent(const AZ::Entity& editorEntity, AZ::Entity& gameEntity)
{
// Only allow single StaticRigidBodyComponent per entity
const auto* staticRigidBody = gameEntity.FindComponent<StaticRigidBodyComponent>();
if (staticRigidBody)
{
return false;
}
if (CanCreateRuntimeComponent(editorEntity))
{
gameEntity.CreateComponent<StaticRigidBodyComponent>();
return true;
}
return false;
}
} // namespace StaticRigidBodyUtils
} // namespace PhysX