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/EditorColliderComponent.cpp

1429 lines
62 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 <AzCore/Asset/AssetSerializer.h>
#include <AzCore/Script/ScriptTimePoint.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/std/smart_ptr/shared_ptr.h>
#include <AzFramework/Physics/ColliderComponentBus.h>
#include <AzFramework/Physics/SimulatedBodies/RigidBody.h>
#include <AzFramework/Physics/MaterialBus.h>
#include <AzFramework/Physics/Common/PhysicsSimulatedBody.h>
#include <AzFramework/Physics/Configuration/StaticRigidBodyConfiguration.h>
#include <AzFramework/Viewport/ViewportColors.h>
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
#include <AzToolsFramework/API/EntityPropertyEditorRequestsBus.h>
#include <AzToolsFramework/ComponentModes/BoxComponentMode.h>
#include <AzToolsFramework/Maths/TransformUtils.h>
#include <AzToolsFramework/UI/PropertyEditor/PropertyEditorAPI.h>
#include <AzToolsFramework/Entity/EditorEntityHelpers.h>
#include <Editor/EditorClassConverters.h>
#include <LmbrCentral/Geometry/GeometrySystemComponentBus.h>
#include <LmbrCentral/Shape/BoxShapeComponentBus.h>
#include <Source/BoxColliderComponent.h>
#include <Source/CapsuleColliderComponent.h>
#include <Source/EditorColliderComponent.h>
#include <Source/EditorRigidBodyComponent.h>
#include <Editor/Source/Components/EditorSystemComponent.h>
#include <Source/MeshColliderComponent.h>
#include <Source/SphereColliderComponent.h>
#include <Source/Utils.h>
#include <Atom/RPI.Reflect/Model/ModelAsset.h>
#include <LyViewPaneNames.h>
#include <Editor/ConfigurationWindowBus.h>
#include <Editor/ColliderComponentMode.h>
namespace PhysX
{
void EditorProxyAssetShapeConfig::Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<EditorProxyAssetShapeConfig>()
->Version(1)
->Field("Asset", &EditorProxyAssetShapeConfig::m_pxAsset)
->Field("Configuration", &EditorProxyAssetShapeConfig::m_configuration)
;
if (auto editContext = serializeContext->GetEditContext())
{
editContext->Class<EditorProxyAssetShapeConfig>("EditorProxyShapeConfig", "PhysX Base collider.")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->DataElement(AZ::Edit::UIHandlers::Default, &EditorProxyAssetShapeConfig::m_pxAsset, "PhysX Mesh",
"Specifies the PhysX mesh collider asset for this PhysX collider component.")
->Attribute(AZ_CRC_CE("EditButton"), "")
->Attribute(AZ_CRC_CE("EditDescription"), "Open in Scene Settings")
->Attribute(AZ_CRC_CE("DisableEditButtonWhenNoAssetSelected"), true)
->DataElement(AZ::Edit::UIHandlers::Default, &EditorProxyAssetShapeConfig::m_configuration, "Configuration",
"PhysX mesh asset collider configuration.")
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly);
}
}
}
void EditorProxyShapeConfig::Reflect(AZ::ReflectContext* context)
{
EditorProxyAssetShapeConfig::Reflect(context);
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<EditorProxyShapeConfig>()
->Version(2, &PhysX::ClassConverters::EditorProxyShapeConfigVersionConverter)
->Field("ShapeType", &EditorProxyShapeConfig::m_shapeType)
->Field("Sphere", &EditorProxyShapeConfig::m_sphere)
->Field("Box", &EditorProxyShapeConfig::m_box)
->Field("Capsule", &EditorProxyShapeConfig::m_capsule)
->Field("PhysicsAsset", &EditorProxyShapeConfig::m_physicsAsset)
->Field("HasNonUniformScale", &EditorProxyShapeConfig::m_hasNonUniformScale)
->Field("SubdivisionLevel", &EditorProxyShapeConfig::m_subdivisionLevel)
;
if (auto editContext = serializeContext->GetEditContext())
{
editContext->Class<EditorProxyShapeConfig>(
"EditorProxyShapeConfig", "PhysX Base shape collider")
->DataElement(AZ::Edit::UIHandlers::ComboBox, &EditorProxyShapeConfig::m_shapeType, "Shape", "The shape of the collider.")
->EnumAttribute(Physics::ShapeType::Sphere, "Sphere")
->EnumAttribute(Physics::ShapeType::Box, "Box")
->EnumAttribute(Physics::ShapeType::Capsule, "Capsule")
->EnumAttribute(Physics::ShapeType::PhysicsAsset, "PhysicsAsset")
->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorProxyShapeConfig::OnShapeTypeChanged)
// note: we do not want the user to be able to change shape types while in ComponentMode (there will
// potentially be different ComponentModes for different shape types)
->Attribute(AZ::Edit::Attributes::ReadOnly, &AzToolsFramework::ComponentModeFramework::InComponentMode)
->DataElement(AZ::Edit::UIHandlers::Default, &EditorProxyShapeConfig::m_sphere, "Sphere", "Configuration of sphere shape.")
->Attribute(AZ::Edit::Attributes::Visibility, &EditorProxyShapeConfig::IsSphereConfig)
->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorProxyShapeConfig::OnConfigurationChanged)
->DataElement(AZ::Edit::UIHandlers::Default, &EditorProxyShapeConfig::m_box, "Box", "Configuration of box shape.")
->Attribute(AZ::Edit::Attributes::Visibility, &EditorProxyShapeConfig::IsBoxConfig)
->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorProxyShapeConfig::OnConfigurationChanged)
->DataElement(AZ::Edit::UIHandlers::Default, &EditorProxyShapeConfig::m_capsule, "Capsule", "Configuration of capsule shape.")
->Attribute(AZ::Edit::Attributes::Visibility, &EditorProxyShapeConfig::IsCapsuleConfig)
->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorProxyShapeConfig::OnConfigurationChanged)
->DataElement(AZ::Edit::UIHandlers::Default, &EditorProxyShapeConfig::m_physicsAsset, "Asset", "Configuration of asset shape.")
->Attribute(AZ::Edit::Attributes::Visibility, &EditorProxyShapeConfig::IsAssetConfig)
->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorProxyShapeConfig::OnConfigurationChanged)
->DataElement(AZ::Edit::UIHandlers::Default, &EditorProxyShapeConfig::m_subdivisionLevel, "Subdivision level",
"The level of subdivision if a primitive shape is replaced with a convex mesh due to scaling.")
->Attribute(AZ::Edit::Attributes::Min, Utils::MinCapsuleSubdivisionLevel)
->Attribute(AZ::Edit::Attributes::Max, Utils::MaxCapsuleSubdivisionLevel)
->Attribute(AZ::Edit::Attributes::Visibility, &EditorProxyShapeConfig::ShowingSubdivisionLevel)
->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorProxyShapeConfig::OnConfigurationChanged)
;
}
}
}
AZ::u32 EditorProxyShapeConfig::OnShapeTypeChanged()
{
// reset the physics asset if the shape type was Physics Asset
if (m_shapeType != Physics::ShapeType::PhysicsAsset && m_lastShapeType == Physics::ShapeType::PhysicsAsset)
{
// clean up any reference to a physics assets, and re-initialize to an empty Pipeline::MeshAsset asset.
m_physicsAsset.m_pxAsset.Reset();
m_physicsAsset.m_pxAsset = AZ::Data::Asset<Pipeline::MeshAsset>(AZ::Data::AssetLoadBehavior::QueueLoad);
m_physicsAsset.m_configuration = Physics::PhysicsAssetShapeConfiguration();
}
m_lastShapeType = m_shapeType;
return AZ::Edit::PropertyRefreshLevels::EntireTree;
}
AZ::u32 EditorProxyShapeConfig::OnConfigurationChanged()
{
return AZ::Edit::PropertyRefreshLevels::ValuesOnly;
}
void EditorColliderComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC_CE("PhysicsWorldBodyService"));
provided.push_back(AZ_CRC_CE("PhysXColliderService"));
provided.push_back(AZ_CRC_CE("PhysXTriggerService"));
}
void EditorColliderComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
{
required.push_back(AZ_CRC_CE("TransformService"));
}
void EditorColliderComponent::GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent)
{
dependent.push_back(AZ_CRC_CE("NonUniformScaleService"));
}
void EditorColliderComponent::Reflect(AZ::ReflectContext* context)
{
EditorProxyShapeConfig::Reflect(context);
DebugDraw::Collider::Reflect(context);
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
// Deprecate old separate components
serializeContext->ClassDeprecate(
"EditorCapsuleColliderComponent",
"{0BD5AF3A-35C0-4386-9930-54A2A3E97432}",
&ClassConverters::DeprecateEditorCapsuleColliderComponent)
;
serializeContext->ClassDeprecate(
"EditorBoxColliderComponent",
"{FAECF2BE-625B-469D-BBFF-E345BBB12D66}",
&ClassConverters::DeprecateEditorBoxColliderComponent)
;
serializeContext->ClassDeprecate(
"EditorSphereColliderComponent",
"{D11C1624-4AE9-4B66-A6F6-40EDB9CDCE99}",
&ClassConverters::DeprecateEditorSphereColliderComponent)
;
serializeContext->ClassDeprecate(
"EditorMeshColliderComponent",
"{214185DA-ABD9-4410-9819-7C177801CF7A}",
&ClassConverters::DeprecateEditorMeshColliderComponent)
;
serializeContext->Class<EditorColliderComponent, EditorComponentBase>()
->Version(9, &PhysX::ClassConverters::UpgradeEditorColliderComponent)
->Field("ColliderConfiguration", &EditorColliderComponent::m_configuration)
->Field("ShapeConfiguration", &EditorColliderComponent::m_shapeConfiguration)
->Field("DebugDrawSettings", &EditorColliderComponent::m_colliderDebugDraw)
->Field("ComponentMode", &EditorColliderComponent::m_componentModeDelegate)
->Field("HasNonUniformScale", &EditorColliderComponent::m_hasNonUniformScale)
;
if (auto editContext = serializeContext->GetEditContext())
{
editContext->Class<EditorColliderComponent>(
"PhysX Collider", "Creates geometry in the PhysX simulation, using either a primitive shape or geometry from an asset.")
->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/Viewport/PhysXCollider.svg")
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c))
->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/physx/collider/")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->DataElement(AZ::Edit::UIHandlers::Default, &EditorColliderComponent::m_configuration, "Collider Configuration", "Configuration of the collider.")
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorColliderComponent::OnConfigurationChanged)
->DataElement(AZ::Edit::UIHandlers::Default, &EditorColliderComponent::m_shapeConfiguration, "Shape Configuration", "Configuration of the shape.")
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorColliderComponent::OnConfigurationChanged)
->Attribute(AZ::Edit::Attributes::RemoveNotify, &EditorColliderComponent::ValidateRigidBodyMeshGeometryType)
->DataElement(AZ::Edit::UIHandlers::Default, &EditorColliderComponent::m_componentModeDelegate, "Component Mode", "Collider Component Mode.")
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
->DataElement(AZ::Edit::UIHandlers::Default, &EditorColliderComponent::m_colliderDebugDraw,
"Debug draw settings", "Debug draw settings.")
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
;
}
}
}
AZ::ComponentDescriptor* EditorColliderComponent::CreateDescriptor()
{
return aznew EditorColliderComponentDescriptor();
}
EditorColliderComponent::EditorColliderComponent(
const Physics::ColliderConfiguration& colliderConfiguration,
const Physics::ShapeConfiguration& shapeConfiguration)
: m_configuration(colliderConfiguration)
, m_shapeConfiguration(shapeConfiguration)
{
}
const EditorProxyShapeConfig& EditorColliderComponent::GetShapeConfiguration() const
{
return m_shapeConfiguration;
}
const Physics::ColliderConfiguration& EditorColliderComponent::GetColliderConfiguration() const
{
return m_configuration;
}
Physics::ColliderConfiguration EditorColliderComponent::GetColliderConfigurationScaled() const
{
// Scale the collider offset
Physics::ColliderConfiguration colliderConfiguration = m_configuration;
colliderConfiguration.m_position *= Utils::GetTransformScale(GetEntityId()) * m_cachedNonUniformScale;
return colliderConfiguration;
}
EditorProxyShapeConfig::EditorProxyShapeConfig(const Physics::ShapeConfiguration& shapeConfiguration)
{
m_shapeType = shapeConfiguration.GetShapeType();
switch (m_shapeType)
{
case Physics::ShapeType::Sphere:
m_sphere = static_cast<const Physics::SphereShapeConfiguration&>(shapeConfiguration);
break;
case Physics::ShapeType::Box:
m_box = static_cast<const Physics::BoxShapeConfiguration&>(shapeConfiguration);
break;
case Physics::ShapeType::Capsule:
m_capsule = static_cast<const Physics::CapsuleShapeConfiguration&>(shapeConfiguration);
break;
case Physics::ShapeType::PhysicsAsset:
m_physicsAsset.m_configuration = static_cast<const Physics::PhysicsAssetShapeConfiguration&>(shapeConfiguration);
break;
case Physics::ShapeType::CookedMesh:
m_cookedMesh = static_cast<const Physics::CookedMeshShapeConfiguration&>(shapeConfiguration);
break;
default:
AZ_Warning("EditorProxyShapeConfig", false, "Invalid shape type!");
}
}
bool EditorProxyShapeConfig::IsSphereConfig() const
{
return m_shapeType == Physics::ShapeType::Sphere;
}
bool EditorProxyShapeConfig::IsBoxConfig() const
{
return m_shapeType == Physics::ShapeType::Box;
}
bool EditorProxyShapeConfig::IsCapsuleConfig() const
{
return m_shapeType == Physics::ShapeType::Capsule;
}
bool EditorProxyShapeConfig::IsAssetConfig() const
{
return m_shapeType == Physics::ShapeType::PhysicsAsset;
}
Physics::ShapeConfiguration& EditorProxyShapeConfig::GetCurrent()
{
return const_cast<Physics::ShapeConfiguration&>(static_cast<const EditorProxyShapeConfig&>(*this).GetCurrent());
}
const Physics::ShapeConfiguration& EditorProxyShapeConfig::GetCurrent() const
{
switch (m_shapeType)
{
case Physics::ShapeType::Sphere:
return m_sphere;
case Physics::ShapeType::Box:
return m_box;
case Physics::ShapeType::Capsule:
return m_capsule;
case Physics::ShapeType::PhysicsAsset:
return m_physicsAsset.m_configuration;
case Physics::ShapeType::CookedMesh:
return m_cookedMesh;
default:
AZ_Warning("EditorProxyShapeConfig", false, "Unsupported shape type");
return m_box;
}
}
AZStd::shared_ptr<Physics::ShapeConfiguration> EditorProxyShapeConfig::CloneCurrent() const
{
switch (m_shapeType)
{
case Physics::ShapeType::Sphere:
return AZStd::make_shared<Physics::SphereShapeConfiguration>(m_sphere);
case Physics::ShapeType::Capsule:
return AZStd::make_shared<Physics::CapsuleShapeConfiguration>(m_capsule);
case Physics::ShapeType::PhysicsAsset:
return AZStd::make_shared<Physics::PhysicsAssetShapeConfiguration>(m_physicsAsset.m_configuration);
case Physics::ShapeType::CookedMesh:
return AZStd::make_shared<Physics::CookedMeshShapeConfiguration>(m_cookedMesh);
default:
AZ_Warning("EditorProxyShapeConfig", false, "Unsupported shape type, defaulting to Box.");
[[fallthrough]];
case Physics::ShapeType::Box:
return AZStd::make_shared<Physics::BoxShapeConfiguration>(m_box);
}
}
bool EditorProxyShapeConfig::ShowingSubdivisionLevel() const
{
return (m_hasNonUniformScale && (IsCapsuleConfig() || IsSphereConfig() || IsAssetConfig()));
}
void EditorColliderComponent::Activate()
{
m_sceneInterface = AZ::Interface<AzPhysics::SceneInterface>::Get();
if (m_sceneInterface)
{
m_editorSceneHandle = m_sceneInterface->GetSceneHandle(AzPhysics::EditorPhysicsSceneName);
}
m_physXConfigChangedHandler = AzPhysics::SystemEvents::OnConfigurationChangedEvent::Handler(
[]([[maybe_unused]] const AzPhysics::SystemConfiguration* config)
{
AzToolsFramework::PropertyEditorGUIMessages::Bus::Broadcast(&AzToolsFramework::PropertyEditorGUIMessages::RequestRefresh,
AzToolsFramework::PropertyModificationRefreshLevel::Refresh_AttributesAndValues);
});
m_onMaterialLibraryChangedEventHandler = AzPhysics::SystemEvents::OnMaterialLibraryChangedEvent::Handler(
[this](const AZ::Data::AssetId& defaultMaterialLibrary)
{
m_configuration.m_materialSelection.OnMaterialLibraryChanged(defaultMaterialLibrary);
UpdateMaterialSlotsFromMeshAsset();
AzToolsFramework::PropertyEditorGUIMessages::Bus::Broadcast(&AzToolsFramework::PropertyEditorGUIMessages::RequestRefresh,
AzToolsFramework::PropertyModificationRefreshLevel::Refresh_AttributesAndValues);
});
AzToolsFramework::Components::EditorComponentBase::Activate();
AzToolsFramework::EntitySelectionEvents::Bus::Handler::BusConnect(GetEntityId());
PhysX::MeshColliderComponentRequestsBus::Handler::BusConnect(GetEntityId());
AZ::TransformNotificationBus::Handler::BusConnect(GetEntityId());
AzToolsFramework::BoxManipulatorRequestBus::Handler::BusConnect(
AZ::EntityComponentIdPair(GetEntityId(), GetId()));
ColliderShapeRequestBus::Handler::BusConnect(GetEntityId());
AZ::Render::MeshComponentNotificationBus::Handler::BusConnect(GetEntityId());
EditorColliderComponentRequestBus::Handler::BusConnect(AZ::EntityComponentIdPair(GetEntityId(), GetId()));
EditorColliderValidationRequestBus::Handler::BusConnect(GetEntityId());
m_nonUniformScaleChangedHandler = AZ::NonUniformScaleChangedEvent::Handler(
[this](const AZ::Vector3& scale) {OnNonUniformScaleChanged(scale); });
AZ::NonUniformScaleRequestBus::Event(GetEntityId(), &AZ::NonUniformScaleRequests::RegisterScaleChangedEvent,
m_nonUniformScaleChangedHandler);
m_hasNonUniformScale = (AZ::NonUniformScaleRequestBus::FindFirstHandler(GetEntityId()) != nullptr);
m_shapeConfiguration.m_hasNonUniformScale = m_hasNonUniformScale;
AZ::TransformBus::EventResult(m_cachedWorldTransform, GetEntityId(), &AZ::TransformInterface::GetWorldTM);
m_cachedNonUniformScale = AZ::Vector3::CreateOne();
if (m_hasNonUniformScale)
{
AZ::NonUniformScaleRequestBus::EventResult(m_cachedNonUniformScale, GetEntityId(), &AZ::NonUniformScaleRequests::GetScale);
}
// Debug drawing
m_colliderDebugDraw.Connect(GetEntityId());
m_colliderDebugDraw.SetDisplayCallback(this);
// ComponentMode
m_componentModeDelegate.ConnectWithSingleComponentMode<
EditorColliderComponent, ColliderComponentMode>(
AZ::EntityComponentIdPair(GetEntityId(), GetId()), nullptr);
bool usingMaterialsFromAsset = IsAssetConfig() ? m_shapeConfiguration.m_physicsAsset.m_configuration.m_useMaterialsFromAsset : false;
m_configuration.m_materialSelection.SetSlotsReadOnly(usingMaterialsFromAsset);
if (ShouldUpdateCollisionMeshFromRender())
{
SetCollisionMeshFromRender();
}
UpdateMeshAsset();
UpdateShapeConfigurationScale();
CreateStaticEditorCollider();
Physics::ColliderComponentEventBus::Event(GetEntityId(), &Physics::ColliderComponentEvents::OnColliderChanged);
}
void EditorColliderComponent::Deactivate()
{
AzPhysics::SimulatedBodyComponentRequestsBus::Handler::BusDisconnect();
m_colliderDebugDraw.Disconnect();
AZ::Data::AssetBus::MultiHandler::BusDisconnect();
m_nonUniformScaleChangedHandler.Disconnect();
EditorColliderValidationRequestBus::Handler::BusDisconnect();
EditorColliderComponentRequestBus::Handler::BusDisconnect();
AZ::Render::MeshComponentNotificationBus::Handler::BusDisconnect();
ColliderShapeRequestBus::Handler::BusDisconnect();
AzToolsFramework::BoxManipulatorRequestBus::Handler::BusDisconnect();
AZ::TransformNotificationBus::Handler::BusDisconnect();
PhysX::MeshColliderComponentRequestsBus::Handler::BusDisconnect();
AzToolsFramework::EntitySelectionEvents::Bus::Handler::BusDisconnect();
AzToolsFramework::Components::EditorComponentBase::Deactivate();
m_componentModeDelegate.Disconnect();
if (m_sceneInterface)
{
m_sceneInterface->RemoveSimulatedBody(m_editorSceneHandle, m_editorBodyHandle);
}
}
AZ::u32 EditorColliderComponent::OnConfigurationChanged()
{
if (m_shapeConfiguration.IsAssetConfig())
{
UpdateMeshAsset();
m_configuration.m_materialSelection.SetSlotsReadOnly(m_shapeConfiguration.m_physicsAsset.m_configuration.m_useMaterialsFromAsset);
}
else
{
m_configuration.m_materialSelection.SetMaterialSlots(Physics::MaterialSelection::SlotsArray());
m_configuration.m_materialSelection.SetSlotsReadOnly(false);
}
// ensure we refresh the ComponentMode (and Manipulators) when the configuration
// changes to keep the ComponentMode in sync with the shape (otherwise the manipulators
// will move out of alignment with the shape)
AzToolsFramework::ComponentModeFramework::ComponentModeSystemRequestBus::Broadcast(
&AzToolsFramework::ComponentModeFramework::ComponentModeSystemRequests::Refresh,
AZ::EntityComponentIdPair(GetEntityId(), GetId()));
UpdateShapeConfigurationScale();
CreateStaticEditorCollider();
ValidateRigidBodyMeshGeometryType();
m_colliderDebugDraw.ClearCachedGeometry();
Physics::ColliderComponentEventBus::Event(GetEntityId(), &Physics::ColliderComponentEvents::OnColliderChanged);
return AZ::Edit::PropertyRefreshLevels::None;
}
void EditorColliderComponent::OnSelected()
{
if (auto* physXSystem = GetPhysXSystem())
{
physXSystem->RegisterSystemConfigurationChangedEvent(m_physXConfigChangedHandler);
physXSystem->RegisterOnMaterialLibraryChangedEventHandler(m_onMaterialLibraryChangedEventHandler);
}
}
void EditorColliderComponent::OnDeselected()
{
m_onMaterialLibraryChangedEventHandler.Disconnect();
m_physXConfigChangedHandler.Disconnect();
}
void EditorColliderComponent::BuildGameEntity(AZ::Entity* gameEntity)
{
auto sharedColliderConfig = AZStd::make_shared<Physics::ColliderConfiguration>(m_configuration);
BaseColliderComponent* colliderComponent = nullptr;
auto buildGameEntityScaledPrimitive = [gameEntity](AZStd::shared_ptr<Physics::ColliderConfiguration>& colliderConfig,
Physics::ShapeConfiguration& shapeConfig, AZ::u8 subdivisionLevel)
{
auto scaledPrimitiveConfig = Utils::CreateConvexFromPrimitive(*colliderConfig,
shapeConfig, subdivisionLevel, shapeConfig.m_scale);
if (scaledPrimitiveConfig.has_value())
{
colliderConfig->m_rotation = AZ::Quaternion::CreateIdentity();
colliderConfig->m_position = AZ::Vector3::CreateZero();
BaseColliderComponent* colliderComponent = gameEntity->CreateComponent<BaseColliderComponent>();
colliderComponent->SetShapeConfigurationList({ AZStd::make_pair(colliderConfig,
AZStd::make_shared<Physics::CookedMeshShapeConfiguration>(scaledPrimitiveConfig.value())) });
}
};
switch (m_shapeConfiguration.m_shapeType)
{
case Physics::ShapeType::Sphere:
if (!m_hasNonUniformScale)
{
colliderComponent = gameEntity->CreateComponent<SphereColliderComponent>();
colliderComponent->SetShapeConfigurationList({ AZStd::make_pair(sharedColliderConfig,
AZStd::make_shared<Physics::SphereShapeConfiguration>(m_shapeConfiguration.m_sphere)) });
}
else
{
buildGameEntityScaledPrimitive(sharedColliderConfig, m_shapeConfiguration.m_sphere, m_shapeConfiguration.m_subdivisionLevel);
}
break;
case Physics::ShapeType::Box:
if (!m_hasNonUniformScale)
{
colliderComponent = gameEntity->CreateComponent<BoxColliderComponent>();
colliderComponent->SetShapeConfigurationList({ AZStd::make_pair(sharedColliderConfig,
AZStd::make_shared<Physics::BoxShapeConfiguration>(m_shapeConfiguration.m_box)) });
}
else
{
buildGameEntityScaledPrimitive(sharedColliderConfig, m_shapeConfiguration.m_box, m_shapeConfiguration.m_subdivisionLevel);
}
break;
case Physics::ShapeType::Capsule:
if (!m_hasNonUniformScale)
{
colliderComponent = gameEntity->CreateComponent<CapsuleColliderComponent>();
colliderComponent->SetShapeConfigurationList({ AZStd::make_pair(sharedColliderConfig,
AZStd::make_shared<Physics::CapsuleShapeConfiguration>(m_shapeConfiguration.m_capsule)) });
}
else
{
buildGameEntityScaledPrimitive(sharedColliderConfig, m_shapeConfiguration.m_capsule, m_shapeConfiguration.m_subdivisionLevel);
}
break;
case Physics::ShapeType::PhysicsAsset:
colliderComponent = gameEntity->CreateComponent<MeshColliderComponent>();
m_shapeConfiguration.m_physicsAsset.m_configuration.m_subdivisionLevel = m_shapeConfiguration.m_subdivisionLevel;
colliderComponent->SetShapeConfigurationList({ AZStd::make_pair(sharedColliderConfig,
AZStd::make_shared<Physics::PhysicsAssetShapeConfiguration>(m_shapeConfiguration.m_physicsAsset.m_configuration)) });
AZ_Warning("PhysX", m_shapeConfiguration.m_physicsAsset.m_pxAsset.GetId().IsValid(),
"EditorColliderComponent::BuildGameEntity. No asset assigned to Collider Component. Entity: %s",
GetEntity()->GetName().c_str());
break;
case Physics::ShapeType::CookedMesh:
colliderComponent = gameEntity->CreateComponent<BaseColliderComponent>();
colliderComponent->SetShapeConfigurationList({ AZStd::make_pair(sharedColliderConfig,
AZStd::make_shared<Physics::CookedMeshShapeConfiguration>(m_shapeConfiguration.m_cookedMesh)) });
break;
default:
AZ_Warning("EditorColliderComponent", false, "Unsupported shape type for building game entity!");
break;
}
StaticRigidBodyUtils::TryCreateRuntimeComponent(*GetEntity(), *gameEntity);
}
AZ::Transform EditorColliderComponent::GetColliderLocalTransform() const
{
const AZ::Vector3 nonUniformScale = Utils::GetTransformScale(GetEntityId());
return AZ::Transform::CreateFromQuaternionAndTranslation(
m_configuration.m_rotation, m_configuration.m_position * nonUniformScale);
}
void EditorColliderComponent::UpdateMeshAsset()
{
if (m_shapeConfiguration.m_physicsAsset.m_pxAsset.GetId().IsValid())
{
AZ::Data::AssetBus::MultiHandler::BusConnect(m_shapeConfiguration.m_physicsAsset.m_pxAsset.GetId());
m_shapeConfiguration.m_physicsAsset.m_pxAsset.QueueLoad();
m_shapeConfiguration.m_physicsAsset.m_configuration.m_asset = m_shapeConfiguration.m_physicsAsset.m_pxAsset;
m_colliderDebugDraw.ClearCachedGeometry();
}
UpdateMaterialSlotsFromMeshAsset();
}
bool IsNonUniformlyScaledPrimitive(const EditorProxyShapeConfig& shapeConfig)
{
return shapeConfig.m_hasNonUniformScale && Utils::IsPrimitiveShape(shapeConfig.GetCurrent());
}
void EditorColliderComponent::CreateStaticEditorCollider()
{
m_cachedAabbDirty = true;
// 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;
}
if (m_shapeConfiguration.IsAssetConfig() && m_shapeConfiguration.m_physicsAsset.m_pxAsset.GetStatus() != AZ::Data::AssetData::AssetStatus::Ready)
{
// Mesh asset has not been loaded, wait for OnAssetReady to be invoked.
// We specifically check Ready state here rather than ReadyPreNotify to ensure OnAssetReady has been invoked
if (m_sceneInterface && m_editorBodyHandle != AzPhysics::InvalidSimulatedBodyHandle)
{
m_sceneInterface->RemoveSimulatedBody(m_editorSceneHandle, m_editorBodyHandle);
}
return;
}
AZ::Transform colliderTransform = GetWorldTM();
colliderTransform.ExtractUniformScale();
AzPhysics::StaticRigidBodyConfiguration configuration;
configuration.m_orientation = colliderTransform.GetRotation();
configuration.m_position = colliderTransform.GetTranslation();
configuration.m_entityId = GetEntityId();
configuration.m_debugName = GetEntity()->GetName();
if (m_shapeConfiguration.IsAssetConfig())
{
AZStd::vector<AZStd::shared_ptr<Physics::Shape>> shapes;
Utils::GetShapesFromAsset(m_shapeConfiguration.m_physicsAsset.m_configuration,
m_configuration, m_hasNonUniformScale, m_shapeConfiguration.m_subdivisionLevel, shapes);
configuration.m_colliderAndShapeData = shapes;
}
else
{
AZStd::shared_ptr<Physics::ColliderConfiguration> colliderConfig = AZStd::make_shared<Physics::ColliderConfiguration>(
GetColliderConfigurationScaled());
AZStd::shared_ptr<Physics::ShapeConfiguration> shapeConfig = m_shapeConfiguration.CloneCurrent();
if (IsNonUniformlyScaledPrimitive(m_shapeConfiguration))
{
auto convexConfig = Utils::CreateConvexFromPrimitive(GetColliderConfiguration(), *(shapeConfig.get()),
m_shapeConfiguration.m_subdivisionLevel, shapeConfig->m_scale);
Physics::ColliderConfiguration colliderConfigurationNoOffset = *colliderConfig;
colliderConfigurationNoOffset.m_rotation = AZ::Quaternion::CreateIdentity();
colliderConfigurationNoOffset.m_position = AZ::Vector3::CreateZero();
if (convexConfig.has_value())
{
AZStd::shared_ptr<Physics::Shape> shape = AZ::Interface<Physics::System>::Get()->CreateShape(
colliderConfigurationNoOffset, convexConfig.value());
configuration.m_colliderAndShapeData = shape;
}
}
else
{
configuration.m_colliderAndShapeData = AzPhysics::ShapeColliderPair(colliderConfig, shapeConfig);
}
}
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);
}
m_colliderDebugDraw.ClearCachedGeometry();
AzPhysics::SimulatedBodyComponentRequestsBus::Handler::BusConnect(GetEntityId());
}
AZ::Data::Asset<Pipeline::MeshAsset> EditorColliderComponent::GetMeshAsset() const
{
return m_shapeConfiguration.m_physicsAsset.m_pxAsset;
}
Physics::MaterialId EditorColliderComponent::GetMaterialId() const
{
return m_configuration.m_materialSelection.GetMaterialId();
}
void EditorColliderComponent::SetMeshAsset(const AZ::Data::AssetId& id)
{
if (id.IsValid())
{
m_shapeConfiguration.m_shapeType = Physics::ShapeType::PhysicsAsset;
m_shapeConfiguration.m_physicsAsset.m_pxAsset.Create(id);
UpdateMeshAsset();
m_colliderDebugDraw.ClearCachedGeometry();
}
}
void EditorColliderComponent::SetMaterialId(const Physics::MaterialId& id)
{
m_configuration.m_materialSelection.SetMaterialId(id);
}
void EditorColliderComponent::UpdateMaterialSlotsFromMeshAsset()
{
Physics::PhysicsMaterialRequestBus::Broadcast(
&Physics::PhysicsMaterialRequestBus::Events::UpdateMaterialSelectionFromPhysicsAsset,
m_shapeConfiguration.GetCurrent(),
m_configuration.m_materialSelection);
AzToolsFramework::ToolsApplicationEvents::Bus::Broadcast(&AzToolsFramework::ToolsApplicationEvents::InvalidatePropertyDisplay, AzToolsFramework::Refresh_EntireTree);
ValidateAssetMaterials();
}
void EditorColliderComponent::ValidateAssetMaterials()
{
const AZ::Data::Asset<Pipeline::MeshAsset>& physicsAsset = m_shapeConfiguration.m_physicsAsset.m_pxAsset;
if (!IsAssetConfig() || !physicsAsset.IsReady())
{
return;
}
// Here we check the material indices assigned to every shape and validate that every index is used at least once.
// It's not an error if the validation fails here but something we want to let the designers know about.
[[maybe_unused]] size_t materialsNum = physicsAsset->m_assetData.m_materialNames.size();
const AZStd::vector<AZ::u16>& indexPerShape = physicsAsset->m_assetData.m_materialIndexPerShape;
AZStd::unordered_set<AZ::u16> usedIndices;
for (AZ::u16 index : indexPerShape)
{
if (index == Pipeline::MeshAssetData::TriangleMeshMaterialIndex)
{
// Triangle mesh indices are cooked into binary data, pass the validation in this case.
return;
}
usedIndices.insert(index);
}
AZ_Warning("PhysX", usedIndices.size() == materialsNum,
"EditorColliderComponent::ValidateMaterialSurfaces. Entity: %s. Number of materials used by the shape (%d) does not match the "
"total number of materials in the asset (%d). Please check that there are no convex meshes with per-face materials. Asset: %s",
GetEntity()->GetName().c_str(), usedIndices.size(), materialsNum, physicsAsset.GetHint().c_str())
}
void EditorColliderComponent::OnAssetReady(AZ::Data::Asset<AZ::Data::AssetData> asset)
{
if (asset == m_shapeConfiguration.m_physicsAsset.m_pxAsset)
{
m_shapeConfiguration.m_physicsAsset.m_pxAsset = asset;
m_shapeConfiguration.m_physicsAsset.m_configuration.m_asset = asset;
UpdateMaterialSlotsFromMeshAsset();
CreateStaticEditorCollider();
// Invalidate debug draw cached data
m_colliderDebugDraw.ClearCachedGeometry();
// Notify about the data update of the collider
Physics::ColliderComponentEventBus::Event(GetEntityId(), &Physics::ColliderComponentEvents::OnColliderChanged);
ValidateRigidBodyMeshGeometryType();
}
else
{
m_componentWarnings.clear();
m_configuration.m_materialSelection.SetMaterialSlots(Physics::MaterialSelection::SlotsArray());
AzToolsFramework::ToolsApplicationEvents::Bus::Broadcast(
&AzToolsFramework::ToolsApplicationEvents::InvalidatePropertyDisplay, AzToolsFramework::Refresh_EntireTree);
}
}
void EditorColliderComponent::ValidateRigidBodyMeshGeometryType()
{
const PhysX::EditorRigidBodyComponent* entityRigidbody = m_entity->FindComponent<PhysX::EditorRigidBodyComponent>();
if (entityRigidbody &&
m_shapeConfiguration.m_shapeType == Physics::ShapeType::PhysicsAsset &&
m_shapeConfiguration.m_physicsAsset.m_pxAsset.IsReady())
{
AZStd::vector<AZStd::shared_ptr<Physics::Shape>> shapes;
Utils::GetShapesFromAsset(m_shapeConfiguration.m_physicsAsset.m_configuration, m_configuration, m_hasNonUniformScale,
m_shapeConfiguration.m_subdivisionLevel, shapes);
if (shapes.empty())
{
m_componentWarnings.clear();
AzToolsFramework::ToolsApplicationEvents::Bus::Broadcast(
&AzToolsFramework::ToolsApplicationEvents::InvalidatePropertyDisplay, AzToolsFramework::Refresh_EntireTree);
return;
}
//We check if the shapes are triangle meshes, if any mesh is a triangle mesh we activate the warning.
bool shapeIsTriangleMesh = false;
for (const auto& shape : shapes)
{
auto current_shape = AZStd::rtti_pointer_cast<PhysX::Shape>(shape);
if (current_shape &&
current_shape->GetPxShape()->getGeometryType() == physx::PxGeometryType::eTRIANGLEMESH &&
entityRigidbody->GetRigidBody() &&
entityRigidbody->GetRigidBody()->IsKinematic() == false)
{
shapeIsTriangleMesh = true;
break;
}
}
if (shapeIsTriangleMesh)
{
m_componentWarnings.clear();
AZStd::string assetPath = m_shapeConfiguration.m_physicsAsset.m_configuration.m_asset.GetHint().c_str();
const size_t lastSlash = assetPath.rfind('/');
if (lastSlash != AZStd::string::npos)
{
assetPath = assetPath.substr(lastSlash + 1);
}
m_componentWarnings.push_back(AZStd::string::format(
"The physics asset \"%s\" was exported using triangle mesh geometry, which is not compatible with non-kinematic "
"dynamic rigid bodies. To make the collider compatible, you can export the asset using primitive or convex mesh "
"geometry, use mesh decomposition when exporting the asset, or set the rigid body to kinematic. Learn more about "
"<a href=\"https://o3de.org/docs/user-guide/components/reference/physx/collider/\">colliders</a>.",
assetPath.c_str()));
// make sure the entity inspector scrolls so the warning is visible by marking this component as having
// new content
AzToolsFramework::EntityPropertyEditorRequestBus::Broadcast(
&AzToolsFramework::EntityPropertyEditorRequests::SetNewComponentId, GetId());
}
else
{
m_componentWarnings.clear();
}
}
else
{
m_componentWarnings.clear();
}
AzToolsFramework::ToolsApplicationEvents::Bus::Broadcast(
&AzToolsFramework::ToolsApplicationEvents::InvalidatePropertyDisplay,
m_componentWarnings.empty() ? AzToolsFramework::Refresh_EntireTree : AzToolsFramework::Refresh_EntireTree_NewContent);
}
void EditorColliderComponent::OnAssetReloaded(AZ::Data::Asset<AZ::Data::AssetData> asset)
{
OnAssetReady(asset);
}
void EditorColliderComponent::BuildDebugDrawMesh() const
{
if (m_shapeConfiguration.IsAssetConfig())
{
const AZ::Data::Asset<Pipeline::MeshAsset>& physicsAsset = m_shapeConfiguration.m_physicsAsset.m_pxAsset;
const Physics::PhysicsAssetShapeConfiguration& physicsAssetConfiguration = m_shapeConfiguration.m_physicsAsset.m_configuration;
if (!physicsAsset.IsReady())
{
// Skip processing if the asset isn't ready
return;
}
AzPhysics::ShapeColliderPairList shapeConfigList;
Utils::GetColliderShapeConfigsFromAsset(physicsAssetConfiguration, m_configuration, m_hasNonUniformScale,
m_shapeConfiguration.m_subdivisionLevel, shapeConfigList);
for (size_t shapeIndex = 0; shapeIndex < shapeConfigList.size(); shapeIndex++)
{
const Physics::ShapeConfiguration* shapeConfiguration = shapeConfigList[shapeIndex].second.get();
AZ_Assert(shapeConfiguration, "BuildDebugDrawMesh: Invalid shape configuration");
if (shapeConfiguration)
{
m_colliderDebugDraw.BuildMeshes(*shapeConfiguration, static_cast<AZ::u32>(shapeIndex));
}
}
}
else
{
const AZ::u32 shapeIndex = 0; // There's only one mesh gets built from the primitive collider, hence use geomIndex 0.
if (!m_hasNonUniformScale)
{
m_colliderDebugDraw.BuildMeshes(m_shapeConfiguration.GetCurrent(), shapeIndex);
}
else
{
m_scaledPrimitive = Utils::CreateConvexFromPrimitive(GetColliderConfiguration(), m_shapeConfiguration.GetCurrent(),
m_shapeConfiguration.m_subdivisionLevel, m_shapeConfiguration.GetCurrent().m_scale);
if (m_scaledPrimitive.has_value())
{
physx::PxGeometryHolder pxGeometryHolder;
Utils::CreatePxGeometryFromConfig(m_scaledPrimitive.value(), pxGeometryHolder); // this will cause the native mesh to be cached
m_colliderDebugDraw.BuildMeshes(m_scaledPrimitive.value(), shapeIndex);
}
}
}
}
void EditorColliderComponent::DisplayScaledPrimitiveCollider(AzFramework::DebugDisplayRequests& debugDisplay) const
{
if (m_scaledPrimitive.has_value())
{
const AZ::u32 shapeIndex = 0;
Physics::ColliderConfiguration colliderConfigNoOffset = m_configuration;
colliderConfigNoOffset.m_rotation = AZ::Quaternion::CreateIdentity();
colliderConfigNoOffset.m_position = AZ::Vector3::CreateZero();
m_colliderDebugDraw.DrawMesh(debugDisplay, colliderConfigNoOffset, m_scaledPrimitive.value(),
GetWorldTM().GetUniformScale() * m_cachedNonUniformScale, shapeIndex);
}
}
void EditorColliderComponent::DisplayUnscaledPrimitiveCollider(AzFramework::DebugDisplayRequests& debugDisplay) const
{
switch (m_shapeConfiguration.m_shapeType)
{
case Physics::ShapeType::Sphere:
m_colliderDebugDraw.DrawSphere(debugDisplay, m_configuration, m_shapeConfiguration.m_sphere);
break;
case Physics::ShapeType::Box:
m_colliderDebugDraw.DrawBox(debugDisplay, m_configuration, m_shapeConfiguration.m_box);
break;
case Physics::ShapeType::Capsule:
m_colliderDebugDraw.DrawCapsule(debugDisplay, m_configuration, m_shapeConfiguration.m_capsule);
break;
}
}
void EditorColliderComponent::DisplayMeshCollider(AzFramework::DebugDisplayRequests& debugDisplay) const
{
if (!m_colliderDebugDraw.HasCachedGeometry())
{
return;
}
const Physics::PhysicsAssetShapeConfiguration& physicsAssetConfiguration = m_shapeConfiguration.m_physicsAsset.m_configuration;
AzPhysics::ShapeColliderPairList shapeConfigList;
Utils::GetColliderShapeConfigsFromAsset(physicsAssetConfiguration, m_configuration, m_hasNonUniformScale,
m_shapeConfiguration.m_subdivisionLevel, shapeConfigList);
const AZ::Vector3& assetScale = physicsAssetConfiguration.m_assetScale;
for (size_t shapeIndex = 0; shapeIndex < shapeConfigList.size(); shapeIndex++)
{
const Physics::ColliderConfiguration* colliderConfiguration = shapeConfigList[shapeIndex].first.get();
const Physics::ShapeConfiguration* shapeConfiguration = shapeConfigList[shapeIndex].second.get();
AZ_Assert(shapeConfiguration && colliderConfiguration, "DisplayMeshCollider: Invalid shape-collider configuration pair");
switch (shapeConfiguration->GetShapeType())
{
case Physics::ShapeType::CookedMesh:
{
const Physics::CookedMeshShapeConfiguration* cookedMeshShapeConfiguration =
static_cast<const Physics::CookedMeshShapeConfiguration*>(shapeConfiguration);
const AZ::Vector3 overallScale = Utils::GetTransformScale(GetEntityId()) * m_cachedNonUniformScale * assetScale;
m_colliderDebugDraw.DrawMesh(debugDisplay, *colliderConfiguration, *cookedMeshShapeConfiguration,
overallScale, static_cast<AZ::u32>(shapeIndex));
break;
}
case Physics::ShapeType::Sphere:
{
const Physics::SphereShapeConfiguration* sphereShapeConfiguration =
static_cast<const Physics::SphereShapeConfiguration*>(shapeConfiguration);
m_colliderDebugDraw.DrawSphere(debugDisplay, *colliderConfiguration, *sphereShapeConfiguration, assetScale);
break;
}
case Physics::ShapeType::Box:
{
const Physics::BoxShapeConfiguration* boxShapeConfiguration =
static_cast<const Physics::BoxShapeConfiguration*>(shapeConfiguration);
m_colliderDebugDraw.DrawBox(debugDisplay, *colliderConfiguration, *boxShapeConfiguration, assetScale);
break;
}
case Physics::ShapeType::Capsule:
{
const Physics::CapsuleShapeConfiguration* capsuleShapeConfiguration =
static_cast<const Physics::CapsuleShapeConfiguration*>(shapeConfiguration);
m_colliderDebugDraw.DrawCapsule(debugDisplay, *colliderConfiguration, *capsuleShapeConfiguration, assetScale);
break;
}
default:
{
AZ_Error("EditorColliderComponent", false, "DisplayMeshCollider: Unsupported ShapeType %d. Entity %s, ID: %llu",
static_cast<AZ::u32>(shapeConfiguration->GetShapeType()), GetEntity()->GetName().c_str(), GetEntityId());
break;
}
}
}
}
void EditorColliderComponent::Display(AzFramework::DebugDisplayRequests& debugDisplay) const
{
if (!m_colliderDebugDraw.HasCachedGeometry())
{
BuildDebugDrawMesh();
}
if (m_colliderDebugDraw.HasCachedGeometry())
{
if (m_shapeConfiguration.IsAssetConfig())
{
DisplayMeshCollider(debugDisplay);
}
else
{
if (m_hasNonUniformScale)
{
DisplayScaledPrimitiveCollider(debugDisplay);
}
else
{
DisplayUnscaledPrimitiveCollider(debugDisplay);
}
}
}
}
bool EditorColliderComponent::IsAssetConfig() const
{
return m_shapeConfiguration.IsAssetConfig();
}
AZ::Vector3 EditorColliderComponent::GetDimensions()
{
return m_shapeConfiguration.m_box.m_dimensions;
}
void EditorColliderComponent::SetDimensions(const AZ::Vector3& dimensions)
{
m_shapeConfiguration.m_box.m_dimensions = dimensions;
CreateStaticEditorCollider();
}
AZ::Transform EditorColliderComponent::GetCurrentTransform()
{
return GetColliderWorldTransform();
}
AZ::Vector3 EditorColliderComponent::GetBoxScale()
{
return AZ::Vector3(GetWorldTM().GetUniformScale());
}
void EditorColliderComponent::OnTransformChanged(const AZ::Transform& /*local*/, const AZ::Transform& world)
{
if (world.IsClose(m_cachedWorldTransform))
{
return;
}
m_cachedWorldTransform = world;
UpdateShapeConfigurationScale();
CreateStaticEditorCollider();
}
void EditorColliderComponent::OnNonUniformScaleChanged(const AZ::Vector3& nonUniformScale)
{
m_cachedNonUniformScale = nonUniformScale;
UpdateShapeConfigurationScale();
CreateStaticEditorCollider();
}
// PhysX::ColliderShapeBus
AZ::Aabb EditorColliderComponent::GetColliderShapeAabb()
{
if (m_cachedAabbDirty)
{
m_cachedAabb = PhysX::Utils::GetColliderAabb(GetWorldTM()
, m_hasNonUniformScale
, m_shapeConfiguration.m_subdivisionLevel
, m_shapeConfiguration.GetCurrent()
, m_configuration);
m_cachedAabbDirty = false;
}
return m_cachedAabb;
}
void EditorColliderComponent::UpdateShapeConfigurationScale()
{
auto& shapeConfiguration = m_shapeConfiguration.GetCurrent();
shapeConfiguration.m_scale = GetWorldTM().ExtractUniformScale() * m_cachedNonUniformScale;
m_colliderDebugDraw.ClearCachedGeometry();
}
void EditorColliderComponent::EnablePhysics()
{
if (!IsPhysicsEnabled())
{
CreateStaticEditorCollider();
}
}
void EditorColliderComponent::DisablePhysics()
{
if (m_sceneInterface && m_editorBodyHandle != AzPhysics::InvalidSimulatedBodyHandle)
{
m_sceneInterface->RemoveSimulatedBody(m_editorSceneHandle, m_editorBodyHandle);
}
}
bool EditorColliderComponent::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 EditorColliderComponent::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* EditorColliderComponent::GetSimulatedBody()
{
if (m_sceneInterface && m_editorBodyHandle != AzPhysics::InvalidSimulatedBodyHandle)
{
if (auto* body = m_sceneInterface->GetSimulatedBodyFromHandle(m_editorSceneHandle, m_editorBodyHandle))
{
return body;
}
}
return nullptr;
}
AzPhysics::SimulatedBodyHandle EditorColliderComponent::GetSimulatedBodyHandle() const
{
return m_editorBodyHandle;
}
AzPhysics::SceneQueryHit EditorColliderComponent::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();
}
bool EditorColliderComponent::IsTrigger()
{
return m_configuration.m_isTrigger;
}
void EditorColliderComponent::SetColliderOffset(const AZ::Vector3& offset)
{
m_configuration.m_position = offset;
CreateStaticEditorCollider();
}
AZ::Vector3 EditorColliderComponent::GetColliderOffset()
{
return m_configuration.m_position;
}
void EditorColliderComponent::SetColliderRotation(const AZ::Quaternion& rotation)
{
m_configuration.m_rotation = rotation;
CreateStaticEditorCollider();
}
AZ::Quaternion EditorColliderComponent::GetColliderRotation()
{
return m_configuration.m_rotation;
}
AZ::Transform EditorColliderComponent::GetColliderWorldTransform()
{
return AzToolsFramework::TransformNormalizedScale(GetWorldTM()) * GetColliderLocalTransform();
}
bool EditorColliderComponent::ShouldUpdateCollisionMeshFromRender() const
{
if (!m_shapeConfiguration.IsAssetConfig())
{
return false;
}
bool collisionMeshNotSet = !m_shapeConfiguration.m_physicsAsset.m_pxAsset.GetId().IsValid();
return collisionMeshNotSet;
}
AZ::Data::AssetId EditorColliderComponent::FindMatchingPhysicsAsset(
const AZ::Data::Asset<AZ::Data::AssetData>& renderMeshAsset,
const AZStd::vector<AZ::Data::AssetId>& physicsAssets)
{
AZ::Data::AssetId foundAssetId;
// Extract the file name from the path to the asset
AZStd::string renderMeshFileName;
AzFramework::StringFunc::Path::Split(renderMeshAsset.GetHint().c_str(),
nullptr, nullptr, &renderMeshFileName);
// Find the collision mesh asset matching the render mesh
for (const AZ::Data::AssetId& assetId : physicsAssets)
{
AZStd::string assetPath;
AZ::Data::AssetCatalogRequestBus::BroadcastResult(assetPath,
&AZ::Data::AssetCatalogRequests::GetAssetPathById, assetId);
AZStd::string physicsAssetFileName;
AzFramework::StringFunc::Path::Split(assetPath.c_str(), nullptr, nullptr, &physicsAssetFileName);
if (physicsAssetFileName == renderMeshFileName)
{
foundAssetId = assetId;
break;
}
}
return foundAssetId;
};
AZ::Data::Asset<AZ::Data::AssetData> EditorColliderComponent::GetRenderMeshAsset() const
{
// Try Atom MeshComponent
AZ::Data::Asset<AZ::RPI::ModelAsset> atomMeshAsset;
AZ::Render::MeshComponentRequestBus::EventResult(atomMeshAsset, GetEntityId(),
&AZ::Render::MeshComponentRequestBus::Events::GetModelAsset);
return atomMeshAsset;
}
void EditorColliderComponent::SetCollisionMeshFromRender()
{
AZ::Data::Asset<AZ::Data::AssetData> renderMeshAsset = GetRenderMeshAsset();
if (!renderMeshAsset.GetId().IsValid())
{
// No render mesh component assigned
return;
}
bool productsQueryResult = false;
AZStd::vector<AZ::Data::AssetInfo> productsInfo;
AzToolsFramework::AssetSystemRequestBus::BroadcastResult(productsQueryResult,
&AzToolsFramework::AssetSystemRequestBus::Events::GetAssetsProducedBySourceUUID,
renderMeshAsset.GetId().m_guid, productsInfo);
if (productsQueryResult)
{
AZStd::vector<AZ::Data::AssetId> physicsAssets;
physicsAssets.reserve(productsInfo.size());
for (const AZ::Data::AssetInfo& info : productsInfo)
{
if (info.m_assetType == AZ::AzTypeInfo<Pipeline::MeshAsset>::Uuid())
{
physicsAssets.push_back(info.m_assetId);
}
}
// If there's only one physics asset, we set it regardless of the name
if (physicsAssets.size() == 1)
{
SetMeshAsset(physicsAssets[0]);
}
// For multiple assets we pick the one matching the name of the render mesh asset
else if (physicsAssets.size() > 1)
{
AZ::Data::AssetId matchingPhysicsAsset = FindMatchingPhysicsAsset(renderMeshAsset, physicsAssets);
if (matchingPhysicsAsset.IsValid())
{
SetMeshAsset(matchingPhysicsAsset);
}
else
{
AZ_Warning("EditorColliderComponent", false,
"SetCollisionMeshFromRender on entity %s: Unable to find a matching physics asset "
"for the render mesh asset GUID: %s, hint: %s",
GetEntity()->GetName().c_str(),
renderMeshAsset.GetId().m_guid.ToString<AZStd::string>().c_str(),
renderMeshAsset.GetHint().c_str());
}
}
// This is not necessarily an incorrect case but it's worth reporting
// in case if we forgot to configure the source asset to produce the collision mesh
else if (physicsAssets.empty())
{
AZ_TracePrintf("EditorColliderComponent",
"SetCollisionMeshFromRender on entity %s: The source asset for %s did not produce any physics assets",
GetEntity()->GetName().c_str(),
renderMeshAsset.GetHint().c_str());
}
}
else
{
AZ_Warning("EditorColliderComponent", false,
"SetCollisionMeshFromRender on entity %s: Unable to get the assets produced by the render mesh asset GUID: %s, hint: %s",
GetEntity()->GetName().c_str(),
renderMeshAsset.GetId().m_guid.ToString<AZStd::string>().c_str(),
renderMeshAsset.GetHint().c_str());
}
}
void EditorColliderComponent::OnModelReady([[maybe_unused]] const AZ::Data::Asset<AZ::RPI::ModelAsset>& modelAsset,
[[maybe_unused]] const AZ::Data::Instance<AZ::RPI::Model>& model)
{
if (ShouldUpdateCollisionMeshFromRender())
{
SetCollisionMeshFromRender();
}
}
void EditorColliderComponent::SetShapeType(Physics::ShapeType shapeType)
{
m_shapeConfiguration.m_shapeType = shapeType;
CreateStaticEditorCollider();
}
Physics::ShapeType EditorColliderComponent::GetShapeType()
{
return m_shapeConfiguration.GetCurrent().GetShapeType();
}
void EditorColliderComponent::SetSphereRadius(float radius)
{
m_shapeConfiguration.m_sphere.m_radius = radius;
CreateStaticEditorCollider();
}
float EditorColliderComponent::GetSphereRadius()
{
return m_shapeConfiguration.m_sphere.m_radius;
}
void EditorColliderComponent::SetCapsuleRadius(float radius)
{
m_shapeConfiguration.m_capsule.m_radius = radius;
CreateStaticEditorCollider();
}
float EditorColliderComponent::GetCapsuleRadius()
{
return m_shapeConfiguration.m_capsule.m_radius;
}
void EditorColliderComponent::SetCapsuleHeight(float height)
{
m_shapeConfiguration.m_capsule.m_height = height;
CreateStaticEditorCollider();
}
float EditorColliderComponent::GetCapsuleHeight()
{
return m_shapeConfiguration.m_capsule.m_height;
}
void EditorColliderComponent::SetAssetScale(const AZ::Vector3& scale)
{
m_shapeConfiguration.m_physicsAsset.m_configuration.m_assetScale = scale;
CreateStaticEditorCollider();
}
AZ::Vector3 EditorColliderComponent::GetAssetScale()
{
return m_shapeConfiguration.m_physicsAsset.m_configuration.m_assetScale;
}
void EditorColliderComponentDescriptor::Reflect(AZ::ReflectContext* reflection) const
{
EditorColliderComponent::Reflect(reflection);
}
void EditorColliderComponentDescriptor::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided, [[maybe_unused]] const AZ::Component* instance) const
{
EditorColliderComponent::GetProvidedServices(provided);
}
void EditorColliderComponentDescriptor::GetDependentServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& dependent, [[maybe_unused]] const AZ::Component* instance) const
{
EditorColliderComponent::GetDependentServices(dependent);
}
void EditorColliderComponentDescriptor::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required, [[maybe_unused]] const AZ::Component* instance) const
{
EditorColliderComponent::GetRequiredServices(required);
}
void EditorColliderComponentDescriptor::GetWarnings(AZ::ComponentDescriptor::StringWarningArray& warnings, const AZ::Component* instance) const
{
const PhysX::EditorColliderComponent* editorColliderComponent = azrtti_cast<const PhysX::EditorColliderComponent*>(instance);
if (editorColliderComponent != nullptr)
{
warnings = editorColliderComponent->GetComponentWarnings();
}
}
} // namespace PhysX