#include "../../../include/assets/data_parsers" #include #include #include #include "util/logger.hpp" #include "util/for_each.hpp" #include "util/line_parser.hpp" #include "../../../include/assets/data_loaders" namespace mtl_loader_error { struct category : std::error_category { [[nodiscard]] const char* name() const noexcept override { return "mtl_loader"; } [[nodiscard]] std::string message(int ev) const override { switch (static_cast(ev)) { using enum codes; case cannot_open_file: return "Cannot open mtl file."; case cannot_open_texture: return "Cannot open texture file."; case malformed_ambient_color: return "File contains malformed 'Ka' statement."; case malformed_diffuse_color: return "File contains malformed 'Kd' statement."; case malformed_specular_color: return "File contains malformed 'Ks' statement."; case malformed_specular_exponent: return "File contains malformed 'Ns' statement."; case malformed_dissolve: return "File contains malformed 'd' statement."; case 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 {}; }; void mtl_loader::find_textures( std::span buffer, std::filesystem::path& path_buffer, const std::filesystem::path& base_directory, std::ifstream& in, ztu::string_list& texture_filenames ) { using namespace std::string_view_literals; // TODO 'bump' is missing!!! static constexpr auto keyword = "\nmap_"sv; using long_postfix_type = std::array; static constexpr auto postfix_length = std::tuple_size_v; static constexpr auto make_postfix = [](const std::string_view str) static constexpr { auto postfix = long_postfix_type{}; assert(str.length() >= postfix_length); std::copy_n(str.begin(), postfix.size(), postfix.begin()); return postfix; }; static constexpr auto postfixes = std::array{ make_postfix("d "), make_postfix("Ka"), make_postfix("Kd"), make_postfix("Ks"), make_postfix("Ns") }; const auto buffer_view = std::string_view(buffer); // Add linebreak to simplify line begin search. buffer.front() = '\n'; auto leftover = std::size_t{ 1 }; enum class match { exact, overflowed, none }; const auto check_match = [](std::string_view& potential_match) static -> match { std::cout << '\'' << potential_match.substr(0, std::min(40ul, potential_match.size())) << '\'' << std::endl; if (potential_match.length() < postfix_length) { return match::overflowed; } const auto postfix = make_postfix(potential_match); // Optimized for SIMD. if (not std::ranges::contains(postfixes, postfix)) { return match::none; } const auto long_match = postfix.back() != ' '; if (long_match and ( potential_match.length() < postfix_length + sizeof(' ') or potential_match[postfix_length] != ' ' )) { return match::overflowed; } const auto actual_postfix_length = std::size_t{ 1 } + static_cast(long_match); const auto filename_begin = actual_postfix_length + sizeof(' '); const auto filename_end = potential_match.find('\n', filename_begin); if (filename_end == std::string_view::npos) { return match::overflowed; } const auto length = filename_end - filename_begin; potential_match = potential_match.substr(filename_begin, length); return match::exact; }; do { // Keep some old characters to continue matching interrupted sequence. std::copy(buffer.end() - leftover, buffer.end(), buffer.begin()); in.read(buffer.data() + leftover, buffer.size() - leftover); const auto str = buffer_view.substr(0, leftover + in.gcount()); auto pos = std::string_view::size_type{}; while ((pos = str.find(keyword, pos)) != std::string_view::npos) { const auto keyword_end = pos + keyword.size(); auto potential_match = str.substr(keyword_end); const auto match_type = check_match(potential_match); if (match_type == match::exact) { path_buffer.assign(potential_match); if (path_buffer.is_relative()) { path_buffer = base_directory; path_buffer /= potential_match; } texture_filenames.push_back(path_buffer.c_str()); pos += potential_match.size(); leftover = 0; } else if (match_type == match::overflowed) { if (pos == 0) [[unlikely]] { ztu::logger::error("Ignoring string match, as it exceeds buffer size of % characters.", buffer.size()); leftover = 0; } else { leftover = str.size() - pos; } break; } else { leftover = keyword.size(); } } } while (not in.eof()); } std::error_code mtl_loader::prefetch( const file_dir_list& paths, prefetch_queue& queue ) { namespace fs = std::filesystem; using mtl_loader_error::codes; using mtl_loader_error::make_error_code; auto buffer = std::vector(8 * 1024, '\0'); auto in = std::ifstream{}; auto path_buffer = fs::path{}; auto filename_buffer = fs::path{}; const auto process_file = [&]() { in.open(filename_buffer); if (not in.is_open()) { ztu::logger::error("Could not open .mtl file '%'", filename_buffer); return; } filename_buffer.remove_filename(); find_textures(buffer, path_buffer, filename_buffer, in, queue.texture.files); in.close(); }; for (const auto file : paths.files) { filename_buffer.assign(file); process_file(); } for (const auto directory : paths.directories) { for (const auto& file : fs::directory_iterator{ directory }) { filename_buffer.assign(file.path()); // Avoid heap allocation of .extension() if (not std::string_view(filename_buffer.c_str()).ends_with(".obj")) { continue; } process_file(); } } return {}; } std::error_code mtl_loader::load_directory( dynamic_data_loader_ctx& ctx, dynamic_material_store& store, material_components::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, material_components::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 = material_components::flags; const auto component_disabled = [&](const material_components::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::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::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::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::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::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::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 {}; }