feat: fix scene loading that it loads from disk if not already loaded
This commit is contained in:
@@ -58,6 +58,8 @@ set(SRC_FILES
|
|||||||
"src/FS/AssetFS.cpp"
|
"src/FS/AssetFS.cpp"
|
||||||
"src/FS/Manifest.cpp"
|
"src/FS/Manifest.cpp"
|
||||||
|
|
||||||
|
"src/Assets/AssetManager.cpp"
|
||||||
|
|
||||||
"src/Serialization/SceneSerializer.cpp"
|
"src/Serialization/SceneSerializer.cpp"
|
||||||
"src/Serialization/ComponentRegistry.cpp"
|
"src/Serialization/ComponentRegistry.cpp"
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
#ifndef DESTRUM_ASSETMANAGER_H
|
||||||
|
#define DESTRUM_ASSETMANAGER_H
|
||||||
|
|
||||||
|
#include <filesystem>
|
||||||
|
#include <string_view>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <destrum/Graphics/SkeletalAnimation.h>
|
||||||
|
#include <destrum/Graphics/Skeleton.h>
|
||||||
|
#include <destrum/Assets/AssetReference.h>
|
||||||
|
#include <destrum/Singleton.h>
|
||||||
|
#include <spdlog/spdlog.h>
|
||||||
|
#include <destrum/Util/ModelDoc.h>
|
||||||
|
|
||||||
|
class AssetManager final : public Singleton<AssetManager> {
|
||||||
|
public:
|
||||||
|
friend class Singleton<AssetManager>;
|
||||||
|
|
||||||
|
[[nodiscard]] std::filesystem::path Resolve(std::string_view reference) const;
|
||||||
|
|
||||||
|
[[nodiscard]] ModelDoc::ModelAsset LoadModel(
|
||||||
|
std::string_view reference,
|
||||||
|
const ModelDoc::LoadOptions& options = {}) const;
|
||||||
|
|
||||||
|
[[nodiscard]] std::vector<SkeletalAnimation> LoadAnimationClips(
|
||||||
|
std::string_view reference,
|
||||||
|
const Skeleton& targetSkeleton) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
AssetManager() = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // DESTRUM_ASSETMANAGER_H
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
#ifndef DESTRUM_ASSETREFERENCE_H
|
||||||
|
#define DESTRUM_ASSETREFERENCE_H
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
struct AssetReference {
|
||||||
|
std::string path;
|
||||||
|
std::string subresource;
|
||||||
|
|
||||||
|
[[nodiscard]] bool empty() const { return path.empty(); }
|
||||||
|
|
||||||
|
[[nodiscard]] std::string cacheKey() const {
|
||||||
|
return path.empty() ? std::string{} : path + "#" + subresource;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] static std::optional<AssetReference> fromCacheKey(
|
||||||
|
std::string_view key)
|
||||||
|
{
|
||||||
|
const auto separator = key.rfind('#');
|
||||||
|
if (separator == std::string_view::npos ||
|
||||||
|
separator == 0 ||
|
||||||
|
separator + 1 >= key.size()) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
return AssetReference{
|
||||||
|
.path = std::string{key.substr(0, separator)},
|
||||||
|
.subresource = std::string{key.substr(separator + 1)},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // DESTRUM_ASSETREFERENCE_H
|
||||||
@@ -28,6 +28,7 @@ public:
|
|||||||
void Deserialize(const nlohmann::json& data) override;
|
void Deserialize(const nlohmann::json& data) override;
|
||||||
void ImGuiInspector() override;
|
void ImGuiInspector() override;
|
||||||
|
|
||||||
|
// The source reference is carried by the AssetManager-loaded clip.
|
||||||
void addClip(std::shared_ptr<SkeletalAnimation> clip);
|
void addClip(std::shared_ptr<SkeletalAnimation> clip);
|
||||||
void play(const std::string& name, float blendTime = 0.f);
|
void play(const std::string& name, float blendTime = 0.f);
|
||||||
void stop();
|
void stop();
|
||||||
@@ -41,10 +42,15 @@ public:
|
|||||||
return &m_skeleton;
|
return &m_skeleton;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A source reference is required when this Animator is saved to a scene.
|
||||||
void setSkeleton(Skeleton skeleton) {
|
void setSkeleton(Skeleton skeleton) {
|
||||||
|
m_skeletonAsset = skeleton.assetReference;
|
||||||
m_skeleton = std::move(skeleton);
|
m_skeleton = std::move(skeleton);
|
||||||
|
m_referencesResolved = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ResolveReferences(const ObjectMap& objects) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct PlaybackState {
|
struct PlaybackState {
|
||||||
SkeletalAnimation* clip = nullptr;
|
SkeletalAnimation* clip = nullptr;
|
||||||
@@ -59,14 +65,22 @@ private:
|
|||||||
float m_blendT = 0.f;
|
float m_blendT = 0.f;
|
||||||
float m_blendDuration = 0.f;
|
float m_blendDuration = 0.f;
|
||||||
std::string m_currentClipName;
|
std::string m_currentClipName;
|
||||||
|
std::string m_previousClipName;
|
||||||
|
|
||||||
std::unordered_map<std::string, std::shared_ptr<SkeletalAnimation>> m_clips;
|
std::unordered_map<std::string, std::shared_ptr<SkeletalAnimation>> m_clips;
|
||||||
|
std::vector<std::string> m_clipOrder;
|
||||||
|
std::unordered_map<std::string, AssetReference> m_clipAssets;
|
||||||
|
AssetReference m_skeletonAsset;
|
||||||
|
bool m_referencesResolved{true};
|
||||||
|
|
||||||
std::vector<glm::mat4> computeJointMatrices(const Skeleton& skeleton);
|
std::vector<glm::mat4> computeJointMatrices(const Skeleton& skeleton);
|
||||||
|
|
||||||
glm::vec3 sampleTranslation(const SkeletalAnimation::Track& track, float t);
|
glm::vec3 sampleTranslation(const SkeletalAnimation::Track& track, float t);
|
||||||
glm::quat sampleRotation (const SkeletalAnimation::Track& track, float t);
|
glm::quat sampleRotation (const SkeletalAnimation::Track& track, float t);
|
||||||
glm::vec3 sampleScale (const SkeletalAnimation::Track& track, float t);
|
glm::vec3 sampleScale (const SkeletalAnimation::Track& track, float t);
|
||||||
|
|
||||||
|
void RestorePlaybackState(const nlohmann::json& data);
|
||||||
|
void LoadAssetReferences();
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // ANIMATOR_H
|
#endif // ANIMATOR_H
|
||||||
|
|||||||
@@ -28,10 +28,16 @@ public:
|
|||||||
void cleanup(GfxDevice& gfxDevice);
|
void cleanup(GfxDevice& gfxDevice);
|
||||||
|
|
||||||
MaterialID addMaterial(Material material);
|
MaterialID addMaterial(Material material);
|
||||||
|
MaterialID addSimpleColorMaterial(
|
||||||
|
glm::vec3 color,
|
||||||
|
std::string name = {});
|
||||||
MaterialID addSimpleTextureMaterial(ImageID textureID);
|
MaterialID addSimpleTextureMaterial(ImageID textureID);
|
||||||
[[nodiscard]] const Material& getMaterial(MaterialID id) const;
|
[[nodiscard]] const Material& getMaterial(MaterialID id) const;
|
||||||
[[nodiscard]] const std::string& getMaterialKey(MaterialID id) const;
|
[[nodiscard]] const std::string& getMaterialKey(MaterialID id) const;
|
||||||
[[nodiscard]] std::optional<MaterialID> findMaterialByKey(std::string_view key) const;
|
[[nodiscard]] std::optional<MaterialID> findMaterialByKey(std::string_view key) const;
|
||||||
|
[[nodiscard]] std::optional<MaterialID> findMaterialByName(
|
||||||
|
std::string_view name,
|
||||||
|
std::string_view sourceReference = {}) const;
|
||||||
|
|
||||||
[[nodiscard]] MaterialID getFreeMaterialId() const;
|
[[nodiscard]] MaterialID getFreeMaterialId() const;
|
||||||
[[nodiscard]] MaterialID getPlaceholderMaterialId() const;
|
[[nodiscard]] MaterialID getPlaceholderMaterialId() const;
|
||||||
|
|||||||
@@ -2,7 +2,9 @@
|
|||||||
#define MATERIAL_H
|
#define MATERIAL_H
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
#include <glm/glm.hpp>
|
#include <glm/glm.hpp>
|
||||||
|
#include <destrum/Assets/AssetReference.h>
|
||||||
|
|
||||||
struct alignas(16) MaterialData {
|
struct alignas(16) MaterialData {
|
||||||
alignas(16) glm::vec4 baseColor;
|
alignas(16) glm::vec4 baseColor;
|
||||||
@@ -22,6 +24,19 @@ enum class TextureFilteringMode : std::uint32_t {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct Material {
|
struct Material {
|
||||||
|
[[nodiscard]] static Material SimpleColor(
|
||||||
|
glm::vec3 color,
|
||||||
|
std::string materialName = {})
|
||||||
|
{
|
||||||
|
Material material{};
|
||||||
|
material.baseColor = color;
|
||||||
|
material.metallicFactor = 0.0f;
|
||||||
|
material.roughnessFactor = 1.0f;
|
||||||
|
material.emissiveFactor = 0.0f;
|
||||||
|
material.name = std::move(materialName);
|
||||||
|
return material;
|
||||||
|
}
|
||||||
|
|
||||||
glm::vec3 baseColor{1.f, 1.f, 1.f};
|
glm::vec3 baseColor{1.f, 1.f, 1.f};
|
||||||
float metallicFactor{0.f};
|
float metallicFactor{0.f};
|
||||||
float roughnessFactor{0.7f};
|
float roughnessFactor{0.7f};
|
||||||
@@ -35,6 +50,7 @@ struct Material {
|
|||||||
// ImageId emissiveTexture{NULL_IMAGE_ID};
|
// ImageId emissiveTexture{NULL_IMAGE_ID};
|
||||||
|
|
||||||
std::string name;
|
std::string name;
|
||||||
|
AssetReference assetReference;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif //MATERIAL_H
|
#endif //MATERIAL_H
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
class GfxDevice;
|
class GfxDevice;
|
||||||
|
|
||||||
@@ -44,6 +45,10 @@ public:
|
|||||||
|
|
||||||
ImageID addImageToCache(GPUImage image);
|
ImageID addImageToCache(GPUImage image);
|
||||||
|
|
||||||
|
// Imports a file-backed model and uploads its mesh/material resources.
|
||||||
|
// References are keyed as "source#subresource" in the resource caches.
|
||||||
|
void loadModelAsset(GfxDevice& gfxDevice, std::string_view sourceReference);
|
||||||
|
|
||||||
[[nodiscard]] const GPUImage& getImage(ImageID id) const;
|
[[nodiscard]] const GPUImage& getImage(ImageID id) const;
|
||||||
|
|
||||||
[[nodiscard]] ImageID getWhiteTextureID() const { return whiteImageId; }
|
[[nodiscard]] ImageID getWhiteTextureID() const { return whiteImageId; }
|
||||||
@@ -72,4 +77,4 @@ private:
|
|||||||
ImageID defaultNormalImageId{NULL_IMAGE_ID};
|
ImageID defaultNormalImageId{NULL_IMAGE_ID};
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
#include <destrum/Graphics/Resources/Buffer.h>
|
#include <destrum/Graphics/Resources/Buffer.h>
|
||||||
#include <destrum/Graphics/Frustum.h>
|
#include <destrum/Graphics/Frustum.h>
|
||||||
#include <destrum/Graphics/Skeleton.h>
|
#include <destrum/Graphics/Skeleton.h>
|
||||||
|
#include <destrum/Assets/AssetReference.h>
|
||||||
|
|
||||||
// ─── CPU Mesh ─────────────────────────────────────────────────────────────────
|
// ─── CPU Mesh ─────────────────────────────────────────────────────────────────
|
||||||
struct CPUMesh {
|
struct CPUMesh {
|
||||||
@@ -36,6 +37,7 @@ struct CPUMesh {
|
|||||||
std::vector<SkinningData> skinningData; // empty if no skeleton
|
std::vector<SkinningData> skinningData; // empty if no skeleton
|
||||||
|
|
||||||
std::string name;
|
std::string name;
|
||||||
|
AssetReference assetReference;
|
||||||
|
|
||||||
glm::vec3 minPos;
|
glm::vec3 minPos;
|
||||||
glm::vec3 maxPos;
|
glm::vec3 maxPos;
|
||||||
@@ -115,4 +117,4 @@ inline std::vector<std::uint32_t> indices = {
|
|||||||
|
|
||||||
} // namespace CubeMesh
|
} // namespace CubeMesh
|
||||||
|
|
||||||
#endif // MESH_H
|
#endif // MESH_H
|
||||||
|
|||||||
@@ -7,8 +7,11 @@
|
|||||||
|
|
||||||
#include <glm/gtc/quaternion.hpp>
|
#include <glm/gtc/quaternion.hpp>
|
||||||
#include <glm/vec3.hpp>
|
#include <glm/vec3.hpp>
|
||||||
|
#include <destrum/Assets/AssetReference.h>
|
||||||
|
|
||||||
struct SkeletalAnimation {
|
struct SkeletalAnimation {
|
||||||
|
AssetReference assetReference;
|
||||||
|
|
||||||
struct Keyframe {
|
struct Keyframe {
|
||||||
float time;
|
float time;
|
||||||
glm::vec3 translation;
|
glm::vec3 translation;
|
||||||
@@ -32,4 +35,4 @@ struct SkeletalAnimation {
|
|||||||
const std::vector<std::string>& getEventsForFrame(int frame) const;
|
const std::vector<std::string>& getEventsForFrame(int frame) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // SKELETALANIMATION_H
|
#endif // SKELETALANIMATION_H
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
#include <glm/gtc/matrix_transform.hpp>
|
#include <glm/gtc/matrix_transform.hpp>
|
||||||
|
|
||||||
#include <destrum/Graphics/ids.h>
|
#include <destrum/Graphics/ids.h>
|
||||||
|
#include <destrum/Assets/AssetReference.h>
|
||||||
|
|
||||||
struct Joint {
|
struct Joint {
|
||||||
JointId id{NULL_JOINT_ID};
|
JointId id{NULL_JOINT_ID};
|
||||||
@@ -19,6 +20,8 @@ struct Joint {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct Skeleton {
|
struct Skeleton {
|
||||||
|
AssetReference assetReference;
|
||||||
|
|
||||||
struct JointNode {
|
struct JointNode {
|
||||||
JointId id{NULL_JOINT_ID};
|
JointId id{NULL_JOINT_ID};
|
||||||
std::vector<JointId> children;
|
std::vector<JointId> children;
|
||||||
@@ -55,4 +58,4 @@ inline void buildParentIndex(Skeleton& skeleton) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // SKELETON_H
|
#endif // SKELETON_H
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
#include <glm/gtc/type_ptr.hpp>
|
#include <glm/gtc/type_ptr.hpp>
|
||||||
|
|
||||||
#include <destrum/Graphics/Resources/Mesh.h>
|
#include <destrum/Graphics/Resources/Mesh.h>
|
||||||
|
#include <destrum/Assets/AssetReference.h>
|
||||||
#include <destrum/Graphics/SkeletalAnimation.h>
|
#include <destrum/Graphics/SkeletalAnimation.h>
|
||||||
#include <destrum/Graphics/Skeleton.h>
|
#include <destrum/Graphics/Skeleton.h>
|
||||||
|
|
||||||
@@ -91,6 +92,7 @@ struct TextureInfo {
|
|||||||
|
|
||||||
struct MaterialInfo {
|
struct MaterialInfo {
|
||||||
std::string name;
|
std::string name;
|
||||||
|
AssetReference assetReference;
|
||||||
|
|
||||||
glm::vec4 baseColor{1.0f};
|
glm::vec4 baseColor{1.0f};
|
||||||
glm::vec3 emissiveColor{0.0f};
|
glm::vec3 emissiveColor{0.0f};
|
||||||
@@ -1018,6 +1020,24 @@ static ModelAsset LoadModel(const std::string& path, const LoadOptions& options
|
|||||||
CollectMergedPerNodeMeshes(scene, model.skeleton, options, model.primitives);
|
CollectMergedPerNodeMeshes(scene, model.skeleton, options, model.primitives);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
model.skeleton.assetReference.path = model.sourcePath;
|
||||||
|
for (std::size_t index = 0; index < model.primitives.size(); ++index) {
|
||||||
|
auto& primitive = model.primitives[index];
|
||||||
|
primitive.mesh.assetReference.path = model.sourcePath;
|
||||||
|
primitive.mesh.assetReference.subresource = primitive.sourceMeshIndex != InvalidIndex
|
||||||
|
? "mesh:" + std::to_string(primitive.sourceMeshIndex)
|
||||||
|
: "primitive:" + std::to_string(index);
|
||||||
|
}
|
||||||
|
for (std::size_t index = 0; index < model.materials.size(); ++index) {
|
||||||
|
model.materials[index].assetReference.path = model.sourcePath;
|
||||||
|
model.materials[index].assetReference.subresource = "material:" + std::to_string(index);
|
||||||
|
}
|
||||||
|
for (std::size_t index = 0; index < model.animations.size(); ++index) {
|
||||||
|
auto& animation = model.animations[index];
|
||||||
|
animation.assetReference.path = model.sourcePath;
|
||||||
|
animation.assetReference.subresource = "animation:" + std::to_string(index);
|
||||||
|
}
|
||||||
|
|
||||||
std::cout << "[ModelDoc] Loaded model: " << path
|
std::cout << "[ModelDoc] Loaded model: " << path
|
||||||
<< " | primitives: " << model.primitives.size()
|
<< " | primitives: " << model.primitives.size()
|
||||||
<< " | materials: " << model.materials.size()
|
<< " | materials: " << model.materials.size()
|
||||||
@@ -1121,7 +1141,13 @@ static const aiScene* LoadAnimationScene(
|
|||||||
Assimp::Importer importer;
|
Assimp::Importer importer;
|
||||||
const aiScene* scene = LoadAnimationScene(importer, path);
|
const aiScene* scene = LoadAnimationScene(importer, path);
|
||||||
|
|
||||||
return LoadAnimations(scene, targetSkeleton);
|
auto clips = LoadAnimations(scene, targetSkeleton);
|
||||||
|
for (std::size_t index = 0; index < clips.size(); ++index) {
|
||||||
|
auto& clip = clips[index];
|
||||||
|
clip.assetReference.path = path;
|
||||||
|
clip.assetReference.subresource = "animation:" + std::to_string(index);
|
||||||
|
}
|
||||||
|
return clips;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace ModelDoc
|
} // namespace ModelDoc
|
||||||
|
|||||||
@@ -0,0 +1,76 @@
|
|||||||
|
#include <destrum/Assets/AssetManager.h>
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
|
#include <destrum/FS/AssetFS.h>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
std::string ToReference(std::string_view reference) {
|
||||||
|
if (reference.empty()) {
|
||||||
|
throw std::invalid_argument("Asset reference cannot be empty");
|
||||||
|
}
|
||||||
|
return std::string{reference};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::filesystem::path AssetManager::Resolve(std::string_view reference) const {
|
||||||
|
const std::string value = ToReference(reference);
|
||||||
|
if (value.find("://") != std::string::npos) {
|
||||||
|
return AssetFS::GetInstance().GetFullPath(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::filesystem::path path{value};
|
||||||
|
if (path.is_absolute()) {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
throw std::invalid_argument(
|
||||||
|
"Non-virtual asset references must be absolute: " + value);
|
||||||
|
}
|
||||||
|
|
||||||
|
ModelDoc::ModelAsset AssetManager::LoadModel(
|
||||||
|
std::string_view reference,
|
||||||
|
const ModelDoc::LoadOptions& options) const
|
||||||
|
{
|
||||||
|
const std::string sourceReference = ToReference(reference);
|
||||||
|
ModelDoc::ModelAsset model = ModelDoc::LoadModel(
|
||||||
|
Resolve(sourceReference).generic_string(),
|
||||||
|
options);
|
||||||
|
|
||||||
|
model.sourcePath = sourceReference;
|
||||||
|
model.skeleton.assetReference.path = sourceReference;
|
||||||
|
for (std::size_t index = 0; index < model.primitives.size(); ++index) {
|
||||||
|
auto& primitive = model.primitives[index];
|
||||||
|
primitive.mesh.assetReference.path = sourceReference;
|
||||||
|
primitive.mesh.assetReference.subresource = primitive.sourceMeshIndex != ModelDoc::InvalidIndex
|
||||||
|
? "mesh:" + std::to_string(primitive.sourceMeshIndex)
|
||||||
|
: "primitive:" + std::to_string(index);
|
||||||
|
}
|
||||||
|
for (std::size_t index = 0; index < model.materials.size(); ++index) {
|
||||||
|
auto& material = model.materials[index];
|
||||||
|
material.assetReference.path = sourceReference;
|
||||||
|
material.assetReference.subresource = "material:" + std::to_string(index);
|
||||||
|
}
|
||||||
|
for (std::size_t index = 0; index < model.animations.size(); ++index) {
|
||||||
|
auto& animation = model.animations[index];
|
||||||
|
animation.assetReference.path = sourceReference;
|
||||||
|
animation.assetReference.subresource = "animation:" + std::to_string(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<SkeletalAnimation> AssetManager::LoadAnimationClips(
|
||||||
|
std::string_view reference,
|
||||||
|
const Skeleton& targetSkeleton) const
|
||||||
|
{
|
||||||
|
const std::string sourceReference = ToReference(reference);
|
||||||
|
auto clips = ModelDoc::LoadAnimationClips(
|
||||||
|
Resolve(sourceReference).generic_string(),
|
||||||
|
targetSkeleton);
|
||||||
|
for (std::size_t index = 0; index < clips.size(); ++index) {
|
||||||
|
auto& clip = clips[index];
|
||||||
|
clip.assetReference.path = sourceReference;
|
||||||
|
clip.assetReference.subresource = "animation:" + std::to_string(index);
|
||||||
|
}
|
||||||
|
return clips;
|
||||||
|
}
|
||||||
+242
-152
@@ -1,56 +1,45 @@
|
|||||||
#include <destrum/Components/Animator.h>
|
#include <destrum/Components/Animator.h>
|
||||||
|
#include <destrum/Assets/AssetManager.h>
|
||||||
#include <destrum/Graphics/Pipelines/SkinningPipeline.h>
|
#include <destrum/Graphics/Pipelines/SkinningPipeline.h>
|
||||||
#include <destrum/ObjectModel/GameObject.h>
|
#include <destrum/ObjectModel/GameObject.h>
|
||||||
|
#include "spdlog/spdlog.h"
|
||||||
#include <destrum/Util/DeltaTime.h>
|
#include <destrum/Util/DeltaTime.h>
|
||||||
|
|
||||||
#include <glm/gtc/matrix_transform.hpp>
|
#include <glm/gtc/matrix_transform.hpp>
|
||||||
#include <glm/gtc/quaternion.hpp>
|
#include <glm/gtc/quaternion.hpp>
|
||||||
|
#include <algorithm>
|
||||||
#include "spdlog/spdlog.h"
|
#include <optional>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
nlohmann::json Vec3Json(const glm::vec3& value) {
|
std::optional<std::size_t> ParseAnimationIndex(const AssetReference& asset)
|
||||||
return {value.x, value.y, value.z};
|
{
|
||||||
}
|
constexpr std::string_view prefix = "animation:";
|
||||||
|
if (asset.subresource.rfind(prefix, 0) != 0 ||
|
||||||
glm::vec3 ReadVec3(const nlohmann::json& value) {
|
asset.subresource.size() == prefix.size()) {
|
||||||
return {value.at(0).get<float>(), value.at(1).get<float>(), value.at(2).get<float>()};
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
nlohmann::json QuatJson(const glm::quat& value) {
|
try {
|
||||||
return {value.x, value.y, value.z, value.w};
|
return std::stoull(asset.subresource.substr(prefix.size()));
|
||||||
}
|
} catch (const std::exception&) {
|
||||||
|
return std::nullopt;
|
||||||
glm::quat ReadQuat(const nlohmann::json& value) {
|
|
||||||
return {
|
|
||||||
value.at(3).get<float>(),
|
|
||||||
value.at(0).get<float>(),
|
|
||||||
value.at(1).get<float>(),
|
|
||||||
value.at(2).get<float>()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
nlohmann::json Mat4Json(const glm::mat4& value) {
|
|
||||||
nlohmann::json result = nlohmann::json::array();
|
|
||||||
for (std::size_t column = 0; column < 4; ++column) {
|
|
||||||
for (std::size_t row = 0; row < 4; ++row) {
|
|
||||||
result.push_back(value[static_cast<glm::mat4::length_type>(column)]
|
|
||||||
[static_cast<glm::mat4::length_type>(row)]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
glm::mat4 ReadMat4(const nlohmann::json& value) {
|
std::string FormatAvailableClips(
|
||||||
glm::mat4 result{1.0f};
|
const std::vector<SkeletalAnimation>& clips)
|
||||||
for (std::size_t column = 0; column < 4; ++column) {
|
{
|
||||||
for (std::size_t row = 0; row < 4; ++row) {
|
std::ostringstream result;
|
||||||
result[static_cast<glm::mat4::length_type>(column)]
|
result << "[";
|
||||||
[static_cast<glm::mat4::length_type>(row)] =
|
for (std::size_t index = 0; index < clips.size(); ++index) {
|
||||||
value.at(column * 4 + row).get<float>();
|
if (index != 0) {
|
||||||
|
result << ", ";
|
||||||
}
|
}
|
||||||
|
result << '\'' << clips[index].name << '\'';
|
||||||
}
|
}
|
||||||
return result;
|
result << "]";
|
||||||
|
return result.str();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,60 +47,40 @@ Animator::Animator(GameObject& parent)
|
|||||||
: Component(parent, "Animator") {}
|
: Component(parent, "Animator") {}
|
||||||
|
|
||||||
nlohmann::json Animator::Serialize() const {
|
nlohmann::json Animator::Serialize() const {
|
||||||
nlohmann::json skeleton;
|
if (!m_skeleton.joints.empty() && m_skeletonAsset.empty()) {
|
||||||
skeleton["hierarchy"] = nlohmann::json::array();
|
throw std::runtime_error(
|
||||||
for (const auto& node : m_skeleton.hierarchy) {
|
"Animator cannot be saved without a skeleton asset reference");
|
||||||
skeleton["hierarchy"].push_back({
|
|
||||||
{"id", node.id},
|
|
||||||
{"children", node.children}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
skeleton["inverseBindMatrices"] = nlohmann::json::array();
|
|
||||||
for (const auto& matrix : m_skeleton.inverseBindMatrices) {
|
|
||||||
skeleton["inverseBindMatrices"].push_back(Mat4Json(matrix));
|
|
||||||
}
|
|
||||||
skeleton["joints"] = nlohmann::json::array();
|
|
||||||
for (const auto& joint : m_skeleton.joints) {
|
|
||||||
skeleton["joints"].push_back({
|
|
||||||
{"id", joint.id},
|
|
||||||
{"translation", Vec3Json(joint.localTranslation)},
|
|
||||||
{"rotation", QuatJson(joint.localRotation)},
|
|
||||||
{"scale", Vec3Json(joint.localScale)}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
skeleton["jointNames"] = m_skeleton.jointNames;
|
|
||||||
skeleton["parentIndex"] = m_skeleton.parentIndex;
|
|
||||||
skeleton["rootPreTransform"] = Mat4Json(m_skeleton.rootPreTransform);
|
|
||||||
|
|
||||||
nlohmann::json clips = nlohmann::json::array();
|
nlohmann::json animations = nlohmann::json::array();
|
||||||
|
const auto appendAnimation = [&animations, this](const std::string& name) {
|
||||||
|
const auto clipIt = m_clips.find(name);
|
||||||
|
if (clipIt == m_clips.end()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& clip = clipIt->second;
|
||||||
|
if (clip->assetReference.empty()) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
"Animator clip has no asset reference: " + name);
|
||||||
|
}
|
||||||
|
animations.push_back({
|
||||||
|
{"clip", name},
|
||||||
|
{"asset", {
|
||||||
|
{"path", clip->assetReference.path},
|
||||||
|
{"subresource", clip->assetReference.subresource}
|
||||||
|
}}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto& name : m_clipOrder) {
|
||||||
|
appendAnimation(name);
|
||||||
|
}
|
||||||
for (const auto& [name, clip] : m_clips) {
|
for (const auto& [name, clip] : m_clips) {
|
||||||
nlohmann::json clipJson{
|
(void)clip;
|
||||||
{"name", name},
|
if (std::find(m_clipOrder.begin(), m_clipOrder.end(), name) == m_clipOrder.end()) {
|
||||||
{"duration", clip->duration},
|
appendAnimation(name);
|
||||||
{"looped", clip->looped},
|
|
||||||
{"startFrame", clip->startFrame},
|
|
||||||
{"tracks", nlohmann::json::array()},
|
|
||||||
{"events", nlohmann::json::object()}
|
|
||||||
};
|
|
||||||
for (const auto& track : clip->tracks) {
|
|
||||||
nlohmann::json trackJson{
|
|
||||||
{"jointIndex", track.jointIndex},
|
|
||||||
{"keyframes", nlohmann::json::array()}
|
|
||||||
};
|
|
||||||
for (const auto& keyframe : track.keyframes) {
|
|
||||||
trackJson["keyframes"].push_back({
|
|
||||||
{"time", keyframe.time},
|
|
||||||
{"translation", Vec3Json(keyframe.translation)},
|
|
||||||
{"rotation", QuatJson(keyframe.rotation)},
|
|
||||||
{"scale", Vec3Json(keyframe.scale)}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
clipJson["tracks"].push_back(std::move(trackJson));
|
|
||||||
}
|
}
|
||||||
for (const auto& [frame, events] : clip->events) {
|
|
||||||
clipJson["events"][std::to_string(frame)] = events;
|
|
||||||
}
|
|
||||||
clips.push_back(std::move(clipJson));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto playback = [](const PlaybackState& state, const std::string& name) {
|
const auto playback = [](const PlaybackState& state, const std::string& name) {
|
||||||
@@ -123,10 +92,15 @@ nlohmann::json Animator::Serialize() const {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
{"skeleton", std::move(skeleton)},
|
{"assetReferences", {
|
||||||
{"clips", std::move(clips)},
|
{"skeleton", {
|
||||||
|
{"path", m_skeletonAsset.path},
|
||||||
|
{"subresource", m_skeletonAsset.subresource}
|
||||||
|
}},
|
||||||
|
{"animations", std::move(animations)}
|
||||||
|
}},
|
||||||
{"current", playback(m_current, m_currentClipName)},
|
{"current", playback(m_current, m_currentClipName)},
|
||||||
{"previous", playback(m_previous, m_previous.clip ? m_previous.clip->name : std::string{})},
|
{"previous", playback(m_previous, m_previousClipName)},
|
||||||
{"blendT", m_blendT},
|
{"blendT", m_blendT},
|
||||||
{"blendDuration", m_blendDuration}
|
{"blendDuration", m_blendDuration}
|
||||||
};
|
};
|
||||||
@@ -135,94 +109,196 @@ nlohmann::json Animator::Serialize() const {
|
|||||||
void Animator::Deserialize(const nlohmann::json& data) {
|
void Animator::Deserialize(const nlohmann::json& data) {
|
||||||
m_skeleton = {};
|
m_skeleton = {};
|
||||||
m_clips.clear();
|
m_clips.clear();
|
||||||
|
m_clipOrder.clear();
|
||||||
|
m_clipAssets.clear();
|
||||||
|
m_skeletonAsset = {};
|
||||||
m_current = {};
|
m_current = {};
|
||||||
m_previous = {};
|
m_previous = {};
|
||||||
m_currentClipName.clear();
|
m_currentClipName.clear();
|
||||||
|
m_previousClipName.clear();
|
||||||
|
m_referencesResolved = true;
|
||||||
|
|
||||||
if (data.contains("skeleton")) {
|
if (!data.contains("assetReferences") || !data.at("assetReferences").is_object()) {
|
||||||
const auto& skeleton = data.at("skeleton");
|
throw std::runtime_error("Animator data must contain assetReferences");
|
||||||
for (const auto& node : skeleton.value("hierarchy", nlohmann::json::array())) {
|
}
|
||||||
m_skeleton.hierarchy.push_back({
|
const auto& references = data.at("assetReferences");
|
||||||
node.at("id").get<JointId>(),
|
const auto& skeleton = references.at("skeleton");
|
||||||
node.value("children", std::vector<JointId>{})
|
m_skeletonAsset.path = skeleton.value("path", std::string{});
|
||||||
|
m_skeletonAsset.subresource = skeleton.value("subresource", std::string{});
|
||||||
|
for (const auto& animation : references.value("animations", nlohmann::json::array())) {
|
||||||
|
const std::string name = animation.at("clip").get<std::string>();
|
||||||
|
const auto& asset = animation.at("asset");
|
||||||
|
m_clipAssets.insert_or_assign(
|
||||||
|
name,
|
||||||
|
AssetReference{
|
||||||
|
asset.at("path").get<std::string>(),
|
||||||
|
asset.value("subresource", std::string{})
|
||||||
});
|
});
|
||||||
}
|
if (std::find(m_clipOrder.begin(), m_clipOrder.end(), name) == m_clipOrder.end()) {
|
||||||
for (const auto& matrix : skeleton.value("inverseBindMatrices", nlohmann::json::array())) {
|
m_clipOrder.push_back(name);
|
||||||
m_skeleton.inverseBindMatrices.push_back(ReadMat4(matrix));
|
|
||||||
}
|
|
||||||
for (const auto& joint : skeleton.value("joints", nlohmann::json::array())) {
|
|
||||||
m_skeleton.joints.push_back({
|
|
||||||
joint.at("id").get<JointId>(),
|
|
||||||
ReadVec3(joint.at("translation")),
|
|
||||||
ReadQuat(joint.at("rotation")),
|
|
||||||
ReadVec3(joint.at("scale"))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
m_skeleton.jointNames = skeleton.value("jointNames", std::vector<std::string>{});
|
|
||||||
m_skeleton.parentIndex = skeleton.value("parentIndex", std::vector<int>{});
|
|
||||||
if (skeleton.contains("rootPreTransform")) {
|
|
||||||
m_skeleton.rootPreTransform = ReadMat4(skeleton.at("rootPreTransform"));
|
|
||||||
}
|
|
||||||
if (m_skeleton.parentIndex.size() != m_skeleton.joints.size()) {
|
|
||||||
buildParentIndex(m_skeleton);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
m_referencesResolved = m_skeletonAsset.empty() && m_clipAssets.empty();
|
||||||
|
RestorePlaybackState(data);
|
||||||
|
}
|
||||||
|
|
||||||
for (const auto& clipJson : data.value("clips", nlohmann::json::array())) {
|
void Animator::RestorePlaybackState(const nlohmann::json& data) {
|
||||||
auto clip = std::make_shared<SkeletalAnimation>();
|
|
||||||
clip->name = clipJson.at("name").get<std::string>();
|
|
||||||
clip->duration = clipJson.value("duration", 0.0f);
|
|
||||||
clip->looped = clipJson.value("looped", true);
|
|
||||||
clip->startFrame = clipJson.value("startFrame", 0);
|
|
||||||
for (const auto& trackJson : clipJson.value("tracks", nlohmann::json::array())) {
|
|
||||||
SkeletalAnimation::Track track;
|
|
||||||
track.jointIndex = trackJson.at("jointIndex").get<std::uint32_t>();
|
|
||||||
for (const auto& keyframeJson : trackJson.value("keyframes", nlohmann::json::array())) {
|
|
||||||
track.keyframes.push_back({
|
|
||||||
keyframeJson.at("time").get<float>(),
|
|
||||||
ReadVec3(keyframeJson.at("translation")),
|
|
||||||
ReadQuat(keyframeJson.at("rotation")),
|
|
||||||
ReadVec3(keyframeJson.at("scale"))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
clip->tracks.push_back(std::move(track));
|
|
||||||
}
|
|
||||||
if (clipJson.contains("events")) {
|
|
||||||
for (auto it = clipJson.at("events").begin(); it != clipJson.at("events").end(); ++it) {
|
|
||||||
clip->events[std::stoi(it.key())] = it.value().get<std::vector<std::string>>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
m_clips[clip->name] = std::move(clip);
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto restorePlayback = [this](const nlohmann::json& playback, PlaybackState& state,
|
const auto restorePlayback = [this](const nlohmann::json& playback, PlaybackState& state,
|
||||||
std::string* clipName) {
|
std::string& clipName) {
|
||||||
const std::string name = playback.value("clip", std::string{});
|
const std::string name = playback.value("clip", std::string{});
|
||||||
state.time = playback.value("time", 0.0f);
|
state.time = playback.value("time", 0.0f);
|
||||||
state.speed = playback.value("speed", 1.0f);
|
state.speed = playback.value("speed", 1.0f);
|
||||||
state.clip = nullptr;
|
state.clip = nullptr;
|
||||||
if (!name.empty()) {
|
if (!name.empty() && m_referencesResolved) {
|
||||||
const auto it = m_clips.find(name);
|
const auto it = m_clips.find(name);
|
||||||
if (it == m_clips.end()) {
|
if (it == m_clips.end()) {
|
||||||
throw std::runtime_error("Animator clip not found: " + name);
|
throw std::runtime_error("Animator clip not found: " + name);
|
||||||
}
|
}
|
||||||
state.clip = it->second.get();
|
state.clip = it->second.get();
|
||||||
}
|
}
|
||||||
if (clipName != nullptr) {
|
clipName = name;
|
||||||
*clipName = name;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if (data.contains("current")) {
|
if (data.contains("current")) {
|
||||||
restorePlayback(data.at("current"), m_current, &m_currentClipName);
|
restorePlayback(data.at("current"), m_current, m_currentClipName);
|
||||||
}
|
}
|
||||||
if (data.contains("previous")) {
|
if (data.contains("previous")) {
|
||||||
restorePlayback(data.at("previous"), m_previous, nullptr);
|
restorePlayback(data.at("previous"), m_previous, m_previousClipName);
|
||||||
}
|
}
|
||||||
m_blendT = data.value("blendT", 0.0f);
|
m_blendT = data.value("blendT", 0.0f);
|
||||||
m_blendDuration = data.value("blendDuration", 0.0f);
|
m_blendDuration = data.value("blendDuration", 0.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Animator::ResolveReferences(const ObjectMap&) {
|
||||||
|
LoadAssetReferences();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Animator::LoadAssetReferences() {
|
||||||
|
if (m_referencesResolved) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_skeletonAsset.empty()) {
|
||||||
|
throw std::runtime_error("Animator asset references have no skeleton source");
|
||||||
|
}
|
||||||
|
|
||||||
|
ModelDoc::LoadOptions options{};
|
||||||
|
options.meshImportMode = ModelDoc::MeshImportMode::MergedPerNode;
|
||||||
|
options.loadMaterials = false;
|
||||||
|
options.loadSkeleton = true;
|
||||||
|
options.loadAnimations = false;
|
||||||
|
|
||||||
|
auto skeletonAsset = AssetManager::GetInstance().LoadModel(m_skeletonAsset.path, options);
|
||||||
|
if (skeletonAsset.skeleton.joints.empty()) {
|
||||||
|
throw std::runtime_error("Animator skeleton source has no joints: " + m_skeletonAsset.path);
|
||||||
|
}
|
||||||
|
m_skeleton = std::move(skeletonAsset.skeleton);
|
||||||
|
m_skeleton.assetReference = m_skeletonAsset;
|
||||||
|
|
||||||
|
std::unordered_map<std::string, std::vector<std::string>> requestedBySource;
|
||||||
|
for (const auto& name : m_clipOrder) {
|
||||||
|
const auto assetIt = m_clipAssets.find(name);
|
||||||
|
if (assetIt != m_clipAssets.end()) {
|
||||||
|
requestedBySource[assetIt->second.path].push_back(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const auto& [name, asset] : m_clipAssets) {
|
||||||
|
if (std::find(m_clipOrder.begin(), m_clipOrder.end(), name) == m_clipOrder.end()) {
|
||||||
|
requestedBySource[asset.path].push_back(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_clips.clear();
|
||||||
|
for (const auto& [source, requestedNames] : requestedBySource) {
|
||||||
|
auto clips = AssetManager::GetInstance().LoadAnimationClips(source, m_skeleton);
|
||||||
|
std::unordered_map<std::string, std::size_t> clipIndicesByName;
|
||||||
|
for (std::size_t index = 0; index < clips.size(); ++index) {
|
||||||
|
clipIndicesByName.emplace(clips[index].name, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const std::string& requestedName : requestedNames) {
|
||||||
|
const auto sourceIt = m_clipAssets.find(requestedName);
|
||||||
|
if (sourceIt == m_clipAssets.end()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::size_t> clipIndex =
|
||||||
|
ParseAnimationIndex(sourceIt->second);
|
||||||
|
|
||||||
|
if (clipIndex && *clipIndex >= clips.size()) {
|
||||||
|
clipIndex = std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!clipIndex) {
|
||||||
|
const auto nameIt = clipIndicesByName.find(requestedName);
|
||||||
|
if (nameIt != clipIndicesByName.end()) {
|
||||||
|
clipIndex = nameIt->second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Older scene files did not store animation subresources. If the
|
||||||
|
// source contains exactly one clip, it is unambiguous even when
|
||||||
|
// the imported name changed between loader versions.
|
||||||
|
if (!clipIndex && clips.size() == 1 && requestedNames.size() == 1) {
|
||||||
|
clipIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!clipIndex && clips.size() == requestedNames.size()) {
|
||||||
|
const auto requestedIt = std::find(
|
||||||
|
requestedNames.begin(),
|
||||||
|
requestedNames.end(),
|
||||||
|
requestedName);
|
||||||
|
if (requestedIt != requestedNames.end()) {
|
||||||
|
clipIndex = static_cast<std::size_t>(
|
||||||
|
std::distance(requestedNames.begin(), requestedIt));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!clipIndex) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
"Animator clip was not found in its source: " + requestedName +
|
||||||
|
". Available clips: " + FormatAvailableClips(clips));
|
||||||
|
}
|
||||||
|
|
||||||
|
auto clip = std::move(clips[*clipIndex]);
|
||||||
|
AssetReference resolvedReference = sourceIt->second;
|
||||||
|
if (resolvedReference.subresource.empty()) {
|
||||||
|
resolvedReference.subresource = clip.assetReference.subresource;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The serialized name is the stable scene-facing name. Keep it
|
||||||
|
// even when the importer supplied a different display name.
|
||||||
|
clip.name = requestedName;
|
||||||
|
clip.assetReference = std::move(resolvedReference);
|
||||||
|
m_clips[requestedName] =
|
||||||
|
std::make_shared<SkeletalAnimation>(std::move(clip));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& [name, asset] : m_clipAssets) {
|
||||||
|
(void)asset;
|
||||||
|
if (!m_clips.contains(name)) {
|
||||||
|
throw std::runtime_error("Animator clip was not found in its source: " + name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_referencesResolved = true;
|
||||||
|
RestorePlaybackState({
|
||||||
|
{"current", {
|
||||||
|
{"clip", m_currentClipName},
|
||||||
|
{"time", m_current.time},
|
||||||
|
{"speed", m_current.speed}
|
||||||
|
}},
|
||||||
|
{"previous", {
|
||||||
|
{"clip", m_previousClipName},
|
||||||
|
{"time", m_previous.time},
|
||||||
|
{"speed", m_previous.speed}
|
||||||
|
}},
|
||||||
|
{"blendT", m_blendT},
|
||||||
|
{"blendDuration", m_blendDuration}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void Animator::Update(float dt) {
|
void Animator::Update(float dt) {
|
||||||
if (!m_current.clip) return;
|
if (!m_current.clip) return;
|
||||||
|
|
||||||
@@ -245,6 +321,7 @@ void Animator::Update(float dt) {
|
|||||||
if (m_blendT >= 1.f) {
|
if (m_blendT >= 1.f) {
|
||||||
m_blendT = 1.f;
|
m_blendT = 1.f;
|
||||||
m_previous = {};
|
m_previous = {};
|
||||||
|
m_previousClipName.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -271,7 +348,17 @@ void Animator::ImGuiInspector() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Animator::addClip(std::shared_ptr<SkeletalAnimation> clip) {
|
void Animator::addClip(std::shared_ptr<SkeletalAnimation> clip) {
|
||||||
m_clips[clip->name] = std::move(clip);
|
if (!clip) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const std::string name = clip->name;
|
||||||
|
if (!m_clips.contains(name)) {
|
||||||
|
m_clipOrder.push_back(name);
|
||||||
|
}
|
||||||
|
if (!clip->assetReference.empty()) {
|
||||||
|
m_clipAssets[name] = clip->assetReference;
|
||||||
|
}
|
||||||
|
m_clips[name] = std::move(clip);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Animator::play(const std::string& name, float blendTime) {
|
void Animator::play(const std::string& name, float blendTime) {
|
||||||
@@ -290,10 +377,12 @@ void Animator::play(const std::string& name, float blendTime) {
|
|||||||
|
|
||||||
if (m_current.clip && blendTime > 0.f) {
|
if (m_current.clip && blendTime > 0.f) {
|
||||||
m_previous = m_current;
|
m_previous = m_current;
|
||||||
|
m_previousClipName = m_currentClipName;
|
||||||
m_blendT = 0.f;
|
m_blendT = 0.f;
|
||||||
m_blendDuration = blendTime;
|
m_blendDuration = blendTime;
|
||||||
} else {
|
} else {
|
||||||
m_previous = {};
|
m_previous = {};
|
||||||
|
m_previousClipName.clear();
|
||||||
m_blendT = 1.f;
|
m_blendT = 1.f;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -305,6 +394,7 @@ void Animator::stop() {
|
|||||||
m_current = {};
|
m_current = {};
|
||||||
m_previous = {};
|
m_previous = {};
|
||||||
m_currentClipName = {};
|
m_currentClipName = {};
|
||||||
|
m_previousClipName = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
std::size_t Animator::uploadJointMatrices(const RenderContext& ctx, const Skeleton& skeleton, std::size_t frameIndex) {
|
std::size_t Animator::uploadJointMatrices(const RenderContext& ctx, const Skeleton& skeleton, std::size_t frameIndex) {
|
||||||
|
|||||||
@@ -1,4 +1,7 @@
|
|||||||
#include <destrum/Components/MeshRendererComponent.h>
|
#include <destrum/Components/MeshRendererComponent.h>
|
||||||
|
#include <destrum/Assets/AssetReference.h>
|
||||||
|
#include <destrum/Graphics/GfxDevice.h>
|
||||||
|
#include <destrum/Graphics/Renderer.h>
|
||||||
#include <destrum/ObjectModel/Transform.h>
|
#include <destrum/ObjectModel/Transform.h>
|
||||||
|
|
||||||
#include "destrum/Components/Animator.h"
|
#include "destrum/Components/Animator.h"
|
||||||
@@ -79,19 +82,53 @@ void MeshRendererComponent::ResolveReferences(const ObjectMap&) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auto loadAssetForKey = [&](const std::string& key) {
|
||||||
|
const auto asset = AssetReference::fromCacheKey(key);
|
||||||
|
if (!asset) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!GameState::GetInstance().HasGfxDevice()) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
"Cannot load mesh asset without a graphics device: " + asset->path);
|
||||||
|
}
|
||||||
|
|
||||||
|
resources->loadModelAsset(GameState::GetInstance().Gfx(), asset->path);
|
||||||
|
};
|
||||||
|
|
||||||
if (!meshKey.empty()) {
|
if (!meshKey.empty()) {
|
||||||
const auto resolved = resources->meshes().findMeshByKey(meshKey);
|
auto resolved = resources->meshes().findMeshByKey(meshKey);
|
||||||
|
if (!resolved) {
|
||||||
|
loadAssetForKey(meshKey);
|
||||||
|
resolved = resources->meshes().findMeshByKey(meshKey);
|
||||||
|
}
|
||||||
if (!resolved) {
|
if (!resolved) {
|
||||||
throw std::runtime_error("Mesh resource not found: " + meshKey);
|
throw std::runtime_error("Mesh resource not found: " + meshKey);
|
||||||
}
|
}
|
||||||
meshID = *resolved;
|
meshID = *resolved;
|
||||||
}
|
}
|
||||||
if (!materialKey.empty()) {
|
if (!materialKey.empty()) {
|
||||||
const auto resolved = resources->materials().findMaterialByKey(materialKey);
|
auto resolved = resources->materials().findMaterialByKey(materialKey);
|
||||||
|
if (!resolved) {
|
||||||
|
loadAssetForKey(materialKey);
|
||||||
|
resolved = resources->materials().findMaterialByKey(materialKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!resolved) {
|
||||||
|
const auto meshAsset = AssetReference::fromCacheKey(meshKey);
|
||||||
|
resolved = resources->materials().findMaterialByName(
|
||||||
|
materialKey,
|
||||||
|
meshAsset ? meshAsset->path : std::string_view{});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!resolved) {
|
||||||
|
resolved = resources->materials().findMaterialByName(materialKey);
|
||||||
|
}
|
||||||
|
|
||||||
if (!resolved) {
|
if (!resolved) {
|
||||||
throw std::runtime_error("Material resource not found: " + materialKey);
|
throw std::runtime_error("Material resource not found: " + materialKey);
|
||||||
}
|
}
|
||||||
materialID = *resolved;
|
materialID = *resolved;
|
||||||
|
materialKey = resources->materials().getMaterialKey(*resolved);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (meshID != NULL_MESH_ID && meshKey.empty()) {
|
if (meshID != NULL_MESH_ID && meshKey.empty()) {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
void MaterialCache::init(
|
void MaterialCache::init(
|
||||||
GfxDevice& gfxDevice,
|
GfxDevice& gfxDevice,
|
||||||
@@ -26,9 +27,9 @@ void MaterialCache::init(
|
|||||||
materialDataBuffer.buffer,
|
materialDataBuffer.buffer,
|
||||||
"material data");
|
"material data");
|
||||||
|
|
||||||
Material placeholderMaterial{};
|
Material placeholderMaterial = Material::SimpleColor(
|
||||||
placeholderMaterial.name = "PLACEHOLDER_MATERIAL";
|
glm::vec3{1.0f},
|
||||||
placeholderMaterial.diffuseTexture = defaultTextures.white;
|
"PLACEHOLDER_MATERIAL");
|
||||||
|
|
||||||
placeholderMaterialId = addMaterial(placeholderMaterial);
|
placeholderMaterialId = addMaterial(placeholderMaterial);
|
||||||
gfxDevice.getMemoryManager().flushAllocation(materialDataBuffer);
|
gfxDevice.getMemoryManager().flushAllocation(materialDataBuffer);
|
||||||
@@ -104,9 +105,14 @@ MaterialID MaterialCache::addMaterial(Material material)
|
|||||||
// exact range after batching updates.
|
// exact range after batching updates.
|
||||||
|
|
||||||
const auto materialId = static_cast<MaterialID>(materials.size());
|
const auto materialId = static_cast<MaterialID>(materials.size());
|
||||||
std::string key = material.name.empty()
|
std::string key;
|
||||||
? "material:" + std::to_string(materialId)
|
if (!material.assetReference.empty()) {
|
||||||
: material.name;
|
key = material.assetReference.cacheKey();
|
||||||
|
} else {
|
||||||
|
key = material.name.empty()
|
||||||
|
? "material:" + std::to_string(materialId)
|
||||||
|
: material.name;
|
||||||
|
}
|
||||||
const std::string baseKey = key;
|
const std::string baseKey = key;
|
||||||
std::size_t suffix = 1;
|
std::size_t suffix = 1;
|
||||||
while (std::find(materialKeys.begin(), materialKeys.end(), key) != materialKeys.end()) {
|
while (std::find(materialKeys.begin(), materialKeys.end(), key) != materialKeys.end()) {
|
||||||
@@ -121,13 +127,17 @@ MaterialID MaterialCache::addMaterial(Material material)
|
|||||||
|
|
||||||
MaterialID MaterialCache::addSimpleTextureMaterial(ImageID textureID)
|
MaterialID MaterialCache::addSimpleTextureMaterial(ImageID textureID)
|
||||||
{
|
{
|
||||||
Material material{};
|
Material material = Material::SimpleColor(
|
||||||
material.name = "simple texture material";
|
glm::vec3{1.0f},
|
||||||
|
"simple texture material");
|
||||||
material.diffuseTexture = textureID;
|
material.diffuseTexture = textureID;
|
||||||
material.metallicFactor = 0.0f;
|
|
||||||
material.roughnessFactor = 1.0f;
|
|
||||||
|
|
||||||
return addMaterial(material);
|
return addMaterial(std::move(material));
|
||||||
|
}
|
||||||
|
|
||||||
|
MaterialID MaterialCache::addSimpleColorMaterial(glm::vec3 color, std::string name)
|
||||||
|
{
|
||||||
|
return addMaterial(Material::SimpleColor(std::move(color), std::move(name)));
|
||||||
}
|
}
|
||||||
|
|
||||||
const Material& MaterialCache::getMaterial(MaterialID id) const
|
const Material& MaterialCache::getMaterial(MaterialID id) const
|
||||||
@@ -149,6 +159,37 @@ std::optional<MaterialID> MaterialCache::findMaterialByKey(std::string_view key)
|
|||||||
return static_cast<MaterialID>(std::distance(materialKeys.begin(), it));
|
return static_cast<MaterialID>(std::distance(materialKeys.begin(), it));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::optional<MaterialID> MaterialCache::findMaterialByName(
|
||||||
|
std::string_view name,
|
||||||
|
std::string_view sourceReference) const
|
||||||
|
{
|
||||||
|
std::optional<MaterialID> fallback;
|
||||||
|
for (MaterialID id = 0; id < materials.size(); ++id) {
|
||||||
|
const auto& material = materials[id];
|
||||||
|
if (material.name != name) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sourceReference.empty()) {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!material.assetReference.empty() &&
|
||||||
|
material.assetReference.path == sourceReference) {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// A manually-created material may have the same name but no source
|
||||||
|
// reference. Keep it as a fallback for scenes saved before canonical
|
||||||
|
// file-backed material keys were introduced.
|
||||||
|
if (!fallback.has_value() && material.assetReference.empty()) {
|
||||||
|
fallback = id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
MaterialID MaterialCache::getFreeMaterialId() const
|
MaterialID MaterialCache::getFreeMaterialId() const
|
||||||
{
|
{
|
||||||
return static_cast<MaterialID>(materials.size());
|
return static_cast<MaterialID>(materials.size());
|
||||||
|
|||||||
@@ -32,9 +32,14 @@ MeshID MeshCache::addMesh(GfxDevice& gfxDevice, const CPUMesh& cpuMesh)
|
|||||||
const auto id = meshes.size();
|
const auto id = meshes.size();
|
||||||
meshes.push_back(std::move(gpuMesh));
|
meshes.push_back(std::move(gpuMesh));
|
||||||
cpuMeshes.push_back(cpuMesh); // store a copy of the CPU mesh
|
cpuMeshes.push_back(cpuMesh); // store a copy of the CPU mesh
|
||||||
std::string key = cpuMesh.name.empty()
|
std::string key;
|
||||||
? "mesh:" + std::to_string(id)
|
if (!cpuMesh.assetReference.empty()) {
|
||||||
: cpuMesh.name;
|
key = cpuMesh.assetReference.cacheKey();
|
||||||
|
} else {
|
||||||
|
key = cpuMesh.name.empty()
|
||||||
|
? "mesh:" + std::to_string(id)
|
||||||
|
: cpuMesh.name;
|
||||||
|
}
|
||||||
const std::string baseKey = key;
|
const std::string baseKey = key;
|
||||||
std::size_t suffix = 1;
|
std::size_t suffix = 1;
|
||||||
while (std::find(meshKeys.begin(), meshKeys.end(), key) != meshKeys.end()) {
|
while (std::find(meshKeys.begin(), meshKeys.end(), key) != meshKeys.end()) {
|
||||||
|
|||||||
@@ -1,11 +1,35 @@
|
|||||||
#include <destrum/Graphics/RenderResources.h>
|
#include <destrum/Graphics/RenderResources.h>
|
||||||
|
|
||||||
|
#include <destrum/Assets/AssetManager.h>
|
||||||
#include <destrum/Graphics/GfxDevice.h>
|
#include <destrum/Graphics/GfxDevice.h>
|
||||||
#include <destrum/Graphics/Util.h>
|
#include <destrum/Graphics/Util.h>
|
||||||
|
|
||||||
#include "volk.h"
|
#include "volk.h"
|
||||||
#include "spdlog/spdlog.h"
|
#include "spdlog/spdlog.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
const ModelDoc::TextureInfo* FindDiffuseTexture(
|
||||||
|
const ModelDoc::MaterialInfo& material)
|
||||||
|
{
|
||||||
|
for (const auto semantic : std::array{
|
||||||
|
ModelDoc::TextureSemantic::BaseColor,
|
||||||
|
ModelDoc::TextureSemantic::Diffuse,
|
||||||
|
}) {
|
||||||
|
for (const auto& texture : material.textures) {
|
||||||
|
if (texture.semantic == semantic) {
|
||||||
|
return &texture;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
RenderResources::RenderResources() = default;
|
RenderResources::RenderResources() = default;
|
||||||
|
|
||||||
void RenderResources::init(GfxDevice& gfxDevice)
|
void RenderResources::init(GfxDevice& gfxDevice)
|
||||||
@@ -175,6 +199,61 @@ ImageID RenderResources::addImageToCache(GPUImage image) {
|
|||||||
return imageCache->addImage(std::move(image));
|
return imageCache->addImage(std::move(image));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RenderResources::loadModelAsset(
|
||||||
|
GfxDevice& gfxDevice,
|
||||||
|
std::string_view sourceReference)
|
||||||
|
{
|
||||||
|
ModelDoc::LoadOptions options{};
|
||||||
|
options.meshImportMode = ModelDoc::MeshImportMode::MergedPerNode;
|
||||||
|
options.loadMaterials = true;
|
||||||
|
options.loadSkeleton = true;
|
||||||
|
options.loadAnimations = false;
|
||||||
|
|
||||||
|
const auto model = AssetManager::GetInstance().LoadModel(
|
||||||
|
sourceReference,
|
||||||
|
options);
|
||||||
|
|
||||||
|
for (const auto& primitive : model.primitives) {
|
||||||
|
const std::string key = primitive.mesh.assetReference.cacheKey();
|
||||||
|
if (!key.empty() && meshes().findMeshByKey(key).has_value()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
meshes().addMesh(gfxDevice, primitive.mesh);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& sourceMaterial : model.materials) {
|
||||||
|
const std::string key = sourceMaterial.assetReference.cacheKey();
|
||||||
|
if (!key.empty() && materials().findMaterialByKey(key).has_value()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Material material = Material::SimpleColor(
|
||||||
|
glm::vec3{sourceMaterial.baseColor},
|
||||||
|
sourceMaterial.name);
|
||||||
|
material.assetReference = sourceMaterial.assetReference;
|
||||||
|
material.metallicFactor = sourceMaterial.metallicFactor;
|
||||||
|
material.roughnessFactor = sourceMaterial.roughnessFactor;
|
||||||
|
material.emissiveFactor = std::max({
|
||||||
|
sourceMaterial.emissiveColor.x,
|
||||||
|
sourceMaterial.emissiveColor.y,
|
||||||
|
sourceMaterial.emissiveColor.z,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (const auto* texture = FindDiffuseTexture(sourceMaterial);
|
||||||
|
texture != nullptr && !texture->embedded && !texture->path.empty()) {
|
||||||
|
material.diffuseTexture = loadImageFromFile(
|
||||||
|
gfxDevice,
|
||||||
|
std::filesystem::path{texture->path},
|
||||||
|
VK_IMAGE_USAGE_SAMPLED_BIT,
|
||||||
|
false,
|
||||||
|
TextureIntent::ColorSrgb);
|
||||||
|
}
|
||||||
|
|
||||||
|
materials().addMaterial(std::move(material));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
BindlessSetManager& RenderResources::getBindlessSetManager() {
|
BindlessSetManager& RenderResources::getBindlessSetManager() {
|
||||||
return imageCache->bindlessSetManager;
|
return imageCache->bindlessSetManager;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ private:
|
|||||||
|
|
||||||
GameObject* capybara = nullptr;
|
GameObject* capybara = nullptr;
|
||||||
|
|
||||||
char scenePath[512]{"game://scenes/debug_scene.json"};
|
char scenePath[512]{"scenes/debug_scene.json"};
|
||||||
char newSceneName[128]{"EmptyScene"};
|
char newSceneName[128]{"EmptyScene"};
|
||||||
std::string sceneStatus;
|
std::string sceneStatus;
|
||||||
};
|
};
|
||||||
|
|||||||
+226
-260
@@ -1,6 +1,7 @@
|
|||||||
#include "Lightkeeper.h"
|
#include "Lightkeeper.h"
|
||||||
|
|
||||||
#include <destrum/FS/AssetFS.h>
|
#include <destrum/FS/AssetFS.h>
|
||||||
|
#include <destrum/Assets/AssetManager.h>
|
||||||
#include "glm/gtx/transform.hpp"
|
#include "glm/gtx/transform.hpp"
|
||||||
#include "spdlog/spdlog.h"
|
#include "spdlog/spdlog.h"
|
||||||
#include <destrum/Components/Physics/Rigidbody.h>
|
#include <destrum/Components/Physics/Rigidbody.h>
|
||||||
@@ -27,7 +28,9 @@
|
|||||||
#include "destrum/Util/ModelDocUtils.h"
|
#include "destrum/Util/ModelDocUtils.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
std::filesystem::path ResolveScenePath(std::string_view value) {
|
std::filesystem::path ResolveScenePath(
|
||||||
|
std::string_view value,
|
||||||
|
const std::filesystem::path& exeDir) {
|
||||||
if (value.empty()) {
|
if (value.empty()) {
|
||||||
throw std::invalid_argument("Scene path cannot be empty");
|
throw std::invalid_argument("Scene path cannot be empty");
|
||||||
}
|
}
|
||||||
@@ -36,7 +39,8 @@ namespace {
|
|||||||
return AssetFS::GetInstance().GetFullPath(value);
|
return AssetFS::GetInstance().GetFullPath(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
return std::filesystem::path{value};
|
const std::filesystem::path path{value};
|
||||||
|
return path.is_absolute() ? path : exeDir / path;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,37 +72,6 @@ void LightKeeper::customInit()
|
|||||||
staticModelOptions.loadSkeleton = false;
|
staticModelOptions.loadSkeleton = false;
|
||||||
staticModelOptions.loadAnimations = false;
|
staticModelOptions.loadAnimations = false;
|
||||||
|
|
||||||
auto kittyModel = ModelDoc::LoadModel(
|
|
||||||
AssetFS::GetInstance().GetFullPath("game://kitty.glb").generic_string(),
|
|
||||||
staticModelOptions
|
|
||||||
);
|
|
||||||
ModelDocUtils::LogModelDocSummary(kittyModel, "kitty.glb");
|
|
||||||
|
|
||||||
const auto& kittyPrimitive = ModelDocUtils::GetFirstPrimitiveOrThrow(kittyModel, "game://kitty.glb");
|
|
||||||
auto testMesh = kittyPrimitive.mesh;
|
|
||||||
testMesh.name = "Test Mesh";
|
|
||||||
|
|
||||||
auto testMeshID = resources.meshes().addMesh(gfxDevice, testMesh);
|
|
||||||
spdlog::info("TestMesh uploaded with id: {}", testMeshID);
|
|
||||||
|
|
||||||
const auto testTexturePath = ModelDocUtils::PickTexturePath(
|
|
||||||
kittyModel,
|
|
||||||
kittyPrimitive,
|
|
||||||
AssetFS::GetInstance().GetFullPath("game://kitty.png")
|
|
||||||
);
|
|
||||||
|
|
||||||
const auto testimgID = resources.loadImageFromFile(gfxDevice, testTexturePath);
|
|
||||||
spdlog::info("Test image loaded from '{}' with id: {}", testTexturePath.generic_string(), testimgID);
|
|
||||||
|
|
||||||
auto testMaterialID = resources.materials().addMaterial({
|
|
||||||
.baseColor = ModelDocUtils::GetImportedBaseColor(
|
|
||||||
kittyModel, kittyPrimitive),
|
|
||||||
.diffuseTexture = testimgID,
|
|
||||||
.name = ModelDocUtils::GetImportedMaterialName(
|
|
||||||
kittyModel, kittyPrimitive, "TestMaterial"),
|
|
||||||
});
|
|
||||||
spdlog::info("Test material created with id: {}", testMaterialID);
|
|
||||||
|
|
||||||
camera.SetRotation(glm::radians(glm::vec2(90.f, 0.f)));
|
camera.SetRotation(glm::radians(glm::vec2(90.f, 0.f)));
|
||||||
|
|
||||||
auto& scene = SceneManager::GetInstance().CreateScene("Main");
|
auto& scene = SceneManager::GetInstance().CreateScene("Main");
|
||||||
@@ -168,64 +141,64 @@ void LightKeeper::customInit()
|
|||||||
characterModelOptions.loadSkeleton = true;
|
characterModelOptions.loadSkeleton = true;
|
||||||
characterModelOptions.loadAnimations = true;
|
characterModelOptions.loadAnimations = true;
|
||||||
|
|
||||||
auto charModel = ModelDoc::LoadModel(
|
// auto charModel = ModelDoc::LoadModel(
|
||||||
AssetFS::GetInstance().GetFullPath("engine://cotw-capybara-male/source/capybara.fbx").generic_string(),
|
// AssetFS::GetInstance().GetFullPath("engine://cotw-capybara-male/source/capybara.fbx").generic_string(),
|
||||||
characterModelOptions
|
// characterModelOptions
|
||||||
);
|
// );
|
||||||
ModelDocUtils::LogModelDocSummary(charModel, "capybara.fbx");
|
// ModelDocUtils::LogModelDocSummary(charModel, "capybara.fbx");
|
||||||
|
//
|
||||||
const auto& charPrimitive = ModelDocUtils::GetFirstSkinnedPrimitiveOrFirstOrThrow(
|
// const auto& charPrimitive = ModelDocUtils::GetFirstSkinnedPrimitiveOrFirstOrThrow(
|
||||||
charModel,
|
// charModel,
|
||||||
"engine://cotw-capybara-male/source/capybara.fbx"
|
// "engine://cotw-capybara-male/source/capybara.fbx"
|
||||||
);
|
// );
|
||||||
|
//
|
||||||
const auto charMeshID = resources.meshes().addMesh(gfxDevice, charPrimitive.mesh);
|
// const auto charMeshID = resources.meshes().addMesh(gfxDevice, charPrimitive.mesh);
|
||||||
|
//
|
||||||
const auto charTexturePath = ModelDocUtils::PickTexturePath(
|
// const auto charTexturePath = ModelDocUtils::PickTexturePath(
|
||||||
charModel,
|
// charModel,
|
||||||
charPrimitive,
|
// charPrimitive,
|
||||||
AssetFS::GetInstance().GetFullPath(
|
// AssetFS::GetInstance().GetFullPath(
|
||||||
"engine://cotw-capybara-male/textures/capybara_male_light_brown_dif.ddsc.DECA.RE.pngballs")
|
// "engine://cotw-capybara-male/textures/capybara_male_light_brown_dif.ddsc.DECA.RE.pngballs")
|
||||||
);
|
// );
|
||||||
|
//
|
||||||
const auto charTextureID = resources.loadImageFromFile(gfxDevice, charTexturePath);
|
// const auto charTextureID = resources.loadImageFromFile(gfxDevice, charTexturePath);
|
||||||
// const auto charTextureID = gfxDevice.loadImageFromFile(AssetFS::GetInstance().GetFullPath("engine://char.jpg"));
|
// // const auto charTextureID = gfxDevice.loadImageFromFile(AssetFS::GetInstance().GetFullPath("engine://char.jpg"));
|
||||||
const auto charMaterialID = resources.materials().addMaterial({
|
// const auto charMaterialID = resources.materials().addMaterial({
|
||||||
.baseColor = ModelDocUtils::GetImportedBaseColor(
|
// .baseColor = ModelDocUtils::GetImportedBaseColor(
|
||||||
charModel, charPrimitive),
|
// charModel, charPrimitive),
|
||||||
.diffuseTexture = charTextureID,
|
// .diffuseTexture = charTextureID,
|
||||||
.name = ModelDocUtils::GetImportedMaterialName(
|
// .name = ModelDocUtils::GetImportedMaterialName(
|
||||||
charModel, charPrimitive, "CharacterMaterial"),
|
// charModel, charPrimitive, "CharacterMaterial"),
|
||||||
});
|
// });
|
||||||
|
//
|
||||||
const auto charMeshComp = CharObj->AddComponent<MeshRendererComponent>();
|
// const auto charMeshComp = CharObj->AddComponent<MeshRendererComponent>();
|
||||||
charMeshComp->SetMeshID(charMeshID);
|
// charMeshComp->SetMeshID(charMeshID);
|
||||||
charMeshComp->SetMaterialID(charMaterialID);
|
// charMeshComp->SetMaterialID(charMaterialID);
|
||||||
|
//
|
||||||
const auto animator = CharObj->AddComponent<Animator>();
|
// const auto animator = CharObj->AddComponent<Animator>();
|
||||||
animator->setSkeleton(std::move(charModel.skeleton));
|
// animator->setSkeleton(std::move(charModel.skeleton));
|
||||||
|
//
|
||||||
std::string firstAnimationName;
|
// std::string firstAnimationName;
|
||||||
|
//
|
||||||
for (auto& clip : charModel.animations)
|
// for (auto& clip : charModel.animations)
|
||||||
{
|
// {
|
||||||
spdlog::info("Loaded animation: '{}' ({:.2f}s)", clip.name, clip.duration);
|
// spdlog::info("Loaded animation: '{}' ({:.2f}s)", clip.name, clip.duration);
|
||||||
|
//
|
||||||
if (firstAnimationName.empty())
|
// if (firstAnimationName.empty())
|
||||||
firstAnimationName = clip.name;
|
// firstAnimationName = clip.name;
|
||||||
|
//
|
||||||
animator->addClip(std::make_shared<SkeletalAnimation>(std::move(clip)));
|
// animator->addClip(std::make_shared<SkeletalAnimation>(std::move(clip)));
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
if (!firstAnimationName.empty())
|
// if (!firstAnimationName.empty())
|
||||||
{
|
// {
|
||||||
spdlog::info("Playing animation: '{}'", firstAnimationName);
|
// spdlog::info("Playing animation: '{}'", firstAnimationName);
|
||||||
animator->play(firstAnimationName);
|
// animator->play(firstAnimationName);
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
animator->play("capybara_canter_fwd_01|capybara_canter_fwd_01|run");
|
// animator->play("capybara_canter_fwd_01|capybara_canter_fwd_01|run");
|
||||||
|
//
|
||||||
CharObj->GetTransform().SetWorldPosition(glm::vec3(0.f, 0.f, 0.f));
|
// CharObj->GetTransform().SetWorldPosition(glm::vec3(0.f, 0.f, 0.f));
|
||||||
// CharObj->GetTransform().SetWorldScale(0.01f, 0.01f, 0.01f);
|
// CharObj->GetTransform().SetWorldScale(0.01f, 0.01f, 0.01f);
|
||||||
|
|
||||||
// ModelDoc::LoadOptions options{};
|
// ModelDoc::LoadOptions options{};
|
||||||
@@ -282,184 +255,177 @@ void LightKeeper::customInit()
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
|
|
||||||
{
|
|
||||||
const auto CharObj = scene.CreateGameObject("Character");
|
|
||||||
|
|
||||||
ModelDoc::LoadOptions characterOptions{};
|
|
||||||
characterOptions.meshImportMode = ModelDoc::MeshImportMode::MergedPerNode;
|
|
||||||
characterOptions.loadMaterials = true;
|
|
||||||
characterOptions.loadSkeleton = true;
|
|
||||||
characterOptions.loadAnimations = false;
|
|
||||||
|
|
||||||
auto charModel = ModelDoc::LoadModel(
|
|
||||||
AssetFS::GetInstance()
|
|
||||||
.GetFullPath("engine://characterMedium.fbx")
|
|
||||||
.generic_string(),
|
|
||||||
characterOptions
|
|
||||||
);
|
|
||||||
|
|
||||||
const auto& charPrimitive =
|
|
||||||
ModelDocUtils::GetFirstSkinnedPrimitiveOrFirstOrThrow(
|
|
||||||
charModel,
|
|
||||||
"engine://characterMedium.fbx"
|
|
||||||
);
|
|
||||||
|
|
||||||
const auto charMeshID = resources.meshes().addMesh(gfxDevice, charPrimitive.mesh);
|
|
||||||
|
|
||||||
const auto charTextureID = resources.loadImageFromFile(gfxDevice,
|
|
||||||
AssetFS::GetInstance().GetFullPath("engine://textures/criminalMaleA.png")
|
|
||||||
);
|
|
||||||
|
|
||||||
const auto charMaterialID = resources.materials().addMaterial({
|
|
||||||
.baseColor = ModelDocUtils::GetImportedBaseColor(
|
|
||||||
charModel, charPrimitive),
|
|
||||||
.diffuseTexture = charTextureID,
|
|
||||||
.name = ModelDocUtils::GetImportedMaterialName(
|
|
||||||
charModel,
|
|
||||||
charPrimitive,
|
|
||||||
"CharacterMaterial"
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
const auto charMeshComp = CharObj->AddComponent<MeshRendererComponent>();
|
|
||||||
charMeshComp->SetMeshID(charMeshID);
|
|
||||||
charMeshComp->SetMaterialID(charMaterialID);
|
|
||||||
|
|
||||||
const auto animator = CharObj->AddComponent<Animator>();
|
|
||||||
animator->setSkeleton(charModel.skeleton);
|
|
||||||
|
|
||||||
auto runClips = ModelDoc::LoadAnimationClips(
|
|
||||||
AssetFS::GetInstance()
|
|
||||||
.GetFullPath("engine://run.fbx")
|
|
||||||
.generic_string(),
|
|
||||||
charModel.skeleton
|
|
||||||
);
|
|
||||||
|
|
||||||
for (auto& clip : runClips)
|
|
||||||
{
|
|
||||||
spdlog::info("Loaded animation: '{}' ({:.2f}s)", clip.name, clip.duration);
|
|
||||||
animator->addClip(std::make_shared<SkeletalAnimation>(std::move(clip)));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!runClips.empty())
|
|
||||||
{
|
|
||||||
animator->play("Root|Run");
|
|
||||||
}
|
|
||||||
|
|
||||||
CharObj->GetTransform().SetWorldPosition(glm::vec3(0.f));
|
|
||||||
CharObj->GetTransform().SetWorldPosition(glm::vec3(5, 0, 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
auto cubeModel = ModelDoc::LoadModel(
|
|
||||||
AssetFS::GetInstance()
|
|
||||||
.GetFullPath("engine://cube.fbx")
|
|
||||||
.generic_string(),
|
|
||||||
staticModelOptions
|
|
||||||
);
|
|
||||||
ModelDocUtils::LogModelDocSummary(cubeModel, "cube.fbx");
|
|
||||||
|
|
||||||
const auto& cubePrimitive = ModelDocUtils::GetFirstPrimitiveOrThrow(cubeModel, "game://cube.fbx");
|
|
||||||
|
|
||||||
|
|
||||||
const auto eliasTextuerPath = ModelDocUtils::PickTexturePath(
|
|
||||||
cubeModel,
|
|
||||||
cubePrimitive,
|
|
||||||
AssetFS::GetInstance().GetFullPath("game://grass.png")
|
|
||||||
);
|
|
||||||
|
|
||||||
const auto eliasTextueID = resources.loadImageFromFile(gfxDevice, eliasTextuerPath);
|
|
||||||
const auto eliasMaterialID = resources.materials().addMaterial({
|
|
||||||
.baseColor = ModelDocUtils::GetImportedBaseColor(
|
|
||||||
planeModel, planePrimitive),
|
|
||||||
.textureFilteringMode =
|
|
||||||
TextureFilteringMode::Anisotropic,
|
|
||||||
.diffuseTexture = eliasTextueID,
|
|
||||||
.name = ModelDocUtils::GetImportedMaterialName(
|
|
||||||
planeModel, planePrimitive, "GroundPlaneMaterial"),
|
|
||||||
});
|
|
||||||
|
|
||||||
const auto cubeMeshID = resources.meshes().addMesh(gfxDevice, cubePrimitive.mesh);
|
|
||||||
//
|
|
||||||
// for (int i{0}; i < 100; i++)
|
|
||||||
// {
|
// {
|
||||||
// auto cube = std::make_shared<GameObject>("Cube");
|
// const auto CharObj = scene.CreateGameObject("Character");
|
||||||
//
|
//
|
||||||
// cube->AddComponent<BoxCollider>(glm::vec3{0.5f});
|
// ModelDoc::LoadOptions characterOptions{};
|
||||||
// cube->AddComponent<Rigidbody>();
|
// characterOptions.meshImportMode = ModelDoc::MeshImportMode::MergedPerNode;
|
||||||
|
// characterOptions.loadMaterials = true;
|
||||||
|
// characterOptions.loadSkeleton = true;
|
||||||
|
// characterOptions.loadAnimations = false;
|
||||||
//
|
//
|
||||||
// auto meshComp = cube->AddComponent<MeshRendererComponent>();
|
// auto charModel = AssetManager::GetInstance().LoadModel("engine://characterMedium.fbx", characterOptions);
|
||||||
// meshComp->SetMeshID(cubeMeshID);
|
|
||||||
// meshComp->SetMaterialID(eliasMaterialID);
|
|
||||||
//
|
//
|
||||||
// cube->GetTransform().SetWorldPosition(glm::vec3(0.0f, i, 0.0f));
|
// const auto& charPrimitive =
|
||||||
// cube->GetTransform().SetWorldScale(glm::vec3(0.005f));
|
// ModelDocUtils::GetFirstSkinnedPrimitiveOrFirstOrThrow(
|
||||||
|
// charModel,
|
||||||
|
// "engine://characterMedium.fbx"
|
||||||
|
// );
|
||||||
//
|
//
|
||||||
// scene.Add(cube);
|
// const auto charMeshID = resources.meshes().addMesh(gfxDevice, charPrimitive.mesh);
|
||||||
// scene.GetPhysics().RegisterGameObject(*cube);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// const int cubeCount = 10;
|
|
||||||
// const float spacing = 1.0f;
|
|
||||||
//
|
//
|
||||||
// for (int x = 0; x < cubeCount; x++)
|
// const auto charTextureID = resources.loadImageFromFile(gfxDevice,
|
||||||
// {
|
// AssetFS::GetInstance().GetFullPath("engine://textures/criminalMaleA.png")
|
||||||
// for (int y = 0; y < 3; y++)
|
// );
|
||||||
|
//
|
||||||
|
// const auto charMaterialID = resources.materials().addMaterial({
|
||||||
|
// .baseColor = ModelDocUtils::GetImportedBaseColor(
|
||||||
|
// charModel, charPrimitive),
|
||||||
|
// .diffuseTexture = charTextureID,
|
||||||
|
// .name = ModelDocUtils::GetImportedMaterialName(
|
||||||
|
// charModel,
|
||||||
|
// charPrimitive,
|
||||||
|
// "CharacterMaterial"
|
||||||
|
// ),
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// const auto charMeshComp = CharObj->AddComponent<MeshRendererComponent>();
|
||||||
|
// charMeshComp->SetMeshID(charMeshID);
|
||||||
|
// charMeshComp->SetMaterialID(charMaterialID);
|
||||||
|
//
|
||||||
|
// const auto animator = CharObj->AddComponent<Animator>();
|
||||||
|
// animator->setSkeleton(charModel.skeleton);
|
||||||
|
//
|
||||||
|
// auto runClips = AssetManager::GetInstance().LoadAnimationClips(
|
||||||
|
// "engine://run.fbx",
|
||||||
|
// charModel.skeleton
|
||||||
|
// );
|
||||||
|
//
|
||||||
|
// for (auto& clip : runClips)
|
||||||
// {
|
// {
|
||||||
// for (int z = 0; z < cubeCount; z++)
|
// spdlog::info("Loaded animation: '{}' ({:.2f}s)", clip.name, clip.duration);
|
||||||
// {
|
// animator->addClip(std::make_shared<SkeletalAnimation>(std::move(clip)));
|
||||||
// auto cube = std::make_shared<GameObject>("Cube");
|
|
||||||
//
|
|
||||||
// cube->AddComponent<BoxCollider>(glm::vec3{0.5f});
|
|
||||||
// cube->AddComponent<Rigidbody>();
|
|
||||||
//
|
|
||||||
// auto meshComp = cube->AddComponent<MeshRendererComponent>();
|
|
||||||
// meshComp->SetMeshID(cubeMeshID);
|
|
||||||
// meshComp->SetMaterialID(eliasMaterialID);
|
|
||||||
//
|
|
||||||
// cube->GetTransform().SetWorldPosition(glm::vec3(
|
|
||||||
// (x - cubeCount / 2.0f) * spacing,
|
|
||||||
// y * spacing + 0,
|
|
||||||
// (z - cubeCount / 2.0f) * spacing
|
|
||||||
// ));
|
|
||||||
//
|
|
||||||
// cube->GetTransform().SetWorldScale(glm::vec3(0.005f));
|
|
||||||
//
|
|
||||||
// scene.Add(cube);
|
|
||||||
// scene.GetPhysics().RegisterGameObject(*cube);
|
|
||||||
// }
|
|
||||||
// }
|
// }
|
||||||
|
//
|
||||||
|
// if (!runClips.empty())
|
||||||
|
// {
|
||||||
|
// animator->play("Root|Run");
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// CharObj->GetTransform().SetWorldPosition(glm::vec3(0.f));
|
||||||
|
// CharObj->GetTransform().SetWorldPosition(glm::vec3(5, 0, 0));
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// auto cubeModel = ModelDoc::LoadModel(
|
||||||
|
// AssetFS::GetInstance()
|
||||||
|
// .GetFullPath("engine://cube.fbx")
|
||||||
|
// .generic_string(),
|
||||||
|
// staticModelOptions
|
||||||
|
// );
|
||||||
|
// ModelDocUtils::LogModelDocSummary(cubeModel, "cube.fbx");
|
||||||
|
//
|
||||||
|
// const auto& cubePrimitive = ModelDocUtils::GetFirstPrimitiveOrThrow(cubeModel, "game://cube.fbx");
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// const auto eliasTextuerPath = ModelDocUtils::PickTexturePath(
|
||||||
|
// cubeModel,
|
||||||
|
// cubePrimitive,
|
||||||
|
// AssetFS::GetInstance().GetFullPath("game://grass.png")
|
||||||
|
// );
|
||||||
|
//
|
||||||
|
// const auto eliasTextueID = resources.loadImageFromFile(gfxDevice, eliasTextuerPath);
|
||||||
|
// const auto eliasMaterialID = resources.materials().addMaterial({
|
||||||
|
// .baseColor = ModelDocUtils::GetImportedBaseColor(
|
||||||
|
// planeModel, planePrimitive),
|
||||||
|
// .textureFilteringMode =
|
||||||
|
// TextureFilteringMode::Anisotropic,
|
||||||
|
// .diffuseTexture = eliasTextueID,
|
||||||
|
// .name = ModelDocUtils::GetImportedMaterialName(
|
||||||
|
// planeModel, planePrimitive, "GroundPlaneMaterial"),
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
// const auto cubeMeshID = resources.meshes().addMesh(gfxDevice, cubePrimitive.mesh);
|
||||||
|
// //
|
||||||
|
// // for (int i{0}; i < 100; i++)
|
||||||
|
// // {
|
||||||
|
// // auto cube = std::make_shared<GameObject>("Cube");
|
||||||
|
// //
|
||||||
|
// // cube->AddComponent<BoxCollider>(glm::vec3{0.5f});
|
||||||
|
// // cube->AddComponent<Rigidbody>();
|
||||||
|
// //
|
||||||
|
// // auto meshComp = cube->AddComponent<MeshRendererComponent>();
|
||||||
|
// // meshComp->SetMeshID(cubeMeshID);
|
||||||
|
// // meshComp->SetMaterialID(eliasMaterialID);
|
||||||
|
// //
|
||||||
|
// // cube->GetTransform().SetWorldPosition(glm::vec3(0.0f, i, 0.0f));
|
||||||
|
// // cube->GetTransform().SetWorldScale(glm::vec3(0.005f));
|
||||||
|
// //
|
||||||
|
// // scene.Add(cube);
|
||||||
|
// // scene.GetPhysics().RegisterGameObject(*cube);
|
||||||
|
// // }
|
||||||
|
//
|
||||||
|
// // const int cubeCount = 10;
|
||||||
|
// // const float spacing = 1.0f;
|
||||||
|
// //
|
||||||
|
// // for (int x = 0; x < cubeCount; x++)
|
||||||
|
// // {
|
||||||
|
// // for (int y = 0; y < 3; y++)
|
||||||
|
// // {
|
||||||
|
// // for (int z = 0; z < cubeCount; z++)
|
||||||
|
// // {
|
||||||
|
// // auto cube = std::make_shared<GameObject>("Cube");
|
||||||
|
// //
|
||||||
|
// // cube->AddComponent<BoxCollider>(glm::vec3{0.5f});
|
||||||
|
// // cube->AddComponent<Rigidbody>();
|
||||||
|
// //
|
||||||
|
// // auto meshComp = cube->AddComponent<MeshRendererComponent>();
|
||||||
|
// // meshComp->SetMeshID(cubeMeshID);
|
||||||
|
// // meshComp->SetMaterialID(eliasMaterialID);
|
||||||
|
// //
|
||||||
|
// // cube->GetTransform().SetWorldPosition(glm::vec3(
|
||||||
|
// // (x - cubeCount / 2.0f) * spacing,
|
||||||
|
// // y * spacing + 0,
|
||||||
|
// // (z - cubeCount / 2.0f) * spacing
|
||||||
|
// // ));
|
||||||
|
// //
|
||||||
|
// // cube->GetTransform().SetWorldScale(glm::vec3(0.005f));
|
||||||
|
// //
|
||||||
|
// // scene.Add(cube);
|
||||||
|
// // scene.GetPhysics().RegisterGameObject(*cube);
|
||||||
|
// // }
|
||||||
|
// // }
|
||||||
|
// // }
|
||||||
|
|
||||||
|
|
||||||
|
// {
|
||||||
|
auto sphereModel = AssetManager::GetInstance().LoadModel("engine://sphere.fbx", staticModelOptions);
|
||||||
|
ModelDocUtils::LogModelDocSummary(sphereModel, "sphere.fbx");
|
||||||
|
//
|
||||||
|
const auto& spherePrimitive = ModelDocUtils::GetFirstPrimitiveOrThrow(sphereModel, "game://sphere.fbx");
|
||||||
|
//
|
||||||
|
//
|
||||||
|
// const auto sphereTexturePath = ModelDocUtils::PickTexturePath(
|
||||||
|
// sphereModel,
|
||||||
|
// spherePrimitive,
|
||||||
|
// AssetFS::GetInstance().GetFullPath("game://238882.png")
|
||||||
|
// );
|
||||||
|
//
|
||||||
|
// const auto sphereTextureID = resources.loadImageFromFile(gfxDevice, sphereTexturePath);
|
||||||
|
// sphereMaterial = resources.materials().addMaterial({
|
||||||
|
// .baseColor = ModelDocUtils::GetImportedBaseColor(planeModel, planePrimitive),
|
||||||
|
// .textureFilteringMode =TextureFilteringMode::Anisotropic,
|
||||||
|
// .diffuseTexture = sphereTextureID,
|
||||||
|
// .name = ModelDocUtils::GetImportedMaterialName(
|
||||||
|
// planeModel, planePrimitive,
|
||||||
|
// "GroundPlaneMaterial"),
|
||||||
|
// });
|
||||||
|
//
|
||||||
|
sphereMesh = resources.meshes().addMesh(gfxDevice, spherePrimitive.mesh);
|
||||||
|
//
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
sphereMaterial = resources.materials().addSimpleColorMaterial({0.5f, 0.3f, 0.8f}, "Blue");
|
||||||
|
|
||||||
{
|
|
||||||
auto sphereModel = ModelDoc::LoadModel(
|
|
||||||
AssetFS::GetInstance().GetFullPath("engine://sphere.fbx").generic_string(),
|
|
||||||
staticModelOptions
|
|
||||||
);
|
|
||||||
ModelDocUtils::LogModelDocSummary(sphereModel, "sphere.fbx");
|
|
||||||
|
|
||||||
const auto& spherePrimitive = ModelDocUtils::GetFirstPrimitiveOrThrow(sphereModel, "game://sphere.fbx");
|
|
||||||
|
|
||||||
|
|
||||||
const auto sphereTexturePath = ModelDocUtils::PickTexturePath(
|
|
||||||
sphereModel,
|
|
||||||
spherePrimitive,
|
|
||||||
AssetFS::GetInstance().GetFullPath("game://238882.png")
|
|
||||||
);
|
|
||||||
|
|
||||||
const auto sphereTextureID = resources.loadImageFromFile(gfxDevice, sphereTexturePath);
|
|
||||||
sphereMaterial = resources.materials().addMaterial({
|
|
||||||
.baseColor = ModelDocUtils::GetImportedBaseColor(planeModel, planePrimitive),
|
|
||||||
.textureFilteringMode =TextureFilteringMode::Anisotropic,
|
|
||||||
.diffuseTexture = sphereTextureID,
|
|
||||||
.name = ModelDocUtils::GetImportedMaterialName(
|
|
||||||
planeModel, planePrimitive,
|
|
||||||
"GroundPlaneMaterial"),
|
|
||||||
});
|
|
||||||
|
|
||||||
sphereMesh = resources.meshes().addMesh(gfxDevice, spherePrimitive.mesh);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void LightKeeper::customUpdate(float dt)
|
void LightKeeper::customUpdate(float dt)
|
||||||
@@ -501,7 +467,7 @@ void LightKeeper::customUpdate(float dt)
|
|||||||
|
|
||||||
if (ImGui::Button("Save Current Scene")) {
|
if (ImGui::Button("Save Current Scene")) {
|
||||||
try {
|
try {
|
||||||
const auto path = ResolveScenePath(scenePath);
|
const auto path = ResolveScenePath(scenePath, m_params.exeDir);
|
||||||
const auto parent = path.parent_path();
|
const auto parent = path.parent_path();
|
||||||
if (!parent.empty()) {
|
if (!parent.empty()) {
|
||||||
std::filesystem::create_directories(parent);
|
std::filesystem::create_directories(parent);
|
||||||
@@ -522,7 +488,7 @@ void LightKeeper::customUpdate(float dt)
|
|||||||
ImGui::SameLine();
|
ImGui::SameLine();
|
||||||
if (ImGui::Button("Load Scene")) {
|
if (ImGui::Button("Load Scene")) {
|
||||||
try {
|
try {
|
||||||
const auto path = ResolveScenePath(scenePath);
|
const auto path = ResolveScenePath(scenePath, m_params.exeDir);
|
||||||
if (SceneSerializer::Load(
|
if (SceneSerializer::Load(
|
||||||
SceneManager::GetInstance().GetCurrentScene(),
|
SceneManager::GetInstance().GetCurrentScene(),
|
||||||
path)) {
|
path)) {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include <destrum/Event.h>
|
#include <destrum/Event.h>
|
||||||
|
#include <destrum/Assets/AssetReference.h>
|
||||||
#include <destrum/FS/AssetFS.h>
|
#include <destrum/FS/AssetFS.h>
|
||||||
#include <destrum/ObjectModel/Component.h>
|
#include <destrum/ObjectModel/Component.h>
|
||||||
#include <destrum/ObjectModel/GameObject.h>
|
#include <destrum/ObjectModel/GameObject.h>
|
||||||
@@ -17,7 +18,9 @@
|
|||||||
#include <destrum/Physics/JoltPhysicsWorld.h>
|
#include <destrum/Physics/JoltPhysicsWorld.h>
|
||||||
#include <destrum/Components/Physics/SphereCollider.h>
|
#include <destrum/Components/Physics/SphereCollider.h>
|
||||||
#include <destrum/Components/Physics/BoxCollider.h>
|
#include <destrum/Components/Physics/BoxCollider.h>
|
||||||
|
#include <destrum/Components/Animator.h>
|
||||||
#include <destrum/Components/Physics/Rigidbody.h>
|
#include <destrum/Components/Physics/Rigidbody.h>
|
||||||
|
#include <destrum/Graphics/Material.h>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
class TestComponent final : public Component {
|
class TestComponent final : public Component {
|
||||||
@@ -343,6 +346,91 @@ namespace {
|
|||||||
|
|
||||||
SceneManager::GetInstance().Destroy();
|
SceneManager::GetInstance().Destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void testAnimatorAssetReferences()
|
||||||
|
{
|
||||||
|
GameObject object{"animated"};
|
||||||
|
auto* animator = object.AddComponent<Animator>();
|
||||||
|
|
||||||
|
Skeleton skeleton;
|
||||||
|
Joint joint;
|
||||||
|
joint.id = 0;
|
||||||
|
joint.localTranslation = glm::vec3{0.0f};
|
||||||
|
joint.localRotation = glm::identity<glm::quat>();
|
||||||
|
joint.localScale = glm::vec3{1.0f};
|
||||||
|
skeleton.joints.push_back(joint);
|
||||||
|
skeleton.inverseBindMatrices.push_back(glm::mat4{1.0f});
|
||||||
|
skeleton.jointNames.push_back("root");
|
||||||
|
skeleton.assetReference.path = "engine://character.fbx";
|
||||||
|
buildParentIndex(skeleton);
|
||||||
|
animator->setSkeleton(std::move(skeleton));
|
||||||
|
|
||||||
|
auto clip = std::make_shared<SkeletalAnimation>();
|
||||||
|
clip->name = "walk";
|
||||||
|
clip->assetReference.path = "engine://walk.fbx";
|
||||||
|
clip->assetReference.subresource = "animation:0";
|
||||||
|
clip->duration = 1.0f;
|
||||||
|
SkeletalAnimation::Keyframe keyframe;
|
||||||
|
keyframe.time = 0.0f;
|
||||||
|
keyframe.translation = glm::vec3{0.0f};
|
||||||
|
keyframe.rotation = glm::identity<glm::quat>();
|
||||||
|
keyframe.scale = glm::vec3{1.0f};
|
||||||
|
SkeletalAnimation::Track track;
|
||||||
|
track.jointIndex = 0;
|
||||||
|
track.keyframes.push_back(keyframe);
|
||||||
|
clip->tracks.push_back(std::move(track));
|
||||||
|
animator->addClip(std::move(clip));
|
||||||
|
animator->play("walk");
|
||||||
|
|
||||||
|
const auto data = animator->Serialize();
|
||||||
|
check(data.contains("assetReferences"),
|
||||||
|
"Animator scenes must store asset references");
|
||||||
|
check(data.at("assetReferences").at("skeleton").at("path") == "engine://character.fbx",
|
||||||
|
"Animator serialization must store the skeleton source");
|
||||||
|
check(data.at("assetReferences").at("animations").at(0).at("asset").at("subresource") ==
|
||||||
|
"animation:0",
|
||||||
|
"Animator serialization must store the animation subresource");
|
||||||
|
check(data.dump().find("keyframes") == std::string::npos,
|
||||||
|
"Animator scene data must not embed animation keyframes");
|
||||||
|
}
|
||||||
|
|
||||||
|
void testSimpleColorMaterial()
|
||||||
|
{
|
||||||
|
const auto material = Material::SimpleColor(
|
||||||
|
glm::vec3{0.2f, 0.4f, 0.8f},
|
||||||
|
"blue material");
|
||||||
|
|
||||||
|
check(material.baseColor == glm::vec3{0.2f, 0.4f, 0.8f},
|
||||||
|
"simple materials must preserve their color");
|
||||||
|
check(material.metallicFactor == 0.0f &&
|
||||||
|
material.roughnessFactor == 1.0f &&
|
||||||
|
material.emissiveFactor == 0.0f,
|
||||||
|
"simple materials must use non-metallic default factors");
|
||||||
|
check(material.diffuseTexture == NULL_IMAGE_ID,
|
||||||
|
"simple materials must not require a diffuse image");
|
||||||
|
check(material.name == "blue material",
|
||||||
|
"simple materials must preserve their optional name");
|
||||||
|
}
|
||||||
|
|
||||||
|
void testAssetReferenceCacheKeys()
|
||||||
|
{
|
||||||
|
const AssetReference reference{
|
||||||
|
.path = "game://CharacterMedium.fbx",
|
||||||
|
.subresource = "mesh:0",
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto key = reference.cacheKey();
|
||||||
|
check(key == "game://CharacterMedium.fbx#mesh:0",
|
||||||
|
"asset references must produce stable cache keys");
|
||||||
|
|
||||||
|
const auto parsed = AssetReference::fromCacheKey(key);
|
||||||
|
check(parsed.has_value() &&
|
||||||
|
parsed->path == reference.path &&
|
||||||
|
parsed->subresource == reference.subresource,
|
||||||
|
"asset cache keys must recover their source reference");
|
||||||
|
check(!AssetReference::fromCacheKey("CharacterMedium.fbx").has_value(),
|
||||||
|
"plain cache names must not be treated as file-backed assets");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int main()
|
int main()
|
||||||
@@ -358,6 +446,9 @@ int main()
|
|||||||
testPhysicsWorldUnits();
|
testPhysicsWorldUnits();
|
||||||
testJoltSphereCollisions();
|
testJoltSphereCollisions();
|
||||||
testSceneJoltSphereCollisions();
|
testSceneJoltSphereCollisions();
|
||||||
|
testAnimatorAssetReferences();
|
||||||
|
testSimpleColorMaterial();
|
||||||
|
testAssetReferenceCacheKeys();
|
||||||
std::cout << "destrum tests passed\n";
|
std::cout << "destrum tests passed\n";
|
||||||
return EXIT_SUCCESS;
|
return EXIT_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user