#include "assetload_funcs.hpp" #include #include #include #include "audio/audio.hpp" #include "coders/GLSLExtension.hpp" #include "coders/commons.hpp" #include "coders/imageio.hpp" #include "coders/json.hpp" #include "coders/obj.hpp" #include "coders/vec3.hpp" #include "constants.hpp" #include "debug/Logger.hpp" #include "io/engine_paths.hpp" #include "io/io.hpp" #include "frontend/UiDocument.hpp" #include "graphics/core/Atlas.hpp" #include "graphics/core/Font.hpp" #include "graphics/core/ImageData.hpp" #include "graphics/core/Shader.hpp" #include "graphics/core/Texture.hpp" #include "graphics/core/TextureAnimation.hpp" #include "graphics/commons/Model.hpp" #include "objects/rigging.hpp" #include "util/stringutil.hpp" #include "Assets.hpp" #include "AssetsLoader.hpp" static debug::Logger logger("assetload-funcs"); namespace fs = std::filesystem; static bool load_animation( Assets* assets, const ResPaths* paths, const std::string& atlasName, const std::string& directory, const std::string& name, Atlas* dstAtlas ); assetload::postfunc assetload::texture( AssetsLoader*, const ResPaths* paths, const std::string& filename, const std::string& name, const std::shared_ptr& ) { auto actualFile = paths->find(filename + ".png"); try { std::shared_ptr image(imageio::read(actualFile).release()); return [name, image, actualFile](auto assets) { assets->store(Texture::from(image.get()), name); }; } catch (const std::runtime_error& err) { logger.error() << actualFile.string() << ": " << err.what(); return [](auto) {}; } } assetload::postfunc assetload::shader( AssetsLoader*, const ResPaths* paths, const std::string& filename, const std::string& name, const std::shared_ptr& ) { io::path vertexFile = paths->find(filename + ".glslv"); io::path fragmentFile = paths->find(filename + ".glslf"); std::string vertexSource = io::read_string(vertexFile); std::string fragmentSource = io::read_string(fragmentFile); vertexSource = Shader::preprocessor->process(vertexFile, vertexSource); fragmentSource = Shader::preprocessor->process(fragmentFile, fragmentSource); return [=](auto assets) { assets->store( Shader::create( vertexFile.string(), fragmentFile.string(), vertexSource, fragmentSource ), name ); }; } static bool append_atlas(AtlasBuilder& atlas, const io::path& file) { std::string name = file.stem(); // skip duplicates if (atlas.has(name)) { return false; } auto image = imageio::read(file); image->fixAlphaColor(); atlas.add(name, std::move(image)); return true; } assetload::postfunc assetload::atlas( AssetsLoader* loader, const ResPaths* paths, const std::string& directory, const std::string& name, const std::shared_ptr& config ) { auto atlasConfig = std::dynamic_pointer_cast(config); if (atlasConfig && atlasConfig->type == AtlasType::SEPARATE) { for (const auto& file : paths->listdir(directory)) { if (!imageio::is_read_supported(file.extension())) continue; loader->add( AssetType::TEXTURE, directory + "/" + file.stem(), name + "/" + file.stem() ); } return [](auto){}; } AtlasBuilder builder; for (const auto& file : paths->listdir(directory)) { if (!imageio::is_read_supported(file.extension())) continue; if (!append_atlas(builder, file)) continue; } std::set names = builder.getNames(); Atlas* atlas = builder.build(2, false).release(); return [=](auto assets) { atlas->prepare(); assets->store(std::unique_ptr(atlas), name); for (const auto& file : names) { load_animation(assets, paths, name, directory, file, atlas); } }; } assetload::postfunc assetload::font( AssetsLoader*, const ResPaths* paths, const std::string& filename, const std::string& name, const std::shared_ptr& ) { auto pages = std::make_shared>>(); for (size_t i = 0; i <= 1024; i++) { std::string pagefile = filename + "_" + std::to_string(i) + ".png"; auto file = paths->find(pagefile); if (io::exists(file)) { pages->push_back(imageio::read(file)); } else if (i == 0) { throw std::runtime_error("font must have page 0"); } else { pages->push_back(nullptr); } } return [=](auto assets) { int res = pages->at(0)->getHeight() / 16; std::vector> textures; for (auto& page : *pages) { if (page == nullptr) { textures.emplace_back(nullptr); } else { auto texture = Texture::from(page.get()); texture->setMipMapping(false); textures.emplace_back(std::move(texture)); } } assets->store( std::make_unique(std::move(textures), res, 4), name ); }; } assetload::postfunc assetload::layout( AssetsLoader*, const ResPaths* paths, const std::string& file, const std::string& name, const std::shared_ptr& config ) { return [=](auto assets) { try { auto cfg = std::dynamic_pointer_cast(config); size_t pos = name.find(':'); auto prefix = name.substr(0, pos); assets->store( UiDocument::read( cfg->env, name, file, prefix + ":layouts/" + name.substr(pos + 1) + ".xml" ), name ); } catch (const parsing_error& err) { throw std::runtime_error( "failed to parse layout XML '" + file + "':\n" + err.errorLog() ); } }; } assetload::postfunc assetload::sound( AssetsLoader*, const ResPaths* paths, const std::string& file, const std::string& name, const std::shared_ptr& config ) { auto cfg = std::dynamic_pointer_cast(config); bool keepPCM = cfg ? cfg->keepPCM : false; std::unique_ptr baseSound = nullptr; static std::vector extensions {".ogg", ".wav"}; std::string extension; for (size_t i = 0; i < extensions.size(); i++) { extension = extensions[i]; // looking for 'sound_name' as base sound auto soundFile = paths->find(file + extension); if (io::exists(soundFile)) { baseSound = audio::load_sound(soundFile, keepPCM); break; } // looking for 'sound_name_0' as base sound auto variantFile = paths->find(file + "_0" + extension); if (io::exists(variantFile)) { baseSound = audio::load_sound(variantFile, keepPCM); break; } } if (baseSound == nullptr) { throw std::runtime_error("could not to find sound: " + file); } // loading sound variants for (uint i = 1;; i++) { auto variantFile = paths->find(file + "_" + std::to_string(i) + extension); if (!io::exists(variantFile)) { break; } baseSound->variants.emplace_back(audio::load_sound(variantFile, keepPCM)); } auto sound = baseSound.release(); return [=](auto assets) { assets->store(std::unique_ptr(sound), name); }; } static void request_textures(AssetsLoader* loader, const model::Model& model) { for (auto& mesh : model.meshes) { if (mesh.texture.find('$') == std::string::npos) { auto filename = TEXTURES_FOLDER + "/" + mesh.texture; loader->add( AssetType::TEXTURE, filename, mesh.texture, nullptr ); } } } assetload::postfunc assetload::model( AssetsLoader* loader, const ResPaths* paths, const std::string& file, const std::string& name, const std::shared_ptr& ) { auto path = paths->find(file + ".vec3"); if (io::exists(path)) { auto bytes = io::read_bytes_buffer(path); auto modelVEC3 = std::make_shared(vec3::load(path.string(), bytes)); return [loader, name, modelVEC3=std::move(modelVEC3)](Assets* assets) { for (auto& [modelName, model] : modelVEC3->models) { request_textures(loader, model.model); std::string fullName = name; if (name != modelName) { fullName += "." + modelName; } assets->store( std::make_unique(model.model), fullName ); logger.info() << "store model " << util::quote(modelName) << " as " << util::quote(fullName); } }; } path = paths->find(file + ".obj"); auto text = io::read_string(path); try { auto model = obj::parse(path.string(), text).release(); return [=](Assets* assets) { request_textures(loader, *model); assets->store(std::unique_ptr(model), name); }; } catch (const parsing_error& err) { std::cerr << err.errorLog() << std::endl; throw; } } static void read_anim_file( const std::string& animFile, std::vector>& frameList ) { auto root = io::read_json(animFile); float frameDuration = DEFAULT_FRAME_DURATION; std::string frameName; if (auto found = root.at("frames")) { auto& frameArr = *found; for (size_t i = 0; i < frameArr.size(); i++) { const auto& currentFrame = frameArr[i]; frameName = currentFrame[0].asString(); if (currentFrame.size() > 1) { frameDuration = currentFrame[1].asNumber(); } frameList.emplace_back(frameName, frameDuration); } } } static TextureAnimation create_animation( Atlas* srcAtlas, Atlas* dstAtlas, const std::string& name, const std::set& frameNames, const std::vector>& frameList ) { Texture* srcTex = srcAtlas->getTexture(); Texture* dstTex = dstAtlas->getTexture(); UVRegion region = dstAtlas->get(name); TextureAnimation animation(srcTex, dstTex); Frame frame; uint dstWidth = dstTex->getWidth(); uint dstHeight = dstTex->getHeight(); uint srcWidth = srcTex->getWidth(); uint srcHeight = srcTex->getHeight(); const int extension = 2; frame.dstPos = glm::ivec2(region.u1 * dstWidth, region.v1 * dstHeight) - extension; frame.size = glm::ivec2(region.u2 * dstWidth, region.v2 * dstHeight) - frame.dstPos + extension; for (const auto& elem : frameList) { if (!srcAtlas->has(elem.first)) { logger.error() << "unknown frame name: " << elem.first; continue; } region = srcAtlas->get(elem.first); if (elem.second > 0) { frame.duration = static_cast(elem.second) / 1000.0f; } frame.srcPos = glm::ivec2( region.u1 * srcWidth, srcHeight - region.v2 * srcHeight ) - extension; animation.addFrame(frame); } return animation; } inline bool contains( const std::vector>& frameList, const std::string& frameName ) { for (const auto& elem : frameList) { if (frameName == elem.first) { return true; } } return false; } static bool load_animation( Assets* assets, const ResPaths* paths, const std::string& atlasName, const std::string& directory, const std::string& name, Atlas* dstAtlas ) { std::string animsDir = directory + "/animation"; for (const auto& folder : paths->listdir(animsDir)) { if (!io::is_directory(folder)) continue; if (folder.name() != name) continue; //FIXME: if (fs::is_empty(folder)) continue; AtlasBuilder builder; append_atlas(builder, paths->find(directory + "/" + name + ".png")); std::vector> frameList; std::string animFile = folder.string() + "/animation.json"; if (io::exists(animFile)) { read_anim_file(animFile, frameList); } for (const auto& file : paths->listdir(animsDir + "/" + name)) { if (!frameList.empty() && !contains(frameList, file.stem())) { continue; } if (!append_atlas(builder, file)) continue; } auto srcAtlas = builder.build(2, true); if (frameList.empty()) { for (const auto& frameName : builder.getNames()) { frameList.emplace_back(frameName, 0); } } auto animation = create_animation( srcAtlas.get(), dstAtlas, name, builder.getNames(), frameList ); assets->store( std::move(srcAtlas), atlasName + "/" + name + "_animation" ); assets->store(animation); return true; } return true; }