Initial commit

This commit is contained in:
alexpete
2021-03-05 11:26:34 -08:00
commit a10351f38d
27091 changed files with 5521199 additions and 0 deletions
@@ -0,0 +1,681 @@
/*
* 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 <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzQtComponents/Components/Widgets/Card.h>
#include <AzQtComponents/Components/Widgets/CardNotification.h>
#include <AzToolsFramework/UI/PropertyEditor/PropertyEditorAPI.h>
#include <EMotionFX/CommandSystem/Source/CommandManager.h>
#include <EMotionFX/CommandSystem/Source/SimulatedObjectCommands.h>
#include <EMotionFX/Source/Actor.h>
#include <EMotionFX/Source/Node.h>
#include <EMotionFX/Source/SimulatedObjectSetup.h>
#include <Editor/ColliderContainerWidget.h>
#include <Editor/ColliderHelpers.h>
#include <Editor/ObjectEditor.h>
#include <Editor/NotificationWidget.h>
#include <Editor/Plugins/SimulatedObject/SimulatedJointWidget.h>
#include <Editor/Plugins/SimulatedObject/SimulatedObjectColliderWidget.h>
#include <Editor/Plugins/SimulatedObject/SimulatedObjectWidget.h>
#include <Editor/SimulatedObjectHelpers.h>
#include <Editor/SkeletonModel.h>
#include <MCore/Source/StringConversions.h>
#include <MysticQt/Source/MysticQtManager.h>
#include <QGridLayout>
#include <QHBoxLayout>
#include <QItemSelectionModel>
#include <QLabel>
#include <QMessageBox>
#include <QModelIndex>
#include <QPushButton>
#include <QVBoxLayout>
#include <QVariant>
namespace EMotionFX
{
class SimulatedObjectPropertyNotify
: public AzToolsFramework::IPropertyEditorNotify
{
public:
// this function gets called each time you are about to actually modify
// a property (not when the editor opens)
void BeforePropertyModified(AzToolsFramework::InstanceDataNode* pNode) override;
// this function gets called each time a property is actually modified
// (not just when the editor appears), for each and every change - so
// for example, as a slider moves. its meant for undo state capture.
void AfterPropertyModified(AzToolsFramework::InstanceDataNode* /*pNode*/) override {}
// this funciton is called when some stateful operation begins, such as
// dragging starts in the world editor or such in which case you don't
// want to blow away the tree and rebuild it until editing is complete
// since doing so is flickery and intensive.
void SetPropertyEditingActive(AzToolsFramework::InstanceDataNode* /*pNode*/) override {}
void SetPropertyEditingComplete(AzToolsFramework::InstanceDataNode* pNode) override;
// this will cause the current undo operation to complete, sealing it
// and beginning a new one if there are further edits.
void SealUndoStack() override {}
private:
MCore::CommandGroup m_commandGroup;
};
void SimulatedObjectPropertyNotify::BeforePropertyModified(AzToolsFramework::InstanceDataNode* pNode)
{
if (!m_commandGroup.IsEmpty())
{
return;
}
const AzToolsFramework::InstanceDataNode* parent = pNode->GetParent();
if (parent && parent->GetSerializeContext()->CanDowncast(parent->GetClassMetadata()->m_typeId, azrtti_typeid<EMotionFX::SimulatedObject>(), parent->GetClassMetadata()->m_azRtti, nullptr))
{
const size_t instanceCount = pNode->GetNumInstances();
m_commandGroup.SetGroupName(AZStd::string::format("Adjust simulated object%s", instanceCount > 1 ? "s" : ""));
for (size_t instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex)
{
const SimulatedObject* simulatedObject = static_cast<SimulatedObject*>(parent->GetInstance(instanceIndex));
const SimulatedObjectSetup* simulatedObjectSetup = simulatedObject->GetSimulatedObjectSetup();
const AZ::u32 actorId = simulatedObjectSetup->GetActor()->GetID();
const size_t objectIndex = simulatedObjectSetup->FindSimulatedObjectIndex(simulatedObject).GetValue();
CommandAdjustSimulatedObject* command = aznew CommandAdjustSimulatedObject(actorId, objectIndex);
m_commandGroup.AddCommand(command);
const void* instance = pNode->GetInstance(instanceIndex);
const AZ::SerializeContext::ClassElement* elementData = pNode->GetElementMetadata();
if (elementData->m_nameCrc == AZ_CRC("objectName", 0xd403db79))
{
command->SetOldObjectName(*static_cast<const AZStd::string*>(instance));
}
else if (elementData->m_nameCrc == AZ_CRC("gravityFactor", 0x23584906))
{
command->SetOldGravityFactor(*static_cast<const float*>(instance));
}
else if (elementData->m_nameCrc == AZ_CRC("stiffnessFactor", 0xbf262b07))
{
command->SetOldStiffnessFactor(*static_cast<const float*>(instance));
}
else if (elementData->m_nameCrc == AZ_CRC("dampingFactor", 0x388a3234))
{
command->SetOldDampingFactor(*static_cast<const float*>(instance));
}
else if (elementData->m_nameCrc == AZ_CRC("colliderTags", 0xb337393d))
{
command->SetOldColliderTags(*static_cast<const AZStd::vector<AZStd::string>*>(instance));
}
}
}
else if (parent && parent->GetSerializeContext()->CanDowncast(parent->GetClassMetadata()->m_typeId, azrtti_typeid<EMotionFX::SimulatedJoint>(), parent->GetClassMetadata()->m_azRtti, nullptr))
{
const size_t instanceCount = pNode->GetNumInstances();
m_commandGroup.SetGroupName(AZStd::string::format("Adjust simulated joint%s", instanceCount > 1 ? "s" : ""));
for (size_t instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex)
{
const SimulatedJoint* simulatedJoint = static_cast<SimulatedJoint*>(parent->GetInstance(instanceIndex));
const SimulatedObject* simulatedObject = simulatedJoint->GetSimulatedObject();
const SimulatedObjectSetup* simulatedObjectSetup = simulatedObject->GetSimulatedObjectSetup();
const AZ::u32 actorId = simulatedObjectSetup->GetActor()->GetID();
const size_t objectIndex = simulatedObjectSetup->FindSimulatedObjectIndex(simulatedObject).GetValue();
const size_t jointIndex = simulatedJoint->CalculateSimulatedJointIndex().GetValue();
CommandAdjustSimulatedJoint* command = aznew CommandAdjustSimulatedJoint(actorId, objectIndex, jointIndex);
m_commandGroup.AddCommand(command);
const void* instance = pNode->GetInstance(instanceIndex);
const AZ::SerializeContext::ClassElement* elementData = pNode->GetElementMetadata();
if (elementData->m_nameCrc == AZ_CRC("coneAngleLimit", 0x355562ed))
{
command->SetOldConeAngleLimit(*static_cast<const float*>(instance));
}
else if (elementData->m_nameCrc == AZ_CRC("mass", 0x6c035b66))
{
command->SetOldMass(*static_cast<const float*>(instance));
}
else if (elementData->m_nameCrc == AZ_CRC("stiffness", 0x89379000))
{
command->SetOldStiffness(*static_cast<const float*>(instance));
}
else if (elementData->m_nameCrc == AZ_CRC("damping", 0x440e3a6a))
{
command->SetOldDamping(*static_cast<const float*>(instance));
}
else if (elementData->m_nameCrc == AZ_CRC("gravityFactor", 0x23584906))
{
command->SetOldGravityFactor(*static_cast<const float*>(instance));
}
else if (elementData->m_nameCrc == AZ_CRC("friction", 0x120c180b))
{
command->SetOldFriction(*static_cast<const float*>(instance));
}
else if (elementData->m_nameCrc == AZ_CRC("pinned", 0xe527e5e7))
{
command->SetOldPinned(*static_cast<const bool*>(instance));
}
else if (elementData->m_nameCrc == AZ_CRC("colliderExclusionTags", 0xdbaea6e9))
{
command->SetOldColliderExclusionTags(*static_cast<const AZStd::vector<AZStd::string>*>(instance));
}
else if (elementData->m_nameCrc == AZ_CRC("autoExcludeMode", 0x8e8f8066))
{
command->SetOldAutoExcludeMode(*static_cast<const SimulatedJoint::AutoExcludeMode*>(instance));
}
else if (elementData->m_nameCrc == AZ_CRC("autoExcludeGeometric", 0x1aa4f9b6))
{
command->SetOldGeometricAutoExclusion(*static_cast<const bool*>(instance));
}
}
}
}
void SimulatedObjectPropertyNotify::SetPropertyEditingComplete(AzToolsFramework::InstanceDataNode* pNode)
{
const AzToolsFramework::InstanceDataNode* parent = pNode->GetParent();
if (!m_commandGroup.IsEmpty() && parent && parent->GetSerializeContext()->CanDowncast(parent->GetClassMetadata()->m_typeId, azrtti_typeid<EMotionFX::SimulatedObject>(), parent->GetClassMetadata()->m_azRtti, nullptr))
{
const size_t instanceCount = pNode->GetNumInstances();
for (size_t instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex)
{
CommandAdjustSimulatedObject* command = static_cast<CommandAdjustSimulatedObject*>(m_commandGroup.GetCommand(instanceIndex));
const void* instance = pNode->GetInstance(instanceIndex);
const AZ::SerializeContext::ClassElement* elementData = pNode->GetElementMetadata();
if (elementData->m_nameCrc == AZ_CRC("objectName", 0xd403db79))
{
command->SetObjectName(*static_cast<const AZStd::string*>(instance));
}
else if (elementData->m_nameCrc == AZ_CRC("gravityFactor", 0x23584906))
{
command->SetGravityFactor(*static_cast<const float*>(instance));
}
else if (elementData->m_nameCrc == AZ_CRC("stiffnessFactor", 0xbf262b07))
{
command->SetStiffnessFactor(*static_cast<const float*>(instance));
}
else if (elementData->m_nameCrc == AZ_CRC("dampingFactor", 0x388a3234))
{
command->SetDampingFactor(*static_cast<const float*>(instance));
}
else if (elementData->m_nameCrc == AZ_CRC("colliderTags", 0xb337393d))
{
AZStd::string commandString;
const EMotionFX::SimulatedObject* simulatedObject = static_cast<const EMotionFX::SimulatedObject*>(parent->GetInstance(instanceIndex));
const AZStd::vector<AZStd::string>& colliderTags = simulatedObject->GetColliderTags();
AZStd::vector<AZStd::string> colliderExclusionTags;
const AZStd::vector<SimulatedJoint*>& simulatedJoints = simulatedObject->GetSimulatedJoints();
for (const SimulatedJoint* simulatedJoint : simulatedJoints)
{
// Copy the current exclusion tags to a temporary buffer.
colliderExclusionTags = simulatedJoint->GetColliderExclusionTags();
// Remove all tags that are no longer part of the collider tags of the simulated object.
bool changed = false;
colliderExclusionTags.erase(AZStd::remove_if(colliderExclusionTags.begin(), colliderExclusionTags.end(),
[&colliderTags, &changed](const AZStd::string& tag)->bool
{
if (AZStd::find(colliderTags.begin(), colliderTags.end(), tag) == colliderTags.end())
{
// The exclusion tag is not part of the collider tags in the simulated object.
// Remove the tag from the exclusion tags.
changed = true;
return true;
}
return false;
}),
colliderExclusionTags.end());
if (changed)
{
const SimulatedObjectSetup* simulatedObjectSetup = simulatedObject->GetSimulatedObjectSetup();
const AZ::u32 actorId = simulatedObjectSetup->GetActor()->GetID();
const size_t objectIndex = simulatedObjectSetup->FindSimulatedObjectIndex(simulatedObject).GetValue();
const size_t jointIndex = simulatedJoint->CalculateSimulatedJointIndex().GetValue();
const AZStd::string colliderExclusionTagString = MCore::ConstructStringSeparatedBySemicolons(colliderExclusionTags);
commandString = AZStd::string::format("%s -%s %d -%s %zu -%s %zu -%s \"%s\"",
CommandAdjustSimulatedJoint::s_commandName,
CommandAdjustSimulatedJoint::s_actorIdParameterName, actorId,
CommandAdjustSimulatedJoint::s_objectIndexParameterName, objectIndex,
CommandAdjustSimulatedJoint::s_jointIndexParameterName, jointIndex,
CommandAdjustSimulatedJoint::s_colliderExclusionTagsParameterName, colliderExclusionTagString.c_str());
m_commandGroup.AddCommandString(commandString);
}
}
command->SetColliderTags(*static_cast<const AZStd::vector<AZStd::string>*>(instance));
}
}
}
else if (!m_commandGroup.IsEmpty() && parent && parent->GetSerializeContext()->CanDowncast(parent->GetClassMetadata()->m_typeId, azrtti_typeid<EMotionFX::SimulatedJoint>(), parent->GetClassMetadata()->m_azRtti, nullptr))
{
const size_t instanceCount = pNode->GetNumInstances();
for (size_t instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex)
{
CommandAdjustSimulatedJoint* command = static_cast<CommandAdjustSimulatedJoint*>(m_commandGroup.GetCommand(instanceIndex));
const void* instance = pNode->GetInstance(instanceIndex);
const AZ::SerializeContext::ClassElement* elementData = pNode->GetElementMetadata();
if (elementData->m_nameCrc == AZ_CRC("coneAngleLimit", 0x355562ed))
{
command->SetConeAngleLimit(*static_cast<const float*>(instance));
}
else if (elementData->m_nameCrc == AZ_CRC("mass", 0x6c035b66))
{
command->SetMass(*static_cast<const float*>(instance));
}
else if (elementData->m_nameCrc == AZ_CRC("stiffness", 0x89379000))
{
command->SetStiffness(*static_cast<const float*>(instance));
}
else if (elementData->m_nameCrc == AZ_CRC("damping", 0x440e3a6a))
{
command->SetDamping(*static_cast<const float*>(instance));
}
else if (elementData->m_nameCrc == AZ_CRC("gravityFactor", 0x23584906))
{
command->SetGravityFactor(*static_cast<const float*>(instance));
}
else if (elementData->m_nameCrc == AZ_CRC("friction", 0x120c180b))
{
command->SetFriction(*static_cast<const float*>(instance));
}
else if (elementData->m_nameCrc == AZ_CRC("pinned", 0xe527e5e7))
{
command->SetPinned(*static_cast<const bool*>(instance));
}
else if (elementData->m_nameCrc == AZ_CRC("colliderExclusionTags", 0xdbaea6e9))
{
command->SetColliderExclusionTags(*static_cast<const AZStd::vector<AZStd::string>*>(instance));
}
else if (elementData->m_nameCrc == AZ_CRC("autoExcludeMode", 0x8e8f8066))
{
command->SetAutoExcludeMode(*static_cast<const SimulatedJoint::AutoExcludeMode*>(instance));
}
else if (elementData->m_nameCrc == AZ_CRC("autoExcludeGeometric", 0x1aa4f9b6))
{
command->SetGeometricAutoExclusion(*static_cast<const bool*>(instance));
}
}
}
AZStd::string result;
CommandSystem::GetCommandManager()->ExecuteCommandGroup(m_commandGroup, result);
m_commandGroup.Clear();
}
///////////////////////////////////////////////////////////////////////////
const int SimulatedJointWidget::s_jointLabelSpacing = 17;
const int SimulatedJointWidget::s_jointNameSpacing = 90;
SimulatedJointWidget::SimulatedJointWidget(SimulatedObjectWidget* plugin, QWidget* parent)
: QScrollArea(parent)
, m_plugin(plugin)
, m_contentsWidget(new QWidget(this))
, m_removeButton(new QPushButton("Remove from simulated object", this))
, m_backButton(new QPushButton("Back to simulated object", this))
, m_simulatedObjectEditorCard(new AzQtComponents::Card(this))
, m_simulatedJointEditorCard(new AzQtComponents::Card(this))
, m_nameLeftLabel(new QLabel(this))
, m_nameRightLabel(new QLabel(this))
, m_propertyNotify(AZStd::make_unique<SimulatedObjectPropertyNotify>())
{
connect(m_removeButton, &QPushButton::clicked, this, &SimulatedJointWidget::RemoveSelectedSimulatedJoint);
connect(m_backButton, &QPushButton::clicked, this, &SimulatedJointWidget::BackToSimulatedObject);
AZ::SerializeContext* serializeContext = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext);
AZ_Error("EMotionFX", serializeContext, "Can't get serialize context from component application.");
// Setup the object editor.
{
m_simulatedObjectEditor = new ObjectEditor(serializeContext, m_propertyNotify.get());
QWidget* objectCardContents = new QWidget(this);
QVBoxLayout* objectCardLayout = new QVBoxLayout(objectCardContents);
objectCardLayout->addWidget(m_simulatedObjectEditor);
m_simulatedObjectNotification1 = new NotificationWidget(m_simulatedObjectEditorCard, "To add a joint to this simulated object, right click a joint in the outliner, choose Add to Simulated Object, and select this object.");
m_simulatedObjectNotification2 = new NotificationWidget(m_simulatedObjectEditorCard, "There are no simulated object colliders. To add a collider, right click a joint in the outliner, choose Add Collider, and select a primitive shape. Simulated objects will collide with the primitive shape.");
objectCardLayout->addWidget(m_simulatedObjectNotification1);
objectCardLayout->addWidget(m_simulatedObjectNotification2);
m_simulatedObjectNotification1->hide();
m_simulatedObjectNotification2->hide();
m_simulatedObjectEditorCard->setContentWidget(objectCardContents);
}
// Setup the joint editor.
{
m_simulatedJointEditor = new ObjectEditor(serializeContext, m_propertyNotify.get());
m_simulatedJointEditor->setObjectName("EMFX.SimulatedJointWidget.SimulatedJointEditor");
NotificationWidget* notif = new NotificationWidget(m_simulatedJointEditorCard, "To have the selected joints to collider against other primitive shape, set up 'collide with' setting in their Simulated Object.");
notif->addFeature(m_backButton);
QWidget* jointCardContents = new QWidget(this);
QVBoxLayout* jointCardLayout = new QVBoxLayout(jointCardContents);
jointCardLayout->addWidget(m_simulatedJointEditor);
jointCardLayout->addWidget(notif);
m_simulatedJointEditorCard->setContentWidget(jointCardContents);
}
m_colliderWidget = new QWidget();
QVBoxLayout* colliderWidgetLayout = new QVBoxLayout(m_colliderWidget);
if (ColliderHelpers::AreCollidersReflected())
{
SimulatedObjectColliderWidget* colliderWidget = new SimulatedObjectColliderWidget();
colliderWidget->setObjectName("EMFX.SimulatedJointWidget.SimulatedObjectColliderWidget");
colliderWidget->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
colliderWidget->CreateGUI();
colliderWidgetLayout->addWidget(colliderWidget);
}
else
{
QLabel* noColliders = new QLabel(
"To adjust the properties of the Simulated Object Colliders, "
"enable the PhysX gem via the Project Configurator");
colliderWidgetLayout->addWidget(noColliders);
}
SkeletonModel* skeletonModel = nullptr;
SkeletonOutlinerRequestBus::BroadcastResult(skeletonModel, &SkeletonOutlinerRequests::GetModel);
if (skeletonModel)
{
connect(&skeletonModel->GetSelectionModel(), &QItemSelectionModel::selectionChanged, this, &SimulatedJointWidget::OnSkeletonOutlinerSelectionChanged);
}
// Add the name label
QWidget* nameWidget = new QWidget();
QBoxLayout* nameLayout = new QHBoxLayout(nameWidget);
m_nameLeftLabel->setStyleSheet("font-weight: bold;");
nameLayout->addWidget(m_nameLeftLabel);
nameLayout->addWidget(m_nameRightLabel);
nameLayout->setStretchFactor(m_nameLeftLabel, 3);
nameLayout->setStretchFactor(m_nameRightLabel, 2);
// Contents widget
m_contentsWidget->setVisible(false);
QVBoxLayout* contentsLayout = new QVBoxLayout(m_contentsWidget);
contentsLayout->setSpacing(ColliderContainerWidget::s_layoutSpacing);
contentsLayout->addWidget(nameWidget);
contentsLayout->addWidget(m_removeButton);
contentsLayout->addWidget(m_simulatedObjectEditorCard);
contentsLayout->addWidget(m_simulatedJointEditorCard);
QWidget* scrolledWidget = new QWidget();
QVBoxLayout* mainLayout = new QVBoxLayout(scrolledWidget);
mainLayout->setAlignment(Qt::AlignTop);
mainLayout->setMargin(0);
mainLayout->addWidget(m_contentsWidget);
mainLayout->addWidget(m_colliderWidget);
setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
setWidget(scrolledWidget);
setWidgetResizable(true);
SimulatedObjectModel* model = m_plugin->GetSimulatedObjectModel();
connect(model->GetSelectionModel(), &QItemSelectionModel::selectionChanged, this, &SimulatedJointWidget::UpdateDetailsView);
connect(model, &QAbstractItemModel::dataChanged, this, &SimulatedJointWidget::UpdateObjectNotification);
connect(model, &QAbstractItemModel::dataChanged, m_simulatedObjectEditor, &ObjectEditor::InvalidateValues);
connect(model, &QAbstractItemModel::dataChanged, m_simulatedJointEditor, &ObjectEditor::InvalidateValues);
}
SimulatedJointWidget::~SimulatedJointWidget() = default;
void SimulatedJointWidget::UpdateDetailsView(const QItemSelection& selected, const QItemSelection& deselected)
{
AZ_UNUSED(selected)
AZ_UNUSED(deselected)
const SimulatedObjectModel* model = m_plugin->GetSimulatedObjectModel();
const QItemSelectionModel* selectionModel = model->GetSelectionModel();
const QModelIndexList selectedIndexes = selectionModel->selectedIndexes();
if (selectedIndexes.empty())
{
m_contentsWidget->setVisible(false);
m_simulatedObjectEditor->ClearInstances(true);
m_simulatedJointEditor->ClearInstances(true);
return;
}
m_simulatedObjectEditor->ClearInstances(false);
m_simulatedJointEditor->ClearInstances(false);
QString jointName;
QString objectName;
size_t numSelectedObjects = 0;
size_t numSelectedJoints = 0;
AZStd::unordered_map<AZ::Uuid, AZStd::vector<void*>> typeIdToAggregateInstance;
for (const QModelIndex& modelIndex : selectedIndexes)
{
if (modelIndex.column() != 0)
{
continue;
}
void* object = modelIndex.data(SimulatedObjectModel::ROLE_JOINT_PTR).value<SimulatedJoint*>();
AZ::Uuid typeId = azrtti_typeid<SimulatedJoint>();
ObjectEditor* objectEditor = m_simulatedJointEditor;
if (!object)
{
object = modelIndex.data(SimulatedObjectModel::ROLE_OBJECT_PTR).value<SimulatedObject*>();
typeId = azrtti_typeid<SimulatedObject>();
objectEditor = m_simulatedObjectEditor;
if (objectName.isNull())
{
objectName = modelIndex.data().toString();
}
}
else if (jointName.isNull())
{
jointName = modelIndex.data().toString();
objectName = modelIndex.data(SimulatedObjectModel::ROLE_OBJECT_NAME).value<QString>();
}
if (object)
{
auto foundAggregate = typeIdToAggregateInstance.find(typeId);
if (foundAggregate != typeIdToAggregateInstance.end())
{
objectEditor->AddInstance(object, typeId, foundAggregate->second[0]);
foundAggregate->second.emplace_back(object);
}
else
{
objectEditor->AddInstance(object, typeId);
typeIdToAggregateInstance.emplace(typeId, AZStd::vector<void*> {object});
}
}
}
auto selectedSimulatedObjects = typeIdToAggregateInstance.find(azrtti_typeid<SimulatedObject>());
if (selectedSimulatedObjects != typeIdToAggregateInstance.end())
{
m_simulatedObjectEditorCard->show();
numSelectedObjects = selectedSimulatedObjects->second.size();
if (numSelectedObjects == 1)
{
m_simulatedObjectEditorCard->setTitle("Simulated Object Settings");
}
else
{
m_simulatedObjectEditorCard->setTitle(QString("%1 Simulated Object%2").arg(numSelectedObjects).arg(numSelectedObjects > 1 ? "s" : ""));
}
}
else
{
m_simulatedObjectEditorCard->hide();
}
auto selectedSimulatedJoints = typeIdToAggregateInstance.find(azrtti_typeid<SimulatedJoint>());
if (selectedSimulatedJoints != typeIdToAggregateInstance.end())
{
m_simulatedJointEditorCard->show();
numSelectedJoints = selectedSimulatedJoints->second.size();
if (numSelectedJoints == 1)
{
m_simulatedJointEditorCard->setTitle("Simulated Joint Settings");
}
else
{
m_simulatedJointEditorCard->setTitle(QString("%1 Simulated Joint%2").arg(numSelectedJoints).arg(numSelectedJoints > 1 ? "s" : ""));
}
// We only want to show the button when only joints are selected.
if (numSelectedObjects == 0)
{
m_backButton->show();
m_backButton->setText(QString("Back to '%1'").arg(objectName));
m_removeButton->show();
m_removeButton->setText(QString("Remove from '%1'").arg(objectName));
}
else
{
m_backButton->hide();
m_removeButton->hide();
}
}
else
{
m_simulatedJointEditorCard->hide();
m_backButton->hide();
m_removeButton->hide();
}
// Update the name label
{
QString objectPlural = numSelectedObjects == 1 ? "" : "s";
QString jointPlural = numSelectedJoints == 1 ? "" : "s";
if (numSelectedObjects > 0 && numSelectedJoints > 0)
{
m_nameLeftLabel->setText("Multiple selected");
m_nameRightLabel->setText(QString("%1 object%2, %3 joint%4 selected").arg(numSelectedObjects).arg(objectPlural).arg(numSelectedJoints).arg(jointPlural));
}
else if (numSelectedObjects > 0)
{
m_nameLeftLabel->setText("Object name");
if (numSelectedObjects == 1)
{
m_nameRightLabel->setText(objectName);
}
else
{
m_nameRightLabel->setText(QString("%1 object%2 selected").arg(numSelectedObjects).arg(objectPlural));
}
}
else if (numSelectedJoints > 0)
{
m_nameLeftLabel->setText("Joint name");
if (numSelectedJoints == 1)
{
m_nameRightLabel->setText(jointName);
}
else
{
m_nameRightLabel->setText(QString("%1 joint%2 selected").arg(numSelectedJoints).arg(jointPlural));
}
}
}
UpdateObjectNotification();
m_contentsWidget->setVisible(!selectedIndexes.empty());
// Hide the collider widget as the joint in the Simulated Object widget
// was the last thing selected
m_colliderWidget->hide();
}
void SimulatedJointWidget::UpdateObjectNotification()
{
if (m_simulatedObjectEditorCard->isHidden())
{
return;
}
m_simulatedObjectNotification1->hide();
m_simulatedObjectNotification2->hide();
const SimulatedObjectModel* model = m_plugin->GetSimulatedObjectModel();
const QModelIndexList selectedIndexes = model->GetSelectionModel()->selectedIndexes();
if (selectedIndexes.size() != 1)
{
return;
}
// Add notification when a single object is selected.
SimulatedObject* object = selectedIndexes[0].data(SimulatedObjectModel::ROLE_OBJECT_PTR).value<SimulatedObject*>();
if (!object)
{
return;
}
if (object->GetNumSimulatedJoints() == 0)
{
m_simulatedObjectNotification1->show();
}
if (object->GetColliderTags().empty())
{
m_simulatedObjectNotification2->show();
}
}
void SimulatedJointWidget::OnSkeletonOutlinerSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected)
{
AZ_UNUSED(deselected);
if (!selected.isEmpty())
{
// Show the collider widget as the joint in the skeleton outliner is the last selected.
m_contentsWidget->hide();
m_colliderWidget->show();
}
}
void SimulatedJointWidget::RemoveSelectedSimulatedJoint() const
{
const SimulatedObjectModel* model = m_plugin->GetSimulatedObjectModel();
SimulatedObjectHelpers::RemoveSimulatedJoints(model->GetSelectionModel()->selectedRows(0), false);
}
void SimulatedJointWidget::BackToSimulatedObject()
{
SimulatedObjectModel* model = m_plugin->GetSimulatedObjectModel();
QItemSelectionModel* selectionModel = model->GetSelectionModel();
const QModelIndexList selectedIndexes = selectionModel->selectedIndexes();
if (selectedIndexes.empty())
{
return;
}
// Note: If multiple joints are selected and they are from different objects, select the first.
const size_t objectIndex = selectedIndexes[0].data(SimulatedObjectModel::ROLE_OBJECT_INDEX).value<quint64>();
const QModelIndex modelIndex = model->GetModelIndexByObjectIndex(objectIndex);
selectionModel->select(modelIndex, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
}
} // namespace EMotionFX
@@ -0,0 +1,84 @@
/*
* 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.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/RTTI/TypeInfo.h>
#include <QScrollArea>
#include <QItemSelection>
#endif
QT_FORWARD_DECLARE_CLASS(QLabel)
QT_FORWARD_DECLARE_CLASS(QPushButton)
namespace AzQtComponents
{
class Card;
} // namespace AzQtComponents
namespace EMotionFX
{
class Actor;
class Node;
class ObjectEditor;
class AddToSimulatedObjectButton;
class SimulatedObjectWidget;
class ObjectEditor;
class SimulatedObjectPropertyNotify;
class SimulatedObjectColliderWidget;
class NotificationWidget;
class SimulatedJointWidget
: public QScrollArea
{
Q_OBJECT //AUTOMOC
public:
explicit SimulatedJointWidget(SimulatedObjectWidget* plugin, QWidget* parent = nullptr);
~SimulatedJointWidget() override;
void UpdateDetailsView(const QItemSelection& selected, const QItemSelection& deselected);
void RemoveSelectedSimulatedJoint() const;
void BackToSimulatedObject();
private slots:
void OnSkeletonOutlinerSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected);
private:
void UpdateObjectNotification();
static const int s_jointLabelSpacing;
static const int s_jointNameSpacing;
SimulatedObjectWidget* m_plugin;
QWidget* m_contentsWidget;
QPushButton* m_removeButton;
QPushButton* m_backButton;
ObjectEditor* m_simulatedObjectEditor = nullptr;
ObjectEditor* m_simulatedJointEditor = nullptr;
AzQtComponents::Card* m_simulatedObjectEditorCard;
AzQtComponents::Card* m_simulatedJointEditorCard;
// Simulated Joint/Object name label
QLabel* m_nameLeftLabel = nullptr;
QLabel* m_nameRightLabel = nullptr;
NotificationWidget* m_simulatedObjectNotification1 = nullptr;
NotificationWidget* m_simulatedObjectNotification2 = nullptr;
AZStd::unique_ptr<SimulatedObjectPropertyNotify> m_propertyNotify;
QWidget* m_colliderWidget = nullptr;
};
} // namespace EMotionFX
@@ -0,0 +1,68 @@
/*
* 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 <EMotionFX/Source/Actor.h>
#include <EMotionFX/Source/SimulatedObjectSetup.h>
#include <EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/EMStudioManager.h>
#include <Editor/Plugins/SimulatedObject/SimulatedObjectActionManager.h>
#include <Editor/InputDialogValidatable.h>
#include <Editor/SimulatedObjectHelpers.h>
#include <QWidget>
namespace EMStudio
{
void SimulatedObjectActionManager::OnAddNewObjectAndAddJoints(EMotionFX::Actor* actor, const QModelIndexList& selectedJoints, bool addChildJoints, QWidget* parent)
{
if (!actor)
{
AZ_Error("EMotionFX", false, "Cannot add new simulated object. Actor is not valid.");
return;
}
InputDialogValidatable* inputDialog = new InputDialogValidatable(parent, /*labelText=*/"Name:");
inputDialog->setWindowTitle("New simulated object name");
inputDialog->setMinimumWidth(300);
inputDialog->setObjectName("EMFX.SimulatedObjectActionManager.SimulatedObjectDialog");
inputDialog->SetValidatorFunc([inputDialog, actor]() {
EMotionFX::SimulatedObjectSetup* simulatedObjectSetup = actor->GetSimulatedObjectSetup().get();
if (simulatedObjectSetup)
{
return simulatedObjectSetup->IsSimulatedObjectNameUnique(inputDialog->GetText().toUtf8().data(), /*checkedSimulatedObject=*/nullptr);
}
return false;
});
EMStudio::InputDialogValidatable::connect(inputDialog, &QDialog::finished, [actor, selectedJoints, inputDialog, addChildJoints](int resultCode) {
inputDialog->deleteLater();
if (resultCode == QDialog::Rejected)
{
return;
}
const AZStd::string commadGroupName = AZStd::string::format("Add simulated object%s", selectedJoints.empty() ? "" : " and joints");
MCore::CommandGroup commandGroup(commadGroupName);
EMotionFX::SimulatedObjectHelpers::AddSimulatedObject(actor->GetID(), inputDialog->GetText().toUtf8().data(), &commandGroup);
EMotionFX::SimulatedObjectHelpers::AddSimulatedJoints(selectedJoints, actor->GetSimulatedObjectSetup()->GetNumSimulatedObjects(), addChildJoints, &commandGroup);
AZStd::string result;
if (!EMStudio::GetCommandManager()->ExecuteCommandGroup(commandGroup, result))
{
AZ_Error("EMotionFX", false, result.c_str())
}
});
inputDialog->open();
}
} // namespace EMStudio
@@ -0,0 +1,44 @@
/*
* 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.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <QModelIndexList>
#include <QObject>
#endif
QT_FORWARD_DECLARE_CLASS(QWidget)
namespace EMotionFX
{
class Actor;
}
namespace EMStudio
{
class SimulatedObjectActionManager
: public QObject
{
Q_OBJECT // AUTOMOC
public slots:
/**
* Creates a new simulated object and adds the given joints to it.
* @param actor The actor to create the simulated object for.
* @param selectedJoints Model index list from the skeletal model.
* @param addChildJoints Automatically add all children for all given joints recursively.
* @param parent The parent widget.
*/
void OnAddNewObjectAndAddJoints(EMotionFX::Actor* actor, const QModelIndexList& selectedJoints, bool addChildJoints, QWidget* parent);
};
} // namespace EMStudio
@@ -0,0 +1,419 @@
/*
* 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 <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzFramework/Physics/Character.h>
#include <EMotionFX/Source/Actor.h>
#include <EMotionFX/Source/Node.h>
#include <EMotionFX/CommandSystem/Source/ColliderCommands.h>
#include <Editor/ColliderContainerWidget.h>
#include <Editor/ColliderHelpers.h>
#include <Editor/NotificationWidget.h>
#include <Editor/SimulatedObjectHelpers.h>
#include <Editor/SkeletonModel.h>
#include <Editor/Plugins/SimulatedObject/SimulatedObjectColliderWidget.h>
#include <Editor/Plugins/SkeletonOutliner/SkeletonOutlinerBus.h>
#include <MysticQt/Source/MysticQtManager.h>
#include <QLabel>
#include <QMessageBox>
#include <QHBoxLayout>
#include <QVBoxLayout>
namespace EMotionFX
{
SimulatedObjectColliderWidget::SimulatedObjectColliderWidget(QWidget* parent)
: SkeletonModelJointWidget(parent)
{
}
QWidget* SimulatedObjectColliderWidget::CreateContentWidget(QWidget* parent)
{
QWidget* result = new QWidget(parent);
QVBoxLayout* layout = new QVBoxLayout();
layout->setMargin(0);
layout->setSpacing(ColliderContainerWidget::s_layoutSpacing);
result->setLayout(layout);
// Object ownership label
{
m_ownershipWidget = new QWidget(result);
QHBoxLayout* ownershipLayout = new QHBoxLayout(m_ownershipWidget);
ownershipLayout->setAlignment(Qt::AlignTop | Qt::AlignLeft);
ownershipLayout->setMargin(0);
ownershipLayout->setSpacing(0);
m_ownershipWidget->setLayout(ownershipLayout);
ownershipLayout->addSpacerItem(new QSpacerItem(s_jointLabelSpacing, 0, QSizePolicy::Fixed));
QLabel* tempLabel = new QLabel("Part of Simulated Objects");
tempLabel->setStyleSheet("font-weight: bold;");
ownershipLayout->addWidget(tempLabel);
ownershipLayout->addSpacerItem(new QSpacerItem(44, 0, QSizePolicy::Fixed));
m_ownershipLabel = new QLabel();
m_ownershipLabel->setWordWrap(true);
ownershipLayout->addWidget(m_ownershipLabel);
ownershipLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Ignored, QSizePolicy::Ignored));
layout->addWidget(m_ownershipWidget);
}
// Collide with object label
{
m_collideWithWidget = new QWidget(result);
QHBoxLayout* collideWithLayout = new QHBoxLayout(m_collideWithWidget);
collideWithLayout->setAlignment(Qt::AlignTop | Qt::AlignLeft);
collideWithLayout->setMargin(0);
collideWithLayout->setSpacing(0);
m_collideWithWidget->setLayout(collideWithLayout);
collideWithLayout->addSpacerItem(new QSpacerItem(s_jointLabelSpacing, 0, QSizePolicy::Fixed));
QLabel* tempLabel = new QLabel("Collide with Simulated Objects");
tempLabel->setStyleSheet("font-weight: bold;");
collideWithLayout->addWidget(tempLabel);
collideWithLayout->addSpacerItem(new QSpacerItem(13, 0, QSizePolicy::Fixed));
m_collideWithLabel = new QLabel();
m_collideWithLabel->setWordWrap(true);
collideWithLayout->addWidget(m_collideWithLabel);
collideWithLayout->addSpacerItem(new QSpacerItem(0, 0, QSizePolicy::Ignored, QSizePolicy::Ignored));
layout->addWidget(m_collideWithWidget);
}
// Add to simulated object button
AddToSimulatedObjectButton* addObjectButtonn = new AddToSimulatedObjectButton("Add to simulated object", result);
layout->addWidget(addObjectButtonn);
// Add collider button
AddColliderButton* addColliderButton = new AddColliderButton("Add simulated object collider", result,
PhysicsSetup::ColliderConfigType::SimulatedObjectCollider,
{ azrtti_typeid<Physics::CapsuleShapeConfiguration>(),
azrtti_typeid<Physics::SphereShapeConfiguration>() });
addColliderButton->setObjectName("EMFX.SimulatedObjectColliderWidget.AddColliderButton");
connect(addColliderButton, &AddColliderButton::AddCollider, this, &SimulatedObjectColliderWidget::OnAddCollider);
layout->addWidget(addColliderButton);
m_instruction1 = new QLabel("To simulated the selected joint, add it to a Simulated Object by clicking on the \"Add to Simulated Object\" button above", result);
m_instruction1->setWordWrap(true);
m_instruction2 = new QLabel("If you want the selected joint to collide against a Simulated Object, add a collider to the selected joint, and then set up the \"Collide with\" settings under the Simulated Object", result);
m_instruction2->setWordWrap(true);
layout->addWidget(m_instruction1);
layout->addWidget(m_instruction2);
// Collider notification
m_colliderNotif = new NotificationWidget(result, "Currently, this collider doesn't collide against any simulated object. Select the Simulated Object you want to collide with from the Simulated Object Window, and choose this collider in the \"Collide with\" setting.");
layout->addWidget(m_colliderNotif);
m_colliderNotif->hide();
// Colliders widget
m_collidersWidget = new ColliderContainerWidget(QIcon(SkeletonModel::s_simulatedColliderIconPath), result); // use the ragdoll white collider icon because it's generic to all colliders.
m_collidersWidget->setObjectName("EMFX.SimulatedObjectColliderWidget.ColliderContainerWidget");
connect(m_collidersWidget, &ColliderContainerWidget::CopyCollider, this, &SimulatedObjectColliderWidget::OnCopyCollider);
connect(m_collidersWidget, &ColliderContainerWidget::PasteCollider, this, &SimulatedObjectColliderWidget::OnPasteCollider);
connect(m_collidersWidget, &ColliderContainerWidget::RemoveCollider, this, &SimulatedObjectColliderWidget::OnRemoveCollider);
layout->addWidget(m_collidersWidget);
return result;
}
QWidget* SimulatedObjectColliderWidget::CreateNoSelectionWidget(QWidget* parent)
{
QLabel* noSelectionLabel = new QLabel("Select a joint from the Skeleton Outliner", parent);
noSelectionLabel->setWordWrap(true);
return noSelectionLabel;
}
void SimulatedObjectColliderWidget::InternalReinit()
{
if (m_selectedModelIndices.size() == 1)
{
Physics::CharacterColliderNodeConfiguration* nodeConfig = GetNodeConfig();
if (nodeConfig)
{
AZ::SerializeContext* serializeContext = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext);
AZ_Error("EMotionFX", serializeContext, "Can't get serialize context from component application.");
m_collidersWidget->Update(GetActor(), GetNode(), PhysicsSetup::ColliderConfigType::SimulatedObjectCollider, nodeConfig->m_shapes, serializeContext);
m_collidersWidget->show();
m_instruction1->hide();
m_instruction2->hide();
}
else
{
m_collidersWidget->Reset();
m_instruction1->show();
m_instruction2->show();
}
}
else
{
m_collidersWidget->Reset();
m_instruction1->show();
m_instruction2->show();
}
UpdateOwnershipLabel();
UpdateColliderNotification();
}
void SimulatedObjectColliderWidget::UpdateOwnershipLabel()
{
Actor* actor = GetActor();
if (!actor)
{
return;
}
AZStd::string labelText;
const AZStd::vector<SimulatedObject*>& simObjs = actor->GetSimulatedObjectSetup()->GetSimulatedObjects();
for (const SimulatedObject* obj : simObjs)
{
for (int i = 0; i < m_selectedModelIndices.size(); ++i)
{
Node* node = m_selectedModelIndices[i].data(SkeletonModel::ROLE_POINTER).value<Node*>();
if (obj->FindSimulatedJointBySkeletonJointIndex(node->GetNodeIndex()))
{
if (!labelText.empty())
{
labelText += ", ";
}
labelText += obj->GetName();
break;
}
}
}
if (labelText.empty())
{
labelText = "N/A";
}
m_ownershipLabel->setText(labelText.c_str());
}
void SimulatedObjectColliderWidget::UpdateColliderNotification()
{
m_colliderNotif->hide();
m_collideWithWidget->hide();
Actor* actor = GetActor();
Node* joint = GetNode();
if (!actor || !joint)
{
return;
}
// Only show the notification when it is single selection.
if (m_selectedModelIndices.size() != 1)
{
return;
}
Physics::CharacterColliderNodeConfiguration* nodeConfig = GetNodeConfig();
if (!nodeConfig)
{
return;
}
m_collideWithWidget->show();
AZStd::string collideWith;
const AZStd::vector<SimulatedObject*>& simObjs = actor->GetSimulatedObjectSetup()->GetSimulatedObjects();
for (const SimulatedObject* obj : simObjs)
{
if (AZStd::find(obj->GetColliderTags().begin(), obj->GetColliderTags().end(), joint->GetName()) != obj->GetColliderTags().end())
{
if (!collideWith.empty())
{
collideWith += ", ";
}
collideWith += obj->GetName();
}
}
if (collideWith.empty())
{
m_colliderNotif->show();
m_collideWithLabel->setText("N/A");
}
else
{
m_colliderNotif->hide();
m_collideWithLabel->setText(collideWith.c_str());
}
}
void SimulatedObjectColliderWidget::OnAddCollider(const AZ::TypeId& colliderType)
{
ColliderHelpers::AddCollider(m_selectedModelIndices, PhysicsSetup::SimulatedObjectCollider, colliderType);
}
void SimulatedObjectColliderWidget::OnCopyCollider(size_t colliderIndex)
{
ColliderHelpers::CopyColliderToClipboard(m_selectedModelIndices.first(), colliderIndex, PhysicsSetup::SimulatedObjectCollider);
}
void SimulatedObjectColliderWidget::OnPasteCollider(size_t colliderIndex, bool replace)
{
ColliderHelpers::PasteColliderFromClipboard(m_selectedModelIndices.first(), colliderIndex, PhysicsSetup::SimulatedObjectCollider, replace);
}
void SimulatedObjectColliderWidget::OnRemoveCollider(size_t colliderIndex)
{
CommandColliderHelpers::RemoveCollider(GetActor()->GetID(), GetNode()->GetNameString(), PhysicsSetup::SimulatedObjectCollider, colliderIndex);
}
Physics::CharacterColliderNodeConfiguration* SimulatedObjectColliderWidget::GetNodeConfig() const
{
AZ_Assert(m_selectedModelIndices.size() == 1, "Get Node config function only return the config when it is single seleted");
Actor* actor = GetActor();
Node* joint = GetNode();
if (!actor || !joint)
{
return nullptr;
}
const AZStd::shared_ptr<EMotionFX::PhysicsSetup>& physicsSetup = actor->GetPhysicsSetup();
if (!physicsSetup)
{
return nullptr;
}
const Physics::CharacterColliderConfiguration& simulatedObjectColliderConfig = physicsSetup->GetSimulatedObjectColliderConfig();
return simulatedObjectColliderConfig.FindNodeConfigByName(joint->GetNameString());
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
AddToSimulatedObjectButton::AddToSimulatedObjectButton(const QString& text, QWidget* parent)
: QPushButton(text, parent)
{
m_actionManager = AZStd::make_unique<EMStudio::SimulatedObjectActionManager>();
setIcon(MysticQt::GetMysticQt()->FindIcon("Images/Icons/ArrowDownGray.png"));
connect(this, &QPushButton::clicked, this, &AddToSimulatedObjectButton::OnCreateContextMenu);
}
void AddToSimulatedObjectButton::OnCreateContextMenu()
{
AZ::Outcome<const QModelIndexList&> selectedRowIndicesOutcome;
SkeletonOutlinerRequestBus::BroadcastResult(selectedRowIndicesOutcome, &SkeletonOutlinerRequests::GetSelectedRowIndices);
if (!selectedRowIndicesOutcome.IsSuccess())
{
return;
}
const QModelIndexList& selectedRowIndices = selectedRowIndicesOutcome.GetValue();
if (selectedRowIndices.empty())
{
return;
}
const Actor* actor = selectedRowIndices[0].data(SkeletonModel::ROLE_ACTOR_POINTER).value<Actor*>();
if (!actor || !actor->GetSimulatedObjectSetup())
{
return;
}
const SimulatedObjectSetup* simObjSetup = actor->GetSimulatedObjectSetup().get();
const size_t simObjCounts = simObjSetup->GetNumSimulatedObjects();
// Find the object we can add the joints to, excluded the one that already contains all the selected joints.
AZStd::vector<bool> flags(simObjCounts, false);
for (const QModelIndex& index : selectedRowIndices)
{
const Node* joint = index.data(SkeletonModel::ROLE_POINTER).value<Node*>();
for (size_t i = 0; i < simObjCounts; ++i)
{
const SimulatedObject* object = simObjSetup->GetSimulatedObject(i);
if (!object->FindSimulatedJointBySkeletonJointIndex(joint->GetNodeIndex()))
{
flags[i] = true;
}
}
}
QMenu* contextMenu = new QMenu(this);
if (simObjCounts == 0)
{
QAction* action = contextMenu->addAction("0 simulated objects created.");
action->setEnabled(false);
contextMenu->addSeparator();
}
// Add all the object that we can add joints to in the menu.
for (size_t i = 0; i < simObjCounts; ++i)
{
if (!flags[i])
{
continue;
}
const SimulatedObject* obj = simObjSetup->GetSimulatedObject(i);
QAction* action = contextMenu->addAction(obj->GetName().c_str());
action->setProperty("simObjName", obj->GetName().c_str());
action->setProperty("simObjIndex", QVariant::fromValue(i));
connect(action, &QAction::triggered, this, &AddToSimulatedObjectButton::OnAddJointsToObjectActionTriggered);
}
contextMenu->addSeparator();
// Add the action to add simulated object, then add the joint to the object.
QAction* addObjectAction = contextMenu->addAction("New simulated object...");
connect(addObjectAction, &QAction::triggered, this, &AddToSimulatedObjectButton::OnCreateObjectAndAddJointsActionTriggered);
contextMenu->setFixedWidth(width());
if (!contextMenu->isEmpty())
{
contextMenu->popup(mapToGlobal(QPoint(0, height())));
}
connect(contextMenu, &QMenu::triggered, contextMenu, &QMenu::deleteLater);
}
void AddToSimulatedObjectButton::OnAddJointsToObjectActionTriggered([[maybe_unused]] bool checked)
{
AZ::Outcome<const QModelIndexList&> selectedRowIndicesOutcome;
SkeletonOutlinerRequestBus::BroadcastResult(selectedRowIndicesOutcome, &SkeletonOutlinerRequests::GetSelectedRowIndices);
if (!selectedRowIndicesOutcome.IsSuccess())
{
return;
}
QAction* action = static_cast<QAction*>(sender());
size_t objIndex = static_cast<size_t>(action->property("simObjIndex").toInt());
SimulatedObjectHelpers::AddSimulatedJoints(selectedRowIndicesOutcome.GetValue(), objIndex, false);
}
void AddToSimulatedObjectButton::OnCreateObjectAndAddJointsActionTriggered()
{
AZ::Outcome<const QModelIndexList&> selectedRowIndicesOutcome;
SkeletonOutlinerRequestBus::BroadcastResult(selectedRowIndicesOutcome, &SkeletonOutlinerRequests::GetSelectedRowIndices);
if (!selectedRowIndicesOutcome.IsSuccess())
{
return;
}
const QModelIndexList& selectedRowIndices = selectedRowIndicesOutcome.GetValue();
if (selectedRowIndices.empty())
{
return;
}
Actor* actor = selectedRowIndices[0].data(SkeletonModel::ROLE_ACTOR_POINTER).value<Actor*>();
if (!actor || !actor->GetSimulatedObjectSetup())
{
return;
}
const bool addChildren = (QMessageBox::question(this,
"Add children of joints?", "Add all children of selected joints to the simulated object?") == QMessageBox::Yes);
m_actionManager->OnAddNewObjectAndAddJoints(actor, selectedRowIndices, addChildren, this);
}
} // namespace EMotionFX
@@ -0,0 +1,90 @@
/*
* 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.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <Editor/SkeletonModelJointWidget.h>
#include <Editor/Plugins/SimulatedObject/SimulatedObjectActionManager.h>
#endif
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <QPushButton>
namespace Physics
{
class CharacterColliderNodeConfiguration;
}
namespace EMotionFX
{
class AddColliderButton;
class ColliderContainerWidget;
class NotificationWidget;
class SimulatedObjectColliderWidget
: public SkeletonModelJointWidget
{
Q_OBJECT //AUTOMOC
public:
SimulatedObjectColliderWidget(QWidget* parent = nullptr);
public slots:
void OnAddCollider(const AZ::TypeId& colliderType);
void OnCopyCollider(size_t colliderIndex);
void OnPasteCollider(size_t colliderIndex, bool replace);
void OnRemoveCollider(size_t colliderIndex);
private:
// SkeletonModelJointWidget
QWidget* CreateContentWidget(QWidget* parent) override;
QWidget* CreateNoSelectionWidget(QWidget* parent) override;
void InternalReinit() override;
void UpdateOwnershipLabel();
void UpdateColliderNotification();
Physics::CharacterColliderNodeConfiguration* GetNodeConfig() const;
ColliderContainerWidget* m_collidersWidget = nullptr;
QLabel* m_ownershipLabel = nullptr;
QWidget* m_ownershipWidget = nullptr;
QLabel* m_collideWithLabel = nullptr;
QWidget* m_collideWithWidget = nullptr;
QLabel* m_instruction1 = nullptr;
QLabel* m_instruction2 = nullptr;
NotificationWidget* m_colliderNotif = nullptr;
};
class AddToSimulatedObjectButton
: public QPushButton
{
Q_OBJECT //AUTOMOC
public:
AddToSimulatedObjectButton(const QString& text, QWidget* parent = nullptr);
signals:
void AddToSimulatedObject();
private slots:
void OnCreateContextMenu();
void OnAddJointsToObjectActionTriggered(bool checked);
void OnCreateObjectAndAddJointsActionTriggered();
private:
AZStd::unique_ptr<EMStudio::SimulatedObjectActionManager> m_actionManager;
};
} // namespace EMotionFX
@@ -0,0 +1,156 @@
/*
* 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 <AzQtComponents/Components/FilteredSearchWidget.h>
#include "SimulatedObjectSelectionWidget.h"
#include <EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/EMStudioManager.h>
#include <MCore/Source/StringConversions.h>
#include <MCore/Source/LogManager.h>
#include <EMotionFX/Source/SimulatedObjectSetup.h>
#include <QLabel>
#include <QSizePolicy>
#include <QTreeWidget>
#include <QPixmap>
#include <QPushButton>
#include <QVBoxLayout>
#include <QIcon>
#include <QHeaderView>
namespace EMStudio
{
SimulatedObjectSelectionWidget::SimulatedObjectSelectionWidget(QWidget* parent)
: QWidget(parent)
{
m_searchWidget = new AzQtComponents::FilteredSearchWidget(this);
connect(m_searchWidget, &AzQtComponents::FilteredSearchWidget::TextFilterChanged, this, &SimulatedObjectSelectionWidget::OnTextFilterChanged);
m_treeWidget = new QTreeWidget();
m_treeWidget->setColumnCount(1);
const QStringList headerList { "Name" };
m_treeWidget->setHeaderLabels(headerList);
m_treeWidget->setSortingEnabled(false);
m_treeWidget->setSelectionMode(QAbstractItemView::MultiSelection);
m_treeWidget->setAlternatingRowColors(true);
m_treeWidget->setExpandsOnDoubleClick(true);
m_treeWidget->setAnimated(true);
m_treeWidget->header()->setSectionsMovable(false);
QVBoxLayout* layout = new QVBoxLayout(this);
layout->setMargin(0);
layout->addWidget(m_searchWidget);
layout->addWidget(m_treeWidget);
connect(m_treeWidget, &QTreeWidget::itemSelectionChanged, this, &SimulatedObjectSelectionWidget::UpdateSelection);
connect(m_treeWidget, &QTreeWidget::itemDoubleClicked, this, &SimulatedObjectSelectionWidget::ItemDoubleClicked);
}
void SimulatedObjectSelectionWidget::Update(EMotionFX::Actor* actor, const AZStd::vector<AZStd::string>& selectedSimulatedObjects)
{
m_actor = actor;
m_selectedSimulatedObjectNames = selectedSimulatedObjects;
m_oldSelectedSimulatedObjectNames = selectedSimulatedObjects;
Update();
}
void SimulatedObjectSelectionWidget::AddSimulatedObjectToInterface(const EMotionFX::SimulatedObject* object)
{
// Make sure we only show the simulated objects that are wanted after the name are filtering
if (m_searchWidgetText.empty() || AzFramework::StringFunc::Find(object->GetName(), m_searchWidgetText) != AZStd::string::npos)
{
QTreeWidgetItem* item = new QTreeWidgetItem(m_treeWidget);
m_treeWidget->addTopLevelItem(item);
item->setText(0, object->GetName().c_str());
item->setExpanded(true);
// Check if the given object is selected
if (AZStd::find(m_oldSelectedSimulatedObjectNames.begin(), m_oldSelectedSimulatedObjectNames.end(), object->GetName()) != m_oldSelectedSimulatedObjectNames.end())
{
item->setSelected(true);
}
}
}
void SimulatedObjectSelectionWidget::Update()
{
m_treeWidget->clear();
m_treeWidget->blockSignals(true);
const AZStd::vector<EMotionFX::SimulatedObject*>& simulatedObjects = m_actor->GetSimulatedObjectSetup()->GetSimulatedObjects();
for (const EMotionFX::SimulatedObject* simulatedObject : simulatedObjects)
{
AddSimulatedObjectToInterface(simulatedObject);
}
m_treeWidget->blockSignals(false);
UpdateSelection();
}
AZStd::vector<AZStd::string>& SimulatedObjectSelectionWidget::GetSelectedSimulatedObjectNames()
{
UpdateSelection();
return m_selectedSimulatedObjectNames;
}
void SimulatedObjectSelectionWidget::UpdateSelection()
{
QList<QTreeWidgetItem*> selectedItems = m_treeWidget->selectedItems();
m_selectedSimulatedObjectNames.clear();
const uint32 numSelectedItems = selectedItems.count();
m_selectedSimulatedObjectNames.reserve(numSelectedItems);
// Iterate through the selected items in the tree widget.
AZStd::string itemName;
for (uint32 i = 0; i < numSelectedItems; ++i)
{
QTreeWidgetItem* item = selectedItems[i];
itemName = item->text(0).toUtf8().data();
// Skip the object that we can't find as they also shouldn't be selectable.
const EMotionFX::SimulatedObject* object = m_actor->GetSimulatedObjectSetup()->FindSimulatedObjectByName(itemName.c_str());
if (!object)
{
continue;
}
// Check if the selected item is a simulated object
if (AZStd::find(m_selectedSimulatedObjectNames.begin(), m_selectedSimulatedObjectNames.end(), itemName) == m_selectedSimulatedObjectNames.end())
{
m_selectedSimulatedObjectNames.emplace_back(itemName);
}
}
}
void SimulatedObjectSelectionWidget::ItemDoubleClicked(QTreeWidgetItem* item, int column)
{
AZ_UNUSED(item);
AZ_UNUSED(column);
UpdateSelection();
if (!m_selectedSimulatedObjectNames.empty())
{
emit OnDoubleClicked(m_selectedSimulatedObjectNames[0]);
}
}
void SimulatedObjectSelectionWidget::OnTextFilterChanged(const QString& text)
{
m_searchWidgetText = text.toUtf8().data();
AZStd::to_lower(m_searchWidgetText.begin(), m_searchWidgetText.end());
Update();
}
} // namespace EMStudio
@@ -0,0 +1,78 @@
/*
* 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.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzCore/std/containers/vector.h>
#include <MCore/Source/StandardHeaders.h>
#include <EMotionFX/CommandSystem/Source/SelectionCommands.h>
#include <EMotionStudio/Plugins/StandardPlugins/Source/StandardPluginsConfig.h>
#include <QDialog>
#endif
// forward declarations
QT_FORWARD_DECLARE_CLASS(QLabel)
QT_FORWARD_DECLARE_CLASS(QIcon)
QT_FORWARD_DECLARE_CLASS(QTreeWidget)
QT_FORWARD_DECLARE_CLASS(QTreeWidgetItem)
namespace AzQtComponents
{
class FilteredSearchWidget;
}
namespace EMotionFX
{
class SimulatedObject;
}
namespace EMStudio
{
class SimulatedObjectSelectionWidget
: public QWidget
{
Q_OBJECT // AUTOMOC
MCORE_MEMORYOBJECTCATEGORY(SimulatedObjectSelectionWidget, MCore::MCORE_DEFAULT_ALIGNMENT, MEMCATEGORY_STANDARDPLUGINS_ANIMGRAPH)
public:
SimulatedObjectSelectionWidget(QWidget* parent);
void Update(EMotionFX::Actor* actor, const AZStd::vector<AZStd::string>& selectedSimulatedObjectNames);
QTreeWidget* GetTreeWidget() { return m_treeWidget; }
AzQtComponents::FilteredSearchWidget* GetSearchWidget() { return m_searchWidget; }
// This calls UpdateSelection() and then returns the member array containing the selected items
AZStd::vector<AZStd::string>& GetSelectedSimulatedObjectNames();
signals:
void OnSelectionDone(const AZStd::vector<AZStd::string>& selectedItems);
void OnDoubleClicked(const AZStd::string& item);
public slots:
void Update();
void UpdateSelection();
void ItemDoubleClicked(QTreeWidgetItem* item, int column);
void OnTextFilterChanged(const QString& text);
private:
void AddSimulatedObjectToInterface(const EMotionFX::SimulatedObject* object);
EMotionFX::Actor* m_actor = nullptr;
QTreeWidget* m_treeWidget = nullptr;
AzQtComponents::FilteredSearchWidget* m_searchWidget = nullptr;
AZStd::string m_searchWidgetText;
AZStd::vector<AZStd::string> m_selectedSimulatedObjectNames;
AZStd::vector<AZStd::string> m_oldSelectedSimulatedObjectNames;
};
} // namespace EMStudio
@@ -0,0 +1,49 @@
/*
* 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 <Editor/Plugins/SimulatedObject/SimulatedObjectSelectionWindow.h>
#include <MCore/Source/LogManager.h>
#include <QLabel>
#include <QSizePolicy>
#include <QTreeWidget>
#include <QPixmap>
#include <QPushButton>
#include <QVBoxLayout>
#include <QIcon>
#include <QLineEdit>
#include <QGraphicsDropShadowEffect>
namespace EMStudio
{
SimulatedObjectSelectionWindow::SimulatedObjectSelectionWindow(QWidget* parent)
: QDialog(parent)
{
setWindowTitle("SimulatedObject Selection Window");
m_OKButton = new QPushButton("OK");
m_cancelButton = new QPushButton("Cancel");
QHBoxLayout* buttonLayout = new QHBoxLayout();
buttonLayout->addWidget(m_OKButton);
buttonLayout->addWidget(m_cancelButton);
QVBoxLayout* layout = new QVBoxLayout(this);
m_simulatedObjectSelectionWidget = new SimulatedObjectSelectionWidget(this);
layout->addWidget(m_simulatedObjectSelectionWidget);
layout->addLayout(buttonLayout);
connect(m_OKButton, &QPushButton::clicked, this, &SimulatedObjectSelectionWindow::accept);
connect(m_cancelButton, &QPushButton::clicked, this, &SimulatedObjectSelectionWindow::reject);
connect(m_simulatedObjectSelectionWidget, &SimulatedObjectSelectionWidget::OnDoubleClicked, this, &SimulatedObjectSelectionWindow::accept);
}
} // namespace EMStudio
@@ -0,0 +1,44 @@
/*
* 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.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <MCore/Source/StandardHeaders.h>
#include <EMotionFX/CommandSystem/Source/SelectionCommands.h>
#include <Editor/Plugins/SimulatedObject/SimulatedObjectSelectionWidget.h>
#include <EMotionStudio/Plugins/StandardPlugins/Source/StandardPluginsConfig.h>
#include <QDialog>
#endif
namespace EMStudio
{
class SimulatedObjectSelectionWindow
: public QDialog
{
Q_OBJECT // AUTOMOC
MCORE_MEMORYOBJECTCATEGORY(SimulatedObjectSelectionWindow, MCore::MCORE_DEFAULT_ALIGNMENT, MEMCATEGORY_STANDARDPLUGINS_ANIMGRAPH)
public:
SimulatedObjectSelectionWindow(QWidget* parent);
SimulatedObjectSelectionWidget* GetSimulatedObjectSelectionWidget() { return m_simulatedObjectSelectionWidget; }
void Update(EMotionFX::Actor* actor, const AZStd::vector<AZStd::string>& selectedSimulatedObjects) { m_simulatedObjectSelectionWidget->Update(actor, selectedSimulatedObjects); }
private:
SimulatedObjectSelectionWidget* m_simulatedObjectSelectionWidget = nullptr;
QPushButton* m_OKButton = nullptr;
QPushButton* m_cancelButton = nullptr;
bool m_accepted = false;
};
} // namespace EMStudio
@@ -0,0 +1,563 @@
/*
* 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 <AzCore/std/algorithm.h>
#include <AzToolsFramework/UI/PropertyEditor/PropertyEditorAPI.h>
#include <EMotionFX/CommandSystem/Source/SimulatedObjectCommands.h>
#include <EMotionFX/Source/Actor.h>
#include <EMotionFX/Source/ActorInstance.h>
#include <EMotionFX/Source/ActorManager.h>
#include <EMotionFX/Source/DebugDraw.h>
#include <EMotionFX/Source/TransformData.h>
#include <EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/EMStudioManager.h>
#include <EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/RenderPlugin/RenderOptions.h>
#include <EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/RenderPlugin/RenderPlugin.h>
#include <EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/RenderPlugin/RenderViewWidget.h>
#include <Editor/ColliderContainerWidget.h>
#include <Editor/ColliderHelpers.h>
#include <Editor/Plugins/SimulatedObject/SimulatedJointWidget.h>
#include <Editor/Plugins/SimulatedObject/SimulatedObjectWidget.h>
#include <Editor/ReselectingTreeView.h>
#include <Editor/SimulatedObjectHelpers.h>
#include <Editor/SkeletonModel.h>
#include <MCore/Source/AzCoreConversions.h>
#include <QLabel>
#include <QPushButton>
#include <QTreeView>
#include <QVBoxLayout>
#include <QMessageBox>
namespace EMotionFX
{
SimulatedObjectWidget::SimulatedObjectWidget()
: EMStudio::DockWidgetPlugin()
{
m_actionManager = AZStd::make_unique<EMStudio::SimulatedObjectActionManager>();
}
SimulatedObjectWidget::~SimulatedObjectWidget()
{
for (MCore::Command::Callback* callback : m_commandCallbacks)
{
CommandSystem::GetCommandManager()->RemoveCommandCallback(callback, true);
}
m_commandCallbacks.clear();
if (m_simulatedObjectInspectorDock)
{
EMStudio::GetMainWindow()->removeDockWidget(m_simulatedObjectInspectorDock);
delete m_simulatedObjectInspectorDock;
}
SkeletonOutlinerNotificationBus::Handler::BusDisconnect();
SimulatedObjectRequestBus::Handler::BusDisconnect();
ActorEditorNotificationBus::Handler::BusDisconnect();
}
bool SimulatedObjectWidget::Init()
{
m_noSelectionWidget = new QLabel("Add a simulated object first, then add the joints you want to simulate to the object and customize the simulation settings.");
m_noSelectionWidget->setWordWrap(true);
m_simulatedObjectModel = AZStd::make_unique<SimulatedObjectModel>();
m_treeView = new ReselectingTreeView();
m_treeView->setObjectName("EMFX.SimulatedObjectWidget.TreeView");
m_treeView->setModel(m_simulatedObjectModel.get());
m_treeView->setSelectionModel(m_simulatedObjectModel->GetSelectionModel());
m_treeView->setSelectionBehavior(QAbstractItemView::SelectionBehavior::SelectRows);
m_treeView->setSelectionMode(QAbstractItemView::ExtendedSelection);
m_treeView->setContextMenuPolicy(Qt::CustomContextMenu);
m_treeView->setExpandsOnDoubleClick(true);
m_treeView->expandAll();
connect(m_treeView, &QTreeView::customContextMenuRequested, this, static_cast<void (SimulatedObjectWidget::*)(const QPoint&)>(&SimulatedObjectWidget::OnContextMenu));
connect(m_simulatedObjectModel.get(), &QAbstractItemModel::modelReset, m_treeView, &QTreeView::expandAll);
connect(m_simulatedObjectModel->GetSelectionModel(), &QItemSelectionModel::selectionChanged, this, [this]() {
const QModelIndexList& selectedIndices = m_simulatedObjectModel->GetSelectionModel()->selectedRows();
if (selectedIndices.empty())
{
EMStudio::GetManager()->SetSelectedJointIndices({});
}
else
{
AZStd::unordered_set<AZ::u32> selectedJointIndices;
for (const QModelIndex& index : selectedIndices)
{
const SimulatedJoint* joint = index.data(SimulatedObjectModel::ROLE_JOINT_PTR).value<SimulatedJoint*>();
if (joint)
{
selectedJointIndices.emplace(joint->GetSkeletonJointIndex());
}
else
{
const SimulatedObject* object = index.data(SimulatedObjectModel::ROLE_OBJECT_PTR).value<SimulatedObject*>();
if (object)
{
for (const auto& jointInObject : object->GetSimulatedJoints())
{
selectedJointIndices.emplace(jointInObject->GetSkeletonJointIndex());
}
}
}
}
EMStudio::GetManager()->SetSelectedJointIndices(selectedJointIndices);
}
});
m_addSimulatedObjectButton = new QPushButton("Add simulated object");
m_addSimulatedObjectButton->setObjectName("addSimulatedObjectButton");
connect(m_addSimulatedObjectButton, &QPushButton::clicked, this, [this]()
{
m_actionManager->OnAddNewObjectAndAddJoints(m_actor, /*selectedJoints=*/{}, /*addChildJoints=*/false, mDock);
});
AZ::SerializeContext* serializeContext;
AZ::ComponentApplicationBus::BroadcastResult(serializeContext, &AZ::ComponentApplicationBus::Events::GetSerializeContext);
m_selectionWidget = new QWidget();
QVBoxLayout* selectionLayout = new QVBoxLayout(m_selectionWidget);
selectionLayout->addWidget(m_treeView);
m_mainWidget = new QWidget();
QVBoxLayout* mainLayout = new QVBoxLayout(m_mainWidget);
mainLayout->addWidget(m_addSimulatedObjectButton);
mainLayout->addWidget(m_noSelectionWidget);
mainLayout->addWidget(m_selectionWidget, /*stretch=*/1);
mainLayout->addStretch();
mDock->setWidget(m_mainWidget);
m_simulatedObjectInspectorDock = new AzQtComponents::StyledDockWidget("Simulated Object Inspector", mDock);
m_simulatedObjectInspectorDock->setFeatures(QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable);
m_simulatedObjectInspectorDock->setObjectName("EMFX.SimulatedObjectWidget.SimulatedObjectInspectorDock");
m_simulatedJointWidget = new SimulatedJointWidget(this);
m_simulatedObjectInspectorDock->setWidget(m_simulatedJointWidget);
QMainWindow* mainWindow = EMStudio::GetMainWindow();
mainWindow->addDockWidget(Qt::RightDockWidgetArea, m_simulatedObjectInspectorDock);
// Check if there is already an actor selected.
ActorEditorRequestBus::BroadcastResult(m_actorInstance, &ActorEditorRequests::GetSelectedActorInstance);
if (m_actorInstance)
{
// Only need to set the actor instance on the model, as this function also set the actor.
m_simulatedObjectModel->SetActorInstance(m_actorInstance);
m_actor = m_actorInstance->GetActor();
}
else
{
ActorEditorRequestBus::BroadcastResult(m_actor, &ActorEditorRequests::GetSelectedActor);
m_simulatedObjectModel->SetActor(m_actor);
}
Reinit();
// Register command callback
m_commandCallbacks.emplace_back(new DataChangedCallback(/*executePreUndo*/ false));
CommandSystem::GetCommandManager()->RegisterCommandCallback(CommandAddSimulatedObject::s_commandName, m_commandCallbacks.back());
CommandSystem::GetCommandManager()->RegisterCommandCallback(CommandAddSimulatedJoints::s_commandName, m_commandCallbacks.back());
CommandSystem::GetCommandManager()->RegisterCommandCallback(CommandRemoveSimulatedObject::s_commandName, m_commandCallbacks.back());
CommandSystem::GetCommandManager()->RegisterCommandCallback(CommandRemoveSimulatedJoints::s_commandName, m_commandCallbacks.back());
m_commandCallbacks.emplace_back(new AddSimulatedObjectCallback(/*executePreUndo*/ false));
CommandSystem::GetCommandManager()->RegisterCommandCallback(CommandAddSimulatedObject::s_commandName, m_commandCallbacks.back());
m_commandCallbacks.emplace_back(new AddSimulatedJointsCallback(/*executePreUndo*/ false));
CommandSystem::GetCommandManager()->RegisterCommandCallback(CommandAddSimulatedJoints::s_commandName, m_commandCallbacks.back());
// Buses
SkeletonOutlinerNotificationBus::Handler::BusConnect();
SimulatedObjectRequestBus::Handler::BusConnect();
ActorEditorNotificationBus::Handler::BusConnect();
return true;
}
void SimulatedObjectWidget::ActorSelectionChanged(Actor* actor)
{
m_actor = actor;
m_simulatedObjectModel->SetActor(actor);
Reinit();
}
void SimulatedObjectWidget::ActorInstanceSelectionChanged(EMotionFX::ActorInstance* actorInstance)
{
m_actorInstance = actorInstance;
m_actor = nullptr;
if (m_actorInstance)
{
m_actor = m_actorInstance->GetActor();
}
m_simulatedObjectModel->SetActorInstance(actorInstance);
Reinit();
}
void SimulatedObjectWidget::Reinit()
{
const bool showSelectionWidget = m_actor ? (m_actor->GetSimulatedObjectSetup()->GetNumSimulatedObjects() != 0) : false;
m_noSelectionWidget->setVisible(!showSelectionWidget);
m_selectionWidget->setVisible(showSelectionWidget);
m_simulatedJointWidget->UpdateDetailsView(QItemSelection(), QItemSelection());
m_addSimulatedObjectButton->setVisible(m_actorInstance != nullptr);
}
SimulatedObjectModel* SimulatedObjectWidget::GetSimulatedObjectModel() const
{
return m_simulatedObjectModel.get();
}
SimulatedJointWidget* SimulatedObjectWidget::GetSimulatedJointWidget() const
{
return m_simulatedJointWidget;
}
void SimulatedObjectWidget::ScrollTo(const QModelIndex& index)
{
m_treeView->scrollTo(index, QAbstractItemView::ScrollHint::PositionAtCenter);
}
// Called when right-clicked the simulated object widget.
void SimulatedObjectWidget::OnContextMenu(const QPoint& position)
{
const QModelIndexList& selectedIndices = m_treeView->selectionModel()->selectedRows(0);
const QModelIndex currentIndex = m_treeView->currentIndex();
if (!currentIndex.isValid())
{
return;
}
QMenu* contextMenu = new QMenu(m_mainWidget);
contextMenu->setObjectName("EMFX.SimulatedObjectWidget.ContextMenu");
const bool isJoint = currentIndex.data(SimulatedObjectModel::ROLE_JOINT_BOOL).toBool();
if (isJoint)
{
if (selectedIndices.count() == 1)
{
QAction* removeJoint = contextMenu->addAction("Remove joint");
connect(removeJoint, &QAction::triggered, [this, currentIndex]() { OnRemoveSimulatedJoint(currentIndex, false); });
QAction* removeJointAndChildren = contextMenu->addAction("Remove joint and children");
connect(removeJointAndChildren, &QAction::triggered, [this, currentIndex]() { OnRemoveSimulatedJoint(currentIndex, true); });
}
else
{
QAction* removeJoints = contextMenu->addAction("Remove joints");
connect(removeJoints, &QAction::triggered, [this, selectedIndices]() { OnRemoveSimulatedJoints(selectedIndices); });
}
}
else
{
QAction* removeObject = contextMenu->addAction("Remove object");
connect(removeObject, &QAction::triggered, [this, currentIndex]() { OnRemoveSimulatedObject(currentIndex); });
}
if (!contextMenu->isEmpty())
{
contextMenu->popup(m_treeView->mapToGlobal(position));
}
connect(contextMenu, &QMenu::triggered, contextMenu, &QMenu::deleteLater);
}
void SimulatedObjectWidget::OnRemoveSimulatedObject(const QModelIndex& objectIndex)
{
SimulatedObjectHelpers::RemoveSimulatedObject(objectIndex);
}
void SimulatedObjectWidget::OnRemoveSimulatedJoint(const QModelIndex& jointIndex, bool removeChildren)
{
SimulatedObjectHelpers::RemoveSimulatedJoint(jointIndex, removeChildren);
}
void SimulatedObjectWidget::OnRemoveSimulatedJoints(const QModelIndexList& jointIndices)
{
// We don't give the option to remove children when multiple joints are selected.
SimulatedObjectHelpers::RemoveSimulatedJoints(jointIndices, false);
}
void SimulatedObjectWidget::OnAddCollider()
{
AZ::Outcome<const QModelIndexList&> selectedRowIndicesOutcome;
SkeletonOutlinerRequestBus::BroadcastResult(selectedRowIndicesOutcome, &SkeletonOutlinerRequests::GetSelectedRowIndices);
if (!selectedRowIndicesOutcome.IsSuccess())
{
return;
}
const QModelIndexList& selectedRowIndices = selectedRowIndicesOutcome.GetValue();
if (selectedRowIndices.empty())
{
return;
}
QAction* action = static_cast<QAction*>(sender());
const QByteArray typeString = action->property("typeId").toString().toUtf8();
const AZ::TypeId& colliderType = AZ::TypeId::CreateString(typeString.data(), typeString.size());
ColliderHelpers::AddCollider(selectedRowIndices, PhysicsSetup::SimulatedObjectCollider, colliderType);
}
void SimulatedObjectWidget::OnClearColliders()
{
AZ::Outcome<const QModelIndexList&> selectedRowIndicesOutcome;
SkeletonOutlinerRequestBus::BroadcastResult(selectedRowIndicesOutcome, &SkeletonOutlinerRequests::GetSelectedRowIndices);
if (!selectedRowIndicesOutcome.IsSuccess())
{
return;
}
const QModelIndexList& selectedRowIndices = selectedRowIndicesOutcome.GetValue();
if (selectedRowIndices.empty())
{
return;
}
ColliderHelpers::ClearColliders(selectedRowIndices, PhysicsSetup::SimulatedObjectCollider);
}
// Called when right-clicked the skeleton outliner widget.
void SimulatedObjectWidget::OnContextMenu(QMenu* menu, const QModelIndexList& selectedRowIndices)
{
if (selectedRowIndices.empty())
{
return;
}
const Actor* actor = selectedRowIndices[0].data(SkeletonModel::ROLE_ACTOR_POINTER).value<Actor*>();
const SimulatedObjectSetup* simulatedObjectSetup = actor->GetSimulatedObjectSetup().get();
AZStd::unordered_set<const SimulatedObject*> addToCandidates;
for (const QModelIndex& index : selectedRowIndices)
{
const Node* joint = index.data(SkeletonModel::ROLE_POINTER).value<Node*>();
for (const SimulatedObject* object : simulatedObjectSetup->GetSimulatedObjects())
{
if (!object->FindSimulatedJointBySkeletonJointIndex(joint->GetNodeIndex()))
{
addToCandidates.emplace(object);
}
}
}
QMenu* addToSimulatedObjectMenu = menu->addMenu("Add to simulated object");
if (!addToCandidates.empty())
{
for (const SimulatedObject* object : addToCandidates)
{
QAction* openItem = addToSimulatedObjectMenu->addAction(object->GetName().c_str());
connect(openItem, &QAction::triggered, [this, selectedRowIndices, simulatedObjectSetup, object]() {
const bool addChildren = (QMessageBox::question(this->GetDockWidget(),
"Add children of joints?", "Add all children of selected joints to the simulated object?") == QMessageBox::Yes);
SimulatedObjectHelpers::AddSimulatedJoints(selectedRowIndices, simulatedObjectSetup->FindSimulatedObjectIndex(object).GetValue(), addChildren);
});
}
addToSimulatedObjectMenu->addSeparator();
}
connect(addToSimulatedObjectMenu->addAction("New simulated object..."), &QAction::triggered, this, [this, selectedRowIndices]() {
const bool addChildren = (QMessageBox::question(this->GetDockWidget(),
"Add children of joints?", "Add all children of selected joints to the simulated object?") == QMessageBox::Yes);
m_actionManager->OnAddNewObjectAndAddJoints(m_actor, selectedRowIndices, addChildren, mDock);
});
menu->addSeparator();
const AZStd::shared_ptr<PhysicsSetup>& physicsSetup = actor->GetPhysicsSetup();
if (!physicsSetup)
{
return;
}
if (ColliderHelpers::AreCollidersReflected())
{
if (selectedRowIndices.count() > 0)
{
QMenu* addColliderMenu = menu->addMenu("Add collider");
QAction* addCapsuleAction = addColliderMenu->addAction("Capsule");
addCapsuleAction->setProperty("typeId", azrtti_typeid<Physics::CapsuleShapeConfiguration>().ToString<AZStd::string>().c_str());
connect(addCapsuleAction, &QAction::triggered, this, &SimulatedObjectWidget::OnAddCollider);
QAction* addSphereAction = addColliderMenu->addAction("Sphere");
addSphereAction->setProperty("typeId", azrtti_typeid<Physics::SphereShapeConfiguration>().ToString<AZStd::string>().c_str());
connect(addSphereAction, &QAction::triggered, this, &SimulatedObjectWidget::OnAddCollider);
ColliderHelpers::AddCopyFromMenu(this, menu, PhysicsSetup::ColliderConfigType::SimulatedObjectCollider, selectedRowIndices);
}
const bool anySelectedJointHasCollider = AZStd::any_of(selectedRowIndices.begin(), selectedRowIndices.end(), [](const QModelIndex& modelIndex)
{
return modelIndex.data(SkeletonModel::ROLE_SIMULATED_OBJECT_COLLIDER).toBool();
});
if (anySelectedJointHasCollider)
{
QAction* removeCollidersAction = menu->addAction("Remove colliders");
removeCollidersAction->setObjectName("EMFX.SimulatedObjectWidget.RemoveCollidersAction");
connect(removeCollidersAction, &QAction::triggered, this, &SimulatedObjectWidget::OnClearColliders);
}
menu->addSeparator();
}
}
void SimulatedObjectWidget::UpdateWidget()
{
Reinit();
}
bool SimulatedObjectWidget::DataChangedCallback::Execute(MCore::Command* command, const MCore::CommandLine& commandLine)
{
AZ_UNUSED(command);
AZ_UNUSED(commandLine);
EMotionFX::SimulatedObjectRequestBus::Broadcast(&EMotionFX::SimulatedObjectRequests::UpdateWidget);
return true;
}
bool SimulatedObjectWidget::DataChangedCallback::Undo(MCore::Command* command, const MCore::CommandLine& commandLine)
{
AZ_UNUSED(command);
AZ_UNUSED(commandLine);
EMotionFX::SimulatedObjectRequestBus::Broadcast(&EMotionFX::SimulatedObjectRequests::UpdateWidget);
return true;
}
bool SimulatedObjectWidget::AddSimulatedObjectCallback::Execute(MCore::Command* command, [[maybe_unused]] const MCore::CommandLine& commandLine)
{
CommandAddSimulatedObject* addSimulatedObjectCommand = static_cast<CommandAddSimulatedObject*>(command);
const size_t objectIndex = addSimulatedObjectCommand->GetObjectIndex();
SimulatedObjectWidget* simulatedObjectPlugin = static_cast<SimulatedObjectWidget*>(EMStudio::GetPluginManager()->FindActivePlugin(SimulatedObjectWidget::CLASS_ID));
if (simulatedObjectPlugin)
{
const QModelIndex modelIndex = simulatedObjectPlugin->GetSimulatedObjectModel()->GetModelIndexByObjectIndex(objectIndex);
simulatedObjectPlugin->GetSimulatedObjectModel()->GetSelectionModel()->select(modelIndex, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
simulatedObjectPlugin->ScrollTo(modelIndex);
}
return true;
}
bool SimulatedObjectWidget::AddSimulatedObjectCallback::Undo(MCore::Command* command, const MCore::CommandLine& commandLine)
{
AZ_UNUSED(command);
AZ_UNUSED(commandLine);
return true;
}
bool SimulatedObjectWidget::AddSimulatedJointsCallback::Execute(MCore::Command* command, [[maybe_unused]] const MCore::CommandLine& commandLine)
{
CommandAddSimulatedJoints* addSimulatedJointsCommand = static_cast<CommandAddSimulatedJoints*>(command);
const size_t objectIndex = addSimulatedJointsCommand->GetObjectIndex();
const AZStd::vector<AZ::u32>& jointIndices = addSimulatedJointsCommand->GetJointIndices();
SimulatedObjectWidget* simulatedObjectPlugin = static_cast<SimulatedObjectWidget*>(EMStudio::GetPluginManager()->FindActivePlugin(SimulatedObjectWidget::CLASS_ID));
if (simulatedObjectPlugin)
{
QItemSelection selection;
simulatedObjectPlugin->GetSimulatedObjectModel()->AddJointsToSelection(selection, objectIndex, jointIndices);
simulatedObjectPlugin->GetSimulatedObjectModel()->GetSelectionModel()->select(selection, QItemSelectionModel::Current | QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
if (!selection.empty())
{
const QModelIndexList list = selection.indexes();
simulatedObjectPlugin->ScrollTo(list[0]);
}
}
return true;
}
bool SimulatedObjectWidget::AddSimulatedJointsCallback::Undo(MCore::Command* command, const MCore::CommandLine& commandLine)
{
AZ_UNUSED(command);
AZ_UNUSED(commandLine);
return true;
}
// -------------------------------------------------- Rendering -------------------------------------------------------------
void SimulatedObjectWidget::Render(EMStudio::RenderPlugin* renderPlugin, RenderInfo* renderInfo)
{
if (!m_actor || !m_actorInstance)
{
return;
}
EMStudio::RenderViewWidget* activeViewWidget = renderPlugin->GetActiveViewWidget();
if (!activeViewWidget)
{
return;
}
const bool renderSimulatedJoints = activeViewWidget->GetRenderFlag(EMStudio::RenderViewWidget::RENDER_SIMULATEJOINTS);
const AZStd::unordered_set<AZ::u32>& selectedJointIndices = EMStudio::GetManager()->GetSelectedJointIndices();
if (renderSimulatedJoints && !selectedJointIndices.empty())
{
// Render the joint radius.
const MCore::RGBAColor defaultColor = renderPlugin->GetRenderOptions()->GetSelectedSimulatedObjectColliderColor();
const AZ::u32 actorInstanceCount = GetActorManager().GetNumActorInstances();
for (AZ::u32 actorInstanceIndex = 0; actorInstanceIndex < actorInstanceCount; ++actorInstanceIndex)
{
ActorInstance* actorInstance = GetActorManager().GetActorInstance(actorInstanceIndex);
const Actor* actor = actorInstance->GetActor();
const SimulatedObjectSetup* setup = actor->GetSimulatedObjectSetup().get();
AZ_Assert(setup, "Expected a simulated object setup on the actor instance.");
const size_t objectCount = setup->GetNumSimulatedObjects();
for (size_t objectIndex = 0; objectIndex < objectCount; ++objectIndex)
{
const SimulatedObject* object = setup->GetSimulatedObject(objectIndex);
const size_t simulatedJointCount = object->GetNumSimulatedJoints();
for (size_t simulatedJointIndex = 0; simulatedJointIndex < simulatedJointCount; ++simulatedJointIndex)
{
const SimulatedJoint* simulatedJoint = object->GetSimulatedJoint(simulatedJointIndex);
const AZ::u32 skeletonJointIndex = simulatedJoint->GetSkeletonJointIndex();
if (selectedJointIndices.find(skeletonJointIndex) != selectedJointIndices.end())
{
RenderJointRadius(simulatedJoint, actorInstance, AZ::Color(1.0f, 0.0f, 1.0f, 1.0f));
}
}
}
}
}
const bool renderColliders = activeViewWidget->GetRenderFlag(EMStudio::RenderViewWidget::RENDER_SIMULATEDOBJECT_COLLIDERS);
if (renderColliders)
{
const EMStudio::RenderOptions* renderOptions = renderPlugin->GetRenderOptions();
ColliderContainerWidget::RenderColliders(PhysicsSetup::SimulatedObjectCollider,
renderOptions->GetSimulatedObjectColliderColor(),
renderOptions->GetSelectedSimulatedObjectColliderColor(),
renderPlugin,
renderInfo);
}
}
void SimulatedObjectWidget::RenderJointRadius(const SimulatedJoint* joint, ActorInstance* actorInstance, const AZ::Color& color)
{
#ifndef EMFX_SCALE_DISABLED
const float scale = actorInstance->GetWorldSpaceTransform().mScale.GetX();
#else
const float scale = 1.0f;
#endif
const float radius = joint->GetCollisionRadius() * scale;
if (radius <= AZ::Constants::FloatEpsilon)
{
return;
}
AZ_Assert(joint->GetSkeletonJointIndex() != MCORE_INVALIDINDEX32, "Expected skeletal joint index to be valid.");
const EMotionFX::Transform jointTransform = actorInstance->GetTransformData()->GetCurrentPose()->GetWorldSpaceTransform(joint->GetSkeletonJointIndex());
DebugDraw& debugDraw = GetDebugDraw();
DebugDraw::ActorInstanceData* drawData = debugDraw.GetActorInstanceData(actorInstance);
drawData->Lock();
drawData->DrawWireframeSphere(jointTransform.mPosition, radius, color, jointTransform.mRotation, 12, 12);
drawData->Unlock();
}
} // namespace EMotionFX
@@ -0,0 +1,123 @@
/*
* 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.
*
*/
#pragma once
#if !defined(Q_MOC_RUN)
#include <EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/DockWidgetPlugin.h>
#include <Editor/Plugins/SimulatedObject/SimulatedObjectActionManager.h>
#include <Editor/Plugins/SkeletonOutliner/SkeletonOutlinerBus.h>
#include <Editor/SimulatedObjectBus.h>
#include <Editor/SimulatedObjectModel.h>
#include <MCore/Source/Command.h>
#include <Source/Editor/ObjectEditor.h>
#endif
QT_FORWARD_DECLARE_CLASS(QLabel)
QT_FORWARD_DECLARE_CLASS(QPushButton)
QT_FORWARD_DECLARE_CLASS(QTreeView)
namespace EMotionFX
{
class Actor;
class ActorInstance;
class SimulatedJointWidget;
class SimulatedObjectWidget
: public EMStudio::DockWidgetPlugin
, private EMotionFX::SkeletonOutlinerNotificationBus::Handler
, private EMotionFX::SimulatedObjectRequestBus::Handler
, private EMotionFX::ActorEditorNotificationBus::Handler
{
Q_OBJECT //AUTOMOC
public:
enum
{
CLASS_ID = 0x00861164
};
SimulatedObjectWidget();
~SimulatedObjectWidget() override;
SimulatedObjectWidget(const SimulatedObjectWidget&) = delete;
SimulatedObjectWidget(SimulatedObjectWidget&&) = delete;
SimulatedObjectWidget& operator=(const SimulatedObjectWidget&) = delete;
SimulatedObjectWidget& operator=(SimulatedObjectWidget&&) = delete;
// EMStudioPlugin overrides
const char* GetName() const override { return "Simulated Object"; }
uint32 GetClassID() const override { return CLASS_ID; }
bool GetIsClosable() const override { return true; }
bool GetIsFloatable() const override { return true; }
bool GetIsVertical() const override { return false; }
EMStudioPlugin* Clone() override { return new SimulatedObjectWidget(); }
bool Init() override;
void Reinit();
// Render
void Render(EMStudio::RenderPlugin* renderPlugin, RenderInfo* renderInfo) override;
void RenderJointRadius(const SimulatedJoint* joint, ActorInstance* actorInstance, const AZ::Color& color);
SimulatedObjectModel* GetSimulatedObjectModel() const;
SimulatedJointWidget* GetSimulatedJointWidget() const;
void ScrollTo(const QModelIndex& index);
// SkeletonOutlinerNotificationBus overrides
void OnContextMenu(QMenu* menu, const QModelIndexList& selectedRowIndices) override;
// SimulatedObjectRequestBus overrides
void UpdateWidget() override;
// ActorEditorNotificationBus overrides
void ActorSelectionChanged(Actor* actor) override;
void ActorInstanceSelectionChanged(EMotionFX::ActorInstance* actorInstance) override;
EMStudio::SimulatedObjectActionManager* GetActionManager() const { return m_actionManager.get(); }
public slots:
void OnContextMenu(const QPoint& position);
void OnRemoveSimulatedObject(const QModelIndex& objectIndex);
void OnRemoveSimulatedJoint(const QModelIndex& jointIndex, bool removeChildren);
void OnRemoveSimulatedJoints(const QModelIndexList& jointIndices);
void OnAddCollider();
void OnClearColliders();
private:
EMotionFX::Actor* m_actor = nullptr;
EMotionFX::ActorInstance* m_actorInstance = nullptr;
QWidget* m_mainWidget = nullptr;
QLabel* m_noSelectionWidget = nullptr;
QWidget* m_selectionWidget = nullptr;
QTreeView* m_treeView = nullptr;
AZStd::unique_ptr<SimulatedObjectModel> m_simulatedObjectModel = nullptr;
AZStd::unique_ptr<EMStudio::SimulatedObjectActionManager> m_actionManager;
QWidget* m_contentsWidget = nullptr;
QDockWidget* m_simulatedObjectInspectorDock = nullptr;
SimulatedJointWidget* m_simulatedJointWidget = nullptr;
QPushButton* m_addSimulatedObjectButton = nullptr;
// Rendering
AZStd::vector<AZ::Vector3> m_vertexBuffer;
AZStd::vector<AZ::u32> m_indexBuffer;
AZStd::vector<AZ::Vector3> m_lineBuffer;
AZStd::vector<bool> m_lineValidityBuffer;
// Callbacks
MCORE_DEFINECOMMANDCALLBACK(DataChangedCallback);
MCORE_DEFINECOMMANDCALLBACK(AddSimulatedObjectCallback);
MCORE_DEFINECOMMANDCALLBACK(AddSimulatedJointsCallback);
// static bool DataChanged(AZ::u32 actorId);
AZStd::vector<MCore::Command::Callback*> m_commandCallbacks;
};
} // namespace EMotionFX