You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1373 lines
59 KiB
C++
1373 lines
59 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/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")
|
|
->Attribute(AZ_CRC_CE("EditButton"), "")
|
|
->Attribute(AZ_CRC_CE("EditDescription"), "Open in Scene Settings")
|
|
->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, "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)
|
|
->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()));
|
|
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);
|
|
|
|
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 = 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
|