Files
o3de/Gems/PhysX/Code/Source/EditorColliderComponent.cpp
T
moraaar dac3bc4ba6 Added option to disable edit button in asset widgets when there are no asset selected. (#7521)
Added new attribute "DisableEditButtonWheNoAssetSelected" to PropertyAssetCtrl. By default it's false, keeping the original behavior of leaving the edit button enabled and if it's clicked while there is no asset assigned it'll try to create a new one.

PhysX mesh asset property uses now this new feature.

Signed-off-by: moraaar moraaar@amazon.com
2022-02-10 12:23:38 +00:00

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