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

1377 lines
60 KiB
C++

/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <PhysX_precompiled.h>
#include <AzCore/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/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 <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 shape collider")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->DataElement(AZ::Edit::UIHandlers::Default, &EditorProxyAssetShapeConfig::m_pxAsset, "PhysX Mesh", "PhysX mesh collider asset")
->DataElement(AZ::Edit::UIHandlers::Default, &EditorProxyAssetShapeConfig::m_configuration, "Configuration", "Configuration of asset shape")
->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, AZ::Edit::PropertyRefreshLevels::EntireTree)
// 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::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", "PhysX shape collider")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Category, "PhysX")
->Attribute(AZ::Edit::Attributes::Icon, "Icons/Components/PhysXCollider.svg")
->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/PhysXCollider.svg")
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c))
->Attribute(AZ::Edit::Attributes::HelpPageURL, "http://docs.aws.amazon.com/console/lumberyard/component/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)
->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);
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()));
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();
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();
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);
ValidateMaterialSurfaces();
}
void EditorColliderComponent::ValidateMaterialSurfaces()
{
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 surfacesNum = physicsAsset->m_assetData.m_surfaceNames.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() == surfacesNum,
"EditorColliderComponent::ValidateMaterialSurfaces. Entity: %s. Number of surfaces used by the shape (%d) does not match the "
"total number of surfaces in the asset (%d). Please check that there are no convex meshes with per-face materials. Asset: %s",
GetEntity()->GetName().c_str(), usedIndices.size(), surfacesNum, 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 = m_shapeConfiguration.m_physicsAsset.m_pxAsset;
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 (m_shapeConfiguration.m_physicsAsset.m_configuration.GetShapeType() == Physics::ShapeType::PhysicsAsset && entityRigidbody)
{
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())
{
return;
}
//We grab the first shape to check if it is a triangle mesh.
auto shape = AZStd::rtti_pointer_cast<PhysX::Shape>(shapes[0]);
if (shape &&
shape->GetPxShape()->getGeometryType() == physx::PxGeometryType::eTRIANGLEMESH &&
entityRigidbody->GetRigidBody() &&
entityRigidbody->GetRigidBody()->IsKinematic() == false)
{
AZStd::string assetPath = m_shapeConfiguration.m_physicsAsset.m_configuration.m_asset.GetHint().c_str();
const uint lastSlash = assetPath.rfind('/');
if (lastSlash != AZStd::string::npos)
{
assetPath = assetPath.substr(lastSlash + 1);
}
m_componentWarnings.push_back(AZStd::string::format(
"The Physics Asset \"%s\" is a Triangle Mesh, it is not compatible with a Dynamic Rigidbody, either:\n"
"Change the PhysicsAsset to Convex Mesh or set the Rigidbody to kinematic.",
assetPath.c_str()));
}
else
{
m_componentWarnings.clear();
}
}
else
{
m_componentWarnings.clear();
}
}
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, 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, 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