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.
1581 lines
73 KiB
C++
1581 lines
73 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;
|
|
}
|
|
|
|
void GetMaterialList(
|
|
AZStd::vector<physx::PxMaterial*>& pxMaterials, const AZStd::vector<int>& terrainSurfaceIdIndexMapping,
|
|
const Physics::TerrainMaterialSurfaceIdMap& terrainMaterialsToSurfaceIds)
|
|
{
|
|
pxMaterials.reserve(terrainSurfaceIdIndexMapping.size());
|
|
|
|
AZStd::shared_ptr<Material> defaultMaterial;
|
|
MaterialManagerRequestsBus::BroadcastResult(defaultMaterial, &MaterialManagerRequestsBus::Events::GetDefaultMaterial);
|
|
|
|
if (terrainSurfaceIdIndexMapping.empty())
|
|
{
|
|
pxMaterials.push_back(defaultMaterial->GetPxMaterial());
|
|
return;
|
|
}
|
|
|
|
AZStd::vector<physx::PxMaterial*> materials;
|
|
|
|
for (auto& surfaceId : terrainSurfaceIdIndexMapping)
|
|
{
|
|
const auto& userAssignedMaterials = terrainMaterialsToSurfaceIds;
|
|
const auto& matSelectionIterator = userAssignedMaterials.find(surfaceId);
|
|
if (matSelectionIterator != userAssignedMaterials.end())
|
|
{
|
|
MaterialManagerRequestsBus::Broadcast(&MaterialManagerRequests::GetPxMaterials, matSelectionIterator->second, materials);
|
|
|
|
if (!materials.empty())
|
|
{
|
|
pxMaterials.push_back(materials.front());
|
|
}
|
|
else
|
|
{
|
|
AZ_Error("PhysX", false, "Creating materials: array with materials can't be empty");
|
|
pxMaterials.push_back(defaultMaterial->GetPxMaterial());
|
|
}
|
|
}
|
|
else
|
|
{
|
|
pxMaterials.push_back(defaultMaterial->GetPxMaterial());
|
|
}
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
float worldUniformScale = 1.0f;
|
|
AZ::TransformBus::EventResult(worldUniformScale, entityId, &AZ::TransformBus::Events::GetWorldUniformScale);
|
|
return AZ::Vector3(worldUniformScale);
|
|
}
|
|
|
|
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
|