Files
o3de/Gems/EMotionFX/Code/Source/Editor/SkeletonModel.cpp
T
Chris Burel 8884227fe6 Remove MCore::Array
This translates all usages of MCore::Array to AZStd::vector. It is
designed to be as minimal of a change as possible (no changing to
range-for loops or other C++11 stuff).

We can decide to submit this wholesale, or submit it to a separate
branch that we can then integrate individual files from once we're ready
to do a specific class's transition.

It does not completely solve the `uint32`->`size_t` transition.

One important finding from doing this: `MCore::Array` uses a `memcpy`
when it reallocates. `AZStd::vector` will use the contained type's copy
or move constructor, per element. This is a significant change in
behavior. If you have type, `SomeStruct` that defines a destructor, that
type is copyable and not movable. So if you have a
`MCore::Array<SomeStruct>`, and you call `Add(); Add(); Add()`, that
reallocates 3 times, copying the contents using `memcpy`, and never
invokes `SomeStruct`'s copy constructor or destructor. Translating that
to `AZStd::vector<SomeStruct>` and calling `push_back(); push_back();
push_back();` will still reallocate 3 times, but it sees that
`SomeStruct` is non-movable, and uses the copy constructor to make the
copies, and then the destructor on the previous values. This call to the
destructor wasn't there before, and can cause things to be deleted that
weren't before. The solution to this is to make that struct be a
move-only type. Where possible, this was done by changing that type to
use `AZStd::unique_ptr` instead of a raw pointer, to get the proper move
behavior. Where that is not possible (types that inherit from
`MCore::MemoryObject`), a hand-written move constructor was created.

In general:
GetLength() becomes size()
GetMaxLength() becomes capacity()
GetIsEmpty() becomes empty()
Reserve() becomes reserve()
ReserveExact() becomes reserve()
Resize() becomes resize()
ResizeFast() becomes resize_no_construct()
Add() becomes emplace_back()
AddExact() becomes emplace_back()
AddEmpty() becomes emplace_back()
AddEmptyExact() becomes emplace_back()
GetPtr() becomes data()
GetItem() becomes at()
Shrink() becomes shrink_to_fit()
GetFirst() becomes front()
GetLast() becomes back()
Remove() becomes erase()
RemoveFirst() becomes erase()
RemoveLast() becomes pop_back()
RemoveByValue() becomes if (const auto it = AZStd::find(...); it != end(container)) container.erase(it);
Insert() becomes emplace()
Swap() becomes swap()
Clear(true) becomes clear(); shrink_to_fit()
Clear() becomes clear(); shrink_to_fit()
Clear(false) becomes clear()
Swap() becomes swap()
Find() becomes AZStd::find
MoveElements() becomes AZStd::move
SetMemoryCategory() is removed

Signed-off-by: Chris Burel <burelc@amazon.com>
2021-08-09 08:36:43 -07:00

658 lines
23 KiB
C++

