399 lines
9.7 KiB
C++
399 lines
9.7 KiB
C++
#include "assets/data_loaders/mtl_loader.hpp"
|
|
|
|
#include <charconv>
|
|
#include <fstream>
|
|
#include <array>
|
|
|
|
#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<codes>(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<int>(e), connector_error_category() };
|
|
}
|
|
|
|
} // namespace mtl_loader_error
|
|
|
|
|
|
template<typename T, std::size_t Count>
|
|
std::errc parse_numeric_vector(std::string_view param, std::array<T, Count>& 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<dynamic_material_store::id_type> 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<codes>(
|
|
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<float, 1> 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<float, 1> 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 {};
|
|
}
|
|
|