diff --git a/include/Nazara/Graphics/SkeletalModel.hpp b/include/Nazara/Graphics/SkeletalModel.hpp new file mode 100644 index 000000000..7938ba4c4 --- /dev/null +++ b/include/Nazara/Graphics/SkeletalModel.hpp @@ -0,0 +1,88 @@ +// Copyright (C) 2014 Jérôme Leclercq +// This file is part of the "Nazara Engine - Graphics module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#pragma once + +#ifndef NAZARA_SKELETALMODEL_HPP +#define NAZARA_SKELETALMODEL_HPP + +#include +#include +#include +#include +#include +#include +#include +#include + +struct NAZARA_API NzSkeletalModelParameters : public NzModelParameters +{ + bool loadAnimation = true; + NzAnimationParams animation; + + bool IsValid() const; +}; + +class NzSkeletalModel; + +using NzSkeletalModelLoader = NzResourceLoader; + +class NAZARA_API NzSkeletalModel : public NzModel, NzUpdatable +{ + friend NzSkeletalModelLoader; + //friend class NzScene; + + public: + NzSkeletalModel(); + NzSkeletalModel(const NzSkeletalModel& model); + NzSkeletalModel(NzSkeletalModel&& model); + ~NzSkeletalModel(); + + void AddToRenderQueue(NzAbstractRenderQueue* renderQueue) const override; + void AdvanceAnimation(float elapsedTime); + + void EnableAnimation(bool animation); + + NzAnimation* GetAnimation() const; + NzSkeleton* GetSkeleton(); + const NzSkeleton* GetSkeleton() const; + + bool HasAnimation() const; + + bool IsAnimated() const; + bool IsAnimationEnabled() const; + bool IsDrawable() const; + + bool LoadFromFile(const NzString& filePath, const NzSkeletalModelParameters& params = NzSkeletalModelParameters()); + bool LoadFromMemory(const void* data, std::size_t size, const NzSkeletalModelParameters& params = NzSkeletalModelParameters()); + bool LoadFromStream(NzInputStream& stream, const NzSkeletalModelParameters& params = NzSkeletalModelParameters()); + + void Reset(); + + bool SetAnimation(NzAnimation* animation); + void SetMesh(NzMesh* mesh) override; + bool SetSequence(const NzString& sequenceName); + void SetSequence(unsigned int sequenceIndex); + + NzSkeletalModel& operator=(const NzSkeletalModel& node); + NzSkeletalModel& operator=(NzSkeletalModel&& node); + + private: + void Register() override; + void Unregister() override; + void Update() override; + void UpdateBoundingVolume() const; + + NzAnimationRef m_animation; + NzSkeleton m_skeleton; + const NzSequence* m_currentSequence; + bool m_animationEnabled; + float m_interpolation; + unsigned int m_currentFrame; + unsigned int m_nextFrame; + + static NzSkeletalModelLoader::LoaderList s_loaders; +}; + +#endif // NAZARA_SKELETALMODEL_HPP diff --git a/src/Nazara/Graphics/Loaders/Mesh/Loader.cpp b/src/Nazara/Graphics/Loaders/Mesh/Loader.cpp index d64597248..2c2d5e514 100644 --- a/src/Nazara/Graphics/Loaders/Mesh/Loader.cpp +++ b/src/Nazara/Graphics/Loaders/Mesh/Loader.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -32,11 +33,71 @@ namespace } if (mesh->IsAnimable()) + { + NazaraError("Can't load animated mesh into static model"); + return false; + } + + // Nous ne pouvons plus avoir recours au smart pointeur à partir d'ici si nous voulons être exception-safe + NzMesh* meshPtr = mesh.get(); + + model->Reset(); + model->SetMesh(meshPtr); + mesh.release(); + + if (parameters.loadMaterials) + { + unsigned int matCount = model->GetMaterialCount(); + + for (unsigned int i = 0; i < matCount; ++i) + { + NzString mat = meshPtr->GetMaterial(i); + if (!mat.IsEmpty()) + { + std::unique_ptr material(new NzMaterial); + material->SetPersistent(false); + + if (material->LoadFromFile(mat, parameters.material)) + { + model->SetMaterial(i, material.get()); + material.release(); + } + else + NazaraWarning("Failed to load material #" + NzString::Number(i)); + } + } + } + + return true; + } + + nzTernary CheckAnimated(NzInputStream& stream, const NzSkeletalModelParameters& parameters) + { + NazaraUnused(stream); + NazaraUnused(parameters); + + return nzTernary_Unknown; + } + + bool LoadAnimated(NzSkeletalModel* model, NzInputStream& stream, const NzSkeletalModelParameters& parameters) + { + NazaraUnused(parameters); + + std::unique_ptr mesh(new NzMesh); + mesh->SetPersistent(false); + if (!mesh->LoadFromStream(stream, parameters.mesh)) + { + NazaraError("Failed to load model mesh"); + return false; + } + + if (!mesh->IsAnimable()) { NazaraError("Can't load static mesh into animated model"); return false; } + // Nous ne pouvons plus avoir recours au smart pointeur à partir d'ici si nous voulons être exception-safe NzMesh* meshPtr = mesh.get(); @@ -74,9 +135,11 @@ namespace void NzLoaders_Mesh_Register() { NzModelLoader::RegisterLoader(NzMeshLoader::IsExtensionSupported, CheckStatic, LoadStatic); + NzSkeletalModelLoader::RegisterLoader(NzMeshLoader::IsExtensionSupported, CheckAnimated, LoadAnimated); } void NzLoaders_Mesh_Unregister() { NzModelLoader::UnregisterLoader(NzMeshLoader::IsExtensionSupported, CheckStatic, LoadStatic); + NzSkeletalModelLoader::UnregisterLoader(NzMeshLoader::IsExtensionSupported, CheckAnimated, LoadAnimated); } diff --git a/src/Nazara/Graphics/SkeletalModel.cpp b/src/Nazara/Graphics/SkeletalModel.cpp new file mode 100644 index 000000000..b7fe39f98 --- /dev/null +++ b/src/Nazara/Graphics/SkeletalModel.cpp @@ -0,0 +1,357 @@ +// Copyright (C) 2014 Jérôme Leclercq +// This file is part of the "Nazara Engine - Graphics module" +// For conditions of distribution and use, see copyright notice in Config.hpp + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +bool NzSkeletalModelParameters::IsValid() const +{ + if (!NzModelParameters::IsValid()) + return false; + + if (loadAnimation && !animation.IsValid()) + return false; + + return true; +} + +NzSkeletalModel::NzSkeletalModel() : +m_currentSequence(nullptr), +m_animationEnabled(true) +{ +} + +NzSkeletalModel::NzSkeletalModel(const NzSkeletalModel& model) : +NzModel(model), +m_skeleton(model.m_skeleton), +m_currentSequence(model.m_currentSequence), +m_animationEnabled(model.m_animationEnabled), +m_interpolation(model.m_interpolation), +m_currentFrame(model.m_currentFrame), +m_nextFrame(model.m_nextFrame) +{ +} + +NzSkeletalModel::~NzSkeletalModel() +{ + Reset(); +} + +void NzSkeletalModel::AddToRenderQueue(NzAbstractRenderQueue* renderQueue) const +{ + const NzMatrix4f& transformMatrix = GetTransformMatrix(); + + unsigned int submeshCount = m_mesh->GetSubMeshCount(); + for (unsigned int i = 0; i < submeshCount; ++i) + { + const NzSkeletalMesh* mesh = static_cast(m_mesh->GetSubMesh(i)); + const NzMaterial* material = m_materials[mesh->GetMaterialIndex()]; + + NzMeshData meshData; + meshData.indexBuffer = mesh->GetIndexBuffer(); + meshData.primitiveMode = mesh->GetPrimitiveMode(); + meshData.vertexBuffer = NzSkinningManager::GetBuffer(mesh, &m_skeleton); + + renderQueue->AddMesh(material, meshData, m_skeleton.GetAABB(), transformMatrix); + } +} + +void NzSkeletalModel::AdvanceAnimation(float elapsedTime) +{ + #if NAZARA_GRAPHICS_SAFE + if (!m_animation) + { + NazaraError("Model has no animation"); + return; + } + #endif + + m_interpolation += m_currentSequence->frameRate * elapsedTime; + while (m_interpolation > 1.f) + { + m_interpolation -= 1.f; + + unsigned lastFrame = m_currentSequence->firstFrame + m_currentSequence->frameCount - 1; + if (m_nextFrame+1 > lastFrame) + { + if (m_animation->IsLoopPointInterpolationEnabled()) + { + m_currentFrame = m_nextFrame; + m_nextFrame = m_currentSequence->firstFrame; + } + else + { + m_currentFrame = m_currentSequence->firstFrame; + m_nextFrame = m_currentFrame+1; + } + } + else + { + m_currentFrame = m_nextFrame; + m_nextFrame++; + } + } + + m_animation->AnimateSkeleton(&m_skeleton, m_currentFrame, m_nextFrame, m_interpolation); + + InvalidateBoundingVolume(); +} + +void NzSkeletalModel::EnableAnimation(bool animation) +{ + m_animationEnabled = animation; +} + +NzAnimation* NzSkeletalModel::GetAnimation() const +{ + return m_animation; +} + +NzSkeleton* NzSkeletalModel::GetSkeleton() +{ + InvalidateBoundingVolume(); + + return &m_skeleton; +} + +const NzSkeleton* NzSkeletalModel::GetSkeleton() const +{ + return &m_skeleton; +} + +bool NzSkeletalModel::HasAnimation() const +{ + return m_animation != nullptr; +} + +bool NzSkeletalModel::IsAnimated() const +{ + return true; +} + +bool NzSkeletalModel::IsAnimationEnabled() const +{ + return m_animationEnabled; +} + +bool NzSkeletalModel::IsDrawable() const +{ + return m_mesh != nullptr && m_mesh->GetSubMeshCount() >= 1; +} + +bool NzSkeletalModel::LoadFromFile(const NzString& filePath, const NzSkeletalModelParameters& params) +{ + return NzSkeletalModelLoader::LoadFromFile(this, filePath, params); +} + +bool NzSkeletalModel::LoadFromMemory(const void* data, std::size_t size, const NzSkeletalModelParameters& params) +{ + return NzSkeletalModelLoader::LoadFromMemory(this, data, size, params); +} + +bool NzSkeletalModel::LoadFromStream(NzInputStream& stream, const NzSkeletalModelParameters& params) +{ + return NzSkeletalModelLoader::LoadFromStream(this, stream, params); +} + +void NzSkeletalModel::Reset() +{ + NzModel::Reset(); + + m_skeleton.Destroy(); + + if (m_scene) + m_scene->UnregisterForUpdate(this); +} + +bool NzSkeletalModel::SetAnimation(NzAnimation* animation) +{ + #if NAZARA_GRAPHICS_SAFE + if (!m_mesh) + { + NazaraError("Model has no mesh"); + return false; + } + + if (animation) + { + if (!animation->IsValid()) + { + NazaraError("Invalid animation"); + return false; + } + + if (animation->GetType() != m_mesh->GetAnimationType()) + { + NazaraError("Animation type must match mesh animation type"); + return false; + } + + if (animation->GetJointCount() != m_mesh->GetJointCount()) + { + NazaraError("Animation joint count must match mesh joint count"); + return false; + } + } + #endif + + m_animation = animation; + if (m_animation) + { + m_currentFrame = 0; + m_interpolation = 0.f; + + SetSequence(0); + + if (m_scene) + m_scene->RegisterForUpdate(this); + } + else if (m_scene) + m_scene->UnregisterForUpdate(this); + + return true; +} + +void NzSkeletalModel::SetMesh(NzMesh* mesh) +{ + #if NAZARA_GRAPHICS_SAFE + if (mesh && mesh->GetAnimationType() != nzAnimationType_Skeletal) + { + NazaraError("Mesh animation type must be skeletal"); + return; + } + #endif + + NzModel::SetMesh(mesh); + + if (m_mesh) + { + if (m_animation && m_animation->GetJointCount() != m_mesh->GetJointCount()) + { + NazaraWarning("Animation joint count is not matching new mesh joint count, disabling animation..."); + SetAnimation(nullptr); + } + + m_skeleton = *m_mesh->GetSkeleton(); // Copie du squelette template + } +} + +bool NzSkeletalModel::SetSequence(const NzString& sequenceName) +{ + ///TODO: Rendre cette erreur "safe" avec le nouveau système de gestions d'erreur (No-log) + #if NAZARA_GRAPHICS_SAFE + if (!m_animation) + { + NazaraError("Model has no animation"); + return false; + } + #endif + + const NzSequence* currentSequence = m_animation->GetSequence(sequenceName); + if (!currentSequence) + { + NazaraError("Sequence not found"); + return false; + } + + m_currentSequence = currentSequence; + m_nextFrame = m_currentSequence->firstFrame; + + return true; +} + +void NzSkeletalModel::SetSequence(unsigned int sequenceIndex) +{ + #if NAZARA_GRAPHICS_SAFE + if (!m_animation) + { + NazaraError("Model has no animation"); + return; + } + #endif + + const NzSequence* currentSequence = m_animation->GetSequence(sequenceIndex); + #if NAZARA_GRAPHICS_SAFE + if (!currentSequence) + { + NazaraError("Sequence not found"); + return; + } + #endif + + m_currentSequence = currentSequence; + m_nextFrame = m_currentSequence->firstFrame; +} + +NzSkeletalModel& NzSkeletalModel::operator=(const NzSkeletalModel& node) +{ + NzSkeletalModel::operator=(node); + + m_animation = node.m_animation; + m_animationEnabled = node.m_animationEnabled; + m_currentFrame = node.m_currentFrame; + m_currentSequence = node.m_currentSequence; + m_interpolation = node.m_interpolation; + m_nextFrame = node.m_nextFrame; + m_skeleton = node.m_skeleton; + + return *this; +} + +NzSkeletalModel& NzSkeletalModel::operator=(NzSkeletalModel&& node) +{ + NzModel::operator=(node); + + // Ressources + m_animation = std::move(node.m_animation); + m_skeleton = std::move(node.m_skeleton); + + // Paramètres + m_animationEnabled = node.m_animationEnabled; + m_currentFrame = node.m_currentFrame; + m_currentSequence = node.m_currentSequence; + m_interpolation = node.m_interpolation; + m_nextFrame = node.m_nextFrame; + + return *this; +} + +void NzSkeletalModel::Register() +{ + if (m_animation) + m_scene->RegisterForUpdate(this); +} + +void NzSkeletalModel::Unregister() +{ + m_scene->UnregisterForUpdate(this); +} + +void NzSkeletalModel::Update() +{ + if (m_animationEnabled && m_animation) + AdvanceAnimation(m_scene->GetUpdateTime()); +} + +void NzSkeletalModel::UpdateBoundingVolume() const +{ + if (m_boundingVolume.IsNull()) + m_boundingVolume.Set(m_skeleton.GetAABB()); + + if (!m_transformMatrixUpdated) + UpdateTransformMatrix(); + + m_boundingVolume.Update(m_transformMatrix); + m_boundingVolumeUpdated = true; +} + +NzSkeletalModelLoader::LoaderList NzSkeletalModel::s_loaders;