/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace PhysX { void EditorProxyAssetShapeConfig::Reflect(AZ::ReflectContext* context) { if (auto serializeContext = azrtti_cast(context)) { serializeContext->Class() ->Version(1) ->Field("Asset", &EditorProxyAssetShapeConfig::m_pxAsset) ->Field("Configuration", &EditorProxyAssetShapeConfig::m_configuration) ; if (auto editContext = serializeContext->GetEditContext()) { editContext->Class("EditorProxyShapeConfig", "PhysX Base collider.") ->ClassElement(AZ::Edit::ClassElements::EditorData, "") ->Attribute(AZ::Edit::Attributes::AutoExpand, true) ->DataElement(AZ::Edit::UIHandlers::Default, &EditorProxyAssetShapeConfig::m_pxAsset, "PhysX Mesh", "Specifies the PhysX mesh collider asset for this PhysX collider component.") ->Attribute(AZ_CRC_CE("EditButton"), "") ->Attribute(AZ_CRC_CE("EditDescription"), "Open in Scene Settings") ->Attribute(AZ_CRC_CE("DisableEditButtonWhenNoAssetSelected"), true) ->DataElement(AZ::Edit::UIHandlers::Default, &EditorProxyAssetShapeConfig::m_configuration, "Configuration", "PhysX mesh asset collider configuration.") ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly); } } } void EditorProxyShapeConfig::Reflect(AZ::ReflectContext* context) { EditorProxyAssetShapeConfig::Reflect(context); if (auto serializeContext = azrtti_cast(context)) { serializeContext->Class() ->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", "PhysX Base shape collider") ->DataElement(AZ::Edit::UIHandlers::ComboBox, &EditorProxyShapeConfig::m_shapeType, "Shape", "The shape of the collider.") ->EnumAttribute(Physics::ShapeType::Sphere, "Sphere") ->EnumAttribute(Physics::ShapeType::Box, "Box") ->EnumAttribute(Physics::ShapeType::Capsule, "Capsule") ->EnumAttribute(Physics::ShapeType::PhysicsAsset, "PhysicsAsset") ->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorProxyShapeConfig::OnShapeTypeChanged) // note: we do not want the user to be able to change shape types while in ComponentMode (there will // potentially be different ComponentModes for different shape types) ->Attribute(AZ::Edit::Attributes::ReadOnly, &AzToolsFramework::ComponentModeFramework::InComponentMode) ->DataElement(AZ::Edit::UIHandlers::Default, &EditorProxyShapeConfig::m_sphere, "Sphere", "Configuration of sphere shape.") ->Attribute(AZ::Edit::Attributes::Visibility, &EditorProxyShapeConfig::IsSphereConfig) ->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorProxyShapeConfig::OnConfigurationChanged) ->DataElement(AZ::Edit::UIHandlers::Default, &EditorProxyShapeConfig::m_box, "Box", "Configuration of box shape.") ->Attribute(AZ::Edit::Attributes::Visibility, &EditorProxyShapeConfig::IsBoxConfig) ->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorProxyShapeConfig::OnConfigurationChanged) ->DataElement(AZ::Edit::UIHandlers::Default, &EditorProxyShapeConfig::m_capsule, "Capsule", "Configuration of capsule shape.") ->Attribute(AZ::Edit::Attributes::Visibility, &EditorProxyShapeConfig::IsCapsuleConfig) ->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorProxyShapeConfig::OnConfigurationChanged) ->DataElement(AZ::Edit::UIHandlers::Default, &EditorProxyShapeConfig::m_physicsAsset, "Asset", "Configuration of asset shape.") ->Attribute(AZ::Edit::Attributes::Visibility, &EditorProxyShapeConfig::IsAssetConfig) ->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorProxyShapeConfig::OnConfigurationChanged) ->DataElement(AZ::Edit::UIHandlers::Default, &EditorProxyShapeConfig::m_subdivisionLevel, "Subdivision level", "The level of subdivision if a primitive shape is replaced with a convex mesh due to scaling.") ->Attribute(AZ::Edit::Attributes::Min, Utils::MinCapsuleSubdivisionLevel) ->Attribute(AZ::Edit::Attributes::Max, Utils::MaxCapsuleSubdivisionLevel) ->Attribute(AZ::Edit::Attributes::Visibility, &EditorProxyShapeConfig::ShowingSubdivisionLevel) ->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorProxyShapeConfig::OnConfigurationChanged) ; } } } AZ::u32 EditorProxyShapeConfig::OnShapeTypeChanged() { // reset the physics asset if the shape type was Physics Asset if (m_shapeType != Physics::ShapeType::PhysicsAsset && m_lastShapeType == Physics::ShapeType::PhysicsAsset) { // clean up any reference to a physics assets, and re-initialize to an empty Pipeline::MeshAsset asset. m_physicsAsset.m_pxAsset.Reset(); m_physicsAsset.m_pxAsset = AZ::Data::Asset(AZ::Data::AssetLoadBehavior::QueueLoad); m_physicsAsset.m_configuration = Physics::PhysicsAssetShapeConfiguration(); } m_lastShapeType = m_shapeType; return AZ::Edit::PropertyRefreshLevels::EntireTree; } AZ::u32 EditorProxyShapeConfig::OnConfigurationChanged() { return AZ::Edit::PropertyRefreshLevels::ValuesOnly; } void EditorColliderComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided) { provided.push_back(AZ_CRC_CE("PhysicsWorldBodyService")); provided.push_back(AZ_CRC_CE("PhysXColliderService")); provided.push_back(AZ_CRC_CE("PhysXTriggerService")); } void EditorColliderComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required) { required.push_back(AZ_CRC_CE("TransformService")); } void EditorColliderComponent::GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent) { dependent.push_back(AZ_CRC_CE("NonUniformScaleService")); } void EditorColliderComponent::Reflect(AZ::ReflectContext* context) { EditorProxyShapeConfig::Reflect(context); DebugDraw::Collider::Reflect(context); if (auto serializeContext = azrtti_cast(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() ->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( "PhysX Collider", "Creates geometry in the PhysX simulation, using either a primitive shape or geometry from an asset.") ->ClassElement(AZ::Edit::ClassElements::EditorData, "") ->Attribute(AZ::Edit::Attributes::Category, "PhysX") ->Attribute(AZ::Edit::Attributes::Icon, "Icons/Components/PhysXCollider.svg") ->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/Viewport/PhysXCollider.svg") ->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c)) ->Attribute(AZ::Edit::Attributes::HelpPageURL, "https://o3de.org/docs/user-guide/components/reference/physx/collider/") ->Attribute(AZ::Edit::Attributes::AutoExpand, true) ->DataElement(AZ::Edit::UIHandlers::Default, &EditorColliderComponent::m_configuration, "Collider Configuration", "Configuration of the collider.") ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) ->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorColliderComponent::OnConfigurationChanged) ->DataElement(AZ::Edit::UIHandlers::Default, &EditorColliderComponent::m_shapeConfiguration, "Shape Configuration", "Configuration of the shape.") ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) ->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorColliderComponent::OnConfigurationChanged) ->Attribute(AZ::Edit::Attributes::RemoveNotify, &EditorColliderComponent::ValidateRigidBodyMeshGeometryType) ->DataElement(AZ::Edit::UIHandlers::Default, &EditorColliderComponent::m_componentModeDelegate, "Component Mode", "Collider Component Mode.") ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) ->DataElement(AZ::Edit::UIHandlers::Default, &EditorColliderComponent::m_colliderDebugDraw, "Debug draw settings", "Debug draw settings.") ->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly) ; } } } AZ::ComponentDescriptor* EditorColliderComponent::CreateDescriptor() { return aznew EditorColliderComponentDescriptor(); } EditorColliderComponent::EditorColliderComponent( const Physics::ColliderConfiguration& colliderConfiguration, const Physics::ShapeConfiguration& shapeConfiguration) : m_configuration(colliderConfiguration) , m_shapeConfiguration(shapeConfiguration) { } const EditorProxyShapeConfig& EditorColliderComponent::GetShapeConfiguration() const { return m_shapeConfiguration; } const Physics::ColliderConfiguration& EditorColliderComponent::GetColliderConfiguration() const { return m_configuration; } Physics::ColliderConfiguration EditorColliderComponent::GetColliderConfigurationScaled() const { // Scale the collider offset Physics::ColliderConfiguration colliderConfiguration = m_configuration; colliderConfiguration.m_position *= Utils::GetTransformScale(GetEntityId()) * m_cachedNonUniformScale; return colliderConfiguration; } EditorProxyShapeConfig::EditorProxyShapeConfig(const Physics::ShapeConfiguration& shapeConfiguration) { m_shapeType = shapeConfiguration.GetShapeType(); switch (m_shapeType) { case Physics::ShapeType::Sphere: m_sphere = static_cast(shapeConfiguration); break; case Physics::ShapeType::Box: m_box = static_cast(shapeConfiguration); break; case Physics::ShapeType::Capsule: m_capsule = static_cast(shapeConfiguration); break; case Physics::ShapeType::PhysicsAsset: m_physicsAsset.m_configuration = static_cast(shapeConfiguration); break; case Physics::ShapeType::CookedMesh: m_cookedMesh = static_cast(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(static_cast(*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 EditorProxyShapeConfig::CloneCurrent() const { switch (m_shapeType) { case Physics::ShapeType::Sphere: return AZStd::make_shared(m_sphere); case Physics::ShapeType::Capsule: return AZStd::make_shared(m_capsule); case Physics::ShapeType::PhysicsAsset: return AZStd::make_shared(m_physicsAsset.m_configuration); case Physics::ShapeType::CookedMesh: return AZStd::make_shared(m_cookedMesh); default: AZ_Warning("EditorProxyShapeConfig", false, "Unsupported shape type, defaulting to Box."); [[fallthrough]]; case Physics::ShapeType::Box: return AZStd::make_shared(m_box); } } bool EditorProxyShapeConfig::ShowingSubdivisionLevel() const { return (m_hasNonUniformScale && (IsCapsuleConfig() || IsSphereConfig() || IsAssetConfig())); } void EditorColliderComponent::Activate() { m_sceneInterface = AZ::Interface::Get(); if (m_sceneInterface) { m_editorSceneHandle = m_sceneInterface->GetSceneHandle(AzPhysics::EditorPhysicsSceneName); } m_physXConfigChangedHandler = AzPhysics::SystemEvents::OnConfigurationChangedEvent::Handler( []([[maybe_unused]] const AzPhysics::SystemConfiguration* config) { AzToolsFramework::PropertyEditorGUIMessages::Bus::Broadcast(&AzToolsFramework::PropertyEditorGUIMessages::RequestRefresh, AzToolsFramework::PropertyModificationRefreshLevel::Refresh_AttributesAndValues); }); m_onMaterialLibraryChangedEventHandler = AzPhysics::SystemEvents::OnMaterialLibraryChangedEvent::Handler( [this](const AZ::Data::AssetId& defaultMaterialLibrary) { m_configuration.m_materialSelection.OnMaterialLibraryChanged(defaultMaterialLibrary); UpdateMaterialSlotsFromMeshAsset(); AzToolsFramework::PropertyEditorGUIMessages::Bus::Broadcast(&AzToolsFramework::PropertyEditorGUIMessages::RequestRefresh, AzToolsFramework::PropertyModificationRefreshLevel::Refresh_AttributesAndValues); }); AzToolsFramework::Components::EditorComponentBase::Activate(); AzToolsFramework::EntitySelectionEvents::Bus::Handler::BusConnect(GetEntityId()); PhysX::MeshColliderComponentRequestsBus::Handler::BusConnect(GetEntityId()); AZ::TransformNotificationBus::Handler::BusConnect(GetEntityId()); AzToolsFramework::BoxManipulatorRequestBus::Handler::BusConnect( AZ::EntityComponentIdPair(GetEntityId(), GetId())); ColliderShapeRequestBus::Handler::BusConnect(GetEntityId()); AZ::Render::MeshComponentNotificationBus::Handler::BusConnect(GetEntityId()); EditorColliderComponentRequestBus::Handler::BusConnect(AZ::EntityComponentIdPair(GetEntityId(), GetId())); EditorColliderValidationRequestBus::Handler::BusConnect(GetEntityId()); m_nonUniformScaleChangedHandler = AZ::NonUniformScaleChangedEvent::Handler( [this](const AZ::Vector3& scale) {OnNonUniformScaleChanged(scale); }); AZ::NonUniformScaleRequestBus::Event(GetEntityId(), &AZ::NonUniformScaleRequests::RegisterScaleChangedEvent, m_nonUniformScaleChangedHandler); m_hasNonUniformScale = (AZ::NonUniformScaleRequestBus::FindFirstHandler(GetEntityId()) != nullptr); m_shapeConfiguration.m_hasNonUniformScale = m_hasNonUniformScale; AZ::TransformBus::EventResult(m_cachedWorldTransform, GetEntityId(), &AZ::TransformInterface::GetWorldTM); m_cachedNonUniformScale = AZ::Vector3::CreateOne(); if (m_hasNonUniformScale) { AZ::NonUniformScaleRequestBus::EventResult(m_cachedNonUniformScale, GetEntityId(), &AZ::NonUniformScaleRequests::GetScale); } // Debug drawing m_colliderDebugDraw.Connect(GetEntityId()); m_colliderDebugDraw.SetDisplayCallback(this); // ComponentMode m_componentModeDelegate.ConnectWithSingleComponentMode< EditorColliderComponent, ColliderComponentMode>( AZ::EntityComponentIdPair(GetEntityId(), GetId()), nullptr); bool usingMaterialsFromAsset = IsAssetConfig() ? m_shapeConfiguration.m_physicsAsset.m_configuration.m_useMaterialsFromAsset : false; m_configuration.m_materialSelection.SetSlotsReadOnly(usingMaterialsFromAsset); if (ShouldUpdateCollisionMeshFromRender()) { SetCollisionMeshFromRender(); } UpdateMeshAsset(); UpdateShapeConfigurationScale(); CreateStaticEditorCollider(); Physics::ColliderComponentEventBus::Event(GetEntityId(), &Physics::ColliderComponentEvents::OnColliderChanged); } void EditorColliderComponent::Deactivate() { AzPhysics::SimulatedBodyComponentRequestsBus::Handler::BusDisconnect(); m_colliderDebugDraw.Disconnect(); AZ::Data::AssetBus::MultiHandler::BusDisconnect(); m_nonUniformScaleChangedHandler.Disconnect(); EditorColliderValidationRequestBus::Handler::BusDisconnect(); EditorColliderComponentRequestBus::Handler::BusDisconnect(); AZ::Render::MeshComponentNotificationBus::Handler::BusDisconnect(); ColliderShapeRequestBus::Handler::BusDisconnect(); AzToolsFramework::BoxManipulatorRequestBus::Handler::BusDisconnect(); AZ::TransformNotificationBus::Handler::BusDisconnect(); PhysX::MeshColliderComponentRequestsBus::Handler::BusDisconnect(); AzToolsFramework::EntitySelectionEvents::Bus::Handler::BusDisconnect(); AzToolsFramework::Components::EditorComponentBase::Deactivate(); m_componentModeDelegate.Disconnect(); if (m_sceneInterface) { m_sceneInterface->RemoveSimulatedBody(m_editorSceneHandle, m_editorBodyHandle); } } AZ::u32 EditorColliderComponent::OnConfigurationChanged() { if (m_shapeConfiguration.IsAssetConfig()) { UpdateMeshAsset(); m_configuration.m_materialSelection.SetSlotsReadOnly(m_shapeConfiguration.m_physicsAsset.m_configuration.m_useMaterialsFromAsset); } else { m_configuration.m_materialSelection.SetMaterialSlots(Physics::MaterialSelection::SlotsArray()); m_configuration.m_materialSelection.SetSlotsReadOnly(false); } // ensure we refresh the ComponentMode (and Manipulators) when the configuration // changes to keep the ComponentMode in sync with the shape (otherwise the manipulators // will move out of alignment with the shape) AzToolsFramework::ComponentModeFramework::ComponentModeSystemRequestBus::Broadcast( &AzToolsFramework::ComponentModeFramework::ComponentModeSystemRequests::Refresh, AZ::EntityComponentIdPair(GetEntityId(), GetId())); UpdateShapeConfigurationScale(); CreateStaticEditorCollider(); ValidateRigidBodyMeshGeometryType(); m_colliderDebugDraw.ClearCachedGeometry(); Physics::ColliderComponentEventBus::Event(GetEntityId(), &Physics::ColliderComponentEvents::OnColliderChanged); return AZ::Edit::PropertyRefreshLevels::None; } void EditorColliderComponent::OnSelected() { if (auto* physXSystem = GetPhysXSystem()) { physXSystem->RegisterSystemConfigurationChangedEvent(m_physXConfigChangedHandler); physXSystem->RegisterOnMaterialLibraryChangedEventHandler(m_onMaterialLibraryChangedEventHandler); } } void EditorColliderComponent::OnDeselected() { m_onMaterialLibraryChangedEventHandler.Disconnect(); m_physXConfigChangedHandler.Disconnect(); } void EditorColliderComponent::BuildGameEntity(AZ::Entity* gameEntity) { auto sharedColliderConfig = AZStd::make_shared(m_configuration); BaseColliderComponent* colliderComponent = nullptr; auto buildGameEntityScaledPrimitive = [gameEntity](AZStd::shared_ptr& 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(); colliderComponent->SetShapeConfigurationList({ AZStd::make_pair(colliderConfig, AZStd::make_shared(scaledPrimitiveConfig.value())) }); } }; switch (m_shapeConfiguration.m_shapeType) { case Physics::ShapeType::Sphere: if (!m_hasNonUniformScale) { colliderComponent = gameEntity->CreateComponent(); colliderComponent->SetShapeConfigurationList({ AZStd::make_pair(sharedColliderConfig, AZStd::make_shared(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(); colliderComponent->SetShapeConfigurationList({ AZStd::make_pair(sharedColliderConfig, AZStd::make_shared(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(); colliderComponent->SetShapeConfigurationList({ AZStd::make_pair(sharedColliderConfig, AZStd::make_shared(m_shapeConfiguration.m_capsule)) }); } else { buildGameEntityScaledPrimitive(sharedColliderConfig, m_shapeConfiguration.m_capsule, m_shapeConfiguration.m_subdivisionLevel); } break; case Physics::ShapeType::PhysicsAsset: colliderComponent = gameEntity->CreateComponent(); m_shapeConfiguration.m_physicsAsset.m_configuration.m_subdivisionLevel = m_shapeConfiguration.m_subdivisionLevel; colliderComponent->SetShapeConfigurationList({ AZStd::make_pair(sharedColliderConfig, AZStd::make_shared(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(); colliderComponent->SetShapeConfigurationList({ AZStd::make_pair(sharedColliderConfig, AZStd::make_shared(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 { return AZ::Transform::CreateFromQuaternionAndTranslation( m_configuration.m_rotation, m_configuration.m_position); } 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> 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 colliderConfig = AZStd::make_shared( GetColliderConfigurationScaled()); AZStd::shared_ptr 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 shape = AZ::Interface::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 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& 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& indexPerShape = physicsAsset->m_assetData.m_materialIndexPerShape; AZStd::unordered_set 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 asset) { if (asset == m_shapeConfiguration.m_physicsAsset.m_pxAsset) { m_shapeConfiguration.m_physicsAsset.m_pxAsset = asset; m_shapeConfiguration.m_physicsAsset.m_configuration.m_asset = asset; UpdateMaterialSlotsFromMeshAsset(); CreateStaticEditorCollider(); // Invalidate debug draw cached data m_colliderDebugDraw.ClearCachedGeometry(); // Notify about the data update of the collider Physics::ColliderComponentEventBus::Event(GetEntityId(), &Physics::ColliderComponentEvents::OnColliderChanged); ValidateRigidBodyMeshGeometryType(); } else { m_componentWarnings.clear(); m_configuration.m_materialSelection.SetMaterialSlots(Physics::MaterialSelection::SlotsArray()); AzToolsFramework::ToolsApplicationEvents::Bus::Broadcast( &AzToolsFramework::ToolsApplicationEvents::InvalidatePropertyDisplay, AzToolsFramework::Refresh_EntireTree); } } void EditorColliderComponent::ValidateRigidBodyMeshGeometryType() { const PhysX::EditorRigidBodyComponent* entityRigidbody = m_entity->FindComponent(); if (entityRigidbody && m_shapeConfiguration.m_shapeType == Physics::ShapeType::PhysicsAsset && m_shapeConfiguration.m_physicsAsset.m_pxAsset.IsReady()) { AZStd::vector> shapes; Utils::GetShapesFromAsset(m_shapeConfiguration.m_physicsAsset.m_configuration, m_configuration, m_hasNonUniformScale, m_shapeConfiguration.m_subdivisionLevel, shapes); if (shapes.empty()) { m_componentWarnings.clear(); AzToolsFramework::ToolsApplicationEvents::Bus::Broadcast( &AzToolsFramework::ToolsApplicationEvents::InvalidatePropertyDisplay, AzToolsFramework::Refresh_EntireTree); return; } //We check if the shapes are triangle meshes, if any mesh is a triangle mesh we activate the warning. bool shapeIsTriangleMesh = false; for (const auto& shape : shapes) { auto current_shape = AZStd::rtti_pointer_cast(shape); if (current_shape && current_shape->GetPxShape()->getGeometryType() == physx::PxGeometryType::eTRIANGLEMESH && entityRigidbody->GetRigidBody() && entityRigidbody->GetRigidBody()->IsKinematic() == false) { shapeIsTriangleMesh = true; break; } } if (shapeIsTriangleMesh) { m_componentWarnings.clear(); AZStd::string assetPath = m_shapeConfiguration.m_physicsAsset.m_configuration.m_asset.GetHint().c_str(); const size_t lastSlash = assetPath.rfind('/'); if (lastSlash != AZStd::string::npos) { assetPath = assetPath.substr(lastSlash + 1); } m_componentWarnings.push_back(AZStd::string::format( "The physics asset \"%s\" was exported using triangle mesh geometry, which is not compatible with non-kinematic " "dynamic rigid bodies. To make the collider compatible, you can export the asset using primitive or convex mesh " "geometry, use mesh decomposition when exporting the asset, or set the rigid body to kinematic. Learn more about " "colliders.", assetPath.c_str())); // make sure the entity inspector scrolls so the warning is visible by marking this component as having // new content AzToolsFramework::EntityPropertyEditorRequestBus::Broadcast( &AzToolsFramework::EntityPropertyEditorRequests::SetNewComponentId, GetId()); } else { m_componentWarnings.clear(); } } else { m_componentWarnings.clear(); } AzToolsFramework::ToolsApplicationEvents::Bus::Broadcast( &AzToolsFramework::ToolsApplicationEvents::InvalidatePropertyDisplay, m_componentWarnings.empty() ? AzToolsFramework::Refresh_EntireTree : AzToolsFramework::Refresh_EntireTree_NewContent); } void EditorColliderComponent::OnAssetReloaded(AZ::Data::Asset asset) { OnAssetReady(asset); } void EditorColliderComponent::BuildDebugDrawMesh() const { if (m_shapeConfiguration.IsAssetConfig()) { const AZ::Data::Asset& physicsAsset = m_shapeConfiguration.m_physicsAsset.m_pxAsset; const Physics::PhysicsAssetShapeConfiguration& physicsAssetConfiguration = m_shapeConfiguration.m_physicsAsset.m_configuration; if (!physicsAsset.IsReady()) { // Skip processing if the asset isn't ready return; } AzPhysics::ShapeColliderPairList shapeConfigList; Utils::GetColliderShapeConfigsFromAsset(physicsAssetConfiguration, m_configuration, m_hasNonUniformScale, m_shapeConfiguration.m_subdivisionLevel, shapeConfigList); for (size_t shapeIndex = 0; shapeIndex < shapeConfigList.size(); shapeIndex++) { const Physics::ShapeConfiguration* shapeConfiguration = shapeConfigList[shapeIndex].second.get(); AZ_Assert(shapeConfiguration, "BuildDebugDrawMesh: Invalid shape configuration"); if (shapeConfiguration) { m_colliderDebugDraw.BuildMeshes(*shapeConfiguration, static_cast(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(shapeConfiguration); const AZ::Vector3 overallScale = Utils::GetTransformScale(GetEntityId()) * m_cachedNonUniformScale * assetScale; Physics::ColliderConfiguration nonUniformScaledColliderConfiguration = *colliderConfiguration; nonUniformScaledColliderConfiguration.m_position *= m_cachedNonUniformScale; m_colliderDebugDraw.DrawMesh(debugDisplay, nonUniformScaledColliderConfiguration, *cookedMeshShapeConfiguration, overallScale, static_cast(shapeIndex)); break; } case Physics::ShapeType::Sphere: { const Physics::SphereShapeConfiguration* sphereShapeConfiguration = static_cast(shapeConfiguration); m_colliderDebugDraw.DrawSphere(debugDisplay, *colliderConfiguration, *sphereShapeConfiguration, assetScale); break; } case Physics::ShapeType::Box: { const Physics::BoxShapeConfiguration* boxShapeConfiguration = static_cast(shapeConfiguration); m_colliderDebugDraw.DrawBox(debugDisplay, *colliderConfiguration, *boxShapeConfiguration, assetScale); break; } case Physics::ShapeType::Capsule: { const Physics::CapsuleShapeConfiguration* capsuleShapeConfiguration = static_cast(shapeConfiguration); m_colliderDebugDraw.DrawCapsule(debugDisplay, *colliderConfiguration, *capsuleShapeConfiguration, assetScale); break; } default: { AZ_Error("EditorColliderComponent", false, "DisplayMeshCollider: Unsupported ShapeType %d. Entity %s, ID: %llu", static_cast(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 GetWorldTM(); } AZ::Transform EditorColliderComponent::GetCurrentLocalTransform() { return GetColliderLocalTransform(); } AZ::Vector3 EditorColliderComponent::GetBoxScale() { return AZ::Vector3::CreateOne(); } 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 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& renderMeshAsset, const AZStd::vector& 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 EditorColliderComponent::GetRenderMeshAsset() const { // Try Atom MeshComponent AZ::Data::Asset atomMeshAsset; AZ::Render::MeshComponentRequestBus::EventResult(atomMeshAsset, GetEntityId(), &AZ::Render::MeshComponentRequestBus::Events::GetModelAsset); return atomMeshAsset; } void EditorColliderComponent::SetCollisionMeshFromRender() { AZ::Data::Asset renderMeshAsset = GetRenderMeshAsset(); if (!renderMeshAsset.GetId().IsValid()) { // No render mesh component assigned return; } bool productsQueryResult = false; AZStd::vector productsInfo; AzToolsFramework::AssetSystemRequestBus::BroadcastResult(productsQueryResult, &AzToolsFramework::AssetSystemRequestBus::Events::GetAssetsProducedBySourceUUID, renderMeshAsset.GetId().m_guid, productsInfo); if (productsQueryResult) { AZStd::vector physicsAssets; physicsAssets.reserve(productsInfo.size()); for (const AZ::Data::AssetInfo& info : productsInfo) { if (info.m_assetType == AZ::AzTypeInfo::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().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().c_str(), renderMeshAsset.GetHint().c_str()); } } void EditorColliderComponent::OnModelReady([[maybe_unused]] const AZ::Data::Asset& modelAsset, [[maybe_unused]] const AZ::Data::Instance& 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(instance); if (editorColliderComponent != nullptr) { warnings = editorColliderComponent->GetComponentWarnings(); } } } // namespace PhysX