| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545 |
- /*
- * Copyright (c) Contributors to the Open 3D Engine Project.
- * For complete copyright and license terms please see the LICENSE at the root of this distribution.
- *
- * SPDX-License-Identifier: Apache-2.0 OR MIT
- *
- */
- #include <Editor/PropertyWidgets/MotionSetMotionIdHandler.h>
- #include <EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/MotionSetSelectionWindow.h>
- #include <Editor/AnimGraphEditorBus.h>
- #include <QHBoxLayout>
- #include <QMessageBox>
- #include <QLocale>
- #include <EMotionFX/Tools/EMotionStudio/EMStudioSDK/Source/EMStudioManager.h>
- #include <AzQtComponents/Components/Widgets/ElidingLabel.h>
- namespace EMotionFX
- {
- float MotionSelectionIdWidgetController::s_displayedRoundingError = 0.0f;
- AZ_CLASS_ALLOCATOR_IMPL(MotionSetMotionIdPicker, EditorAllocator)
- AZ_CLASS_ALLOCATOR_IMPL(MotionIdRandomSelectionWeightsHandler, EditorAllocator)
- AZ_CLASS_ALLOCATOR_IMPL(MotionSetMultiMotionIdHandler, EditorAllocator)
- AZ_CLASS_ALLOCATOR_IMPL(MotionSelectionIdWidgetController, EditorAllocator)
- const float MotionSetMotionIdPicker::s_defaultWeight = 1.0f;
- void MotionSelectionIdWidgetController::ResetDisplayedRoundingError()
- {
- s_displayedRoundingError = 0.0f;
- }
- MotionSelectionIdWidgetController::MotionSelectionIdWidgetController(QGridLayout* layout,
- int graphicLayoutRowIndex,
- const IRandomMotionSelectionDataContainer* dataContainer,
- bool displayMotionSelectionWeight)
- : m_dataContainer(dataContainer),
- m_displayMotionSelectionWeight(displayMotionSelectionWeight)
- {
- int column = 0;
- // Motion name
- m_labelMotion = new QLabel();
- m_labelMotion->setObjectName("m_labelMotion");
- m_labelMotion->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed);
- layout->addWidget(m_labelMotion, graphicLayoutRowIndex, column);
- column++;
- // Motion position x
- QHBoxLayout* layoutX = new QHBoxLayout();
- layoutX->setAlignment(Qt::AlignRight);
- layoutX->setSpacing(2);
- layoutX->setMargin(2);
- m_randomWeightSpinbox = new AzQtComponents::DoubleSpinBox();
- m_randomWeightSpinbox->setSingleStep(0.1);
- m_randomWeightSpinbox->setDecimals(1);
- m_randomWeightSpinbox->setRange(0, FLT_MAX);
- layoutX->addWidget(m_randomWeightSpinbox);
- layout->addLayout(layoutX, graphicLayoutRowIndex, column);
- column++;
- m_normalizedProbabilityText = new QLineEdit();
- // The read only text for the normalized probabilities does not need the space for the SpinBox buttons
- m_normalizedProbabilityText->setMaximumWidth(aznumeric_cast<int>(m_randomWeightSpinbox->maximumWidth() * 0.5));
- m_normalizedProbabilityText->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
- m_normalizedProbabilityText->setEnabled(false);
- layout->addWidget(m_normalizedProbabilityText, graphicLayoutRowIndex, column);
- column++;
- const int iconSize = 20;
- // Remove motion
- m_removeButton = new QPushButton();
- m_removeButton->setToolTip("Remove motion");
- m_removeButton->setMinimumSize(iconSize, iconSize);
- m_removeButton->setMaximumSize(iconSize, iconSize);
- m_removeButton->setIcon(QIcon(":/EMotionFX/Trash.svg"));
- layout->addWidget(m_removeButton, graphicLayoutRowIndex, column);
- if (!m_displayMotionSelectionWeight)
- {
- m_randomWeightSpinbox->setVisible(false);
- m_normalizedProbabilityText->setVisible(false);
- }
- }
- void MotionSelectionIdWidgetController::Hide()
- {
- m_labelMotion->hide();
- if (m_displayMotionSelectionWeight)
- {
- m_randomWeightSpinbox->hide();
- m_normalizedProbabilityText->hide();
- }
- m_removeButton->hide();
- }
- void MotionSelectionIdWidgetController::Show()
- {
- m_labelMotion->show();
- if (m_displayMotionSelectionWeight)
- {
- m_randomWeightSpinbox->show();
- m_normalizedProbabilityText->show();
- }
- m_removeButton->show();
- }
- void MotionSelectionIdWidgetController::UpdateId(size_t id)
- {
- m_id = id;
- }
- void MotionSelectionIdWidgetController::DestroyGuis()
- {
- m_labelMotion->deleteLater();
- m_removeButton->deleteLater();
- m_randomWeightSpinbox->deleteLater();
- m_normalizedProbabilityText->deleteLater();
- }
- size_t MotionSelectionIdWidgetController::GetId() const
- {
- return m_id;
- }
- void MotionSelectionIdWidgetController::Update()
- {
- const double weight = m_dataContainer->GetWeight(m_id);
- m_randomWeightSpinbox->setValue(weight);
- const double actualPercentage = 100.0 * weight / m_dataContainer->GetWeightSum();
- const double compensatedValue = actualPercentage - s_displayedRoundingError;
- const double roundedValue = qRound(compensatedValue);
- s_displayedRoundingError = aznumeric_cast<float>(roundedValue - compensatedValue);
- QString str = m_normalizedProbabilityText->locale().toString(roundedValue, 'f', 1);
- m_normalizedProbabilityText->setText(str);
- m_labelMotion->setText(m_dataContainer->GetMotionId(m_id).c_str());
- }
- MotionSetMotionIdPicker::MotionSetMotionIdPicker(QWidget* parent, bool displaySelectionWeights)
- : QWidget(parent)
- , m_displaySelectionWeights(displaySelectionWeights)
- {
- QVBoxLayout* vLayout = new QVBoxLayout();
- vLayout->setMargin(0);
- setLayout(vLayout);
- }
- MotionSetMotionIdPicker::~MotionSetMotionIdPicker()
- {
- // Destroying the dynamically allocated controller of each row
- m_motionWidgetControllers.clear();
- }
- void MotionSetMotionIdPicker::SetMotionIds(const AZStd::vector<AZStd::string>& motionIds)
- {
- HandleSelectedMotionsUpdate(motionIds);
- InitializeWidgets();
- UpdateGui();
- }
- void MotionSetMotionIdPicker::SetMotions(const AZStd::vector<AZStd::pair<AZStd::string, float>>& motions)
- {
- // Display the weights (not the cumulative non normalized probability that is passed from the serialized data)
- float cumulativeWeight = 0.0f;
- m_motions.clear();
- m_motions.reserve(motions.size());
- for (unsigned int i = 0; i < motions.size(); ++i)
- {
- m_motions.emplace_back(motions[i].first, motions[i].second - cumulativeWeight);
- cumulativeWeight = motions[i].second;
- }
- m_weightsSum = cumulativeWeight;
- InitializeWidgets();
- UpdateGui();
- }
- const AZStd::vector<AZStd::pair<AZStd::string, float>>& MotionSetMotionIdPicker::GetMotions() const
- {
- return m_motions;
- }
- AZStd::vector<AZStd::string> MotionSetMotionIdPicker::GetMotionIds() const
- {
- AZStd::vector<AZStd::string> motionIds;
- motionIds.reserve(m_motions.size());
- for (const auto& motionPair : m_motions)
- {
- motionIds.emplace_back(motionPair.first);
- }
- return motionIds;
- }
- /// This method updates the motion random selection weights
- /// setting default weights for those motions which were not in the data
- /// and deletes the motions that have not been selected if they were in the data
- /// exsisting motions will keep their current weight as set by the user with the GUI
- void MotionSetMotionIdPicker::HandleSelectedMotionsUpdate(const AZStd::vector<AZStd::string>& motionIds)
- {
- AZStd::unordered_map<AZStd::string, float> tmpRandomWeightsTable;
- for (size_t i = 0; i < m_motions.size(); ++i)
- {
- tmpRandomWeightsTable.emplace(m_motions[i]);
- }
- m_weightsSum = 0;
- m_motions.clear();
- m_motions.reserve(motionIds.size());
- AZStd::unordered_map<AZStd::string, float>::iterator tmpWeightTableIterator;
- for (const AZStd::string& motionId : motionIds)
- {
- float weight = s_defaultWeight;
- tmpWeightTableIterator = tmpRandomWeightsTable.find(motionId);
- if (tmpWeightTableIterator != tmpRandomWeightsTable.end())
- {
- weight = tmpWeightTableIterator->second;
- }
- m_weightsSum += weight;
- m_motions.emplace_back(motionId, weight);
- }
- }
- void MotionSetMotionIdPicker::OnPickClicked()
- {
- EMotionFX::MotionSet* motionSet = nullptr;
- AnimGraphEditorRequestBus::BroadcastResult(motionSet, &AnimGraphEditorRequests::GetSelectedMotionSet);
- if (!motionSet)
- {
- QMessageBox::warning(this, "No Motion Set", "Cannot open motion selection window. No valid motion set selected.");
- return;
- }
- // Create and show the motion picker window
- m_motionPickWindow = new EMStudio::MotionSetSelectionWindow(this);
- m_motionPickWindow->GetHierarchyWidget()->SetSelectionMode(false);
- m_motionPickWindow->Update(motionSet);
- m_motionPickWindow->setModal(true);
- AZStd::vector<AZStd::string> motionIds;
- motionIds.reserve(m_motions.size());
- for (const auto& motionIdRandomWeightPair : m_motions)
- {
- motionIds.emplace_back(motionIdRandomWeightPair.first);
- }
- m_motionPickWindow->Select(motionIds, motionSet);
- m_motionPickWindow->setAttribute(Qt::WA_DeleteOnClose);
- connect(m_motionPickWindow, &QDialog::accepted, this, &MotionSetMotionIdPicker::OnPickDialogAccept);
- connect(m_motionPickWindow, &QDialog::rejected, this, &MotionSetMotionIdPicker::OnPickDialogReject);
- m_motionPickWindow->open();
- }
- void MotionSetMotionIdPicker::OnPickDialogAccept()
- {
- EMotionFX::MotionSet* motionSet = nullptr;
- AnimGraphEditorRequestBus::BroadcastResult(motionSet, &AnimGraphEditorRequests::GetSelectedMotionSet);
- if (!motionSet)
- {
- QMessageBox::warning(this, "No Motion Set", "Cannot open motion selection window. No valid motion set selected.");
- m_motionPickWindow->close();
- m_motionPickWindow = nullptr;
- return;
- }
- HandleSelectedMotionsUpdate(m_motionPickWindow->GetHierarchyWidget()->GetSelectedMotionIds(motionSet));
- InitializeWidgets();
- UpdateGui();
- emit SelectionChanged();
- m_motionPickWindow->close();
- m_motionPickWindow = nullptr;
- }
- void MotionSetMotionIdPicker::OnPickDialogReject()
- {
- m_motionPickWindow->close();
- m_motionPickWindow = nullptr;
- }
- void MotionSetMotionIdPicker::InitializeWidgets()
- {
- if (!m_containerWidget)
- {
- m_containerWidget = new QWidget();
- m_containerWidget->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum);
- QVBoxLayout* widgetLayout = new QVBoxLayout();
- QHBoxLayout* topRowLayout = new QHBoxLayout();
- // Add helper label left of the add button.
- m_addMotionsLabel = new QLineEdit("");
- m_addMotionsLabel->setEnabled(false);
- topRowLayout->addWidget(m_addMotionsLabel);
- m_pickButton = new QPushButton(this);
- EMStudio::EMStudioManager::MakeTransparentButton(m_pickButton, "Images/Icons/Plus.svg", "Add motions to blend space");
- m_pickButton->setObjectName("EMFX.MotionSetMotionIdPicker.PickButton");
- connect(m_pickButton, &QPushButton::clicked, this, &MotionSetMotionIdPicker::OnPickClicked);
- topRowLayout->addWidget(m_pickButton);
- m_pickButton->setToolTip(QString("Add motions"));
- widgetLayout->addLayout(topRowLayout);
- QWidget* motionsWidget = new QWidget(m_containerWidget);
- motionsWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed);
- QGridLayout* motionsLayout = new QGridLayout();
- motionsLayout->setHorizontalSpacing(0);
- AzQtComponents::ElidingLabel* labelColumn0 = new AzQtComponents::ElidingLabel();
- motionsLayout->addWidget(labelColumn0, 0, 0);
- AzQtComponents::ElidingLabel* labelColumn1 = new AzQtComponents::ElidingLabel("Probability weight");
- motionsLayout->addWidget(labelColumn1, 0, 1);
- AzQtComponents::ElidingLabel* labelColumn2 = new AzQtComponents::ElidingLabel("Probability (100%)");
- motionsLayout->addWidget(labelColumn2, 0, 2);
- if (!m_displaySelectionWeights)
- {
- labelColumn0->setVisible(false);
- labelColumn1->setVisible(false);
- labelColumn2->setVisible(false);
- }
- motionsWidget->setLayout(motionsLayout);
- widgetLayout->addWidget(motionsWidget);
- m_motionsLayout = motionsLayout;
- m_containerWidget->setLayout(widgetLayout);
- layout()->addWidget(m_containerWidget);
- }
- size_t layoutRowIndex = m_motionWidgetControllers.size();
- if (!m_displaySelectionWeights)
- {
- m_motionsLayout->setAlignment(Qt::AlignLeft);
- }
- else
- {
- // Making room for the grid header row
- layoutRowIndex++;
- }
- // Build more rows if needed
- if (m_motions.size() > m_motionWidgetControllers.size())
- {
- for (size_t widgetcontrollerCounter = m_motions.size() - m_motionWidgetControllers.size(); widgetcontrollerCounter > 0; --widgetcontrollerCounter)
- {
- m_motionWidgetControllers.emplace_back(AZStd::make_unique<MotionSelectionIdWidgetController>(m_motionsLayout, static_cast<int>(layoutRowIndex++), this, m_displaySelectionWeights));
- MotionSelectionIdWidgetController* motionWidget = m_motionWidgetControllers.back().get();
- connect(motionWidget->m_randomWeightSpinbox, qOverload<double>(&QDoubleSpinBox::valueChanged), this,
- [this, motionWidget](double value)
- {
- OnRandomWeightChanged(motionWidget->GetId(), value);
- }
- );
- connect(motionWidget->m_removeButton, &QPushButton::clicked, [this, motionWidget]()
- {
- OnRemoveMotion(motionWidget->GetId());
- });
- }
- }
- // Bind the row to the data and hide those that are not needed
- size_t id = 0;
- for(auto widgetsControllerIterator = m_motionWidgetControllers.begin(); widgetsControllerIterator != m_motionWidgetControllers.end(); ++widgetsControllerIterator)
- {
- if (id < m_motions.size())
- {
- (*widgetsControllerIterator)->UpdateId(id++);
- (*widgetsControllerIterator)->Show();
- }
- else
- {
- (*widgetsControllerIterator)->Hide();
- }
- }
- }
- void MotionSetMotionIdPicker::OnRandomWeightChanged(size_t id, double value)
- {
- m_weightsSum = aznumeric_cast<float>(m_weightsSum + (value - m_motions[id].second));
- m_motions[id].second = aznumeric_cast<float>(value);
- UpdateGui();
- emit SelectionChanged();
- }
- float MotionSetMotionIdPicker::GetWeight(size_t id) const
- {
- return m_motions[id].second;
- }
- float MotionSetMotionIdPicker::GetWeightSum() const
- {
- return m_weightsSum;
- }
- const AZStd::string& MotionSetMotionIdPicker::GetMotionId(size_t id) const
- {
- return m_motions[id].first;
- }
- void MotionSetMotionIdPicker::UpdateGui()
- {
- MotionSelectionIdWidgetController::ResetDisplayedRoundingError();
- auto widgetsIterator = m_motionWidgetControllers.begin();
- size_t validGuisCount = 0;
- for(; validGuisCount < m_motions.size() && widgetsIterator != m_motionWidgetControllers.end(); ++widgetsIterator, ++validGuisCount)
- {
- (*widgetsIterator)->Update();
- }
- if (m_motions.size() > 0)
- {
- m_addMotionsLabel->setText(AZStd::string::format("%zu motions selected", m_motions.size()).c_str());
- }
- else
- {
- m_addMotionsLabel->setText("Select motions");
- }
- }
- void MotionSetMotionIdPicker::OnRemoveMotion(size_t id)
- {
- m_weightsSum -= m_motions[id].second;
- m_motions.erase(m_motions.begin() + id);
- MotionSelectionIdWidgetController::ResetDisplayedRoundingError();
- InitializeWidgets();
- UpdateGui();
- emit SelectionChanged();
- }
- //---------------------------------------------------------------------------------------------------------------------------------------------------------
- AZ::u32 MotionIdRandomSelectionWeightsHandler::GetHandlerName() const
- {
- return AZ_CRC("MotionSetMotionIdsRandomSelectionWeights", 0xc882da3c);
- }
- QWidget* MotionIdRandomSelectionWeightsHandler::CreateGUI(QWidget* parent)
- {
- MotionSetMotionIdPicker* picker = aznew MotionSetMotionIdPicker(parent, true);
- connect(picker, &MotionSetMotionIdPicker::SelectionChanged, this, [picker]()
- {
- AzToolsFramework::PropertyEditorGUIMessages::Bus::Broadcast(
- &AzToolsFramework::PropertyEditorGUIMessages::Bus::Events::RequestWrite, picker);
- });
- return picker;
- }
- void MotionIdRandomSelectionWeightsHandler::ConsumeAttribute(MotionSetMotionIdPicker* GUI, AZ::u32 attrib, AzToolsFramework::PropertyAttributeReader* attrValue, [[maybe_unused]] const char* debugName)
- {
- if (attrib == AZ::Edit::Attributes::ReadOnly)
- {
- bool value;
- if (attrValue->Read<bool>(value))
- {
- GUI->setEnabled(!value);
- }
- }
- }
- void MotionIdRandomSelectionWeightsHandler::WriteGUIValuesIntoProperty([[maybe_unused]] size_t index, MotionSetMotionIdPicker* GUI, property_t& instance, [[maybe_unused]] AzToolsFramework::InstanceDataNode* node)
- {
- // Please Note: the values stored in the serialized data that will be used to randomly select the motion to play
- // contain the cumulative non normalized probability
- // whilst the data in the GUIs contain the randomselection weights
- instance.clear();
- const auto& motions = GUI->GetMotions();
- instance.reserve(motions.size());
- // Store in the node's data the cumulative non normalized probability (not the weights)
- float cumulativeWeight = 0.0f;
- for (size_t i = 0; i < motions.size(); ++i)
- {
- cumulativeWeight += motions[i].second;
- instance.emplace_back(motions[i].first, cumulativeWeight);
- }
- }
- bool MotionIdRandomSelectionWeightsHandler::ReadValuesIntoGUI([[maybe_unused]] size_t index, MotionSetMotionIdPicker* GUI, const property_t& instance, [[maybe_unused]] AzToolsFramework::InstanceDataNode* node)
- {
- QSignalBlocker signalBlocker(GUI);
- GUI->SetMotions(instance);
- return true;
- }
- //---------------------------------------------------------------------------------------------------------------------------------------------------------
- AZ::u32 MotionSetMultiMotionIdHandler::GetHandlerName() const
- {
- return AZ_CRC("MotionSetMotionIds", 0x8695c0fa);
- }
- QWidget* MotionSetMultiMotionIdHandler::CreateGUI(QWidget* parent)
- {
- MotionSetMotionIdPicker* picker = aznew MotionSetMotionIdPicker(parent, false);
- connect(picker, &MotionSetMotionIdPicker::SelectionChanged, this, [picker]()
- {
- AzToolsFramework::PropertyEditorGUIMessages::Bus::Broadcast(
- &AzToolsFramework::PropertyEditorGUIMessages::Bus::Events::RequestWrite, picker);
- });
- return picker;
- }
- void MotionSetMultiMotionIdHandler::ConsumeAttribute(MotionSetMotionIdPicker* GUI, AZ::u32 attrib, AzToolsFramework::PropertyAttributeReader* attrValue, [[maybe_unused]] const char* debugName)
- {
- if (attrib == AZ::Edit::Attributes::ReadOnly)
- {
- bool value;
- if (attrValue->Read<bool>(value))
- {
- GUI->setEnabled(!value);
- }
- }
- }
- void MotionSetMultiMotionIdHandler::WriteGUIValuesIntoProperty([[maybe_unused]] size_t index, MotionSetMotionIdPicker* GUI, property_t& instance, [[maybe_unused]] AzToolsFramework::InstanceDataNode* node)
- {
- instance = GUI->GetMotionIds();
- }
- bool MotionSetMultiMotionIdHandler::ReadValuesIntoGUI([[maybe_unused]] size_t index, MotionSetMotionIdPicker* GUI, const property_t& instance, [[maybe_unused]] AzToolsFramework::InstanceDataNode* node)
- {
- QSignalBlocker signalBlocker(GUI);
- GUI->SetMotionIds(instance);
- return true;
- }
- } // namespace EMotionFX
- #include <Source/Editor/PropertyWidgets/moc_MotionSetMotionIdHandler.cpp>
|