#include "assets/data_loaders/mtl_loader.hpp" #include #include #include #include "util/logger.hpp" #include "util/for_each.hpp" #include "util/line_parser.hpp" #include "assets/dynamic_data_loaders/dynamic_texture_loader.hpp" namespace mtl_loader_error { struct category : std::error_category { [[nodiscard]] const char* name() const noexcept override { return "connector"; } [[nodiscard]] std::string message(int ev) const override { switch (static_cast(ev)) { using enum codes; case mtl_cannot_open_file: return "Cannot open mtl file."; case mtl_cannot_open_texture: return "Cannot open texture file."; case mtl_malformed_ambient_color: return "File contains malformed 'Ka' statement."; case mtl_malformed_diffuse_color: return "File contains malformed 'Kd' statement."; case mtl_malformed_specular_color: return "File contains malformed 'Ks' statement."; case mtl_malformed_specular_exponent: return "File contains malformed 'Ns' statement."; case mtl_malformed_dissolve: return "File contains malformed 'd' statement."; case mlt_unknown_line_begin: return "Unknown mtl line begin"; default: using namespace std::string_literals; return "unrecognized error ("s + std::to_string(ev) + ")"; } } }; } // namespace mesh_loader_error inline std::error_category& connector_error_category() { static mtl_loader_error::category category; return category; } namespace mtl_loader_error { inline std::error_code make_error_code(codes e) { return { static_cast(e), connector_error_category() }; } } // namespace mtl_loader_error template std::errc parse_numeric_vector(std::string_view param, std::array& values) { auto it = param.begin(), end = param.end(); for (auto& value : values) { if (it >= end) { return std::errc::invalid_argument; } const auto [ptr, ec] = std::from_chars(it, end, value); if (ec != std::errc{}) { return ec; } it = ptr + 1; // skip space in between components } return {}; }; std::optional mtl_loader::find_id(std::string_view name) { const auto it = m_id_lookup.find(name); if (it == m_id_lookup.end()) { return it->second; } return std::nullopt; } void mtl_loader::clear_name_lookup() { m_id_lookup.clear(); } std::error_code mtl_loader::load_directory( dynamic_data_loader_ctx& ctx, dynamic_material_store& store, components::material::flags enabled_components, const std::filesystem::path& path, const bool pedantic ) { namespace fs = std::filesystem; if (not fs::exists(path)) { return make_error_code(std::errc::no_such_file_or_directory); } for (const auto& file : fs::directory_iterator{ path }) { const auto& file_path = file.path(); if (file_path.extension() != ".obj") { continue; } if (const auto e = load( ctx, store, enabled_components, path, pedantic )) { ztu::logger::error( "Error while loading obj file '%': [%] %", file_path, e.category().name(), e.message() ); } } return {}; } std::error_code mtl_loader::load( dynamic_data_loader_ctx& ctx, dynamic_material_store& store, components::material::flags enabled_components, const std::filesystem::path& filename, const bool pedantic ) { using mtl_loader_error::codes; using mtl_loader_error::make_error_code; using flags = components::material::flags; const auto component_disabled = [&](const components::material::flags component) { return (enabled_components & component) == flags::none; }; // TODO unroll stuff const auto textures_disabled = component_disabled(flags::ambient_filter_texture); const auto surface_properties_disabled = component_disabled(flags::surface_properties); const auto transparencies_disabled = component_disabled(flags::transparency); auto in = std::ifstream{ filename }; if (not in.is_open()) { return make_error_code(codes::mtl_cannot_open_file); } namespace fs = std::filesystem; const auto directory = fs::canonical(fs::path(filename).parent_path()); auto name = std::string{}; auto material = dynamic_material_data{}; const auto push_material = [&]() { if (not name.empty()) { const auto id = store.add(std::move(material)); m_id_lookup.emplace(std::move(name), id); } name = std::string{}; material = dynamic_material_data{}; }; const auto load_texture = [&]( const std::string_view path, std::string_view texture_type_name, auto&& f ) { auto texture_filename = fs::path(path); if (texture_filename.is_relative()) { texture_filename = directory / texture_filename; } const auto extension = texture_filename.extension().string(); auto texture_type = std::string_view{ extension }; if (not texture_type.empty() and texture_type.front() == '.') { texture_type = texture_type.substr(1); } if (const auto loader_id = ctx.texture_loader.find_loader(texture_type)) { if (auto res = ctx.texture_loader.read( ctx, *loader_id, texture_filename, pedantic )) { f(*res); } else { const auto error = res.error(); ztu::logger::warn( "Error while loading % texture '%': [%] %", texture_type_name, path, error.category().name(), error.message() ); } } else { ztu::logger::warn( "Failed to load % texture '%' because extension is not supported.", texture_type_name, path ); } }; const auto ec = ztu::parse_lines( in, pedantic, ztu::make_line_parser("newmtl ", ztu::is_not_repeating, [&](const auto& param) { push_material(); name = param; return codes::ok; }), ztu::make_line_parser("Ka ", ztu::is_not_repeating, [&](const auto& param) { if (surface_properties_disabled) return codes::ok; auto& properties = material.initialized_surface_properties(); if (parse_numeric_vector(param, properties.ambient_filter) != std::errc{}) [[unlikely]] { return codes::mtl_malformed_ambient_color; } material.components() |= flags::surface_properties; return codes::ok; }), ztu::make_line_parser("Kd ", ztu::is_not_repeating, [&](const auto& param) { if (surface_properties_disabled) return codes::ok; auto& properties = material.initialized_surface_properties(); if (parse_numeric_vector(param, properties.diffuse_filter) != std::errc{}) [[unlikely]] { return codes::mtl_malformed_diffuse_color; } material.components() |= flags::surface_properties; return codes::ok; }), ztu::make_line_parser("Ks ", ztu::is_not_repeating, [&](const auto& param) { if (surface_properties_disabled) return codes::ok; auto& properties = material.initialized_surface_properties(); if (parse_numeric_vector(param, properties.specular_filter) != std::errc{}) [[unlikely]] { return codes::mtl_malformed_specular_color; } material.components() |= flags::surface_properties; return codes::ok; }), ztu::make_line_parser("Ns ", ztu::is_not_repeating, [&](const auto& param) { if (surface_properties_disabled) return codes::ok; auto& properties = material.initialized_surface_properties(); std::array shininess{}; if (parse_numeric_vector(param, shininess) != std::errc{}) [[unlikely]] { return codes::mtl_malformed_specular_exponent; } properties.shininess = shininess.front(); material.components() |= flags::surface_properties; return codes::ok; }), ztu::make_line_parser("d ", ztu::is_not_repeating, [&](const auto& param) { if (transparencies_disabled) return codes::ok; std::array transparency{}; if (parse_numeric_vector(param, transparency) != std::errc{}) [[unlikely]] { return codes::mtl_malformed_dissolve; } material.transparency().emplace(transparency.front()); material.components() |= flags::transparency; return codes::ok; }), ztu::make_line_parser("map_Ka ", ztu::is_not_repeating, [&](const auto& param) { if (textures_disabled) return codes::ok; load_texture(param, "ambient color", [&](const auto id) { material.ambient_color_texture_id() = id; material.components() |= flags::ambient_filter_texture; }); return codes::ok; }), ztu::make_line_parser("map_Kd ", ztu::is_not_repeating, [&](const auto& param) { if (textures_disabled) return codes::ok; load_texture(param, "diffuse color", [&](const auto id) { material.diffuse_color_texture_id() = id; material.components() |= flags::diffuse_filter_texture; }); return codes::ok; }), ztu::make_line_parser("map_Ks ", ztu::is_not_repeating, [&](const auto& param) { if (textures_disabled) return codes::ok; load_texture(param, "specular color", [&](const auto id) { material.specular_color_texture_id() = id; material.components() |= flags::specular_filter_texture; }); return codes::ok; }), ztu::make_line_parser("map_Ns ", ztu::is_not_repeating, [&](const auto& param) { if (textures_disabled) return codes::ok; load_texture(param, "shininess", [&](const auto id) { material.shininess_texture_id() = id; material.components() |= flags::shininess_texture; }); return codes::ok; }), ztu::make_line_parser("map_d ", ztu::is_not_repeating, [&](const auto& param) { if (textures_disabled) return codes::ok; load_texture(param, "alpha", [&](const auto id) { material.alpha_texture_id() = id; material.components() |= flags::alpha_texture; }); return codes::ok; }), ztu::make_line_parser("bump ", ztu::is_not_repeating, [&](const auto& param) { if (textures_disabled) return codes::ok; load_texture(param, "bump", [&](const auto id) { material.bump_texture_id() = id; material.components() |= flags::bump_texture; }); return codes::ok; }) ); if (ec != codes::ok) { return make_error_code(ec); } push_material(); return {}; }