// Copyright (C) 2017 Jérôme Leclercq // This file is part of the "Nazara Engine - Utility module" // For conditions of distribution and use, see copyright notice in Config.hpp #include #include #include namespace Nz { struct SkeletonImpl { std::unordered_map jointMap; std::vector joints; Boxf aabb; bool aabbUpdated = false; bool jointMapUpdated = false; }; Skeleton::Skeleton(const Skeleton& skeleton) : RefCounted(), m_impl(nullptr) { operator=(skeleton); } Skeleton::~Skeleton() { OnSkeletonRelease(this); Destroy(); } bool Skeleton::Create(UInt32 jointCount) { #if NAZARA_UTILITY_SAFE if (jointCount == 0) { NazaraError("Joint count must be over zero"); return false; } #endif m_impl = new SkeletonImpl; m_impl->joints.resize(jointCount, Joint(this)); return true; } void Skeleton::Destroy() { if (m_impl) { OnSkeletonDestroy(this); delete m_impl; m_impl = nullptr; } } const Boxf& Skeleton::GetAABB() const { #if NAZARA_UTILITY_SAFE if (!m_impl) { NazaraError("Skeleton not created"); static Boxf dummy; return dummy; } #endif if (!m_impl->aabbUpdated) { std::size_t jointCount = m_impl->joints.size(); if (jointCount > 0) { Vector3f pos = m_impl->joints[0].GetPosition(); m_impl->aabb.Set(pos.x, pos.y, pos.z, 0.f, 0.f, 0.f); for (std::size_t i = 1; i < jointCount; ++i) m_impl->aabb.ExtendTo(m_impl->joints[i].GetPosition()); } else m_impl->aabb.MakeZero(); m_impl->aabbUpdated = true; } return m_impl->aabb; } Joint* Skeleton::GetJoint(const String& jointName) { #if NAZARA_UTILITY_SAFE if (!m_impl) { NazaraError("Skeleton not created"); return nullptr; } #endif if (!m_impl->jointMapUpdated) UpdateJointMap(); auto it = m_impl->jointMap.find(jointName); #if NAZARA_UTILITY_SAFE if (it == m_impl->jointMap.end()) { NazaraError("Joint not found"); return nullptr; } #endif InvalidateJoints(); return &m_impl->joints[it->second]; } Joint* Skeleton::GetJoint(UInt32 index) { #if NAZARA_UTILITY_SAFE if (!m_impl) { NazaraError("Skeleton not created"); return nullptr; } if (index >= m_impl->joints.size()) { NazaraError("Joint index out of range (" + String::Number(index) + " >= " + String::Number(m_impl->joints.size()) + ')'); return nullptr; } #endif InvalidateJoints(); return &m_impl->joints[index]; } const Joint* Skeleton::GetJoint(const String& jointName) const { #if NAZARA_UTILITY_SAFE if (!m_impl) { NazaraError("Skeleton not created"); return nullptr; } #endif if (!m_impl->jointMapUpdated) UpdateJointMap(); auto it = m_impl->jointMap.find(jointName); #if NAZARA_UTILITY_SAFE if (it == m_impl->jointMap.end()) { NazaraError("Joint not found"); return nullptr; } #endif return &m_impl->joints[it->second]; } const Joint* Skeleton::GetJoint(UInt32 index) const { #if NAZARA_UTILITY_SAFE if (!m_impl) { NazaraError("Skeleton not created"); return nullptr; } if (index >= m_impl->joints.size()) { NazaraError("Joint index out of range (" + String::Number(index) + " >= " + String::Number(m_impl->joints.size()) + ')'); return nullptr; } #endif return &m_impl->joints[index]; } Joint* Skeleton::GetJoints() { #if NAZARA_UTILITY_SAFE if (!m_impl) { NazaraError("Skeleton not created"); return nullptr; } #endif return &m_impl->joints[0]; } const Joint* Skeleton::GetJoints() const { #if NAZARA_UTILITY_SAFE if (!m_impl) { NazaraError("Skeleton not created"); return nullptr; } #endif return &m_impl->joints[0]; } UInt32 Skeleton::GetJointCount() const { #if NAZARA_UTILITY_SAFE if (!m_impl) { NazaraError("Skeleton not created"); return 0; } #endif return static_cast(m_impl->joints.size()); } int Skeleton::GetJointIndex(const String& jointName) const { #if NAZARA_UTILITY_SAFE if (!m_impl) { NazaraError("Skeleton not created"); return -1; } #endif if (!m_impl->jointMapUpdated) UpdateJointMap(); auto it = m_impl->jointMap.find(jointName); #if NAZARA_UTILITY_SAFE if (it == m_impl->jointMap.end()) { NazaraError("Joint not found"); return -1; } #endif return it->second; } void Skeleton::Interpolate(const Skeleton& skeletonA, const Skeleton& skeletonB, float interpolation) { #if NAZARA_UTILITY_SAFE if (!m_impl) { NazaraError("Skeleton not created"); return; } if (!skeletonA.IsValid()) { NazaraError("Skeleton A is invalid"); return; } if (!skeletonB.IsValid()) { NazaraError("Skeleton B is invalid"); return; } if (skeletonA.GetJointCount() != skeletonB.GetJointCount() || m_impl->joints.size() != skeletonA.GetJointCount()) { NazaraError("Skeletons must have the same joint count"); return; } #endif Joint* jointsA = &skeletonA.m_impl->joints[0]; Joint* jointsB = &skeletonB.m_impl->joints[0]; for (std::size_t i = 0; i < m_impl->joints.size(); ++i) m_impl->joints[i].Interpolate(jointsA[i], jointsB[i], interpolation, CoordSys_Local); InvalidateJoints(); } void Skeleton::Interpolate(const Skeleton& skeletonA, const Skeleton& skeletonB, float interpolation, UInt32* indices, UInt32 indiceCount) { #if NAZARA_UTILITY_SAFE if (!m_impl) { NazaraError("Skeleton not created"); return; } if (!skeletonA.IsValid()) { NazaraError("Skeleton A is invalid"); return; } if (!skeletonB.IsValid()) { NazaraError("Skeleton B is invalid"); return; } if (skeletonA.GetJointCount() != skeletonB.GetJointCount() || m_impl->joints.size() != skeletonA.GetJointCount()) { NazaraError("Skeletons must have the same joint count"); return; } #endif const Joint* jointsA = &skeletonA.m_impl->joints[0]; const Joint* jointsB = &skeletonB.m_impl->joints[0]; for (UInt32 i = 0; i < indiceCount; ++i) { UInt32 index = indices[i]; #if NAZARA_UTILITY_SAFE if (index >= m_impl->joints.size()) { NazaraError("Index #" + String::Number(i) + " out of range (" + String::Number(index) + " >= " + String::Number(m_impl->joints.size()) + ')'); return; } #endif m_impl->joints[index].Interpolate(jointsA[index], jointsB[index], interpolation, CoordSys_Local); } InvalidateJoints(); } bool Skeleton::IsValid() const { return m_impl != nullptr; } Skeleton& Skeleton::operator=(const Skeleton& skeleton) { if (this == &skeleton) return *this; Destroy(); if (skeleton.m_impl) { m_impl = new SkeletonImpl; m_impl->jointMap = skeleton.m_impl->jointMap; m_impl->jointMapUpdated = skeleton.m_impl->jointMapUpdated; m_impl->joints = skeleton.m_impl->joints; // Code crade mais son optimisation demanderait de stocker jointCount*sizeof(UInt32) en plus // Ce qui, pour juste une copie qui ne se fera que rarement, ne vaut pas le coup // L'éternel trade-off mémoire/calculs .. std::size_t jointCount = skeleton.m_impl->joints.size(); for (std::size_t i = 0; i < jointCount; ++i) { const Node* parent = skeleton.m_impl->joints[i].GetParent(); if (parent) { for (std::size_t j = 0; j < i; ++j) // Le parent se trouve forcément avant nous { if (parent == &skeleton.m_impl->joints[j]) // A-t-on trouvé le parent ? { m_impl->joints[i].SetParent(m_impl->joints[j]); // Oui, tout ça pour ça break; } } } } } return *this; } void Skeleton::InvalidateJoints() { m_impl->aabbUpdated = false; OnSkeletonJointsInvalidated(this); } void Skeleton::InvalidateJointMap() { #ifdef NAZARA_DEBUG if (!m_impl) { NazaraError("Invalid skeleton"); return; } #endif m_impl->jointMapUpdated = false; } void Skeleton::UpdateJointMap() const { #ifdef NAZARA_DEBUG if (!m_impl) { NazaraError("Invalid skeleton"); return; } #endif m_impl->jointMap.clear(); for (std::size_t i = 0; i < m_impl->joints.size(); ++i) { String name = m_impl->joints[i].GetName(); if (!name.IsEmpty()) { NazaraAssert(m_impl->jointMap.find(name) == m_impl->jointMap.end(), "Joint name \"" + name + "\" is already present in joint map"); m_impl->jointMap[name] = static_cast(i); } } m_impl->jointMapUpdated = true; } bool Skeleton::Initialize() { if (!SkeletonLibrary::Initialize()) { NazaraError("Failed to initialise library"); return false; } return true; } void Skeleton::Uninitialize() { SkeletonLibrary::Uninitialize(); } SkeletonLibrary::LibraryMap Skeleton::s_library; }