Files
o3de/Gems/PhysX/Code/Source/EditorColliderComponent.cpp
T
lumberyard-employee-dm 3dec5d3b71 LYN-2537 engine assets (#254)
* LYN-2537 Moved the Engine and Editor folder to be within the EngineAssets folder

* Fixed Documentation in bootstrap.cfg to correct the path to the user project specific registry file

* Adding a newline to the output of AssetCatalog 'Registering asset..., but type is not set' message

* Updating the AssetProcessorPlatformConfig.setreg Scan Folder to detect
the @ENGINEROOT@/EngineAssets/Engine path for engine runtime assets and
@ENGINEROOT@/EngineAssets/Editor path for engine tool assets

* Updating references to Icons and other assets to account for moving the
Engine and Editor folder under a single EngineAssets folder

* Moving the Engine Settings Registry folder from Engine/Registry -> Registry

* Removed the LY_PROJECT_CMAKE_PATH define as it is not portable to other locations. It is hard coded to the project location that was used for the CMake configuration. Furthermore it paths with backslashes within it are treated as escape characters and not a path separator

* Updated the LyTestTools asset_processor.py script to copy the exclude.filetag from the EngineAssets/Engine directory now

* Fixed Atom Shader Preprocessing when running using an External Project

* Updated the TSGenerateAction.cpp to fix the build error with using a renamed variable

* Updated the Install_Common.cmake ly_setup_others function to install the
EngineAssets directory and the each of the Gem's Assets directory while
maintaining the relative directory structure to the Engine Root
Also updated the install step to install the Registry folder at the
engine root

* Fixed the copying of the Registry folder to be in the install root, instead of under a second 'Registry' folder

* Moving the AssetProcessorPlatformConfig.setreg file over to the Registry folder

* Updated the LyTestTools and C++ code to point that the new location of
the AssetProcessorPlatformConfig.setreg file inside of the Registry
folder

* Renamed Test AssetProcessor*Config.ini files to have the .setreg extension

* Converted the AssetProcessor test setreg files from ini format to json
format using the SerializeContextTools convert-ini command

* Updated the AssetProcessor CMakeLists.txt to copy over the test setreg files to the build folder

* Updated the assetprocessor test file list to point at the renamed AsssetProcessor*Config setreg filenames

* Removed the Output Prefix code from the AssetProcessor. The complexity that it brought to the AP code is not needed, as users can replicate the behavior by just moving there assets underneath a another folder, underneath the scan folder

* Adding back support to read the AssetProcessorPlatformConfig.setreg file from the asset root. This is only needed for C++ UnitTests as they run in an environment where the accessing the Engine Settings Registry is not available

* Updating the Install_common.cmake logic to copy any "Assets" folder to
the install layout.
The Script has also been updated to copy over the "Assets" folder in the
Engine Root to the install layout instead of an "EngineAssets" folder

* Updating References to EngineAssets source asset folder in code to be the Assets source folder

* Moved the Engine Source Asset folder of 'EngineAssets' to a new folder name of 'Assets'. This is inline with the naming scheme we use for Gem asset folders

* Adding the EngineFinder.cmake to the AutomatedTesting project to allow it to work in a project centric manner

* Updating the LyTestTools copy_assets_to_project function to be able to copy assets with folders to the temporary project root
Fixed an issue in LyTestTools where the temporary log directory could have shutil.rmtree being called twice on it leading to an exception which fails an automated test

Updated the asset_procesor_gui_tests_2 AddScanFolder test to not use the
output prefix, but instead place the source asset root into a
subdirectory

* Correct the AssetProcessorPlatformConfig Scan Folders for the EngineAssets directory to point at the Assets directory

* Updated the asset procesor batch dependency test scan folder to point at the 'Assets' folder instead of 'EngineAssets'
2021-04-28 21:38:43 -05:00

1371 lines
59 KiB
C++

/*
* All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
* its licensors.
*
* For complete copyright and license terms please see the LICENSE at the root of this
* distribution (the "License"). All use of this software is governed by the License,
* or, if provided, by the license below or the license accompanying this file. Do not
* remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*
*/
#include <PhysX_precompiled.h>
#include <AzCore/Script/ScriptTimePoint.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/std/smart_ptr/shared_ptr.h>
#include <AzFramework/Physics/ColliderComponentBus.h>
#include <AzFramework/Physics/SimulatedBodies/RigidBody.h>
#include <AzFramework/Physics/SystemBus.h>
#include <AzFramework/Physics/Common/PhysicsSimulatedBody.h>
#include <AzFramework/Physics/Configuration/StaticRigidBodyConfiguration.h>
#include <AzFramework/Viewport/ViewportColors.h>
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
#include <AzToolsFramework/ComponentModes/BoxComponentMode.h>
#include <AzToolsFramework/Maths/TransformUtils.h>
#include <AzToolsFramework/UI/PropertyEditor/PropertyEditorAPI.h>
#include <AzToolsFramework/Entity/EditorEntityHelpers.h>
#include <Editor/EditorClassConverters.h>
#include <LmbrCentral/Geometry/GeometrySystemComponentBus.h>
#include <LmbrCentral/Shape/BoxShapeComponentBus.h>
#include <Source/BoxColliderComponent.h>
#include <Source/CapsuleColliderComponent.h>
#include <Source/EditorColliderComponent.h>
#include <Source/EditorRigidBodyComponent.h>
#include <Editor/Source/Components/EditorSystemComponent.h>
#include <Source/MeshColliderComponent.h>
#include <Source/SphereColliderComponent.h>
#include <Source/Utils.h>
#include <LyViewPaneNames.h>
#include <Editor/ConfigurationWindowBus.h>
#include <Editor/ColliderComponentMode.h>
namespace PhysX
{
void EditorProxyAssetShapeConfig::Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<EditorProxyAssetShapeConfig>()
->Version(1)
->Field("Asset", &EditorProxyAssetShapeConfig::m_pxAsset)
->Field("Configuration", &EditorProxyAssetShapeConfig::m_configuration)
;
if (auto editContext = serializeContext->GetEditContext())
{
editContext->Class<EditorProxyAssetShapeConfig>("EditorProxyShapeConfig", "PhysX Base shape collider")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->DataElement(AZ::Edit::UIHandlers::Default, &EditorProxyAssetShapeConfig::m_pxAsset, "PhysX Mesh", "PhysX mesh collider asset")
->DataElement(AZ::Edit::UIHandlers::Default, &EditorProxyAssetShapeConfig::m_configuration, "Configuration", "Configuration of asset shape")
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly);
}
}
}
void EditorProxyShapeConfig::Reflect(AZ::ReflectContext* context)
{
EditorProxyAssetShapeConfig::Reflect(context);
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<EditorProxyShapeConfig>()
->Version(2, &PhysX::ClassConverters::EditorProxyShapeConfigVersionConverter)
->Field("ShapeType", &EditorProxyShapeConfig::m_shapeType)
->Field("Sphere", &EditorProxyShapeConfig::m_sphere)
->Field("Box", &EditorProxyShapeConfig::m_box)
->Field("Capsule", &EditorProxyShapeConfig::m_capsule)
->Field("PhysicsAsset", &EditorProxyShapeConfig::m_physicsAsset)
->Field("HasNonUniformScale", &EditorProxyShapeConfig::m_hasNonUniformScale)
->Field("SubdivisionLevel", &EditorProxyShapeConfig::m_subdivisionLevel)
;
if (auto editContext = serializeContext->GetEditContext())
{
editContext->Class<EditorProxyShapeConfig>(
"EditorProxyShapeConfig", "PhysX Base shape collider")
->DataElement(AZ::Edit::UIHandlers::ComboBox, &EditorProxyShapeConfig::m_shapeType, "Shape", "The shape of the collider")
->EnumAttribute(Physics::ShapeType::Sphere, "Sphere")
->EnumAttribute(Physics::ShapeType::Box, "Box")
->EnumAttribute(Physics::ShapeType::Capsule, "Capsule")
->EnumAttribute(Physics::ShapeType::PhysicsAsset, "PhysicsAsset")
->Attribute(AZ::Edit::Attributes::ChangeNotify, AZ::Edit::PropertyRefreshLevels::EntireTree)
// note: we do not want the user to be able to change shape types while in ComponentMode (there will
// potentially be different ComponentModes for different shape types)
->Attribute(AZ::Edit::Attributes::ReadOnly, &AzToolsFramework::ComponentModeFramework::InComponentMode)
->DataElement(AZ::Edit::UIHandlers::Default, &EditorProxyShapeConfig::m_sphere, "Sphere", "Configuration of sphere shape")
->Attribute(AZ::Edit::Attributes::Visibility, &EditorProxyShapeConfig::IsSphereConfig)
->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorProxyShapeConfig::OnConfigurationChanged)
->DataElement(AZ::Edit::UIHandlers::Default, &EditorProxyShapeConfig::m_box, "Box", "Configuration of box shape")
->Attribute(AZ::Edit::Attributes::Visibility, &EditorProxyShapeConfig::IsBoxConfig)
->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorProxyShapeConfig::OnConfigurationChanged)
->DataElement(AZ::Edit::UIHandlers::Default, &EditorProxyShapeConfig::m_capsule, "Capsule", "Configuration of capsule shape")
->Attribute(AZ::Edit::Attributes::Visibility, &EditorProxyShapeConfig::IsCapsuleConfig)
->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorProxyShapeConfig::OnConfigurationChanged)
->DataElement(AZ::Edit::UIHandlers::Default, &EditorProxyShapeConfig::m_physicsAsset, "Asset", "Configuration of asset shape")
->Attribute(AZ::Edit::Attributes::Visibility, &EditorProxyShapeConfig::IsAssetConfig)
->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorProxyShapeConfig::OnConfigurationChanged)
->DataElement(AZ::Edit::UIHandlers::Default, &EditorProxyShapeConfig::m_subdivisionLevel, "Subdivision level",
"The level of subdivision if a primitive shape is replaced with a convex mesh due to scaling")
->Attribute(AZ::Edit::Attributes::Min, Utils::MinCapsuleSubdivisionLevel)
->Attribute(AZ::Edit::Attributes::Max, Utils::MaxCapsuleSubdivisionLevel)
->Attribute(AZ::Edit::Attributes::Visibility, &EditorProxyShapeConfig::ShowingSubdivisionLevel)
->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorProxyShapeConfig::OnConfigurationChanged)
;
}
}
}
AZ::u32 EditorProxyShapeConfig::OnConfigurationChanged()
{
return AZ::Edit::PropertyRefreshLevels::ValuesOnly;
}
void EditorColliderComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC_CE("PhysicsWorldBodyService"));
provided.push_back(AZ_CRC_CE("PhysXColliderService"));
provided.push_back(AZ_CRC_CE("PhysXTriggerService"));
}
void EditorColliderComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
{
required.push_back(AZ_CRC_CE("TransformService"));
}
void EditorColliderComponent::GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent)
{
dependent.push_back(AZ_CRC_CE("NonUniformScaleService"));
}
void EditorColliderComponent::Reflect(AZ::ReflectContext* context)
{
EditorProxyShapeConfig::Reflect(context);
DebugDraw::Collider::Reflect(context);
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
// Deprecate old separate components
serializeContext->ClassDeprecate(
"EditorCapsuleColliderComponent",
"{0BD5AF3A-35C0-4386-9930-54A2A3E97432}",
&ClassConverters::DeprecateEditorCapsuleColliderComponent)
;
serializeContext->ClassDeprecate(
"EditorBoxColliderComponent",
"{FAECF2BE-625B-469D-BBFF-E345BBB12D66}",
&ClassConverters::DeprecateEditorBoxColliderComponent)
;
serializeContext->ClassDeprecate(
"EditorSphereColliderComponent",
"{D11C1624-4AE9-4B66-A6F6-40EDB9CDCE99}",
&ClassConverters::DeprecateEditorSphereColliderComponent)
;
serializeContext->ClassDeprecate(
"EditorMeshColliderComponent",
"{214185DA-ABD9-4410-9819-7C177801CF7A}",
&ClassConverters::DeprecateEditorMeshColliderComponent)
;
serializeContext->Class<EditorColliderComponent, EditorComponentBase>()
->Version(9, &PhysX::ClassConverters::UpgradeEditorColliderComponent)
->Field("ColliderConfiguration", &EditorColliderComponent::m_configuration)
->Field("ShapeConfiguration", &EditorColliderComponent::m_shapeConfiguration)
->Field("DebugDrawSettings", &EditorColliderComponent::m_colliderDebugDraw)
->Field("ComponentMode", &EditorColliderComponent::m_componentModeDelegate)
->Field("HasNonUniformScale", &EditorColliderComponent::m_hasNonUniformScale)
;
if (auto editContext = serializeContext->GetEditContext())
{
editContext->Class<EditorColliderComponent>(
"PhysX Collider", "PhysX shape collider")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Category, "PhysX")
->Attribute(AZ::Edit::Attributes::Icon, "Icons/Components/PhysXCollider.svg")
->Attribute(AZ::Edit::Attributes::ViewportIcon, "Icons/Components/PhysXCollider.svg")
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC("Game", 0x232b318c))
->Attribute(AZ::Edit::Attributes::HelpPageURL, "http://docs.aws.amazon.com/console/lumberyard/component/physx/collider")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->DataElement(AZ::Edit::UIHandlers::Default, &EditorColliderComponent::m_configuration, "Collider Configuration", "Configuration of the collider")
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorColliderComponent::OnConfigurationChanged)
->DataElement(AZ::Edit::UIHandlers::Default, &EditorColliderComponent::m_shapeConfiguration, "Shape Configuration", "Configuration of the shape")
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
->Attribute(AZ::Edit::Attributes::ChangeNotify, &EditorColliderComponent::OnConfigurationChanged)
->DataElement(AZ::Edit::UIHandlers::Default, &EditorColliderComponent::m_componentModeDelegate, "Component Mode", "Collider Component Mode")
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
->DataElement(AZ::Edit::UIHandlers::Default, &EditorColliderComponent::m_colliderDebugDraw,
"Debug draw settings", "Debug draw settings")
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
;
}
}
}
AZ::ComponentDescriptor* EditorColliderComponent::CreateDescriptor()
{
return aznew EditorColliderComponentDescriptor();
}
EditorColliderComponent::EditorColliderComponent(
const Physics::ColliderConfiguration& colliderConfiguration,
const Physics::ShapeConfiguration& shapeConfiguration)
: m_configuration(colliderConfiguration)
, m_shapeConfiguration(shapeConfiguration)
{
}
const EditorProxyShapeConfig& EditorColliderComponent::GetShapeConfiguration() const
{
return m_shapeConfiguration;
}
const Physics::ColliderConfiguration& EditorColliderComponent::GetColliderConfiguration() const
{
return m_configuration;
}
Physics::ColliderConfiguration EditorColliderComponent::GetColliderConfigurationScaled() const
{
// Scale the collider offset
Physics::ColliderConfiguration colliderConfiguration = m_configuration;
colliderConfiguration.m_position *= Utils::GetTransformScale(GetEntityId()) * m_cachedNonUniformScale;
return colliderConfiguration;
}
EditorProxyShapeConfig::EditorProxyShapeConfig(const Physics::ShapeConfiguration& shapeConfiguration)
{
m_shapeType = shapeConfiguration.GetShapeType();
switch (m_shapeType)
{
case Physics::ShapeType::Sphere:
m_sphere = static_cast<const Physics::SphereShapeConfiguration&>(shapeConfiguration);
break;
case Physics::ShapeType::Box:
m_box = static_cast<const Physics::BoxShapeConfiguration&>(shapeConfiguration);
break;
case Physics::ShapeType::Capsule:
m_capsule = static_cast<const Physics::CapsuleShapeConfiguration&>(shapeConfiguration);
break;
case Physics::ShapeType::PhysicsAsset:
m_physicsAsset.m_configuration = static_cast<const Physics::PhysicsAssetShapeConfiguration&>(shapeConfiguration);
break;
case Physics::ShapeType::CookedMesh:
m_cookedMesh = static_cast<const Physics::CookedMeshShapeConfiguration&>(shapeConfiguration);
break;
default:
AZ_Warning("EditorProxyShapeConfig", false, "Invalid shape type!");
}
}
bool EditorProxyShapeConfig::IsSphereConfig() const
{
return m_shapeType == Physics::ShapeType::Sphere;
}
bool EditorProxyShapeConfig::IsBoxConfig() const
{
return m_shapeType == Physics::ShapeType::Box;
}
bool EditorProxyShapeConfig::IsCapsuleConfig() const
{
return m_shapeType == Physics::ShapeType::Capsule;
}
bool EditorProxyShapeConfig::IsAssetConfig() const
{
return m_shapeType == Physics::ShapeType::PhysicsAsset;
}
Physics::ShapeConfiguration& EditorProxyShapeConfig::GetCurrent()
{
return const_cast<Physics::ShapeConfiguration&>(static_cast<const EditorProxyShapeConfig&>(*this).GetCurrent());
}
const Physics::ShapeConfiguration& EditorProxyShapeConfig::GetCurrent() const
{
switch (m_shapeType)
{
case Physics::ShapeType::Sphere:
return m_sphere;
case Physics::ShapeType::Box:
return m_box;
case Physics::ShapeType::Capsule:
return m_capsule;
case Physics::ShapeType::PhysicsAsset:
return m_physicsAsset.m_configuration;
case Physics::ShapeType::CookedMesh:
return m_cookedMesh;
default:
AZ_Warning("EditorProxyShapeConfig", false, "Unsupported shape type");
return m_box;
}
}
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_onDefaultMaterialLibraryChangedEventHandler = AzPhysics::SystemEvents::OnDefaultMaterialLibraryChangedEvent::Handler(
[this](const AZ::Data::AssetId& defaultMaterialLibrary)
{
m_configuration.m_materialSelection.OnDefaultMaterialLibraryChanged(defaultMaterialLibrary);
});
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());
LmbrCentral::MeshComponentNotificationBus::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()
{
Physics::WorldBodyRequestBus::Handler::BusDisconnect();
m_colliderDebugDraw.Disconnect();
AZ::Data::AssetBus::MultiHandler::BusDisconnect();
m_nonUniformScaleChangedHandler.Disconnect();
EditorColliderComponentRequestBus::Handler::BusDisconnect();
AZ::Render::MeshComponentNotificationBus::Handler::BusDisconnect();
LmbrCentral::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);
m_editorBodyHandle = AzPhysics::InvalidSimulatedBodyHandle;
}
}
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->RegisterOnDefaultMaterialLibraryChangedEventHandler(m_onDefaultMaterialLibraryChangedEventHandler);
}
}
void EditorColliderComponent::OnDeselected()
{
m_onDefaultMaterialLibraryChangedEventHandler.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);
m_editorBodyHandle = AzPhysics::InvalidSimulatedBodyHandle;
}
return;
}
AZ::Transform colliderTransform = GetWorldTM();
colliderTransform.ExtractScale();
AzPhysics::StaticRigidBodyConfiguration configuration;
configuration.m_orientation = colliderTransform.GetRotation();
configuration.m_position = colliderTransform.GetTranslation();
configuration.m_entityId = GetEntityId();
configuration.m_debugName = GetEntity()->GetName();
// This configuration needs to be at the scope of the function to be added
// to m_colliderAndShapeData as a pointer.
Physics::ColliderConfiguration colliderConfig;
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
{
colliderConfig = GetColliderConfigurationScaled();
Physics::ShapeConfiguration& shapeConfig = m_shapeConfiguration.GetCurrent();
if (IsNonUniformlyScaledPrimitive(m_shapeConfiguration))
{
auto convexConfig = Utils::CreateConvexFromPrimitive(GetColliderConfiguration(), shapeConfig,
m_shapeConfiguration.m_subdivisionLevel, shapeConfig.m_scale);
auto 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 = AzPhysics::InvalidSimulatedBodyHandle;
}
m_editorBodyHandle = m_sceneInterface->AddSimulatedBody(m_editorSceneHandle, &configuration);
}
m_colliderDebugDraw.ClearCachedGeometry();
Physics::WorldBodyRequestBus::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::SetMaterialAsset(const AZ::Data::AssetId& id)
{
m_configuration.m_materialSelection.SetMaterialLibrary(id);
}
void EditorColliderComponent::SetMaterialId(const Physics::MaterialId& id)
{
m_configuration.m_materialSelection.SetMaterialId(id);
}
void EditorColliderComponent::UpdateMaterialSlotsFromMeshAsset()
{
Physics::SystemRequestBus::Broadcast(&Physics::SystemRequests::UpdateMaterialSelection,
m_shapeConfiguration.GetCurrent(), m_configuration);
AzToolsFramework::ToolsApplicationEvents::Bus::Broadcast(&AzToolsFramework::ToolsApplicationEvents::InvalidatePropertyDisplay, AzToolsFramework::Refresh_EntireTree);
ValidateMaterialSurfaces();
}
void EditorColliderComponent::ValidateMaterialSurfaces()
{
const AZ::Data::Asset<Pipeline::MeshAsset>& physicsAsset = m_shapeConfiguration.m_physicsAsset.m_pxAsset;
if (!IsAssetConfig() || !physicsAsset.IsReady())
{
return;
}
// Here we check the material indices assigned to every shape and validate that every index is used at least once.
// It's not an error if the validation fails here but something we want to let the designers know about.
[[maybe_unused]] size_t surfacesNum = physicsAsset->m_assetData.m_surfaceNames.size();
const AZStd::vector<AZ::u16>& indexPerShape = physicsAsset->m_assetData.m_materialIndexPerShape;
AZStd::unordered_set<AZ::u16> usedIndices;
for (AZ::u16 index : indexPerShape)
{
if (index == Pipeline::MeshAssetData::TriangleMeshMaterialIndex)
{
// Triangle mesh indices are cooked into binary data, pass the validation in this case.
return;
}
usedIndices.insert(index);
}
AZ_Warning("PhysX", usedIndices.size() == surfacesNum,
"EditorColliderComponent::ValidateMaterialSurfaces. Entity: %s. Number of surfaces used by the shape (%d) does not match the "
"total number of surfaces in the asset (%d). Please check that there are no convex meshes with per-face materials. Asset: %s",
GetEntity()->GetName().c_str(), usedIndices.size(), surfacesNum, physicsAsset.GetHint().c_str())
}
void EditorColliderComponent::OnAssetReady(AZ::Data::Asset<AZ::Data::AssetData> asset)
{
if (asset == m_shapeConfiguration.m_physicsAsset.m_pxAsset)
{
m_shapeConfiguration.m_physicsAsset.m_pxAsset = asset;
m_shapeConfiguration.m_physicsAsset.m_configuration.m_asset = m_shapeConfiguration.m_physicsAsset.m_pxAsset;
UpdateMaterialSlotsFromMeshAsset();
CreateStaticEditorCollider();
// Invalidate debug draw cached data
m_colliderDebugDraw.ClearCachedGeometry();
// Notify about the data update of the collider
Physics::ColliderComponentEventBus::Event(GetEntityId(), &Physics::ColliderComponentEvents::OnColliderChanged);
ValidateRigidBodyMeshGeometryType();
}
else
{
m_componentWarnings.clear();
m_configuration.m_materialSelection.SetMaterialSlots(Physics::MaterialSelection::SlotsArray());
}
AzToolsFramework::ToolsApplicationEvents::Bus::Broadcast(&AzToolsFramework::ToolsApplicationEvents::InvalidatePropertyDisplay, AzToolsFramework::Refresh_EntireTree);
}
void EditorColliderComponent::ValidateRigidBodyMeshGeometryType()
{
const PhysX::EditorRigidBodyComponent* entityRigidbody = m_entity->FindComponent<PhysX::EditorRigidBodyComponent>();
if (m_shapeConfiguration.m_physicsAsset.m_configuration.GetShapeType() == Physics::ShapeType::PhysicsAsset && entityRigidbody)
{
AZStd::vector<AZStd::shared_ptr<Physics::Shape>> shapes;
Utils::GetShapesFromAsset(m_shapeConfiguration.m_physicsAsset.m_configuration, m_configuration, m_hasNonUniformScale,
m_shapeConfiguration.m_subdivisionLevel, shapes);
if (shapes.empty())
{
return;
}
//We grab the first shape to check if it is a triangle mesh.
auto shape = AZStd::rtti_pointer_cast<PhysX::Shape>(shapes[0]);
if (shape &&
shape->GetPxShape()->getGeometryType() == physx::PxGeometryType::eTRIANGLEMESH &&
entityRigidbody->GetRigidBody() &&
entityRigidbody->GetRigidBody()->IsKinematic() == false)
{
AZStd::string assetPath = m_shapeConfiguration.m_physicsAsset.m_configuration.m_asset.GetHint().c_str();
const uint lastSlash = assetPath.rfind('/');
if (lastSlash != AZStd::string::npos)
{
assetPath = assetPath.substr(lastSlash + 1);
}
m_componentWarnings.push_back(AZStd::string::format(
"The Physics Asset \"%s\" is a Triangle Mesh, it is not compatible with a Dynamic Rigidbody, either:\n"
"Change the PhysicsAsset to Convex Mesh or set the Rigidbody to kinematic.",
assetPath.c_str()));
}
else
{
m_componentWarnings.clear();
}
}
else
{
m_componentWarnings.clear();
}
}
void EditorColliderComponent::OnAssetReloaded(AZ::Data::Asset<AZ::Data::AssetData> asset)
{
OnAssetReady(asset);
}
void EditorColliderComponent::BuildDebugDrawMesh() const
{
if (m_shapeConfiguration.IsAssetConfig())
{
const AZ::Data::Asset<Pipeline::MeshAsset>& physicsAsset = m_shapeConfiguration.m_physicsAsset.m_pxAsset;
const Physics::PhysicsAssetShapeConfiguration& physicsAssetConfiguration = m_shapeConfiguration.m_physicsAsset.m_configuration;
if (!physicsAsset.IsReady())
{
// Skip processing if the asset isn't ready
return;
}
Physics::ShapeConfigurationList 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().GetScale() * 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;
Physics::ShapeConfigurationList 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 GetWorldTM().GetScale();
}
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().ExtractScale() * 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);
m_editorBodyHandle = AzPhysics::InvalidSimulatedBodyHandle;
}
}
bool EditorColliderComponent::IsPhysicsEnabled() const
{
return m_editorBodyHandle != AzPhysics::InvalidSimulatedBodyHandle;
}
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::GetWorldBody()
{
if (m_sceneInterface && m_editorBodyHandle != AzPhysics::InvalidSimulatedBodyHandle)
{
if (auto* body = m_sceneInterface->GetSimulatedBodyFromHandle(m_editorSceneHandle, m_editorBodyHandle))
{
return body;
}
}
return nullptr;
}
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);
if (atomMeshAsset.GetId().IsValid())
{
return atomMeshAsset;
}
// Try legacy render MeshComponent
AZ::Data::Asset<AZ::Data::AssetData> legacyMeshAsset;
LmbrCentral::MeshComponentRequestBus::EventResult(legacyMeshAsset,
GetEntityId(), &LmbrCentral::MeshComponentRequests::GetMeshAsset);
return legacyMeshAsset;
}
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::OnMeshCreated([[maybe_unused]] const AZ::Data::Asset<AZ::Data::AssetData>& asset)
{
if (ShouldUpdateCollisionMeshFromRender())
{
SetCollisionMeshFromRender();
}
}
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