/*
* 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 <EMotionFX/Source/Skeleton.h>
#include <Source/Editor/SkeletonModel.h>
#include <EMotionFX/Source/SimulatedObjectSetup.h>
namespace EMotionFX
{
int SkeletonModel::s_defaultIconSize = 16;
int SkeletonModel::s_columnCount = 7;
const char* SkeletonModel::s_jointIconPath = ":/EMotionFX/Joint.svg";
const char* SkeletonModel::s_clothColliderIconPath = ":/EMotionFX/Cloth.svg";
const char* SkeletonModel::s_hitDetectionColliderIconPath = ":/EMotionFX/HitDetection.svg";
const char* SkeletonModel::s_ragdollColliderIconPath = ":/EMotionFX/RagdollCollider.svg";
const char* SkeletonModel::s_ragdollJointLimitIconPath = ":/EMotionFX/RagdollJointLimit.svg";
const char* SkeletonModel::s_simulatedJointIconPath = ":/EMotionFX/SimulatedObjectColored.svg";
const char* SkeletonModel::s_simulatedColliderIconPath = ":/EMotionFX/SimulatedObjectCollider.svg";
SkeletonModel::SkeletonModel()
: m_selectionModel(this)
, m_skeleton(nullptr)
, m_actor(nullptr)
, m_actorInstance(nullptr)
, m_jointIcon(s_jointIconPath)
, m_clothColliderIcon(s_clothColliderIconPath)
, m_hitDetectionColliderIcon(s_hitDetectionColliderIconPath)
, m_ragdollColliderIcon(s_ragdollColliderIconPath)
, m_ragdollJointLimitIcon(s_ragdollJointLimitIconPath)
, m_simulatedJointIcon(s_simulatedJointIconPath)
, m_simulatedColliderIcon(s_simulatedColliderIconPath)
{
m_selectionModel.setModel(this);
ActorEditorNotificationBus::Handler::BusConnect();
ActorInstance* selectedActorInstance = nullptr;
ActorEditorRequestBus::BroadcastResult(selectedActorInstance, &ActorEditorRequests::GetSelectedActorInstance);
if (selectedActorInstance)
{
SetActorInstance(selectedActorInstance);
}
else
{
Actor* selectedActor = nullptr;
ActorEditorRequestBus::BroadcastResult(selectedActor, &ActorEditorRequests::GetSelectedActor);
SetActor(selectedActor);
}
// UI 2.0 styling paints icons white on row selection. It cannot discern between layers though, so everything that is non-transparent is filled with white.
// As a temp solution, we specify the selected icon.
m_jointIcon.addFile(s_jointIconPath, QSize(), QIcon::Selected);
m_clothColliderIcon.addFile(s_clothColliderIconPath, QSize(), QIcon::Selected);
m_hitDetectionColliderIcon.addFile(s_hitDetectionColliderIconPath, QSize(), QIcon::Selected);
m_ragdollColliderIcon.addFile(s_ragdollColliderIconPath, QSize(), QIcon::Selected);
m_ragdollJointLimitIcon.addFile(s_ragdollJointLimitIconPath, QSize(), QIcon::Selected);
m_simulatedJointIcon.addFile(s_simulatedJointIconPath, QSize(), QIcon::Selected);
m_simulatedColliderIcon.addFile(s_simulatedColliderIconPath, QSize(), QIcon::Selected);
}
SkeletonModel::~SkeletonModel()
{
// Calling Reset will trigger the modelReset signal, thus notify other widgets to clear cached information about this model.
Reset();
ActorEditorNotificationBus::Handler::BusDisconnect();
}
void SkeletonModel::SetActor(Actor* actor)
{
beginResetModel();
m_actorInstance = nullptr;
m_actor = actor;
m_skeleton = nullptr;
if (m_actor)
{
m_skeleton = actor->GetSkeleton();
}
UpdateNodeInfos(m_actor);
endResetModel();
}
void SkeletonModel::SetActorInstance(ActorInstance* actorInstance)
{
beginResetModel();
m_actorInstance = actorInstance;
m_actor = nullptr;
m_skeleton = nullptr;
if (m_actorInstance)
{
m_actor = actorInstance->GetActor();
m_skeleton = m_actor->GetSkeleton();
}
UpdateNodeInfos(m_actor);
endResetModel();
}
QModelIndex SkeletonModel::index(int row, int column, const QModelIndex& parent) const
{
if (!m_skeleton)
{
AZ_Assert(false, "Cannot get model index. Skeleton invalid.");
return QModelIndex();
}
if (parent.isValid())
{
const Node* parentNode = static_cast<const Node*>(parent.internalPointer());
if (row >= static_cast<int>(parentNode->GetNumChildNodes()))
{
AZ_Assert(false, "Cannot get model index. Row out of range.");
return QModelIndex();
}
const AZ::u32 childNodeIndex = parentNode->GetChildIndex(row);
Node* childNode = m_skeleton->GetNode(childNodeIndex);
return createIndex(row, column, childNode);
}
else
{
if (row >= static_cast<int>(m_skeleton->GetNumRootNodes()))
{
AZ_Assert(false, "Cannot get model index. Row out of range.");
return QModelIndex();
}
const AZ::u32 rootNodeIndex = m_skeleton->GetRootNodeIndex(row);
Node* rootNode = m_skeleton->GetNode(rootNodeIndex);
return createIndex(row, column, rootNode);
}
}
QModelIndex SkeletonModel::parent(const QModelIndex& child) const
{
if (!m_skeleton)
{
AZ_Assert(false, "Cannot get parent model index. Skeleton invalid.");
return QModelIndex();
}
AZ_Assert(child.isValid(), "Expected valid child model index.");
Node* childNode = static_cast<Node*>(child.internalPointer());
Node* parentNode = childNode->GetParentNode();
if (parentNode)
{
Node* grandParentNode = parentNode->GetParentNode();
if (grandParentNode)
{
const AZ::u32 numChildNodes = grandParentNode->GetNumChildNodes();
for (AZ::u32 i = 0; i < numChildNodes; ++i)
{
const Node* grandParentChildNode = m_skeleton->GetNode(grandParentNode->GetChildIndex(i));
if (grandParentChildNode == parentNode)
{
return createIndex(i, 0, parentNode);
}
}
}
else
{
const AZ::u32 numRootNodes = m_skeleton->GetNumRootNodes();
for (AZ::u32 i = 0; i < numRootNodes; ++i)
{
const Node* rootNode = m_skeleton->GetNode(m_skeleton->GetRootNodeIndex(i));
if (rootNode == parentNode)
{
return createIndex(i, 0, parentNode);
}
}
}
}
return QModelIndex();
}
int SkeletonModel::rowCount(const QModelIndex& parent) const
{
if (!m_skeleton)
{
return 0;
}
if (parent.isValid())
{
const Node* parentNode = static_cast<const Node*>(parent.internalPointer());
return static_cast<int>(parentNode->GetNumChildNodes());
}
else
{
return static_cast<int>(m_skeleton->GetNumRootNodes());
}
}
int SkeletonModel::columnCount([[maybe_unused]] const QModelIndex& parent) const
{
return s_columnCount;
}
QVariant SkeletonModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
{
switch (section)
{
case COLUMN_NAME:
return "Name";
default:
return "";
}
}
return QVariant();
}
QVariant SkeletonModel::data(const QModelIndex& index, int role) const
{
if (!m_skeleton || !index.isValid())
{
AZ_Assert(false, "Cannot get model data. Skeleton or model index invalid.");
return QVariant();
}
Node* node = static_cast<Node*>(index.internalPointer());
AZ_Assert(node, "Expected valid node pointer.");
const AZ::u32 nodeIndex = node->GetNodeIndex();
const NodeInfo& nodeInfo = m_nodeInfos[nodeIndex];
switch (role)
{
case Qt::ToolTipRole:
{
switch (index.column())
{
case COLUMN_RAGDOLL_LIMIT:
{
return QString("Ragdoll Limit");
}
case COLUMN_CLOTH_COLLIDERS:
{
return QString("Cloth Colliders");
}
case COLUMN_HITDETECTION_COLLIDERS:
{
return QString("Hit Detection Colliders");
}
case COLUMN_RAGDOLL_COLLIDERS:
{
return QString("Ragdoll Colliders");
}
case COLUMN_SIMULATED_JOINTS:
{
return QString("Simulated Joints");
}
case COLUMN_SIMULATED_COLLIDERS:
{
return QString("Simulated Colliders");
}
default:
break;
}
break;
}
case Qt::DisplayRole:
{
switch (index.column())
{
case COLUMN_NAME:
return node->GetName();
default:
break;
}
break;
}
case Qt::CheckStateRole:
{
if (index.column() == 0 && nodeInfo.m_checkable)
{
return nodeInfo.m_checkState;
}
break;
}
case Qt::DecorationRole:
{
switch (index.column())
{
case COLUMN_NAME:
{
return m_jointIcon;
}
case COLUMN_RAGDOLL_LIMIT:
{
const AZStd::shared_ptr<PhysicsSetup>& physicsSetup = m_actor->GetPhysicsSetup();
if (physicsSetup)
{
const Physics::RagdollConfiguration& ragdollConfig = physicsSetup->GetRagdollConfig();
const Physics::RagdollNodeConfiguration* ragdollNodeConfig = ragdollConfig.FindNodeConfigByName(node->GetName());
if (ragdollNodeConfig)
{
return m_ragdollJointLimitIcon;
}
}
break;
}
case COLUMN_RAGDOLL_COLLIDERS:
{
const AZStd::shared_ptr<PhysicsSetup>& physicsSetup = m_actor->GetPhysicsSetup();
if (physicsSetup)
{
const Physics::RagdollConfiguration& ragdollConfig = physicsSetup->GetRagdollConfig();
Physics::CharacterColliderNodeConfiguration* ragdollColliderNodeConfig = ragdollConfig.m_colliders.FindNodeConfigByName(node->GetName());
if (ragdollColliderNodeConfig && !ragdollColliderNodeConfig->m_shapes.empty())
{
return m_ragdollColliderIcon;
}
}
break;
}
case COLUMN_HITDETECTION_COLLIDERS:
{
const AZStd::shared_ptr<PhysicsSetup>& physicsSetup = m_actor->GetPhysicsSetup();
if (physicsSetup)
{
const Physics::CharacterColliderConfiguration& hitDetectionConfig = physicsSetup->GetHitDetectionConfig();
Physics::CharacterColliderNodeConfiguration* hitDetectionNodeConfig = hitDetectionConfig.FindNodeConfigByName(node->GetName());
if (hitDetectionNodeConfig && !hitDetectionNodeConfig->m_shapes.empty())
{
return m_hitDetectionColliderIcon;
}
}
break;
}
case COLUMN_CLOTH_COLLIDERS:
{
const AZStd::shared_ptr<PhysicsSetup>& physicsSetup = m_actor->GetPhysicsSetup();
if (physicsSetup)
{
const Physics::CharacterColliderConfiguration& clothConfig = physicsSetup->GetClothConfig();
Physics::CharacterColliderNodeConfiguration* clothNodeConfig = clothConfig.FindNodeConfigByName(node->GetName());
if (clothNodeConfig && !clothNodeConfig->m_shapes.empty())
{
return m_clothColliderIcon;
}
}
break;
}
case COLUMN_SIMULATED_JOINTS:
{
const AZStd::shared_ptr<SimulatedObjectSetup>& simulatedObjectSetup = m_actor->GetSimulatedObjectSetup();
if (simulatedObjectSetup)
{
const AZStd::vector<SimulatedObject*> objects = simulatedObjectSetup->GetSimulatedObjects();
for (const SimulatedObject* object : objects)
{
if (object->FindSimulatedJointBySkeletonJointIndex(node->GetNodeIndex()))
{
return m_simulatedJointIcon;
}
}
}
break;
}
case COLUMN_SIMULATED_COLLIDERS:
{
const AZStd::shared_ptr<PhysicsSetup>& physicsSetup = m_actor->GetPhysicsSetup();
if (physicsSetup)
{
const Physics::CharacterColliderConfiguration& simulatedColliderConfig = physicsSetup->GetSimulatedObjectColliderConfig();
Physics::CharacterColliderNodeConfiguration* simulatedColliderNodeConfig = simulatedColliderConfig.FindNodeConfigByName(node->GetName());
if (simulatedColliderNodeConfig && !simulatedColliderNodeConfig->m_shapes.empty())
{
return m_simulatedColliderIcon;
}
}
break;
}
}
break;
}
case ROLE_NODE_INDEX:
return nodeIndex;
case ROLE_POINTER:
return QVariant::fromValue(node);
case ROLE_ACTOR_POINTER:
return QVariant::fromValue(m_actor);
case ROLE_ACTOR_INSTANCE_POINTER:
return QVariant::fromValue(m_actorInstance);
case ROLE_BONE:
return nodeInfo.m_isBone;
case ROLE_HASMESH:
return nodeInfo.m_hasMesh;
case ROLE_RAGDOLL:
{
const AZStd::shared_ptr<PhysicsSetup>& physicsSetup = m_actor->GetPhysicsSetup();
if (physicsSetup)
{
const Physics::RagdollConfiguration& ragdollConfig = physicsSetup->GetRagdollConfig();
const Physics::RagdollNodeConfiguration* ragdollNodeConfig = ragdollConfig.FindNodeConfigByName(node->GetName());
return ragdollNodeConfig != nullptr;
}
}
case ROLE_HITDETECTION:
{
const AZStd::shared_ptr<PhysicsSetup>& physicsSetup = m_actor->GetPhysicsSetup();
if (physicsSetup)
{
const Physics::CharacterColliderConfiguration& hitDetectionConfig = physicsSetup->GetHitDetectionConfig();
Physics::CharacterColliderNodeConfiguration* hitDetectionNodeConfig = hitDetectionConfig.FindNodeConfigByName(node->GetName());
return (hitDetectionNodeConfig && !hitDetectionNodeConfig->m_shapes.empty());
}
}
case ROLE_CLOTH:
{
const AZStd::shared_ptr<PhysicsSetup>& physicsSetup = m_actor->GetPhysicsSetup();
if (physicsSetup)
{
const Physics::CharacterColliderConfiguration& clothConfig = physicsSetup->GetClothConfig();
Physics::CharacterColliderNodeConfiguration* clothNodeConfig = clothConfig.FindNodeConfigByName(node->GetName());
return (clothNodeConfig && !clothNodeConfig->m_shapes.empty());
}
}
case ROLE_SIMULATED_JOINT:
{
const AZStd::shared_ptr<SimulatedObjectSetup>& simulatedObjectSetup = m_actor->GetSimulatedObjectSetup();
if (simulatedObjectSetup)
{
const AZStd::vector<SimulatedObject*>& objects = simulatedObjectSetup->GetSimulatedObjects();
const auto found = AZStd::find_if(objects.begin(), objects.end(), [node](const SimulatedObject* object)
{
return object->FindSimulatedJointBySkeletonJointIndex(node->GetNodeIndex());
});
return found != objects.end();
}
}
case ROLE_SIMULATED_OBJECT_COLLIDER:
{
const AZStd::shared_ptr<PhysicsSetup>& physicsSetup = m_actor->GetPhysicsSetup();
if (physicsSetup)
{
// TODO: Get the data from simulated collider setup.
const Physics::CharacterColliderConfiguration& simulatedObjectConfig = physicsSetup->GetSimulatedObjectColliderConfig();
Physics::CharacterColliderNodeConfiguration* simulatedObjectNodeConfig = simulatedObjectConfig.FindNodeConfigByName(node->GetName());
return (simulatedObjectNodeConfig && !simulatedObjectNodeConfig->m_shapes.empty());
}
}
default:
break;
}
return QVariant();
}
Qt::ItemFlags SkeletonModel::flags(const QModelIndex& index) const
{
Qt::ItemFlags result = Qt::ItemIsSelectable | Qt::ItemIsEnabled;
if (!m_skeleton || !index.isValid())
{
AZ_Assert(false, "Cannot get model data. Skeleton or model index invalid.");
return Qt::NoItemFlags;
}
Node* node = static_cast<Node*>(index.internalPointer());
AZ_Assert(node, "Expected valid node pointer.");
const AZ::u32 nodeIndex = node->GetNodeIndex();
const NodeInfo& nodeInfo = m_nodeInfos[nodeIndex];
if (nodeInfo.m_checkable)
{
result |= Qt::ItemIsUserCheckable;
}
return result;
}
bool SkeletonModel::setData(const QModelIndex& index, const QVariant& value, int role)
{
if (!m_skeleton || !index.isValid())
{
AZ_Assert(false, "Cannot get model data. Skeleton or model index invalid.");
return false;
}
const Node* node = static_cast<Node*>(index.internalPointer());
AZ_Assert(node, "Expected valid node pointer.");
const AZ::u32 nodeIndex = node->GetNodeIndex();
NodeInfo& nodeInfo = m_nodeInfos[nodeIndex];
switch (role)
{
case Qt::CheckStateRole:
{
if (index.column() == 0 && nodeInfo.m_checkable)
{
nodeInfo.m_checkState = (Qt::CheckState)value.toInt();
return true;
}
break;
}
}
return true;
}
QModelIndex SkeletonModel::GetModelIndex(Node* node) const
{
if (!node)
{
return QModelIndex();
}
Node* parentNode = node->GetParentNode();
if (parentNode)
{
const AZ::u32 numChildNodes = parentNode->GetNumChildNodes();
for (AZ::u32 i = 0; i < numChildNodes; ++i)
{
const Node* childNode = m_skeleton->GetNode(parentNode->GetChildIndex(i));
if (childNode == node)
{
return createIndex(i, 0, node);
}
}
}
const AZ::u32 numRootNodes = m_skeleton->GetNumRootNodes();
for (AZ::u32 i = 0; i < numRootNodes; ++i)
{
const Node* rootNode = m_skeleton->GetNode(m_skeleton->GetRootNodeIndex(i));
if (rootNode == node)
{
return createIndex(i, 0, node);
}
}
return QModelIndex();
}
QModelIndexList SkeletonModel::GetModelIndicesForFullSkeleton() const
{
QModelIndexList result;
const AZ::u32 jointCount = m_skeleton->GetNumNodes();
for (AZ::u32 i = 0; i < jointCount; ++i)
{
Node* joint = m_skeleton->GetNode(i);
result.push_back(GetModelIndex(joint));
}
return result;
}
void SkeletonModel::Reset()
{
beginResetModel();
endResetModel();
}
void SkeletonModel::SetCheckable(bool isCheckable)
{
if (rowCount() > 0)
{
for (NodeInfo& nodeInfo : m_nodeInfos)
{
nodeInfo.m_checkable = isCheckable;
}
QVector<int> roles;
roles.push_back(Qt::CheckStateRole);
dataChanged(index(0, 0), index(rowCount() - 1, 0), roles);
}
}
void SkeletonModel::ForEach(const AZStd::function<void(const QModelIndex& modelIndex)>& func)
{
QModelIndex modelIndex;
const AZ::u32 jointCount = m_skeleton->GetNumNodes();
for (AZ::u32 i = 0; i < jointCount; ++i)
{
Node* joint = m_skeleton->GetNode(i);
modelIndex = GetModelIndex(joint);
if (modelIndex.isValid())
{
func(modelIndex);
}
}
}
void SkeletonModel::ActorSelectionChanged(Actor* actor)
{
SetActor(actor);
}
void SkeletonModel::ActorInstanceSelectionChanged(EMotionFX::ActorInstance* actorInstance)
{
SetActorInstance(actorInstance);
}
void SkeletonModel::UpdateNodeInfos(Actor* actor)
{
if (!actor)
{
m_nodeInfos.clear();
return;
}
const AZ::u32 numLodLevels = actor->GetNumLODLevels();
const Skeleton* skeleton = actor->GetSkeleton();
const AZ::u32 numNodes = skeleton->GetNumNodes();
m_nodeInfos.resize(numNodes);
AZStd::vector<AZStd::vector<uint32> > boneListPerLodLevel;
boneListPerLodLevel.resize(numLodLevels);
for (AZ::u32 lodLevel = 0; lodLevel < numLodLevels; ++lodLevel)
{
actor->ExtractBoneList(lodLevel, &boneListPerLodLevel[lodLevel]);
}
for (AZ::u32 nodeIndex = 0; nodeIndex < numNodes; ++nodeIndex)
{
NodeInfo& nodeInfo = m_nodeInfos[nodeIndex];
// Is bone?
nodeInfo.m_isBone = false;
for (AZ::u32 lodLevel = 0; lodLevel < numLodLevels; ++lodLevel)
{
if (AZStd::find(begin(boneListPerLodLevel[lodLevel]), end(boneListPerLodLevel[lodLevel]), nodeIndex) != end(boneListPerLodLevel[lodLevel]))
{
nodeInfo.m_isBone = true;
break;
}
}
// Has mesh?
nodeInfo.m_hasMesh = false;
for (AZ::u32 lodLevel = 0; lodLevel < numLodLevels; ++lodLevel)
{
if (actor->GetMesh(lodLevel, nodeIndex))
{
nodeInfo.m_hasMesh = true;
break;
}
}
}
}
} // namespace EMotionFX