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

893 lines
36 KiB
C++

/*
* Copyright (c) Contributors to the Open 3D Engine Project. For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <EditorShapeColliderComponent.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzFramework/Physics/ColliderComponentBus.h>
#include <AzFramework/Physics/SystemBus.h>
#include <AzFramework/Physics/Common/PhysicsSimulatedBody.h>
#include <AzFramework/Physics/Configuration/StaticRigidBodyConfiguration.h>
#include <LyViewPaneNames.h>
#include <ShapeColliderComponent.h>
#include <EditorRigidBodyComponent.h>
#include <Editor/ConfigurationWindowBus.h>
#include <LmbrCentral/Shape/ShapeComponentBus.h>
#include <LmbrCentral/Shape/BoxShapeComponentBus.h>
#include <LmbrCentral/Shape/CapsuleShapeComponentBus.h>
#include <LmbrCentral/Shape/SphereShapeComponentBus.h>
#include <LmbrCentral/Shape/CylinderShapeComponentBus.h>
#include <LmbrCentral/Shape/PolygonPrismShapeComponentBus.h>
#include <PhysX/SystemComponentBus.h>
#include <Source/Utils.h>
#include <AzCore/Math/Geometry2DUtils.h>
#include <AzCore/std/smart_ptr/make_shared.h>
#include <cmath>
#include <RigidBodyStatic.h>
#include <System/PhysXSystem.h>
namespace PhysX
{
EditorShapeColliderComponent::EditorShapeColliderComponent()
: m_physXConfigChangedHandler(
[]([[maybe_unused]] const AzPhysics::SystemConfiguration* config)
{
AzToolsFramework::PropertyEditorGUIMessages::Bus::Broadcast(&AzToolsFramework::PropertyEditorGUIMessages::RequestRefresh,
AzToolsFramework::PropertyModificationRefreshLevel::Refresh_AttributesAndValues);
})
, m_onMaterialLibraryChangedEventHandler(
[this](const AZ::Data::AssetId& defaultMaterialLibrary)
{
m_colliderConfig.m_materialSelection.OnMaterialLibraryChanged(defaultMaterialLibrary);
Physics::ColliderComponentEventBus::Event(GetEntityId(), &Physics::ColliderComponentEvents::OnColliderChanged);
AzToolsFramework::PropertyEditorGUIMessages::Bus::Broadcast(&AzToolsFramework::PropertyEditorGUIMessages::RequestRefresh,
AzToolsFramework::PropertyModificationRefreshLevel::Refresh_AttributesAndValues);
})
, m_nonUniformScaleChangedHandler([this](const AZ::Vector3& scale) {OnNonUniformScaleChanged(scale);})
{
m_colliderConfig.SetPropertyVisibility(Physics::ColliderConfiguration::Offset, false);
}
AZ::Crc32 EditorShapeColliderComponent::SubdivisionCountVisibility()
{
if (m_shapeType == ShapeType::Cylinder)
{
return AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::Show;
}
return AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::Hide;
}
void EditorShapeColliderComponent::Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<EditorShapeColliderComponent, EditorComponentBase>()
->Version(1)
->Field("ColliderConfiguration", &EditorShapeColliderComponent::m_colliderConfig)
->Field("DebugDrawSettings", &EditorShapeColliderComponent::m_colliderDebugDraw)
->Field("ShapeConfigs", &EditorShapeColliderComponent::m_shapeConfigs)
->Field("SubdivisionCount", &EditorShapeColliderComponent::m_subdivisionCount)
;
if (auto editContext = serializeContext->GetEditContext())
{
editContext->Class<EditorShapeColliderComponent>(
"PhysX Shape Collider", "Creates geometry in the PhysX simulation based on an attached shape component")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Category, "PhysX")
->Attribute(AZ::Edit::Attributes::Icon, "Icons/Components/PhysXCollider.svg")
->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/PhysXCollider.svg")
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c))
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->DataElement(AZ::Edit::UIHandlers::Default, &EditorShapeColliderComponent::m_colliderConfig,
"Collider configuration", "Configuration of the collider")
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorShapeColliderComponent::OnConfigurationChanged)
->DataElement(AZ::Edit::UIHandlers::Default, &EditorShapeColliderComponent::m_colliderDebugDraw,
"Debug draw settings", "Debug draw settings")
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
->DataElement(AZ::Edit::UIHandlers::Default, &EditorShapeColliderComponent::m_subdivisionCount, "Subdivision count", "Number of angular subdivisions in the PhysX cylinder")
->Attribute(AZ::Edit::Attributes::Min, Utils::MinFrustumSubdivisions)
->Attribute(AZ::Edit::Attributes::Max, Utils::MaxFrustumSubdivisions)
->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorShapeColliderComponent::OnSubdivisionCountChange)
->Attribute(AZ::Edit::Attributes::Visibility, &EditorShapeColliderComponent::SubdivisionCountVisibility)
;
}
}
}
void EditorShapeColliderComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC("PhysicsWorldBodyService", 0x944da0cc));
provided.push_back(AZ_CRC("PhysXColliderService", 0x4ff43f7c));
provided.push_back(AZ_CRC("PhysXTriggerService", 0x3a117d7b));
provided.push_back(AZ_CRC("PhysXShapeColliderService", 0x98a7e779));
}
void EditorShapeColliderComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
{
required.push_back(AZ_CRC("TransformService", 0x8ee22c50));
required.push_back(AZ_CRC("ShapeService", 0xe86aa5fe));
}
void EditorShapeColliderComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
{
incompatible.push_back(AZ_CRC("LegacyCryPhysicsService", 0xbb370351));
incompatible.push_back(AZ_CRC("PhysXShapeColliderService", 0x98a7e779));
}
const AZStd::vector<AZ::Vector3>& EditorShapeColliderComponent::GetSamplePoints() const
{
if (m_geometryCache.m_cachedSamplePointsDirty)
{
UpdateCachedSamplePoints();
m_geometryCache.m_cachedSamplePointsDirty = false;
}
return m_geometryCache.m_cachedSamplePoints;
}
void EditorShapeColliderComponent::UpdateCachedSamplePoints() const
{
m_geometryCache.m_cachedSamplePoints.clear();
switch (m_shapeType)
{
case ShapeType::Box:
{
const AZ::Vector3 boxMax = 0.5f * m_geometryCache.m_boxDimensions;
const AZ::Vector3 boxMin = -boxMax;
m_geometryCache.m_cachedSamplePoints = Utils::Geometry::GenerateBoxPoints(boxMin, boxMax);
break;
}
case ShapeType::Sphere:
{
m_geometryCache.m_cachedSamplePoints = Utils::Geometry::GenerateSpherePoints(m_geometryCache.m_radius);
break;
}
case ShapeType::Capsule:
{
const float cylinderHeight = m_geometryCache.m_height - 2 * m_geometryCache.m_radius;
if (cylinderHeight > 0.0f)
{
m_geometryCache.m_cachedSamplePoints = Utils::Geometry::GenerateCylinderPoints(cylinderHeight,
m_geometryCache.m_radius);
}
break;
}
case ShapeType::Cylinder:
{
if (m_geometryCache.m_height > 0.0f && m_geometryCache.m_radius > 0.0f)
{
m_geometryCache.m_cachedSamplePoints = Utils::Geometry::GenerateCylinderPoints(
m_geometryCache.m_height,
m_geometryCache.m_radius);
}
break;
}
case ShapeType::PolygonPrism:
{
if (!m_shapeConfigs.empty())
{
AZ::PolygonPrismPtr polygonPrismPtr;
LmbrCentral::PolygonPrismShapeComponentRequestBus::EventResult(polygonPrismPtr, GetEntityId(),
&LmbrCentral::PolygonPrismShapeComponentRequests::GetPolygonPrism);
if (polygonPrismPtr)
{
const AZ::Vector3 uniformScale = Utils::GetUniformScale(GetEntityId());
const AZ::Vector3 overallScale = uniformScale * m_currentNonUniformScale;
const AZStd::vector<AZ::Vector2>& vertices = polygonPrismPtr->m_vertexContainer.GetVertices();
for (const auto& vertex : vertices)
{
const float scaledX = overallScale.GetX() * vertex.GetX();
const float scaledY = overallScale.GetY() * vertex.GetY();
m_geometryCache.m_cachedSamplePoints.emplace_back(AZ::Vector3(scaledX, scaledY, 0.0f));
m_geometryCache.m_cachedSamplePoints.emplace_back(AZ::Vector3(scaledX, scaledY, m_geometryCache.m_height));
}
}
}
break;
}
default:
AZ_WarningOnce("PhysX Shape Collider Component", false, "Unsupported shape type in UpdateCachedSamplePoints");
break;
}
AZ::Transform transform = GetWorldTM();
transform.ExtractUniformScale();
const size_t numPoints = m_geometryCache.m_cachedSamplePoints.size();
for (size_t pointIndex = 0; pointIndex < numPoints; ++pointIndex)
{
m_geometryCache.m_cachedSamplePoints[pointIndex] = transform.TransformPoint(m_geometryCache.m_cachedSamplePoints[pointIndex]);
}
}
const Physics::ColliderConfiguration& EditorShapeColliderComponent::GetColliderConfiguration() const
{
return m_colliderConfig;
}
const AZStd::vector<AZStd::shared_ptr<Physics::ShapeConfiguration>>& EditorShapeColliderComponent::GetShapeConfigurations() const
{
return m_shapeConfigs;
}
void EditorShapeColliderComponent::BuildGameEntity(AZ::Entity* gameEntity)
{
auto* shapeColliderComponent = gameEntity->CreateComponent<ShapeColliderComponent>();
AzPhysics::ShapeColliderPairList shapeConfigurationList;
shapeConfigurationList.reserve(m_shapeConfigs.size());
for (const auto& shapeConfig : m_shapeConfigs)
{
shapeConfigurationList.emplace_back(
AZStd::make_shared<Physics::ColliderConfiguration>(m_colliderConfig),
shapeConfig);
}
shapeColliderComponent->SetShapeConfigurationList(shapeConfigurationList);
StaticRigidBodyUtils::TryCreateRuntimeComponent(*GetEntity(), *gameEntity);
}
void EditorShapeColliderComponent::CreateStaticEditorCollider()
{
// Don't create static rigid body in the editor if current entity components
// don't allow creation of runtime static rigid body component
if (!StaticRigidBodyUtils::CanCreateRuntimeComponent(*GetEntity()))
{
return;
}
const AZ::Transform colliderTransform = GetWorldTM();
AzPhysics::StaticRigidBodyConfiguration configuration;
configuration.m_orientation = colliderTransform.GetRotation();
configuration.m_position = colliderTransform.GetTranslation();
configuration.m_entityId = GetEntityId();
configuration.m_debugName = GetEntity()->GetName();
AzPhysics::ShapeColliderPairList colliderShapePairs;
colliderShapePairs.reserve(m_shapeConfigs.size());
for (const auto& shapeConfig : m_shapeConfigs)
{
colliderShapePairs.emplace_back(
AZStd::make_shared<Physics::ColliderConfiguration>(m_colliderConfig), shapeConfig);
}
configuration.m_colliderAndShapeData = colliderShapePairs;
if (m_sceneInterface)
{
//remove the previous body if any
if (m_editorBodyHandle != AzPhysics::InvalidSimulatedBodyHandle)
{
m_sceneInterface->RemoveSimulatedBody(m_editorSceneHandle, m_editorBodyHandle);
}
m_editorBodyHandle = m_sceneInterface->AddSimulatedBody(m_editorSceneHandle, &configuration);
}
AzPhysics::SimulatedBodyComponentRequestsBus::Handler::BusConnect(GetEntityId());
}
AZ::u32 EditorShapeColliderComponent::OnConfigurationChanged()
{
m_colliderConfig.m_materialSelection.SetMaterialSlots(Physics::MaterialSelection::SlotsArray());
CreateStaticEditorCollider();
return AZ::Edit::PropertyRefreshLevels::None;
}
void EditorShapeColliderComponent::UpdateShapeConfigs()
{
m_geometryCache.m_cachedSamplePointsDirty = true;
AZ::Crc32 shapeCrc;
LmbrCentral::ShapeComponentRequestsBus::EventResult(shapeCrc, GetEntityId(),
&LmbrCentral::ShapeComponentRequests::GetShapeType);
const AZ::Vector3 uniformScale = Utils::GetUniformScale(GetEntityId());
const AZ::Vector3 overallScale = uniformScale * m_currentNonUniformScale;
// using if blocks because switch statements aren't supported for values generated by the crc macro
if (shapeCrc == ShapeConstants::Box)
{
UpdateBoxConfig(overallScale);
}
else if (shapeCrc == ShapeConstants::Capsule)
{
UpdateCapsuleConfig(uniformScale);
}
else if (shapeCrc == ShapeConstants::Sphere)
{
UpdateSphereConfig(uniformScale);
}
else if (shapeCrc == ShapeConstants::PolygonPrism)
{
UpdatePolygonPrismDecomposition();
}
else if (shapeCrc == ShapeConstants::Cylinder)
{
UpdateCylinderConfig(uniformScale);
}
else
{
m_shapeType = !shapeCrc ? ShapeType::None : ShapeType::Unsupported;
m_shapeConfigs.clear();
AZ_Warning("PhysX Shape Collider Component", m_shapeTypeWarningIssued, "Unsupported shape type for "
"entity \"%s\". The following shapes are currently supported - box, capsule, sphere, polygon prism.",
GetEntity()->GetName().c_str());
m_shapeTypeWarningIssued = true;
}
}
void EditorShapeColliderComponent::UpdateBoxConfig(const AZ::Vector3& scale)
{
AZ::Vector3 boxDimensions = AZ::Vector3::CreateOne();
LmbrCentral::BoxShapeComponentRequestsBus::EventResult(boxDimensions, GetEntityId(),
&LmbrCentral::BoxShapeComponentRequests::GetBoxDimensions);
if (m_shapeType != ShapeType::Box)
{
m_shapeConfigs.clear();
m_shapeConfigs.emplace_back(AZStd::make_shared<Physics::BoxShapeConfiguration>(boxDimensions));
m_shapeType = ShapeType::Box;
}
else
{
Physics::BoxShapeConfiguration& configuration =
static_cast<Physics::BoxShapeConfiguration&>(*m_shapeConfigs.back());
configuration = Physics::BoxShapeConfiguration(boxDimensions);
}
m_shapeConfigs.back()->m_scale = scale;
m_geometryCache.m_boxDimensions = scale * boxDimensions;
}
void EditorShapeColliderComponent::UpdateCapsuleConfig(const AZ::Vector3& scale)
{
LmbrCentral::CapsuleShapeConfig lmbrCentralCapsuleShapeConfig;
LmbrCentral::CapsuleShapeComponentRequestsBus::EventResult(lmbrCentralCapsuleShapeConfig, GetEntityId(),
&LmbrCentral::CapsuleShapeComponentRequests::GetCapsuleConfiguration);
const Physics::CapsuleShapeConfiguration& capsuleShapeConfig =
Utils::ConvertFromLmbrCentralCapsuleConfig(lmbrCentralCapsuleShapeConfig);
if (m_shapeType != ShapeType::Capsule)
{
m_shapeConfigs.clear();
m_shapeConfigs.emplace_back(AZStd::make_shared<Physics::CapsuleShapeConfiguration>(capsuleShapeConfig));
m_shapeType = ShapeType::Capsule;
}
else
{
Physics::CapsuleShapeConfiguration& configuration =
static_cast<Physics::CapsuleShapeConfiguration&>(*m_shapeConfigs.back());
configuration = capsuleShapeConfig;
}
m_shapeConfigs.back()->m_scale = scale;
const float scalarScale = scale.GetMaxElement();
m_geometryCache.m_radius = scalarScale * capsuleShapeConfig.m_radius;
m_geometryCache.m_height = scalarScale * capsuleShapeConfig.m_height;
}
void EditorShapeColliderComponent::UpdateSphereConfig(const AZ::Vector3& scale)
{
float radius = 0.0f;
LmbrCentral::SphereShapeComponentRequestsBus::EventResult(radius, GetEntityId(),
&LmbrCentral::SphereShapeComponentRequests::GetRadius);
if (m_shapeType != ShapeType::Sphere)
{
m_shapeConfigs.clear();
m_shapeConfigs.emplace_back(AZStd::make_shared<Physics::SphereShapeConfiguration>(radius));
m_shapeType = ShapeType::Sphere;
}
else
{
Physics::SphereShapeConfiguration& configuration =
static_cast<Physics::SphereShapeConfiguration&>(*m_shapeConfigs.back());
configuration = Physics::SphereShapeConfiguration(radius);
}
m_shapeConfigs.back()->m_scale = scale;
m_geometryCache.m_radius = scale.GetMaxElement() * radius;
}
void EditorShapeColliderComponent::UpdateCylinderConfig(const AZ::Vector3& scale)
{
float height = 1.0f;
float radius = 1.0f;
LmbrCentral::CylinderShapeComponentRequestsBus::EventResult(radius, GetEntityId(),
&LmbrCentral::CylinderShapeComponentRequests::GetRadius);
LmbrCentral::CylinderShapeComponentRequestsBus::EventResult(height, GetEntityId(),
&LmbrCentral::CylinderShapeComponentRequests::GetHeight);
const float scalarScale = scale.GetMaxElement();
m_geometryCache.m_height = scalarScale * height;
m_geometryCache.m_radius = scalarScale * radius;
if (radius <= 0.0f || height <= 0.0f)
{
m_shapeConfigs.clear();
AZ_Warning("PhysX", false, "%s: Negative or zero cylinder dimensions are invalid (radius: '%f', height: '%f).", GetEntity()->GetName().c_str(), radius, height);
return;
}
AZStd::optional<AZStd::vector<AZ::Vector3>> points = Utils::CreatePointsAtFrustumExtents(height, radius, radius, m_subdivisionCount);
if (!points.has_value())
{
m_shapeConfigs.clear();
AZ_Warning("PhysX", false, "Could not generate cylinder shape collider.");
return;
}
AZStd::optional<Physics::CookedMeshShapeConfiguration> shapeConfig = Utils::CreatePxCookedMeshConfiguration(points.value(), scale);
if (shapeConfig.has_value())
{
if (m_shapeType != ShapeType::Cylinder)
{
m_shapeConfigs.clear();
m_shapeConfigs.push_back(AZStd::make_shared<Physics::CookedMeshShapeConfiguration>(shapeConfig.value()));
m_shapeType = ShapeType::Cylinder;
}
else
{
Physics::CookedMeshShapeConfiguration& configuration =
static_cast<Physics::CookedMeshShapeConfiguration&>(*m_shapeConfigs.back());
configuration = Physics::CookedMeshShapeConfiguration(shapeConfig.value());
}
CreateStaticEditorCollider();
}
}
AZ::u32 EditorShapeColliderComponent::OnSubdivisionCountChange()
{
const AZ::Vector3 uniformScale = Utils::GetUniformScale(GetEntityId());
UpdateCylinderConfig(uniformScale);
Physics::ColliderComponentEventBus::Event(GetEntityId(), &Physics::ColliderComponentEvents::OnColliderChanged);
return AZ::Edit::PropertyRefreshLevels::AttributesAndValues;
}
void EditorShapeColliderComponent::UpdatePolygonPrismDecomposition()
{
m_mesh.Clear();
AZ::PolygonPrismPtr polygonPrismPtr;
LmbrCentral::PolygonPrismShapeComponentRequestBus::EventResult(polygonPrismPtr, GetEntityId(),
&LmbrCentral::PolygonPrismShapeComponentRequests::GetPolygonPrism);
if (polygonPrismPtr)
{
UpdatePolygonPrismDecomposition(polygonPrismPtr);
}
CreateStaticEditorCollider();
m_mesh.SetDebugDrawDirty();
m_shapeType = ShapeType::PolygonPrism;
}
void EditorShapeColliderComponent::UpdatePolygonPrismDecomposition(const AZ::PolygonPrismPtr polygonPrismPtr)
{
const AZStd::vector<AZ::Vector2>& vertices = polygonPrismPtr->m_vertexContainer.GetVertices();
// if the polygon prism vertices do not form a simple polygon, we cannot perform the decomposition
if (!AZ::Geometry2DUtils::IsSimplePolygon(vertices))
{
if (!m_simplePolygonErrorIssued)
{
AZ_Error("PhysX Shape Collider Component", false, "Invalid polygon prism for entity \"%s\""
" - must be a simple polygon (no self intersection or duplicate vertices) to be represented in PhysX.",
GetEntity()->GetName().c_str());
m_simplePolygonErrorIssued = true;
}
m_mesh.Clear();
m_shapeConfigs.clear();
RefreshUiProperties();
return;
}
m_simplePolygonErrorIssued = false;
size_t numFacesRemoved = 0;
// If the polygon prism is already convex and meets the PhysX limit on convex mesh vertices/faces,
// then we don't need to do any complicated decomposition
if (vertices.size() <= PolygonPrismMeshUtils::MaxPolygonPrismEdges && AZ::Geometry2DUtils::IsConvex(vertices))
{
m_mesh.CreateFromSimpleConvexPolygon(vertices);
}
else
{
// Compute the constrained Delaunay triangulation using poly2tri
AZStd::vector<p2t::Point> p2tVertices;
std::vector<p2t::Point*> polyline;
p2tVertices.reserve(vertices.size());
polyline.reserve(vertices.size());
int vertexIndex = 0;
for (const AZ::Vector2& vert : vertices)
{
p2tVertices.push_back(p2t::Point(vert.GetX(), vert.GetY()));
polyline.push_back(&(p2tVertices.data()[vertexIndex++]));
}
p2t::CDT constrainedDelaunayTriangulation(polyline);
constrainedDelaunayTriangulation.Triangulate();
const std::vector<p2t::Triangle*>& triangles = constrainedDelaunayTriangulation.GetTriangles();
// Iteratively merge faces if it's possible to do so while maintaining convexity
m_mesh.CreateFromPoly2Tri(triangles);
numFacesRemoved = m_mesh.ConvexMerge();
}
// Create the cooked convex mesh configurations
const AZStd::vector<PolygonPrismMeshUtils::Face>& faces = m_mesh.GetFaces();
size_t numFacesTotal = faces.size();
if (m_shapeType != ShapeType::PolygonPrism)
{
m_shapeConfigs.clear();
m_shapeType = ShapeType::PolygonPrism;
}
if (numFacesRemoved <= numFacesTotal)
{
m_shapeConfigs.reserve(numFacesTotal - numFacesRemoved);
}
const float unscaledPrismHeight = polygonPrismPtr->GetHeight();
const AZ::Vector3 uniformScale = Utils::GetUniformScale(GetEntityId());
const AZ::Vector3 overallScale = uniformScale * m_currentNonUniformScale;
m_geometryCache.m_height = overallScale.GetZ() * unscaledPrismHeight;
int shapeConfigsCount = 0;
bool refreshPropertyTree = false;
for (int faceIndex = 0; faceIndex < numFacesTotal; faceIndex++)
{
if (faces[faceIndex].m_removed)
{
continue;
}
AZStd::vector<AZ::Vector3> points;
points.reserve(2 * faces[faceIndex].m_numEdges);
PolygonPrismMeshUtils::HalfEdge* currentEdge = faces[faceIndex].m_edge;
for (int edgeIndex = 0; edgeIndex < faces[faceIndex].m_numEdges; edgeIndex++)
{
points.emplace_back(currentEdge->m_origin.GetX(), currentEdge->m_origin.GetY(), 0.0f);
points.emplace_back(currentEdge->m_origin.GetX(), currentEdge->m_origin.GetY(), unscaledPrismHeight);
currentEdge = currentEdge->m_next;
}
AZStd::optional<Physics::CookedMeshShapeConfiguration> shapeConfig = Utils::CreatePxCookedMeshConfiguration(points, overallScale);
if (shapeConfig.has_value())
{
if (shapeConfigsCount >= m_shapeConfigs.size())
{
m_shapeConfigs.push_back(AZStd::make_shared<Physics::CookedMeshShapeConfiguration>(shapeConfig.value()));
refreshPropertyTree = true;
}
else
{
Physics::CookedMeshShapeConfiguration& configuration =
static_cast<Physics::CookedMeshShapeConfiguration&>(*m_shapeConfigs[shapeConfigsCount]);
configuration = Physics::CookedMeshShapeConfiguration(shapeConfig.value());
}
++shapeConfigsCount;
}
}
if (m_shapeConfigs.size() != shapeConfigsCount)
{
refreshPropertyTree = true;
m_shapeConfigs.resize(shapeConfigsCount);
}
if (refreshPropertyTree)
{
RefreshUiProperties();
}
}
void EditorShapeColliderComponent::RefreshUiProperties()
{
// Note: This is a workaround for a crash in InstanceDataHierarchy when a polygon prism shape collider is stored in a slice.
// m_shapeConfigs vector is reflected in the component and resizing it without invalidating property tree leads to dangling pointers
// in the hierarchy comparison system.
AzToolsFramework::ToolsApplicationNotificationBus::Broadcast(
&AzToolsFramework::ToolsApplicationNotificationBus::Events::InvalidatePropertyDisplay,
AzToolsFramework::Refresh_EntireTree);
}
// AZ::Component
void EditorShapeColliderComponent::Activate()
{
AzToolsFramework::Components::EditorComponentBase::Activate();
AzToolsFramework::EntitySelectionEvents::Bus::Handler::BusConnect(GetEntityId());
AZ::TransformNotificationBus::Handler::BusConnect(GetEntityId());
LmbrCentral::ShapeComponentNotificationsBus::Handler::BusConnect(GetEntityId());
PhysX::ColliderShapeRequestBus::Handler::BusConnect(GetEntityId());
AZ::NonUniformScaleRequestBus::Event(GetEntityId(), &AZ::NonUniformScaleRequests::RegisterScaleChangedEvent,
m_nonUniformScaleChangedHandler);
AZ::TransformBus::EventResult(m_cachedWorldTransform, GetEntityId(), &AZ::TransformInterface::GetWorldTM);
m_currentNonUniformScale = AZ::Vector3::CreateOne();
AZ::NonUniformScaleRequestBus::EventResult(m_currentNonUniformScale, GetEntityId(), &AZ::NonUniformScaleRequests::GetScale);
m_sceneInterface = AZ::Interface<AzPhysics::SceneInterface>::Get();
if (m_sceneInterface)
{
m_editorSceneHandle = m_sceneInterface->GetSceneHandle(AzPhysics::EditorPhysicsSceneName);
}
UpdateShapeConfigs();
// Debug drawing
m_colliderDebugDraw.Connect(GetEntityId());
m_colliderDebugDraw.SetDisplayCallback(this);
CreateStaticEditorCollider();
Physics::ColliderComponentEventBus::Event(GetEntityId(), &Physics::ColliderComponentEvents::OnColliderChanged);
}
void EditorShapeColliderComponent::Deactivate()
{
AzPhysics::SimulatedBodyComponentRequestsBus::Handler::BusDisconnect();
m_colliderDebugDraw.Disconnect();
m_nonUniformScaleChangedHandler.Disconnect();
PhysX::ColliderShapeRequestBus::Handler::BusDisconnect();
LmbrCentral::ShapeComponentNotificationsBus::Handler::BusDisconnect();
AZ::TransformNotificationBus::Handler::BusDisconnect();
AzToolsFramework::EntitySelectionEvents::Bus::Handler::BusDisconnect();
AzFramework::EntityDebugDisplayEventBus::Handler::BusDisconnect();
AzToolsFramework::Components::EditorComponentBase::Deactivate();
if (m_sceneInterface && m_editorBodyHandle != AzPhysics::InvalidSimulatedBodyHandle)
{
m_sceneInterface->RemoveSimulatedBody(m_editorSceneHandle, m_editorBodyHandle);
}
}
// AzToolsFramework::EntitySelectionEvents
void EditorShapeColliderComponent::OnSelected()
{
if (auto* physXSystem = GetPhysXSystem())
{
if (!m_physXConfigChangedHandler.IsConnected())
{
physXSystem->RegisterSystemConfigurationChangedEvent(m_physXConfigChangedHandler);
}
if (!m_onMaterialLibraryChangedEventHandler.IsConnected())
{
physXSystem->RegisterOnMaterialLibraryChangedEventHandler(m_onMaterialLibraryChangedEventHandler);
}
}
}
void EditorShapeColliderComponent::OnDeselected()
{
m_onMaterialLibraryChangedEventHandler.Disconnect();
m_physXConfigChangedHandler.Disconnect();
}
// TransformBus
void EditorShapeColliderComponent::OnTransformChanged(const AZ::Transform& /*local*/, const AZ::Transform& world)
{
if (world.IsClose(m_cachedWorldTransform))
{
return;
}
m_cachedWorldTransform = world;
UpdateShapeConfigs();
CreateStaticEditorCollider();
m_geometryCache.m_cachedSamplePointsDirty = true;
Physics::ColliderComponentEventBus::Event(GetEntityId(), &Physics::ColliderComponentEvents::OnColliderChanged);
}
void EditorShapeColliderComponent::OnNonUniformScaleChanged(const AZ::Vector3& scale)
{
m_currentNonUniformScale = scale;
UpdateShapeConfigs();
CreateStaticEditorCollider();
m_geometryCache.m_cachedSamplePointsDirty = true;
Physics::ColliderComponentEventBus::Event(GetEntityId(), &Physics::ColliderComponentEvents::OnColliderChanged);
}
void EditorShapeColliderComponent::EnablePhysics()
{
if (!IsPhysicsEnabled() && m_sceneInterface)
{
m_sceneInterface->EnableSimulationOfBody(m_editorSceneHandle, m_editorBodyHandle);
}
}
void EditorShapeColliderComponent::DisablePhysics()
{
if (m_sceneInterface)
{
m_sceneInterface->DisableSimulationOfBody(m_editorSceneHandle, m_editorBodyHandle);
}
}
bool EditorShapeColliderComponent::IsPhysicsEnabled() const
{
if (m_sceneInterface && m_editorBodyHandle != AzPhysics::InvalidSimulatedBodyHandle)
{
if (auto* body = m_sceneInterface->GetSimulatedBodyFromHandle(m_editorSceneHandle, m_editorBodyHandle))
{
return body->m_simulating;
}
}
return false;
}
AZ::Aabb EditorShapeColliderComponent::GetAabb() const
{
if (m_sceneInterface && m_editorBodyHandle != AzPhysics::InvalidSimulatedBodyHandle)
{
if (auto* body = m_sceneInterface->GetSimulatedBodyFromHandle(m_editorSceneHandle, m_editorBodyHandle))
{
return body->GetAabb();
}
}
return AZ::Aabb::CreateNull();
}
AzPhysics::SimulatedBody* EditorShapeColliderComponent::GetSimulatedBody()
{
if (m_sceneInterface && m_editorBodyHandle != AzPhysics::InvalidSimulatedBodyHandle)
{
if (auto* body = m_sceneInterface->GetSimulatedBodyFromHandle(m_editorSceneHandle, m_editorBodyHandle))
{
return body;
}
}
return nullptr;
}
AzPhysics::SimulatedBodyHandle EditorShapeColliderComponent::GetSimulatedBodyHandle() const
{
return m_editorBodyHandle;
}
AzPhysics::SceneQueryHit EditorShapeColliderComponent::RayCast(const AzPhysics::RayCastRequest& request)
{
if (m_sceneInterface && m_editorBodyHandle != AzPhysics::InvalidSimulatedBodyHandle)
{
if (auto* body = m_sceneInterface->GetSimulatedBodyFromHandle(m_editorSceneHandle, m_editorBodyHandle))
{
return body->RayCast(request);
}
}
return AzPhysics::SceneQueryHit();
}
// LmbrCentral::ShapeComponentNotificationBus
void EditorShapeColliderComponent::OnShapeChanged(LmbrCentral::ShapeComponentNotifications::ShapeChangeReasons changeReason)
{
if (changeReason == LmbrCentral::ShapeComponentNotifications::ShapeChangeReasons::ShapeChanged)
{
UpdateShapeConfigs();
CreateStaticEditorCollider();
Physics::ColliderComponentEventBus::Event(GetEntityId(), &Physics::ColliderComponentEvents::OnColliderChanged);
}
}
// DisplayCallback
void EditorShapeColliderComponent::Display(AzFramework::DebugDisplayRequests& debugDisplay) const
{
// polygon prism is a special case
if (m_shapeType == ShapeType::PolygonPrism)
{
AZ::PolygonPrismPtr polygonPrismPtr;
LmbrCentral::PolygonPrismShapeComponentRequestBus::EventResult(polygonPrismPtr, GetEntityId(),
&LmbrCentral::PolygonPrismShapeComponentRequests::GetPolygonPrism);
if (polygonPrismPtr)
{
const AZ::Vector3 uniformScale = Utils::GetUniformScale(GetEntityId());
const AZ::Vector3 overallScale = uniformScale * m_currentNonUniformScale;
const float height = polygonPrismPtr->GetHeight();
m_colliderDebugDraw.DrawPolygonPrism(debugDisplay, m_colliderConfig, m_mesh.GetDebugDrawPoints(height,
overallScale));
}
}
else if (m_shapeType == ShapeType::Cylinder)
{
if (!m_shapeConfigs.empty())
{
const AZ::u32 shapeIndex = 0;
const AZ::Vector3 uniformScale = Utils::GetUniformScale(GetEntityId());
Physics::ShapeConfiguration* shapeConfig = m_shapeConfigs[0].get();
m_colliderDebugDraw.BuildMeshes(*shapeConfig, shapeIndex);
m_colliderDebugDraw.DrawMesh(debugDisplay, m_colliderConfig, *static_cast<Physics::CookedMeshShapeConfiguration*>(shapeConfig), uniformScale, shapeIndex);
}
}
// for primitive shapes just display the shape configs
else
{
for (const auto& shapeConfig : m_shapeConfigs)
{
switch (shapeConfig->GetShapeType())
{
case Physics::ShapeType::Box:
{
const auto& boxConfig = static_cast<const Physics::BoxShapeConfiguration&>(*shapeConfig);
m_colliderDebugDraw.DrawBox(debugDisplay, m_colliderConfig, boxConfig, m_currentNonUniformScale, false);
break;
}
case Physics::ShapeType::Capsule:
{
const auto& capsuleConfig = static_cast<const Physics::CapsuleShapeConfiguration&>(*shapeConfig);
m_colliderDebugDraw.DrawCapsule(debugDisplay, m_colliderConfig, capsuleConfig, AZ::Vector3::CreateOne(), true);
break;
}
case Physics::ShapeType::Sphere:
{
const auto& sphereConfig = static_cast<const Physics::SphereShapeConfiguration&>(*shapeConfig);
m_colliderDebugDraw.DrawSphere(debugDisplay, m_colliderConfig, sphereConfig);
break;
}
default:
break;
}
}
}
}
// ColliderShapeRequestBus
AZ::Aabb EditorShapeColliderComponent::GetColliderShapeAabb()
{
AZ::Aabb aabb = AZ::Aabb::CreateFromPoint(GetWorldTM().GetTranslation());
LmbrCentral::ShapeComponentRequestsBus::EventResult(aabb, GetEntityId(),
&LmbrCentral::ShapeComponentRequests::GetEncompassingAabb);
return aabb;
}
bool EditorShapeColliderComponent::IsTrigger()
{
return m_colliderConfig.m_isTrigger;
}
} // namespace PhysX