Ported the obj parser.

This commit is contained in:
zy4n
2025-04-01 21:51:56 +02:00
parent bc065bc657
commit 27977c1738
34 changed files with 1676 additions and 1554 deletions

View File

@@ -9,6 +9,141 @@
#include "glm/gtx/euler_angles.hpp"
#include <charconv>
#include <fstream>
#include <execution>
#include <queue>
template<bool Normal, bool Color, bool Reflectance>
std::error_code assets::generic_3dtk_loader<Normal, Color, Reflectance>::prefetch(
path_id_lookups& lookups
) {
m_path_buffer.clear();
lookups.meshes.by_extension(".3d", m_path_buffer);
auto path_buffer = std::filesystem::path{};
for (const auto entry : m_path_buffer)
{
path_buffer = entry->first;
path_buffer.replace_extension(".pose");
lookups.poses.try_emplace(path_buffer);
}
return {};
}
template<bool Normal, bool Color, bool Reflectance>
std::error_code assets::generic_3dtk_loader<Normal, Color, Reflectance>::load(
path_id_lookups& lookups,
data_stores& stores,
bool pedantic
) {
m_path_buffer.clear();
lookups.point_clouds.by_extension(".3d", m_path_buffer);
std::for_each(
std::execution::parallel_unsequenced_policy{},
m_path_buffer.begin(),
m_path_buffer.end(),
parser_context{
lookups,
stores,
}
);
return {};
}
template<bool Normal, bool Color, bool Reflectance>
assets::generic_3dtk_loader<Normal, Color, Reflectance>::parser_context::parser_context(
path_id_lookups& m_id_lookups,
data_stores& m_stores
) :
m_id_lookups{ &m_id_lookups },
m_stores{ &m_stores }
{
constexpr auto expected_vertex_count = 8192;
m_mesh.positions().reserve(expected_vertex_count);
m_mesh.normals().reserve(expected_vertex_count);
m_mesh.colors().reserve(expected_vertex_count);
m_mesh.reflectances().reserve(expected_vertex_count);
m_mesh.tex_coords().reserve(expected_vertex_count);
m_mesh.triangles().reserve(2 * expected_vertex_count);
m_position_buffer.reserve(expected_vertex_count);
m_normal_buffer.reserve(expected_vertex_count);
m_tex_coord_buffer.reserve(expected_vertex_count);
}
template<bool Normal, bool Color, bool Reflectance>
void assets::generic_3dtk_loader<Normal, Color, Reflectance>::parser_context::reset()
{
m_mesh.clear();
m_position_buffer.clear();
m_normal_buffer.clear();
m_tex_coord_buffer.clear();
m_vertex_comp_indices_to_vertex_index.clear();
}
template<bool Normal, bool Color, bool Reflectance>
void assets::generic_3dtk_loader<Normal, Color, Reflectance>::parser_context::operator()(lookup_type::const_pointer entry) noexcept
{
// TODO look up pose
const auto& [ filename, id ] = *entry;
auto in = std::ifstream{ filename };
if (not in.is_open())
{
ztu::logger::warn("Cannot open obj file %.", filename);
return;
}
std::size_t scan_index;
if (auto res = parse_index(filename))
{
scan_index = *res;
}
else [[unlikely]]
{
const auto error = res.error();
ztu::logger::error(
"Error occurred while parsing scan index in filename %: [%] %",
error.category().name(),
error.message()
);
}
auto pose = asset_lookup.poses.find();
const auto id_it = id_lookup.find(filename);
if (id_it != id_lookup.end()) [[unlikely]]
{
return;
}
in.open(filename);
if (in.is_open())
{
if ((error = this->read_point_file(filename, buffer))) {
return error;
}
this->transform_point_cloud(buffer.positions(), pose);
const auto id = store.add(std::move(buffer));
id_lookup.emplace_hint(id_it, filename, id);
}
else
{
ztu::logger::error("Cannot open 3dtk file %", filename);
}
in.close();
}
template<bool Normal, bool Color, bool Reflectance>
ztu::result<pose_prefetch_lookup::index_type> generic_3dtk_loader<Normal, Color, Reflectance>::parse_index(
@@ -37,35 +172,12 @@ ztu::result<pose_prefetch_lookup::index_type> generic_3dtk_loader<Normal, Color,
return index;
}
template<bool Normal, bool Color, bool Reflectance>
std::error_code generic_3dtk_loader<Normal, Color, Reflectance>::prefetch(
const file_dir_list& paths,
prefetch_queue& queue
) {
auto path_buffer = std::filesystem::path{};
for (const auto filename : paths.files)
{
path_buffer.assign(filename.begin(), filename.end());
path_buffer.replace_extension(".pose");
queue.uos_queue.files.push_back(path_buffer.c_str());
}
// TODO optimize
for (const auto directory : paths.directories)
{
queue.uos_queue.directories.push_back(directory);
}
}
template<bool Normal, bool Color, bool Reflectance>
std::error_code generic_3dtk_loader<Normal, Color, Reflectance>::load(
dynamic_point_cloud_buffer& buffer,
const file_dir_list& paths,
prefetch_lookup& asset_lookup,
dynamic_point_cloud_store& store,
const bool pedantic
std::error_code assets::generic_3dtk_loader<Normal, Color, Reflectance>::load(
path_id_lookups& lookups,
data_stores& stores,
bool pedantic = false
) {
namespace fs = std::filesystem;
@@ -127,8 +239,6 @@ std::error_code generic_3dtk_loader<Normal, Color, Reflectance>::load(
load_file(filename.data());
}
for (const auto directory : paths.directories)
{
directory_buffer.assign(directory.begin(), directory.end());

View File

@@ -63,58 +63,47 @@ inline std::error_code make_error_code(codes e)
}
} // namespace mtl_loader_error
template<int L, typename T>
std::errc parse_numeric_vector(std::string_view param, z3d::vec<L, T>& vec)
{
auto it = param.begin();
const auto end = param.end();
for (int i{}; i != L; ++i)
{
if (it >= end)
{
return std::errc::invalid_argument;
}
const auto [ptr, ec] = std::from_chars(it, end, vec[i]);
if (ec != std::errc{})
{
return ec;
}
it = ptr + 1; // Skip space in between components.
}
return {};
};
std::error_code assets::mtl_parser::prefetch(
path_id_lookups& lookups
) {
namespace fs = std::filesystem;
using mtl_parser_error::codes;
using mtl_parser_error::make_error_code;
m_path_buffer.clear();
lookups.material_libraries.by_extension(".mtl", m_path_buffer);
auto buffer = std::vector<char>(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;
std::for_each(
std::execution::parallel_unsequenced_policy{},
m_path_buffer.begin(),
m_path_buffer.end(),
prefetcher_context{
lookups
}
filename_buffer.remove_filename();
find_textures(buffer, path_buffer, filename_buffer, in, queue.texture.files);
in.close();
};
// TODO properly extract
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 {};
}
@@ -127,20 +116,14 @@ std::error_code assets::mtl_parser::load(
m_path_buffer.clear();
lookups.material_libraries.by_extension(".mtl", m_path_buffer);
auto store_mutex = std::mutex{};
// TODO who protects the material id lookup
// TODO who protects the material store
std::for_each(
std::execution::parallel_unsequenced_policy{},
m_path_buffer.begin(),
m_path_buffer.end(),
parser_context{
lookups.textures,
lookups.materials,
stores.materials,
stores.material_libraries,
store_mutex
lookups,
stores,
}
);
@@ -149,31 +132,169 @@ std::error_code assets::mtl_parser::load(
inline std::error_category& connector_error_category()
assets::mtl_parser::prefetcher_context::prefetcher_context(
path_id_lookups& lookups
) :
m_lookups{ &lookups }
{
static assets::mtl_parser_error::category category;
return category;
m_buffer.resize(8192);
}
namespace assets::mtl_parser_error
void assets::mtl_parser::prefetcher_context::operator()(lookup_type::const_pointer entry) noexcept
{
inline std::error_code make_error_code(const codes e)
{
return { static_cast<int>(e), connector_error_category() };
}
} // namespace mtl_loader_error
namespace fs = std::filesystem;
using namespace std::string_view_literals;
// TODO 'bump' is missing!!!
static constexpr auto keyword = "\nmap_"sv;
using long_postfix_type = std::array<char, 2>;
static constexpr auto postfix_length = std::tuple_size_v<long_postfix_type>;
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(m_buffer);
// Add linebreak to simplify line begin search.
m_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<std::size_t>(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;
};
const auto& [ filename, id ] = *entry;
auto path_buffer = fs::path{};
const auto base_dir = fs::canonical(fs::path(filename).parent_path());
auto in = std::ifstream{ filename };
if (not in.is_open())
{
ztu::logger::warn("Cannot open obj file %.", filename);
return;
}
do
{
// Keep some old characters to continue matching interrupted sequence.
std::copy(m_buffer.end() - leftover, m_buffer.end(), m_buffer.begin());
in.read(m_buffer.data() + leftover, m_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_dir;
path_buffer /= potential_match;
}
m_lookups->textures.try_emplace(path_buffer);
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.", m_buffer.size());
leftover = 0;
}
else
{
leftover = str.size() - pos;
}
break;
}
else
{
leftover = keyword.size();
}
}
} while (not in.eof());
}
assets::mtl_parser::parser_context::parser_context(
const texture_id_lookup& texture_id_lookup,
material_id_lookup& material_id_lookup,
material_store& material_store,
store_type& store
path_id_lookups& lookups,
data_stores& stores
) :
m_texture_id_lookup{ &texture_id_lookup },
m_material_id_lookup{ &material_id_lookup },
m_material_store{ &material_store },
m_store{ &store }
m_lookups{ &lookups },
m_stores{ &stores }
{
m_buffer.reserve(32);
}
@@ -191,6 +312,7 @@ void assets::mtl_parser::parser_context::operator()(lookup_type::const_pointer e
const auto& [ filename, id ] = *entry;
// TODO unroll stuff
auto in = std::ifstream{ filename };
@@ -209,11 +331,11 @@ void assets::mtl_parser::parser_context::operator()(lookup_type::const_pointer e
{
if (not name.empty())
{
const auto [ id_it, is_new ] = m_material_id_lookup->try_emplace(filename / name);
const auto [ id_it, is_new ] = m_lookups->materials.try_emplace(filename / name);
const auto material_id = id_it->second;
m_material_store->insert(id, material);
m_stores->materials.insert(material_id, material);
m_buffer.emplace(name, material_id);
}
@@ -223,7 +345,7 @@ void assets::mtl_parser::parser_context::operator()(lookup_type::const_pointer e
const auto ec = ztu::parse_lines<codes>(
in,
pedantic,
true, // TODO pedantic,
ztu::make_line_parser("newmtl ", ztu::is_not_repeating, [&](const auto& param)
{
push_material();
@@ -298,7 +420,7 @@ void assets::mtl_parser::parser_context::operator()(lookup_type::const_pointer e
}),
ztu::make_line_parser("map_Ka ", ztu::is_not_repeating, [&](const auto& param)
{
if (const auto texture_id = fetch_texture_id(param, "ambient filter"))
if (const auto texture_id = fetch_texture_id(base_dir, param, "ambient filter"))
{
material.ambient_filter_texture_id() = *texture_id;
material.component_flags |= material_components::flags::ambient_filter_texture;
@@ -308,7 +430,7 @@ void assets::mtl_parser::parser_context::operator()(lookup_type::const_pointer e
}),
ztu::make_line_parser("map_Kd ", ztu::is_not_repeating, [&](const auto& param)
{
if (const auto texture_id = fetch_texture_id(param, "diffuse filter"))
if (const auto texture_id = fetch_texture_id(base_dir, param, "diffuse filter"))
{
material.diffuse_filter_texture_id() = *texture_id;
material.component_flags |= material_components::flags::diffuse_filter_texture;
@@ -318,7 +440,7 @@ void assets::mtl_parser::parser_context::operator()(lookup_type::const_pointer e
}),
ztu::make_line_parser("map_Ks ", ztu::is_not_repeating, [&](const auto& param)
{
if (const auto texture_id = fetch_texture_id(param, "specular filter"))
if (const auto texture_id = fetch_texture_id(base_dir, param, "specular filter"))
{
material.specular_filter_texture_id() = *texture_id;
material.component_flags |= material_components::flags::specular_filter_texture;
@@ -328,7 +450,7 @@ void assets::mtl_parser::parser_context::operator()(lookup_type::const_pointer e
}),
ztu::make_line_parser("map_Ns ", ztu::is_not_repeating, [&](const auto& param)
{
if (const auto texture_id = fetch_texture_id(param, "shininess"))
if (const auto texture_id = fetch_texture_id(base_dir, param, "shininess"))
{
material.shininess_texture_id() = *texture_id;
material.component_flags |= material_components::flags::shininess_texture;
@@ -338,7 +460,7 @@ void assets::mtl_parser::parser_context::operator()(lookup_type::const_pointer e
}),
ztu::make_line_parser("map_d ", ztu::is_not_repeating, [&](const auto& param)
{
if (const auto texture_id = fetch_texture_id(param, "alpha"))
if (const auto texture_id = fetch_texture_id(base_dir, param, "alpha"))
{
material.alpha_texture_id() = *texture_id;
material.component_flags |= material_components::flags::alpha_texture;
@@ -348,7 +470,7 @@ void assets::mtl_parser::parser_context::operator()(lookup_type::const_pointer e
}),
ztu::make_line_parser("bump ", ztu::is_not_repeating, [&](const auto& param)
{
if (const auto texture_id = fetch_texture_id(param, "bump"))
if (const auto texture_id = fetch_texture_id(base_dir, param, "bump"))
{
material.bump_texture_id() = *texture_id;
material.component_flags |= material_components::flags::bump_texture;
@@ -366,7 +488,7 @@ void assets::mtl_parser::parser_context::operator()(lookup_type::const_pointer e
push_material();
m_store->insert(id, m_buffer);
m_stores->material_libraries.insert(id, m_buffer);
}
std::optional<assets::texture_id> assets::mtl_parser::parser_context::fetch_texture_id(
@@ -380,8 +502,8 @@ std::optional<assets::texture_id> assets::mtl_parser::parser_context::fetch_text
texture_filename = mtl_dir / texture_filename;
}
const auto texture_id_it = m_texture_id_lookup->find(texture_filename);
if (texture_id_it == m_texture_id_lookup->end())
const auto texture_id_it = m_lookups->textures.find(texture_filename);
if (texture_id_it == m_lookups->textures.end())
{
ztu::logger::warn(
"%-texture at % has not been registered and can therefor not be used.",
@@ -393,175 +515,3 @@ std::optional<assets::texture_id> assets::mtl_parser::parser_context::fetch_text
return texture_id_it->second;
}
template<typename T, std::size_t Count>
std::errc parse_numeric_vector(std::string_view param, std::array<T, Count>& values)
{
const auto end = param.end();
auto it = param.begin();
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 assets::mtl_parser::find_textures(
std::span<char> 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<char, 2>;
static constexpr auto postfix_length = std::tuple_size_v<long_postfix_type>;
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<std::size_t>(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());
}

View File

@@ -1,602 +0,0 @@
#include "assets/file_parsers/obj_loader.hpp"
#include <charconv>
#include <fstream>
#include <array>
#include "assets/components/mesh_vertex_components.hpp"
#include "util/logger.hpp"
#include "util/for_each.hpp"
#include "util/line_parser.hpp"
namespace assets::obj_loader_error
{
struct category : std::error_category
{
[[nodiscard]] const char* name() const noexcept override {
return "obj_loader";
}
[[nodiscard]] std::string message(int ev) const override
{
switch (static_cast<codes>(ev))
{
using enum codes;
case cannot_open_file:
return "Cannot open given obj file.";
case malformed_vertex:
return "File contains malformed 'v' statement.";
case malformed_texture_coordinate:
return "File contains malformed 'vt' statement.";
case malformed_normal:
return "File contains malformed 'vn' statement.";
case malformed_face:
return "File contains malformed 'f' statement.";
case face_index_out_of_range:
return "Face index out of range.";
case unknown_line_begin:
return "Unknown obj line begin.";
case use_material_without_material_library:
return "'usemtl' statement before material library loaded.";
case unknown_material_name:
return "No matching material name found in material library.";
default:
using namespace std::string_literals;
return "unrecognized error ("s + std::to_string(ev) + ")";
}
}
};
} // namespace mesh_loader_error
inline std::error_category& obj_loader_error_category()
{
static assets::obj_loader_error::category category;
return category;
}
namespace assets::obj_loader_error
{
inline std::error_code make_error_code(codes e)
{
return { static_cast<int>(e), obj_loader_error_category() };
}
} // namespace mesh_loader_error
assets::obj_loader::parser_context::parser_context(
const texture_id_lookup& texture_id_lookup,
material_id_lookup& material_id_lookup,
material_store& material_store,
store_type& store
) :
m_texture_id_lookup{ &texture_id_lookup },
m_material_id_lookup{ &material_id_lookup },
m_material_store{ &material_store },
m_store{ &store }
{
constexpr auto expected_vertex_count = 8192;
m_buffer.positions().reserve(expected_vertex_count);
m_buffer.normals().reserve(expected_vertex_count);
m_buffer.colors().reserve(expected_vertex_count);
m_buffer.reflectances().reserve(expected_vertex_count);
m_buffer.tex_coords().reserve(expected_vertex_count);
m_buffer.triangles().reserve(2 * expected_vertex_count);
m_read_buffer.positions().reserve(expected_vertex_count);
m_read_buffer.normals().reserve(expected_vertex_count);
m_read_buffer.colors().reserve(expected_vertex_count);
m_read_buffer.reflectances().reserve(expected_vertex_count);
m_read_buffer.tex_coords().reserve(expected_vertex_count);
m_read_buffer.triangles().reserve(2 * expected_vertex_count);
}
void assets::obj_loader::parser_context::reset()
{
m_buffer.clear();
m_read_buffer.clear();
vertex_ids.clear();
}
void assets::obj_loader::parser_context::operator()(lookup_type::const_pointer entry) noexcept
{
using obj_loader_error::codes;
using obj_loader_error::make_error_code;
namespace fs = std::filesystem;
reset();
auto path_buffer = fs::path{};
const auto base_dir = fs::canonical(fs::path(filename).parent_path());
// Buffers for storing the vertex component definitions.
auto& position_buffer = m_read_buffer.positions();
auto& normal_buffer = m_read_buffer.normals();
auto& tex_coord_buffer = m_read_buffer.tex_coords();
auto& positions = m_buffer.positions();
auto& normals = m_buffer.normals();
auto& tex_coords = m_buffer.tex_coords();
auto& triangles = m_buffer.triangles();
const auto& [ filename, id ] = *entry;
auto in = std::ifstream{ filename };
if (not in.is_open())
{
ztu::logger::warn("Cannot open obj file %.", filename);
return;
}
const auto push_mesh = [&](const bool clear_read_buffer = false)
{
if (not triangles.empty())
{
ztu::logger::debug("Parsed % positions.", positions.size());
ztu::logger::debug("Parsed % normals.", normals.size());
ztu::logger::debug("Parsed % tex_coords.", tex_coords.size());
ztu::logger::debug("Parsed % triangles.", triangles.size());
// Copy buffer into store and keep capacity.
m_store->insert(id, m_buffer);
}
if (clear_read_buffer)
{
m_read_buffer.clear();
}
m_buffer.clear();
vertex_ids.clear();
};
const auto find_or_push_vertex = [&](const z3d::index_triangle& vertex) -> z3d::vertex_index
{
auto indexed_vid = indexed_vertex_type{
.vertex = vertex,
.buffer_index = static_cast<z3d::vertex_index>(positions.size())
};
// Search through sorted lookup to check if index combination is unique
const auto [ id_it, unique ] = vertex_ids.insert(indexed_vid);
if (unique)
{
const auto& [ position_index, tex_coord_index, normal_index ] = vertex;
// If index is out of range, push default constructed value.
// Not ideal, but better than out of range indices.
auto& position = positions.emplace_back();
if (position_index < position_buffer.size())
{
position = position_buffer[position_index];
}
auto& normal = normals.emplace_back();
if (normal_index < normal_buffer.size())
{
normal = normal_buffer[normal_index];
}
auto& tex_coord = tex_coords.emplace_back();
if (tex_coord_index < tex_coord_buffer.size())
{
tex_coord = tex_coord_buffer[tex_coord_index];
}
}
return id_it->buffer_index;
};
const material_library_data* curr_material_library{};
const auto ec = ztu::parse_lines<codes>(
in,
pedantic,
make_line_parser("v ", ztu::is_repeating, [&](const auto& param)
{
mesh_vertex_components::position position;
if (parse_numeric_vector(param, position) != std::errc{}) [[unlikely]]
{
return codes::malformed_vertex;
}
position_buffer.push_back(position);
return codes::ok;
}),
make_line_parser("vt ", ztu::is_repeating, [&](const auto& param)
{
mesh_vertex_components::tex_coord coord;
if (parse_numeric_vector(param, coord) != std::errc{}) [[unlikely]]
{
return codes::malformed_texture_coordinate;
}
tex_coord_buffer.push_back(coord);
return codes::ok;
}),
make_line_parser("vn ", ztu::is_repeating, [&](const auto& param)
{
mesh_vertex_components::normal normal;
if (parse_numeric_vector(param, normal) != std::errc{}) [[unlikely]]
{
return codes::malformed_normal;
}
normal_buffer.push_back(normal);
return codes::ok;
}),
make_line_parser("o ", ztu::is_not_repeating, [&](const auto&)
{
push_mesh(); // Name is currently ignored
return codes::ok;
}),
make_line_parser("f ", ztu::is_repeating, [&](const auto& param)
{
const auto begin = param.begin().base();
const auto end = param.end().base();
auto vertex = z3d::index_triangle{};
z3d::vertex_index first_index{}, prev_index{};
auto vertex_count = std::size_t{};
for (auto it = begin; it <= end; ++it)
{
for (auto& component_index : vertex)
{
if (it != end and *it == '/')
{
++it;
continue;
}
const auto [ptr, ec] = std::from_chars(it, end, component_index);
if (ec != std::errc()) [[unlikely]]
{
// Discard whole face if one index is malformed.
return codes::malformed_face;
}
--component_index; // Convert to zero based index.
it = ptr;
if (it == end or *it != '/')
{
break;
}
++it;
}
++vertex_count;
if (it != end and *it != ' ') [[unlikely]]
{
return codes::malformed_face;
}
const auto curr_index = find_or_push_vertex(vertex);
if (vertex_count >= 3)
{
triangles.emplace_back() = {
first_index,
prev_index,
curr_index
};
}
else if (vertex_count == 1)
{
first_index = curr_index;
}
prev_index = curr_index;
}
return codes::ok;
}),
make_line_parser("usemtl ", ztu::is_not_repeating, [&](const auto& param)
{
push_mesh(false);
if (not curr_material_library) [[unlikely]]
{
return codes::use_material_without_material_library;
}
const auto material_id_it = curr_material_library->find(param);
if (material_id_it == curr_material_library->end()) [[unlikely]]
{
return codes::unknown_material_name;
}
m_buffer.material() = material_id_it->second;
return codes::ok;
}),
make_line_parser("mtllib ", ztu::is_not_repeating, [&](const auto& param)
{
path_buffer.assign(param);
if (path_buffer.is_relative())
{
path_buffer = base_dir;
path_buffer /= param;
}
const auto material_library_id_it = m_id_lookups->material_libraries.find(path_buffer);
if (material_library_id_it != m_id_lookups->material_libraries.end()) [[likely]]
{
const auto material_library_id = material_library_id_it->second;
const auto [ it, found ] = m_stores->material_libraries.find(material_library_id);
if (found)
{
curr_material_library = &(it->second);
}
else
{
// TODO ALARM!!!
}
}
else [[unlikely]]
{
ztu::logger::warn(
"Could not find a matching material library with path '%'. Proceeding with default material.",
param
);
curr_material_library = nullptr;
}
})
);
if (ec != codes::ok)
{
const auto e = make_error_code(ec);
ztu::logger::error("Error while parsing obj file %: %", filename, e.message());
}
push_mesh();
}
void assets::obj_loader::find_materials(
std::span<char> buffer,
std::filesystem::path& path_buffer,
const std::filesystem::path& base_directory,
std::ifstream& in,
ztu::string_list& material_filenames
) {
static constexpr auto keyword = std::string_view{ "\nmtllib " };
const auto buffer_view = std::string_view(buffer);
// Add linebreak to simplify line begin search.
buffer.front() = '\n';
auto leftover = std::size_t{ 1 };
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());
leftover = keyword.size();
auto pos = std::string_view::size_type{};
while ((pos = str.find(keyword, pos)) != std::string_view::npos)
{
const auto filename_begin = pos + keyword.size();
const auto filename_end = str.find('\n', filename_begin);
if (filename_end != std::string_view::npos)
{
const auto length = filename_end - filename_begin;
const auto filename = str.substr(filename_begin, length);
path_buffer.assign(filename);
if (path_buffer.is_relative())
{
path_buffer = base_directory;
path_buffer /= filename;
}
material_filenames.push_back(path_buffer.c_str());
pos = filename_end;
}
else // String match exceeds buffer.
{
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;
}
}
} while (not in.eof());
}
std::error_code assets::obj_loader::prefetch(
const file_dir_list& paths,
prefetch_queue& queue
) {
namespace fs = std::filesystem;
using assets::obj_loader_error::codes;
using assets::obj_loader_error::make_error_code;
auto buffer = std::vector<char>(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 .obj file '%'", filename_buffer);
return;
}
filename_buffer.remove_filename();
find_materials(buffer, path_buffer, filename_buffer, in, queue.mtl_queue.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 assets::obj_loader::load(
dynamic_mesh_buffer& buffer,
const file_dir_list& paths,
prefetch_lookup& id_lookup,
dynamic_shader_source_store& store,
bool pedantic
) {
namespace fs = std::filesystem;
auto position_buffer = buffer.positions();
auto normal_buffer = buffer.normals();
auto tex_coord_buffer = buffer.tex_coords();
auto read_buffer = dynamic_mesh_buffer{};
auto path_buffer = fs::path{};
auto vertex_ids = std::set<indexed_vertex_type>{};
auto in = std::ifstream{};
auto filename_buffer = fs::path{};
const auto process_file = [&]()
{
in.open(filename_buffer);
if (not in.is_open()) {
ztu::logger::error("Could not open .obj file '%'", filename_buffer);
return;
}
filename_buffer.remove_filename();
// parse file
const auto error = parse_file(
read_buffer,
buffer,
path_buffer,
filename_buffer,
vertex_ids,
in,
id_lookup,
store,
pedantic
);
if (error)
{
ztu::logger::error(
"Error occurred while parsing .obj file: [%] %",
error.category().name(),
error.message()
);
}
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 {};
}
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();
const auto 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 {};
};

View File

@@ -0,0 +1,530 @@
#include "assets/file_parsers/obj_parser.hpp"
#include <charconv>
#include <fstream>
#include <array>
#include "assets/components/mesh_vertex_components.hpp"
#include "util/logger.hpp"
#include "util/for_each.hpp"
#include "util/line_parser.hpp"
#include <execution>
namespace assets::obj_parser_error
{
struct category : std::error_category
{
[[nodiscard]] const char* name() const noexcept override {
return "obj_loader";
}
[[nodiscard]] std::string message(int ev) const override
{
switch (static_cast<codes>(ev))
{
using enum codes;
case cannot_open_file:
return "Cannot open given obj file.";
case malformed_vertex:
return "File contains malformed 'v' statement.";
case malformed_texture_coordinate:
return "File contains malformed 'vt' statement.";
case malformed_normal:
return "File contains malformed 'vn' statement.";
case malformed_face:
return "File contains malformed 'f' statement.";
case face_index_out_of_range:
return "Face index out of range.";
case unknown_line_begin:
return "Unknown obj line begin.";
case use_material_without_material_library:
return "'usemtl' statement before material library loaded.";
case unknown_material_name:
return "No matching material name found in material library.";
default:
using namespace std::string_literals;
return "unrecognized error ("s + std::to_string(ev) + ")";
}
}
};
} // namespace mesh_loader_error
inline std::error_category& obj_loader_error_category()
{
static assets::obj_parser_error::category category;
return category;
}
namespace assets::obj_parser_error
{
inline std::error_code make_error_code(codes e)
{
return { static_cast<int>(e), obj_loader_error_category() };
}
} // namespace mesh_loader_error
std::error_code assets::obj_parser::prefetch(
path_id_lookups& lookups
) {
m_path_buffer.clear();
lookups.meshes.by_extension(".obj", m_path_buffer);
std::for_each(
std::execution::parallel_unsequenced_policy{},
m_path_buffer.begin(),
m_path_buffer.end(),
prefetcher_context{
lookups
}
);
return {};
}
std::error_code assets::obj_parser::load(
path_id_lookups& lookups,
data_stores& stores,
bool pedantic
) {
m_path_buffer.clear();
lookups.meshes.by_extension(".obj", m_path_buffer);
std::for_each(
std::execution::parallel_unsequenced_policy{},
m_path_buffer.begin(),
m_path_buffer.end(),
parser_context{
lookups,
stores,
}
);
return {};
}
template<int L, typename T>
std::errc parse_numeric_vector(std::string_view param, z3d::vec<L, T>& vec)
{
auto it = param.begin();
const auto end = param.end();
for (int i{}; i != L; ++i)
{
if (it >= end)
{
return std::errc::invalid_argument;
}
const auto [ptr, ec] = std::from_chars(it, end, vec[i]);
if (ec != std::errc{})
{
return ec;
}
it = ptr + 1; // Skip space in between components.
}
return {};
};
assets::obj_parser::parser_context::parser_context(
path_id_lookups& m_id_lookups,
data_stores& m_stores
) :
m_id_lookups{ &m_id_lookups },
m_stores{ &m_stores }
{
constexpr auto expected_vertex_count = 8192;
m_mesh.positions().reserve(expected_vertex_count);
m_mesh.normals().reserve(expected_vertex_count);
m_mesh.colors().reserve(expected_vertex_count);
m_mesh.reflectances().reserve(expected_vertex_count);
m_mesh.tex_coords().reserve(expected_vertex_count);
m_mesh.triangles().reserve(2 * expected_vertex_count);
m_position_buffer.reserve(expected_vertex_count);
m_normal_buffer.reserve(expected_vertex_count);
m_tex_coord_buffer.reserve(expected_vertex_count);
}
void assets::obj_parser::parser_context::reset()
{
m_mesh.clear();
m_position_buffer.clear();
m_normal_buffer.clear();
m_tex_coord_buffer.clear();
m_vertex_comp_indices_to_vertex_index.clear();
}
void assets::obj_parser::parser_context::operator()(lookup_type::const_pointer entry) noexcept
{
using obj_parser_error::codes;
using obj_parser_error::make_error_code;
namespace fs = std::filesystem;
reset();
const auto& [ filename, id ] = *entry;
auto path_buffer = fs::path{};
const auto base_dir = fs::canonical(fs::path(filename).parent_path());
const auto push_mesh = [&](const bool clear_read_buffer = false)
{
if (not m_mesh.triangles().empty())
{
ztu::logger::debug("parsed % positions.", m_position_buffer.size());
ztu::logger::debug("parsed % normals.", m_normal_buffer.size());
ztu::logger::debug("parsed % tex_coords.", m_tex_coord_buffer.size());
ztu::logger::debug("stored % positions.", m_mesh.positions().size());
ztu::logger::debug("stored % normals.", m_mesh.normals().size());
ztu::logger::debug("stored % tex_coords.", m_mesh.tex_coords().size());
ztu::logger::debug("stored % triangles.", m_mesh.triangles().size());
m_stores->meshes.insert(id, m_mesh);
}
if (clear_read_buffer)
{
m_position_buffer.clear();
m_normal_buffer.clear();
m_tex_coord_buffer.clear();
}
m_mesh.clear();
m_vertex_comp_indices_to_vertex_index.clear();
};
const material_library_data* curr_material_library{};
auto in = std::ifstream{ filename };
if (not in.is_open())
{
ztu::logger::warn("Cannot open obj file %.", filename);
return;
}
const auto ec = ztu::parse_lines<codes>(
in,
false,
make_line_parser("v ", ztu::is_repeating, [&](const auto& param)
{
mesh_vertex_components::position position;
if (parse_numeric_vector(param, position) != std::errc{}) [[unlikely]]
{
return codes::malformed_vertex;
}
m_position_buffer.push_back(position);
return codes::ok;
}),
make_line_parser("vt ", ztu::is_repeating, [&](const auto& param)
{
mesh_vertex_components::tex_coord coord;
if (parse_numeric_vector(param, coord) != std::errc{}) [[unlikely]]
{
return codes::malformed_texture_coordinate;
}
m_tex_coord_buffer.push_back(coord);
return codes::ok;
}),
make_line_parser("vn ", ztu::is_repeating, [&](const auto& param)
{
mesh_vertex_components::normal normal;
if (parse_numeric_vector(param, normal) != std::errc{}) [[unlikely]]
{
return codes::malformed_normal;
}
m_normal_buffer.push_back(normal);
return codes::ok;
}),
make_line_parser("o ", ztu::is_not_repeating, [&](const auto&)
{
push_mesh(); // Name is currently ignored
return codes::ok;
}),
make_line_parser("f ", ztu::is_repeating, [&](const auto& param)
{
const auto begin = param.begin().base();
const auto end = param.end().base();
z3d::vertex_index first_index{}, prev_index{};
auto vertex_count = std::size_t{};
for (auto it = begin; it <= end; ++it)
{
auto vertex = component_indices{};
for (auto& component_index : vertex)
{
if (it != end and *it == '/')
{
++it;
continue;
}
const auto [ptr, ec] = std::from_chars(it, end, component_index);
if (ec != std::errc()) [[unlikely]]
{
// Discard whole face if one index is malformed.
return codes::malformed_face;
}
it = ptr;
if (it == end or *it != '/')
{
break;
}
++it;
}
++vertex_count;
if (it != end and *it != ' ') [[unlikely]]
{
return codes::malformed_face;
}
const auto curr_index = find_or_push_vertex(vertex);
if (vertex_count >= 3)
{
m_mesh.triangles().emplace_back() = {
first_index,
prev_index,
curr_index
};
}
else if (vertex_count == 1)
{
first_index = curr_index;
}
prev_index = curr_index;
}
return codes::ok;
}),
make_line_parser("g ", ztu::is_not_repeating, [&](const auto& param)
{
push_mesh(false); // Name is currently ignored
return codes::ok;
}),
make_line_parser("usemtl ", ztu::is_not_repeating, [&](const auto& param)
{
push_mesh(false);
if (not curr_material_library) [[unlikely]]
{
return codes::use_material_without_material_library;
}
const auto material_id_it = curr_material_library->find(param);
if (material_id_it == curr_material_library->end()) [[unlikely]]
{
return codes::unknown_material_name;
}
m_mesh.material() = material_id_it->second;
return codes::ok;
}),
make_line_parser("mtllib ", ztu::is_not_repeating, [&](const auto& param)
{
path_buffer.assign(param);
if (path_buffer.is_relative())
{
path_buffer = base_dir;
path_buffer /= param;
}
const auto material_library_id_it = m_id_lookups->material_libraries.find(path_buffer);
if (material_library_id_it != m_id_lookups->material_libraries.end()) [[likely]]
{
const auto material_library_id = material_library_id_it->second;
const auto [ it, found ] = m_stores->material_libraries.find(material_library_id);
if (found)
{
// TODO implement proper dereference operator
curr_material_library = &((*it).second);
}
else
{
ztu::logger::warn(
"Material with name '%' was not loaded and can therefor not be used.",
param
);
}
}
else [[unlikely]]
{
ztu::logger::warn(
"No material with name '%' found in the material library.",
param
);
curr_material_library = nullptr;
}
return codes::ok;
})
);
if (ec != codes::ok)
{
const auto e = make_error_code(ec);
ztu::logger::error("Error while parsing obj file %: %", filename, e.message());
}
push_mesh();
}
z3d::vertex_index assets::obj_parser::parser_context::find_or_push_vertex(const component_indices& vertex_comp_indices)
{
// If the triangle indices are new the vertex will be pushed to the end of the buffer.
const auto new_vertex_index = static_cast<z3d::vertex_index>(m_mesh.positions().size());
// Search through lookup to check if index combination already exists.
const auto [ index_it, is_new ] = m_vertex_comp_indices_to_vertex_index.try_emplace(
vertex_comp_indices,
new_vertex_index
);
if (is_new)
{
const auto& [ position_index, tex_coord_index, normal_index ] = vertex_comp_indices;
// If index is out of range, push default constructed value.
// Not ideal, but better than out of range indices.
// TODO let user at least know that something went wrong.
if (position_index)
{
auto& position = m_mesh.positions().emplace_back();
if (position_index <= m_position_buffer.size())
{
position = m_position_buffer[position_index - 1];
}
}
if (normal_index)
{
auto& normal = m_mesh.normals().emplace_back();
if (normal_index <= m_normal_buffer.size())
{
normal = m_normal_buffer[normal_index - 1];
}
}
if (tex_coord_index)
{
auto& tex_coord = m_mesh.tex_coords().emplace_back();
if (tex_coord_index <= m_tex_coord_buffer.size())
{
tex_coord = m_tex_coord_buffer[tex_coord_index - 1];
}
}
}
return index_it->second;
}
assets::obj_parser::prefetcher_context::prefetcher_context::prefetcher_context(
path_id_lookups& id_lookups
) : m_id_lookups{ &id_lookups }
{
m_buffer.resize(8192);
}
void assets::obj_parser::prefetcher_context::operator()(lookup_type::const_pointer entry) noexcept
{
namespace fs = std::filesystem;
const auto& [ filename, id ] = *entry;
const auto base_dir = fs::canonical(fs::path(filename).parent_path());
const auto buffer_view = std::string_view(m_buffer);
auto filename_buffer = std::filesystem::path{};
auto path_buffer = std::filesystem::path{};
// Add linebreak to simplify line begin search.
m_buffer.front() = '\n';
auto leftover = std::size_t{ 1 };
auto in = std::ifstream{ filename };
static constexpr auto keyword = std::string_view{ "\nmtllib " };
do
{
// Keep some old characters to continue matching interrupted sequence.
std::copy(m_buffer.end() - leftover, m_buffer.end(), m_buffer.begin());
in.read(m_buffer.data() + leftover, m_buffer.size() - leftover);
const auto str = buffer_view.substr(0, leftover + in.gcount());
leftover = keyword.size();
auto pos = std::string_view::size_type{};
while ((pos = str.find(keyword, pos)) != std::string_view::npos)
{
const auto filename_begin = pos + keyword.size();
const auto filename_end = str.find('\n', filename_begin);
if (filename_end != std::string_view::npos)
{
const auto length = filename_end - filename_begin;
filename_buffer = str.substr(filename_begin, length);
if (filename_buffer.is_relative())
{
path_buffer = base_dir;
path_buffer /= filename_buffer;
}
m_id_lookups->material_libraries.try_emplace(path_buffer);
pos = filename_end;
}
else // String match exceeds buffer.
{
if (pos == 0) [[unlikely]]
{
ztu::logger::error("Ignoring string match, as it exceeds buffer size of % characters.", m_buffer.size());
leftover = 0;
}
else
{
leftover = str.size() - pos;
}
break;
}
}
} while (not in.eof());
}

View File

@@ -3,159 +3,90 @@
#include "util/binary_ifstream.hpp"
#include "util/unroll_bool_template.hpp"
#include "util/logger.hpp"
#include <execution>
template<bool Normals>
std::error_code read_body(
binary_ifstream& in,
const std::uint32_t expected_triangle_count,
std::vector<mesh_vertex_components::position>& positions,
std::vector<mesh_vertex_components::normal>& normals,
std::vector<std::array<ztu::u32, 3>>& triangles
std::error_code assets::stl_loader::prefetch(
path_id_lookups& lookups
) {
return {};
}
const auto read_vector = [&in](auto& vector) -> std::error_code
std::error_code assets::stl_loader::load(
path_id_lookups& lookups,
data_stores& stores,
bool pedantic
) {
m_path_buffer.clear();
lookups.meshes.by_extension(".stl", m_path_buffer);
std::for_each(
std::execution::parallel_unsequenced_policy{},
m_path_buffer.begin(),
m_path_buffer.end(),
parser_context{
lookups,
stores,
}
);
return {};
}
assets::stl_loader::parser_context::parser_context(
path_id_lookups& m_id_lookups,
data_stores& m_stores
) :
m_id_lookups{ &m_id_lookups },
m_stores{ &m_stores }
{
constexpr auto expected_vertex_count = 8192;
m_mesh.positions().reserve(expected_vertex_count);
m_mesh.normals().reserve(expected_vertex_count);
m_mesh.colors().reserve(expected_vertex_count);
m_mesh.reflectances().reserve(expected_vertex_count);
m_mesh.tex_coords().reserve(expected_vertex_count);
m_mesh.triangles().reserve(2 * expected_vertex_count);
}
void assets::stl_loader::parser_context::reset()
{
m_mesh.clear();
m_vertex_index_lookup.clear();
}
template<int L>
std::error_code read_vector(binary_ifstream& in, z3d::vec<L, float>& vector)
{
for (int i{}; i != L; ++i)
{
for (auto& component : vector)
{
float component32;
if (const auto e = in.read_ieee754<std::endian::little>(component32))
{
return e;
}
component = component32;
}
return {};
};
for (std::uint32_t i{}; i != expected_triangle_count; ++i) {
auto normal = mesh_vertex_components::normal{};
if constexpr (Normals)
{
if (const auto e = read_vector(normal))
{
return e;
}
}
auto triangle = std::array<ztu::u32, 3>{};
for (auto& index : triangle) {
auto position = mesh_vertex_components::position{};
if (const auto e = read_vector(position))
{
return e;
}
// TODO implement unique insert correctly
/*// Insert vertices sorted, to efficiently remove duplicates.
const auto it = std::ranges::upper_bound(positions, position);
// Set index before `it` is invalidated by insert.
index = it - positions.begin();
if (it != positions.begin() and *std::prev(it) == position)
{
--index;
}
else
{
positions.insert(it, position);
if constexpr (Normals)
{
normals.insert(normals.begin() + index, normal);
}
}*/
index = positions.size();
positions.push_back(position);
if constexpr (Normals)
{
normals.push_back(normal);
}
}
triangles.push_back(triangle);
// Skip attribute bytes
if (const auto e = in.skip<std::uint16_t>())
if (const auto e = in.read_ieee754<std::endian::little>(vector[i))
{
return e;
}
}
return {};
}
};
std::error_code stl_loader::read_directory(
const std::filesystem::path& path,
std::vector<dynamic_mesh_data>& meshes,
mesh_vertex_components::flags enabled_mesh_vertex_componentss,
std::vector<dynamic_material_data>& materials,
material_component::flags enabled_material_components,
const ztu::u32 base_material_id,
bool pedantic
) {
namespace fs = std::filesystem;
if (not fs::exists(path)) {
return make_error_code(std::errc::no_such_file_or_directory);
}
void assets::stl_loader::parser_context::operator()(lookup_type::const_pointer entry) noexcept
{
const auto& [ filename, id ] = *entry;
for (const auto& file : fs::directory_iterator{ path / "frames" })
auto in = binary_ifstream{};
if (const auto e = in.open(filename, true))
{
const auto& file_path = file.path();
if (file_path.extension() != ".stl")
{
continue;
}
if (const auto e = read(
file_path,
meshes,
enabled_mesh_vertex_componentss,
materials,
enabled_material_components,
base_material_id,
pedantic
)) {
ztu::logger::error(
"Error while loading stl file '%': [%] %",
file_path,
e.category().name(),
e.message()
);
}
ztu::logger::error("Cannot open stl file %.", filename);
return;
}
return {};
}
std::error_code stl_loader::read(
const std::filesystem::path& filename,
std::vector<dynamic_mesh_data>& meshes,
mesh_vertex_components::flags enabled_mesh_vertex_componentss,
std::vector<dynamic_material_data>&,
material_component::flags,
ztu::u32,
const bool pedantic
) {
auto error = std::error_code{};
auto in = binary_ifstream{};
if ((error = in.open(filename, true)))
{
return error;
}
reset();
auto header_bytes_left = static_cast<binary_ifstream::size_type>(80);
if (pedantic)
if (true) // TODO pedanatic
{
// Check if ASCII file was provided, these start with a specific character sequence.
@@ -163,9 +94,10 @@ std::error_code stl_loader::read(
auto magic_bytes = std::array<binary_ifstream::char_type, ascii_magic_string.size()>{};
if ((error = in.read(magic_bytes)))
if (const auto e = in.read(magic_bytes))
{
return error;
ztu::logger::error("Error while parsing stl magic bytes of %: %", filename, e.message());
return;
}
const auto magic_string = std::string_view(
@@ -175,79 +107,81 @@ std::error_code stl_loader::read(
if (magic_string == ascii_magic_string)
{
return std::make_error_code(std::errc::illegal_byte_sequence);
ztu::logger::error("Error while parsing stl magic bytes %: ASCII stl files are not supported.", filename);
return;
}
header_bytes_left -= ascii_magic_string.size();
}
// Ignore (rest of) header.
if ((error = in.skip(header_bytes_left)))
if (const auto e = in.skip(header_bytes_left))
{
return error;
ztu::logger::error("Error while parsing stl header of %: %", filename, e.message());
return;
}
// Read number of bytes
auto expected_triangle_count = std::uint32_t{};
if ((error = in.read<std::endian::little>(expected_triangle_count)))
if (const auto e = in.read<std::endian::little>(expected_triangle_count))
{
return error;
ztu::logger::error("Error while parsing stl triangle count %: %", filename, e.message());
return;
}
// Use separate mesh for parsing, so original mesh is only overwritten
// if no errors occurred. This also guarantees unused reserved memory
// is freed immediately in case of an error.
auto mesh = dynamic_mesh_data{};
auto& positions = mesh.positions();
auto& normals = mesh.normals();
auto& triangles = mesh.triangles();
auto& material_id = mesh.material_id();
auto vertex_parsing_error = std::error_code{};
material_id = 0; // Set to default material
for (std::uint32_t i{}; i != expected_triangle_count; ++i) {
positions.reserve(expected_triangle_count * 3);
normals.reserve(expected_triangle_count);
triangles.reserve(expected_triangle_count);
mesh_vertex_components::normal normal;
if (((vertex_parsing_error = read_vector(in, normal))))
{
break;
}
const auto normals_enabled = (
(enabled_mesh_vertex_componentss & mesh_vertex_components::flags::normal) != mesh_vertex_components::flags::none
);
auto triangle = z3d::index_triangle{};
error = unroll_bool_function_template([&]<bool Normals>() {
return read_body<Normals>(
in,expected_triangle_count,
positions,
normals,
triangles
);
}, normals_enabled);
for (auto& index : triangle)
{
mesh_vertex_components::position position;
if (((vertex_parsing_error = read_vector(in, normal))))
{
break;
}
// Free any unused reserved memory
positions.shrink_to_fit();
normals.shrink_to_fit();
triangles.shrink_to_fit();
if (const auto it = m_vertex_index_lookup.find(position); it != m_vertex_index_lookup.end())
{
index = it->second;
}
else
{
index = m_mesh.positions().size();
m_vertex_index_lookup.emplace_hint(it, position, index);
m_mesh.positions().emplace_back(position);
}
}
if (error)
{
return error;
m_mesh.triangles().emplace_back(triangle);
// Skip attribute bytes
if (((vertex_parsing_error = in.skip<std::uint16_t>())))
{
break;
}
}
ztu::logger::debug("Normal count: %", normals.size());
if (not positions.empty())
if (vertex_parsing_error)
{
mesh.components() |= mesh_vertex_components::flags::position;
ztu::logger::error("Error while parsing stl vertices in %: %", filename, vertex_parsing_error.message());
return;
}
if (not normals.empty())
{
ztu::logger::debug("Enabling normals!!!");
mesh.components() |= mesh_vertex_components::flags::normal;
}
m_mesh.component_flags |= mesh_vertex_components::flags::position;
m_mesh.component_flags |= mesh_vertex_components::flags::normal;
}
meshes.emplace_back(std::move(mesh));
return {};
}