305 lines
8.6 KiB
C++
305 lines
8.6 KiB
C++
// Copyright (C) 2024 Jérôme "SirLynix" Leclercq (lynix680@gmail.com)
|
|
// This file is part of the "Nazara Engine - Core module"
|
|
// For conditions of distribution and use, see copyright notice in Config.hpp
|
|
|
|
#include <Nazara/Core/Animation.hpp>
|
|
#include <Nazara/Core/Config.hpp>
|
|
#include <Nazara/Core/Core.hpp>
|
|
#include <Nazara/Core/Error.hpp>
|
|
#include <Nazara/Core/Joint.hpp>
|
|
#include <Nazara/Core/Sequence.hpp>
|
|
#include <Nazara/Core/Skeleton.hpp>
|
|
#include <unordered_map>
|
|
#include <vector>
|
|
#include <Nazara/Core/Debug.hpp>
|
|
|
|
namespace Nz
|
|
{
|
|
struct AnimationImpl
|
|
{
|
|
std::unordered_map<std::string, std::size_t, StringHash<>, std::equal_to<>> sequenceMap;
|
|
std::vector<Sequence> sequences;
|
|
std::vector<SequenceJoint> sequenceJoints; // Uniquement pour les animations squelettiques
|
|
AnimationType type;
|
|
std::size_t frameCount;
|
|
std::size_t jointCount; // Uniquement pour les animations squelettiques
|
|
};
|
|
|
|
bool AnimationParams::IsValid() const
|
|
{
|
|
if (startFrame > endFrame)
|
|
{
|
|
NazaraError("start frame index must be smaller than end frame index");
|
|
return false;
|
|
}
|
|
|
|
if (!skeleton)
|
|
{
|
|
NazaraError("you must set a valid skeleton to load an animation");
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
Animation::Animation() = default;
|
|
Animation::Animation(Animation&&) noexcept = default;
|
|
Animation::~Animation() = default;
|
|
|
|
bool Animation::AddSequence(Sequence sequence)
|
|
{
|
|
NazaraAssert(m_impl, "Animation not created");
|
|
NazaraAssert(sequence.frameCount > 0, "Sequence frame count must be over zero");
|
|
|
|
if (m_impl->type == AnimationType::Skeletal)
|
|
{
|
|
std::size_t endFrame = sequence.firstFrame + sequence.frameCount - 1;
|
|
if (endFrame >= m_impl->frameCount)
|
|
{
|
|
m_impl->frameCount = endFrame+1;
|
|
m_impl->sequenceJoints.resize(m_impl->frameCount*m_impl->jointCount);
|
|
}
|
|
}
|
|
|
|
if (!sequence.name.empty())
|
|
{
|
|
#if NAZARA_CORE_SAFE
|
|
auto it = m_impl->sequenceMap.find(sequence.name);
|
|
if (it != m_impl->sequenceMap.end())
|
|
{
|
|
NazaraErrorFmt("sequence name \"{0}\" is already in use", sequence.name);
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
m_impl->sequenceMap[sequence.name] = static_cast<std::size_t>(m_impl->sequences.size());
|
|
}
|
|
|
|
m_impl->sequences.emplace_back(std::move(sequence));
|
|
|
|
return true;
|
|
}
|
|
|
|
void Animation::AnimateSkeleton(Skeleton* targetSkeleton, std::size_t frameA, std::size_t frameB, float interpolation) const
|
|
{
|
|
NazaraAssert(m_impl, "Animation not created");
|
|
NazaraAssert(m_impl->type == AnimationType::Skeletal, "Animation is not skeletal");
|
|
NazaraAssert(targetSkeleton && targetSkeleton->IsValid(), "Invalid skeleton");
|
|
NazaraAssert(targetSkeleton->GetJointCount() == m_impl->jointCount, "Skeleton joint does not match animation joint count");
|
|
NazaraAssert(frameA < m_impl->frameCount, "FrameA is out of range");
|
|
NazaraAssert(frameB < m_impl->frameCount, "FrameB is out of range");
|
|
|
|
Joint* joints = targetSkeleton->GetJoints();
|
|
for (std::size_t i = 0; i < m_impl->jointCount; ++i)
|
|
{
|
|
const SequenceJoint& sequenceJointA = m_impl->sequenceJoints[frameA*m_impl->jointCount + i];
|
|
const SequenceJoint& sequenceJointB = m_impl->sequenceJoints[frameB*m_impl->jointCount + i];
|
|
|
|
Joint& joint = joints[i];
|
|
joint.SetPosition(Vector3f::Lerp(sequenceJointA.position, sequenceJointB.position, interpolation), Node::Invalidation::DontInvalidate);
|
|
joint.SetRotation(Quaternionf::Slerp(sequenceJointA.rotation, sequenceJointB.rotation, interpolation), Node::Invalidation::DontInvalidate);
|
|
joint.SetScale(Vector3f::Lerp(sequenceJointA.scale, sequenceJointB.scale, interpolation), Node::Invalidation::DontInvalidate);
|
|
}
|
|
|
|
targetSkeleton->GetRootJoint()->Invalidate();
|
|
}
|
|
|
|
bool Animation::CreateSkeletal(std::size_t frameCount, std::size_t jointCount)
|
|
{
|
|
NazaraAssert(frameCount > 0, "Frame count must be over zero");
|
|
NazaraAssert(jointCount > 0, "Frame count must be over zero");
|
|
|
|
Destroy();
|
|
|
|
m_impl = std::make_unique<AnimationImpl>();
|
|
m_impl->frameCount = frameCount;
|
|
m_impl->jointCount = jointCount;
|
|
m_impl->sequenceJoints.resize(frameCount*jointCount);
|
|
m_impl->type = AnimationType::Skeletal;
|
|
|
|
return true;
|
|
}
|
|
|
|
void Animation::Destroy()
|
|
{
|
|
m_impl.reset();
|
|
}
|
|
|
|
std::size_t Animation::GetFrameCount() const
|
|
{
|
|
NazaraAssert(m_impl, "Animation not created");
|
|
|
|
return m_impl->frameCount;
|
|
}
|
|
|
|
std::size_t Animation::GetJointCount() const
|
|
{
|
|
NazaraAssert(m_impl, "Animation not created");
|
|
|
|
return m_impl->jointCount;
|
|
}
|
|
|
|
Sequence* Animation::GetSequence(std::string_view sequenceName)
|
|
{
|
|
NazaraAssert(m_impl, "Animation not created");
|
|
|
|
auto it = m_impl->sequenceMap.find(sequenceName);
|
|
if (it == m_impl->sequenceMap.end())
|
|
{
|
|
NazaraError("sequence not found");
|
|
return nullptr;
|
|
}
|
|
|
|
return &m_impl->sequences[it->second];
|
|
}
|
|
|
|
Sequence* Animation::GetSequence(std::size_t index)
|
|
{
|
|
NazaraAssert(m_impl, "Animation not created");
|
|
NazaraAssert(index < m_impl->sequences.size(), "Sequence index out of range");
|
|
|
|
return &m_impl->sequences[index];
|
|
}
|
|
|
|
const Sequence* Animation::GetSequence(std::string_view sequenceName) const
|
|
{
|
|
NazaraAssert(m_impl, "Animation not created");
|
|
|
|
auto it = m_impl->sequenceMap.find(sequenceName);
|
|
if (it == m_impl->sequenceMap.end())
|
|
{
|
|
NazaraError("sequence not found");
|
|
return nullptr;
|
|
}
|
|
|
|
return &m_impl->sequences[it->second];
|
|
}
|
|
|
|
const Sequence* Animation::GetSequence(std::size_t index) const
|
|
{
|
|
NazaraAssert(m_impl, "Animation not created");
|
|
NazaraAssert(index < m_impl->sequences.size(), "Sequence index out of range");
|
|
|
|
return &m_impl->sequences[index];
|
|
}
|
|
|
|
std::size_t Animation::GetSequenceCount() const
|
|
{
|
|
NazaraAssert(m_impl, "Animation not created");
|
|
|
|
return static_cast<std::size_t>(m_impl->sequences.size());
|
|
}
|
|
|
|
std::size_t Animation::GetSequenceIndex(std::string_view sequenceName) const
|
|
{
|
|
NazaraAssert(m_impl, "Animation not created");
|
|
|
|
auto it = m_impl->sequenceMap.find(sequenceName);
|
|
if (it == m_impl->sequenceMap.end())
|
|
{
|
|
NazaraError("sequence not found");
|
|
return 0xFFFFFFFF;
|
|
}
|
|
|
|
return it->second;
|
|
}
|
|
|
|
SequenceJoint* Animation::GetSequenceJoints(std::size_t frameIndex)
|
|
{
|
|
NazaraAssert(m_impl, "Animation not created");
|
|
NazaraAssert(m_impl->type == AnimationType::Skeletal, "Animation is not skeletal");
|
|
|
|
return &m_impl->sequenceJoints[frameIndex*m_impl->jointCount];
|
|
}
|
|
|
|
const SequenceJoint* Animation::GetSequenceJoints(std::size_t frameIndex) const
|
|
{
|
|
NazaraAssert(m_impl, "Animation not created");
|
|
NazaraAssert(m_impl->type == AnimationType::Skeletal, "Animation is not skeletal");
|
|
|
|
return &m_impl->sequenceJoints[frameIndex*m_impl->jointCount];
|
|
}
|
|
|
|
AnimationType Animation::GetType() const
|
|
{
|
|
NazaraAssert(m_impl, "Animation not created");
|
|
|
|
return m_impl->type;
|
|
}
|
|
|
|
bool Animation::HasSequence(std::string_view sequenceName) const
|
|
{
|
|
NazaraAssert(m_impl, "Animation not created");
|
|
|
|
return m_impl->sequenceMap.find(sequenceName) != m_impl->sequenceMap.end();
|
|
}
|
|
|
|
bool Animation::HasSequence(std::size_t index) const
|
|
{
|
|
NazaraAssert(m_impl, "Animation not created");
|
|
|
|
return index >= m_impl->sequences.size();
|
|
}
|
|
|
|
bool Animation::IsValid() const
|
|
{
|
|
return m_impl != nullptr;
|
|
}
|
|
|
|
void Animation::RemoveSequence(std::string_view identifier)
|
|
{
|
|
NazaraAssert(m_impl, "Animation not created");
|
|
|
|
auto it = m_impl->sequenceMap.find(identifier);
|
|
if (it == m_impl->sequenceMap.end())
|
|
{
|
|
NazaraError("sequence not found");
|
|
return;
|
|
}
|
|
|
|
RemoveSequence(it->second);
|
|
m_impl->sequenceMap.erase(it);
|
|
}
|
|
|
|
void Animation::RemoveSequence(std::size_t index)
|
|
{
|
|
NazaraAssert(m_impl, "Animation not created");
|
|
NazaraAssert(index < m_impl->sequences.size(), "Sequence index out of range");
|
|
|
|
m_impl->sequences.erase(m_impl->sequences.begin() + index);
|
|
|
|
// Shift indices
|
|
for (auto& it : m_impl->sequenceMap)
|
|
{
|
|
if (it.second > index)
|
|
it.second--;
|
|
}
|
|
}
|
|
|
|
Animation& Animation::operator=(Animation&&) noexcept = default;
|
|
|
|
std::shared_ptr<Animation> Animation::LoadFromFile(const std::filesystem::path& filePath, const AnimationParams& params)
|
|
{
|
|
Core* core = Core::Instance();
|
|
NazaraAssert(core, "Core module has not been initialized");
|
|
|
|
return core->GetAnimationLoader().LoadFromFile(filePath, params);
|
|
}
|
|
|
|
std::shared_ptr<Animation> Animation::LoadFromMemory(const void* data, std::size_t size, const AnimationParams& params)
|
|
{
|
|
Core* core = Core::Instance();
|
|
NazaraAssert(core, "Core module has not been initialized");
|
|
|
|
return core->GetAnimationLoader().LoadFromMemory(data, size, params);
|
|
}
|
|
|
|
std::shared_ptr<Animation> Animation::LoadFromStream(Stream& stream, const AnimationParams& params)
|
|
{
|
|
Core* core = Core::Instance();
|
|
NazaraAssert(core, "Core module has not been initialized");
|
|
|
|
return core->GetAnimationLoader().LoadFromStream(stream, params);
|
|
}
|
|
}
|