/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace EMotionFX { ColliderPropertyNotify::ColliderPropertyNotify(ColliderWidget* colliderWidget) : m_colliderWidget(colliderWidget) { } void ColliderPropertyNotify::BeforePropertyModified(AzToolsFramework::InstanceDataNode* pNode) { if (!m_commandGroup.IsEmpty()) { return; } const AzToolsFramework::InstanceDataNode* parentDataNode = pNode->GetParent(); if (!parentDataNode) { return; } const AZ::SerializeContext* serializeContext = parentDataNode->GetSerializeContext(); const AZ::SerializeContext::ClassData* classData = parentDataNode->GetClassMetadata(); const AZ::SerializeContext::ClassElement* elementData = pNode->GetElementMetadata(); const Actor* actor = m_colliderWidget->GetActor(); const Node* joint = m_colliderWidget->GetJoint(); if (!actor || !joint) { return; } const AZ::u32 actorId = actor->GetID(); const AZStd::string& jointName = joint->GetNameString(); const PhysicsSetup::ColliderConfigType colliderType = m_colliderWidget->GetColliderType(); const size_t colliderIndex = m_colliderWidget->GetColliderIndex(); const size_t instanceCount = parentDataNode->GetNumInstances(); m_commandGroup.SetGroupName(AZStd::string::format("Adjust collider%s", instanceCount > 1 ? "s" : "")); for (size_t instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) { CommandAdjustCollider* command = aznew CommandAdjustCollider(actorId, jointName, colliderType, colliderIndex); m_commandGroup.AddCommand(command); // ColliderConfiguration if (serializeContext->CanDowncast(classData->m_typeId, azrtti_typeid(), classData->m_azRtti, nullptr)) { const Physics::ColliderConfiguration* colliderConfig = static_cast(parentDataNode->GetInstance(instanceIndex)); if (elementData->m_nameCrc == AZ_CRC("CollisionLayer", 0x39931633)) { command->SetOldCollisionLayer(colliderConfig->m_collisionLayer); } if (elementData->m_nameCrc == AZ_CRC("CollisionGroupId", 0x84fe4bbe)) { command->SetOldCollisionGroupId(colliderConfig->m_collisionGroupId); } if (elementData->m_nameCrc == AZ_CRC("Trigger", 0x1a6b0f5d)) { command->SetOldIsTrigger(colliderConfig->m_isTrigger); } if (elementData->m_nameCrc == AZ_CRC("Position", 0x462ce4f5)) { command->SetOldPosition(colliderConfig->m_position); } if (elementData->m_nameCrc == AZ_CRC("Rotation", 0x297c98f1)) { command->SetOldRotation(colliderConfig->m_rotation); } if (elementData->m_nameCrc == AZ_CRC("MaterialSelection", 0xfebd6d15)) { command->SetOldMaterial(colliderConfig->m_materialSelection); } if (elementData->m_nameCrc == AZ_CRC("ColliderTag", 0x5e2963ad)) { command->SetOldTag(colliderConfig->m_tag); } } // Box else if (serializeContext->CanDowncast(classData->m_typeId, azrtti_typeid(), classData->m_azRtti, nullptr)) { const Physics::BoxShapeConfiguration* boxShapeConfig = static_cast(parentDataNode->GetInstance(instanceIndex)); if (elementData->m_nameCrc == AZ_CRC("Configuration", 0xa5e2a5d7)) { command->SetOldDimensions(boxShapeConfig->m_dimensions); } } // Capsule else if (serializeContext->CanDowncast(classData->m_typeId, azrtti_typeid(), classData->m_azRtti, nullptr)) { const Physics::CapsuleShapeConfiguration* capsuleShapeConfig = static_cast(parentDataNode->GetInstance(instanceIndex)); if (elementData->m_nameCrc == AZ_CRC("Radius", 0x3b7c6e5a)) { command->SetOldRadius(capsuleShapeConfig->m_radius); } if (elementData->m_nameCrc == AZ_CRC("Height", 0xf54de50f)) { command->SetOldHeight(capsuleShapeConfig->m_height); } } // Sphere else if (serializeContext->CanDowncast(classData->m_typeId, azrtti_typeid(), classData->m_azRtti, nullptr)) { const Physics::SphereShapeConfiguration* sphereShapeConfig = static_cast(parentDataNode->GetInstance(instanceIndex)); if (elementData->m_nameCrc == AZ_CRC("Radius", 0x3b7c6e5a)) { command->SetOldRadius(sphereShapeConfig->m_radius); } } } } void ColliderPropertyNotify::SetPropertyEditingComplete(AzToolsFramework::InstanceDataNode* pNode) { if (m_commandGroup.IsEmpty()) { return; } const AzToolsFramework::InstanceDataNode* parentDataNode = pNode->GetParent(); if (!parentDataNode) { return; } const AZ::SerializeContext* serializeContext = parentDataNode->GetSerializeContext(); const AZ::SerializeContext::ClassData* classData = parentDataNode->GetClassMetadata(); const AZ::SerializeContext::ClassElement* elementData = pNode->GetElementMetadata(); const Actor* actor = m_colliderWidget->GetActor(); const Node* joint = m_colliderWidget->GetJoint(); if (!actor || !joint) { return; } const AZ::u32 actorId = actor->GetID(); const AZStd::string& jointName = joint->GetNameString(); const PhysicsSetup::ColliderConfigType colliderType = m_colliderWidget->GetColliderType(); const size_t colliderIndex = m_colliderWidget->GetColliderIndex(); const size_t instanceCount = parentDataNode->GetNumInstances(); for (size_t instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) { CommandAdjustCollider* command = static_cast(m_commandGroup.GetCommand(instanceIndex)); // ColliderConfiguration if (serializeContext->CanDowncast(classData->m_typeId, azrtti_typeid(), classData->m_azRtti, nullptr)) { const Physics::ColliderConfiguration* colliderConfig = static_cast(parentDataNode->GetInstance(instanceIndex)); if (elementData->m_nameCrc == AZ_CRC("CollisionLayer", 0x39931633)) { command->SetCollisionLayer(colliderConfig->m_collisionLayer); } if (elementData->m_nameCrc == AZ_CRC("CollisionGroupId", 0x84fe4bbe)) { command->SetCollisionGroupId(colliderConfig->m_collisionGroupId); } if (elementData->m_nameCrc == AZ_CRC("Trigger", 0x1a6b0f5d)) { command->SetIsTrigger(colliderConfig->m_isTrigger); } if (elementData->m_nameCrc == AZ_CRC("Position", 0x462ce4f5)) { command->SetPosition(colliderConfig->m_position); } if (elementData->m_nameCrc == AZ_CRC("Rotation", 0x297c98f1)) { command->SetRotation(colliderConfig->m_rotation); } if (elementData->m_nameCrc == AZ_CRC("MaterialSelection", 0xfebd6d15)) { command->SetMaterial(colliderConfig->m_materialSelection); } if (elementData->m_nameCrc == AZ_CRC("ColliderTag", 0x5e2963ad)) { command->SetTag(colliderConfig->m_tag); CommandSimulatedObjectHelpers::ReplaceTag(actor, colliderType, /*oldTag=*/command->GetOldTag().value(), /*newTag=*/colliderConfig->m_tag, m_commandGroup); } } // Box else if (serializeContext->CanDowncast(classData->m_typeId, azrtti_typeid(), classData->m_azRtti, nullptr)) { const Physics::BoxShapeConfiguration* boxShapeConfig = static_cast(parentDataNode->GetInstance(instanceIndex)); if (elementData->m_nameCrc == AZ_CRC("Configuration", 0xa5e2a5d7)) { command->SetDimensions(boxShapeConfig->m_dimensions); } } // Capsule else if (serializeContext->CanDowncast(classData->m_typeId, azrtti_typeid(), classData->m_azRtti, nullptr)) { const Physics::CapsuleShapeConfiguration* capsuleShapeConfig = static_cast(parentDataNode->GetInstance(instanceIndex)); if (elementData->m_nameCrc == AZ_CRC("Radius", 0x3b7c6e5a)) { command->SetRadius(capsuleShapeConfig->m_radius); } if (elementData->m_nameCrc == AZ_CRC("Height", 0xf54de50f)) { command->SetHeight(capsuleShapeConfig->m_height); } } // Sphere else if (serializeContext->CanDowncast(classData->m_typeId, azrtti_typeid(), classData->m_azRtti, nullptr)) { const Physics::SphereShapeConfiguration* sphereShapeConfig = static_cast(parentDataNode->GetInstance(instanceIndex)); if (elementData->m_nameCrc == AZ_CRC("Radius", 0x3b7c6e5a)) { command->SetRadius(sphereShapeConfig->m_radius); } } } AZStd::string result; CommandSystem::GetCommandManager()->ExecuteCommandGroup(m_commandGroup, result); m_commandGroup.Clear(); } /////////////////////////////////////////////////////////////////////////// ColliderWidget::ColliderWidget(QIcon* icon, QWidget* parent, AZ::SerializeContext* serializeContext) : AzQtComponents::Card(parent) , m_propertyNotify(AZStd::make_unique(this)) , m_icon(icon) { m_editor = new EMotionFX::ObjectEditor(serializeContext, m_propertyNotify.get(), this); setContentWidget(m_editor); setExpanded(true); connect(this, &AzQtComponents::Card::contextMenuRequested, this, &ColliderWidget::OnCardContextMenu); } void ColliderWidget::Update(Actor* actor, Node* joint, size_t colliderIndex, PhysicsSetup::ColliderConfigType colliderType, const Physics::ShapeConfigurationPair& collider) { m_actor = actor; m_joint = joint; m_colliderIndex = colliderIndex; m_colliderType = colliderType; if (!collider.first || !collider.second) { m_editor->ClearInstances(true); m_collider = Physics::ShapeConfigurationPair(); return; } if (collider == m_collider) { m_editor->InvalidateAll(); return; } const AZ::TypeId& shapeType = collider.second->RTTI_GetType(); m_editor->ClearInstances(false); m_editor->AddInstance(collider.second.get(), shapeType); m_editor->AddInstance(collider.first.get(), collider.first->RTTI_GetType()); m_collider = collider; AzQtComponents::CardHeader* cardHeader = header(); cardHeader->setIcon(*m_icon); if (shapeType == azrtti_typeid()) { setTitle("Capsule"); } else if (shapeType == azrtti_typeid()) { setTitle("Sphere"); } else if (shapeType == azrtti_typeid()) { setTitle("Box"); } else { setTitle("Unknown"); } setProperty("colliderIndex", static_cast(colliderIndex)); setExpanded(true); } void ColliderWidget::Update() { if (!m_actor || !m_joint) { return; } m_editor->InvalidateValues(); } void ColliderWidget::OnCardContextMenu(const QPoint& position) { const AzQtComponents::Card* card = static_cast(sender()); const int colliderIndex = card->property("colliderIndex").toInt(); QMenu* contextMenu = new QMenu(this); contextMenu->setObjectName("EMFX.ColliderContainerWidget.ContextMenu"); QAction* deleteAction = contextMenu->addAction("Delete collider"); deleteAction->setObjectName("EMFX.ColliderContainerWidget.DeleteColliderAction"); deleteAction->setProperty("colliderIndex", colliderIndex); connect(deleteAction, &QAction::triggered, this, &ColliderWidget::OnRemoveCollider); QObject::connect(contextMenu, &QMenu::triggered, contextMenu, &QObject::deleteLater); if (!contextMenu->isEmpty()) { contextMenu->popup(position); } } void ColliderWidget::OnRemoveCollider() { QAction* action = static_cast(sender()); const int colliderIndex = action->property("colliderIndex").toInt(); emit RemoveCollider(colliderIndex); } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// AddColliderButton::AddColliderButton(const QString& text, QWidget* parent, const AZStd::vector& supportedColliderTypes) : QPushButton(text, parent) , m_supportedColliderTypes(supportedColliderTypes) { setIcon(MysticQt::GetMysticQt()->FindIcon("Images/Icons/ArrowDownGray.png")); connect(this, &QPushButton::clicked, this, &AddColliderButton::OnCreateContextMenuAddCollider); } void AddColliderButton::OnCreateContextMenuAddCollider() { QMenu* contextMenu = new QMenu(this); contextMenu->setObjectName("EMFX.AddColliderButton.ContextMenu"); AZStd::string actionName; for (const AZ::TypeId& typeId : m_supportedColliderTypes) { actionName = AZStd::string::format("Add %s", GetNameForColliderType(typeId).c_str()); QAction* addBoxAction = contextMenu->addAction(actionName.c_str()); addBoxAction->setProperty("typeId", typeId.ToString().c_str()); connect(addBoxAction, &QAction::triggered, this, &AddColliderButton::OnAddColliderActionTriggered); } contextMenu->setFixedWidth(width()); if (!contextMenu->isEmpty()) { contextMenu->popup(mapToGlobal(QPoint(0, height()))); } connect(contextMenu, &QMenu::triggered, contextMenu, &QMenu::deleteLater); } void AddColliderButton::OnAddColliderActionTriggered(bool checked) { QAction* action = static_cast(sender()); const QByteArray typeString = action->property("typeId").toString().toUtf8(); const AZ::TypeId& typeId = AZ::TypeId::CreateString(typeString.data(), typeString.size()); emit AddCollider(typeId); } AZStd::string AddColliderButton::GetNameForColliderType(AZ::TypeId colliderType) const { if (colliderType == azrtti_typeid()) { return "box"; } else if (colliderType == azrtti_typeid()) { return "capsule"; } else if (colliderType == azrtti_typeid()) { return "sphere"; } return colliderType.ToString(); } /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // Align the layout spacing with the entity inspector. int ColliderContainerWidget::s_layoutSpacing = 13; ColliderContainerWidget::ColliderContainerWidget(const QIcon& colliderIcon, QWidget* parent) : QWidget(parent) , m_colliderIcon(colliderIcon) { m_layout = new QVBoxLayout(); m_layout->setAlignment(Qt::AlignTop); m_layout->setMargin(0); m_layout->setSpacing(s_layoutSpacing); setLayout(m_layout); m_commandCallback = new ColliderEditedCallback(this, /*executePreUndo*/false); CommandSystem::GetCommandManager()->RegisterCommandCallback(CommandAdjustCollider::s_commandName, m_commandCallback); } ColliderContainerWidget::~ColliderContainerWidget() { CommandSystem::GetCommandManager()->RemoveCommandCallback(m_commandCallback, /*delFromMem=*/true); } void ColliderContainerWidget::Update(Actor* actor, Node* joint, PhysicsSetup::ColliderConfigType colliderType, const Physics::ShapeConfigurationList& colliders, AZ::SerializeContext* serializeContext) { m_actor = actor; m_joint = joint; m_colliderType = colliderType; const size_t numColliders = colliders.size(); size_t numAvailableColliderWidgets = m_colliderWidgets.size(); // Create new collider widgets in case we don't have enough. if (numColliders > numAvailableColliderWidgets) { const int numWidgetsToCreate = static_cast(numColliders) - static_cast(numAvailableColliderWidgets); for (int i = 0; i < numWidgetsToCreate; ++i) { ColliderWidget* colliderWidget = new ColliderWidget(&m_colliderIcon, this, serializeContext); connect(colliderWidget, &ColliderWidget::RemoveCollider, this, &ColliderContainerWidget::OnRemoveCollider); m_colliderWidgets.emplace_back(colliderWidget); m_layout->addWidget(colliderWidget, 0, Qt::AlignTop); } numAvailableColliderWidgets = m_colliderWidgets.size(); } AZ_Assert(numAvailableColliderWidgets >= numColliders, "Not enough collider widgets available. Something went one with creating new ones."); for (size_t i = 0; i < numColliders; ++i) { ColliderWidget* colliderWidget = m_colliderWidgets[i]; colliderWidget->Update(m_actor, m_joint, i, m_colliderType, colliders[i]); colliderWidget->show(); } // Hide the collider widgets that are too much for the current node. for (size_t i = numColliders; i < numAvailableColliderWidgets; ++i) { m_colliderWidgets[i]->hide(); m_colliderWidgets[i]->Update(nullptr, nullptr, MCORE_INVALIDINDEX32, PhysicsSetup::ColliderConfigType::Unknown, Physics::ShapeConfigurationPair()); } } void ColliderContainerWidget::Update() { for (ColliderWidget* colliderWidget : m_colliderWidgets) { colliderWidget->Update(); } } void ColliderContainerWidget::Reset() { Update(nullptr, nullptr, PhysicsSetup::ColliderConfigType::Unknown, Physics::ShapeConfigurationList(), nullptr); } void ColliderContainerWidget::RenderColliders(const Physics::ShapeConfigurationList& colliders, const ActorInstance* actorInstance, const Node* node, EMStudio::EMStudioPlugin::RenderInfo* renderInfo, const MCore::RGBAColor& colliderColor) { const AZ::u32 nodeIndex = node->GetNodeIndex(); MCommon::RenderUtil* renderUtil = renderInfo->mRenderUtil; for (const auto& collider : colliders) { #ifndef EMFX_SCALE_DISABLED const AZ::Vector3& worldScale = actorInstance->GetTransformData()->GetCurrentPose()->GetModelSpaceTransform(nodeIndex).mScale; #else const AZ::Vector3 worldScale = AZ::Vector3::CreateOne(); #endif const Transform colliderOffsetTransform(collider.first->m_position, collider.first->m_rotation); const Transform& actorInstanceGlobalTransform = actorInstance->GetWorldSpaceTransform(); const Transform& emfxNodeGlobalTransform = actorInstance->GetTransformData()->GetCurrentPose()->GetModelSpaceTransform(nodeIndex); const Transform emfxColliderGlobalTransformNoScale = colliderOffsetTransform * emfxNodeGlobalTransform * actorInstanceGlobalTransform; const AZ::TypeId colliderType = collider.second->RTTI_GetType(); if (colliderType == azrtti_typeid()) { Physics::SphereShapeConfiguration* sphere = static_cast(collider.second.get()); // LY Physics scaling rules: The maximum component from the node scale will be multiplied by the radius of the sphere. const float radius = sphere->m_radius * MCore::Max3(static_cast(worldScale.GetX()), static_cast(worldScale.GetY()), static_cast(worldScale.GetZ())); renderUtil->RenderWireframeSphere(radius, emfxColliderGlobalTransformNoScale.ToAZTransform(), colliderColor); } else if (colliderType == azrtti_typeid()) { Physics::CapsuleShapeConfiguration* capsule = static_cast(collider.second.get()); // LY Physics scaling rules: The maximum of the X/Y scale components of the node scale will be multiplied by the radius of the capsule. The Z component of the entity scale will be multiplied by the height of the capsule. const float radius = capsule->m_radius * MCore::Max(static_cast(worldScale.GetX()), static_cast(worldScale.GetY())); const float height = capsule->m_height * static_cast(worldScale.GetZ()); renderUtil->RenderWireframeCapsule(radius, height, emfxColliderGlobalTransformNoScale.ToAZTransform(), colliderColor); } else if (colliderType == azrtti_typeid()) { Physics::BoxShapeConfiguration* box = static_cast(collider.second.get()); // LY Physics scaling rules: Each component of the box dimensions will be scaled by the node's world scale. AZ::Vector3 dimensions = box->m_dimensions; dimensions *= worldScale; renderUtil->RenderWireframeBox(dimensions, emfxColliderGlobalTransformNoScale.ToAZTransform(), colliderColor); } } } void ColliderContainerWidget::RenderColliders(PhysicsSetup::ColliderConfigType colliderConfigType, const MCore::RGBAColor& defaultColor, const MCore::RGBAColor& selectedColor, EMStudio::RenderPlugin* renderPlugin, EMStudio::EMStudioPlugin::RenderInfo* renderInfo) { if (colliderConfigType == PhysicsSetup::Unknown || !renderPlugin || !renderInfo) { return; } MCommon::RenderUtil* renderUtil = renderInfo->mRenderUtil; const bool oldLightingEnabled = renderUtil->GetLightingEnabled(); renderUtil->EnableLighting(false); const AZStd::unordered_set& selectedJointIndices = EMStudio::GetManager()->GetSelectedJointIndices(); const ActorManager* actorManager = GetEMotionFX().GetActorManager(); const AZ::u32 actorInstanceCount = actorManager->GetNumActorInstances(); for (AZ::u32 i = 0; i < actorInstanceCount; ++i) { const ActorInstance* actorInstance = actorManager->GetActorInstance(i); const Actor* actor = actorInstance->GetActor(); const AZStd::shared_ptr& physicsSetup = actor->GetPhysicsSetup(); const Physics::CharacterColliderConfiguration* colliderConfig = physicsSetup->GetColliderConfigByType(colliderConfigType); if (colliderConfig) { for (const Physics::CharacterColliderNodeConfiguration& nodeConfig : colliderConfig->m_nodes) { const Node* joint = actor->GetSkeleton()->FindNodeByName(nodeConfig.m_name.c_str()); if (joint) { const bool jointSelected = selectedJointIndices.empty() || selectedJointIndices.find(joint->GetNodeIndex()) != selectedJointIndices.end(); const Physics::ShapeConfigurationList& colliders = nodeConfig.m_shapes; RenderColliders(colliders, actorInstance, joint, renderInfo, jointSelected ? selectedColor : defaultColor); } } } } renderUtil->RenderLines(); renderUtil->EnableLighting(oldLightingEnabled); } void ColliderContainerWidget::OnRemoveCollider(int colliderIndex) { // Forward the signals from the collider widgets. emit RemoveCollider(colliderIndex); } /////////////////////////////////////////////////////////////////////////// ColliderContainerWidget::ColliderEditedCallback::ColliderEditedCallback(ColliderContainerWidget* parent, bool executePreUndo, bool executePreCommand) : MCore::Command::Callback(executePreUndo, executePreCommand) , m_widget(parent) { } bool ColliderContainerWidget::ColliderEditedCallback::Execute(MCore::Command* command, const MCore::CommandLine& commandLine) { AZ_UNUSED(command); AZ_UNUSED(commandLine); m_widget->Update(); return true; } bool ColliderContainerWidget::ColliderEditedCallback::Undo(MCore::Command* command, const MCore::CommandLine& commandLine) { AZ_UNUSED(command); AZ_UNUSED(commandLine); m_widget->Update(); return true; } } // namespace EMotionFX