138 lines
5.2 KiB
C++
138 lines
5.2 KiB
C++
#include <destrum/Assets/AssetManager.h>
|
|
|
|
#include <memory>
|
|
#include <optional>
|
|
#include <stdexcept>
|
|
|
|
#include <destrum/Assets/ModelFile.h>
|
|
#include <destrum/FS/AssetFS.h>
|
|
|
|
#include "AssetFSIOSystem.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::optional<std::filesystem::path> FindCookedModel(const std::string& reference) {
|
|
if (reference.find("://") == std::string::npos) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
try {
|
|
const std::filesystem::path cookedPath = AssetFS::GetInstance().GetCookedPathForFile(reference);
|
|
if (cookedPath.extension() == ModelFile::Extension) {
|
|
return cookedPath;
|
|
}
|
|
} catch (const std::exception& exception) {
|
|
spdlog::warn("Importing {} instead of using its cooked model: {}", reference, exception.what());
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
|
|
// Assimp opens files itself, so with packed assets it has to go through AssetFS
|
|
std::unique_ptr<Assimp::IOSystem> MakeIOSystem() {
|
|
if (!AssetFS::GetInstance().IsPacked()) {
|
|
return nullptr;
|
|
}
|
|
return std::make_unique<AssetFSIOSystem>();
|
|
}
|
|
|
|
// Cooked texture paths are relative to the model file, imported ones are absolute
|
|
void ResolveTexturePaths(ModelDoc::ModelAsset& model, const std::filesystem::path& modelDir) {
|
|
for (ModelDoc::MaterialInfo& material : model.materials) {
|
|
for (ModelDoc::TextureInfo& texture : material.textures) {
|
|
if (texture.embedded || texture.path.empty() || std::filesystem::path{texture.path}.is_absolute()) {
|
|
continue;
|
|
}
|
|
texture.path = (modelDir / texture.path).lexically_normal().generic_string();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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 = LoadCookedOrImport(sourceReference, 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);
|
|
if (primitive.mesh.skeleton) {
|
|
primitive.mesh.skeleton->assetReference.path = sourceReference;
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
|
|
ModelDoc::ModelAsset AssetManager::LoadCookedOrImport(
|
|
const std::string& reference,
|
|
const ModelDoc::LoadOptions& options) const
|
|
{
|
|
if (const auto cookedPath = FindCookedModel(reference)) {
|
|
ModelDoc::ModelAsset model = ModelFile::Read(AssetFS::GetInstance().ReadFile(*cookedPath));
|
|
if (ModelFile::SupportsLoadOptions(model, options)) {
|
|
ModelFile::ApplyLoadOptions(model, options);
|
|
ResolveTexturePaths(model, cookedPath->parent_path());
|
|
spdlog::debug("Loaded cooked model {}", reference);
|
|
return model;
|
|
}
|
|
spdlog::debug("Importing {}, its cooked model doesn't match the requested load options", reference);
|
|
}
|
|
|
|
return ModelDoc::LoadModel(Resolve(reference).generic_string(), options, MakeIOSystem());
|
|
}
|
|
|
|
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,
|
|
MakeIOSystem());
|
|
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;
|
|
}
|