feature: stages
stages: separate scanning and parsing, allowing `v`, `vn`, `vt` lines to be mapped to line numbers and store line data (once). first stage parsing: parse face data and validate vertices. TODO: * improve validation (detect partial normals/texcoords); * maybe improve storage of triangle line number instead of storing line number for each vertex;
This commit is contained in:
144
src/scan.cpp
Normal file
144
src/scan.cpp
Normal file
@@ -0,0 +1,144 @@
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <format>
|
||||
#include <iostream>
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
#include <string_view>
|
||||
|
||||
#include "trim.hpp"
|
||||
#include "scan.hpp"
|
||||
|
||||
namespace wavefront {
|
||||
scan_error::scan_error(const std::string &message) : std::runtime_error(message) {}
|
||||
|
||||
scan_result::scan_result() :
|
||||
total_lines(0),
|
||||
line_data(),
|
||||
category_map{
|
||||
{ "v", {0} },
|
||||
{ "vn", {0} },
|
||||
{ "vt", {0} },
|
||||
{ "f", {} },
|
||||
}
|
||||
{}
|
||||
|
||||
scan_result scan(
|
||||
std::istream &input,
|
||||
const std::vector<std::string> &selected_objects,
|
||||
const std::vector<std::string> &selected_groups
|
||||
) {
|
||||
using line_processor_func_t = std::function<
|
||||
void(const std::string &type, const std::size_t &line_number)
|
||||
>;
|
||||
using store_filter_factory_func_t = std::function<
|
||||
line_processor_func_t(
|
||||
const std::map<std::size_t, std::string> &line_data,
|
||||
std::optional<std::string> &target_filter
|
||||
)
|
||||
>;
|
||||
using check_filter_func_t = std::function<bool()>;
|
||||
|
||||
static const auto return_true = []()->bool { return true; };
|
||||
std::optional<std::string> current_object, current_group;
|
||||
check_filter_func_t check_object = return_true;
|
||||
check_filter_func_t check_group = return_true;
|
||||
if (selected_objects.size() > 0) {
|
||||
check_object = [&]()->bool {
|
||||
auto begin = selected_objects.begin();
|
||||
auto end = selected_objects.end();
|
||||
return std::find(begin, end, current_object.value_or("")) != end;
|
||||
};
|
||||
}
|
||||
if (selected_groups.size() > 0) {
|
||||
check_group = [&]()->bool {
|
||||
auto begin = selected_groups.begin();
|
||||
auto end = selected_groups.end();
|
||||
return std::find(begin, end, current_group.value_or("")) != end;
|
||||
};
|
||||
}
|
||||
static const store_filter_factory_func_t create_filter_store = [](
|
||||
const std::map<std::size_t, std::string> &line_data,
|
||||
std::optional<std::string> &target_filter
|
||||
) {
|
||||
return [&](const std::string &type, const std::size_t &line_number) {
|
||||
auto line = line_data.at(line_number);
|
||||
auto trimmed_name = trim(std::string_view(line.data() + type.size(), line.size() - type.size()));
|
||||
if (trimmed_name.empty()) [[unlikely]] {
|
||||
target_filter.reset();
|
||||
} else [[likely]] {
|
||||
target_filter = std::string(trimmed_name);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
std::array<char, 4096 + 1> buffer;
|
||||
|
||||
scan_result result;
|
||||
const line_processor_func_t insert_by_type = [&](
|
||||
const std::string &type,
|
||||
const std::size_t &line_number
|
||||
) {
|
||||
result.category_map[type].push_back(line_number);
|
||||
};
|
||||
const std::map<std::string, line_processor_func_t> line_processor_map{
|
||||
{ "v", insert_by_type },
|
||||
{ "vn", insert_by_type },
|
||||
{ "vt", insert_by_type },
|
||||
{ "f", [&](const std::string &type, const std::size_t &line_number) {
|
||||
if (check_object() && check_group()) {
|
||||
insert_by_type(type, line_number);
|
||||
}
|
||||
}},
|
||||
{ "o", create_filter_store(result.line_data, current_object) },
|
||||
{ "g", create_filter_store(result.line_data, current_group) },
|
||||
};
|
||||
const auto max_type_length = [&]() {
|
||||
std::size_t max_length = 0;
|
||||
for (const auto &[type, _] : line_processor_map) {
|
||||
if (type.size() > max_length) {
|
||||
max_length = type.size();
|
||||
}
|
||||
}
|
||||
return max_length;
|
||||
}();
|
||||
|
||||
while (!input.eof()) {
|
||||
++result.total_lines;
|
||||
input.getline(buffer.data(), buffer.size());
|
||||
auto line_size = input.gcount();
|
||||
|
||||
if (input.fail() && line_size == buffer.size()) [[unlikely]] {
|
||||
throw scan_error(std::format(
|
||||
"[{}]: {}",
|
||||
result.total_lines,
|
||||
"Line too long"
|
||||
));
|
||||
}
|
||||
|
||||
std::string_view line(buffer.data(), line_size);
|
||||
|
||||
if (line.empty() || line[0] == '#' || trim(line).empty()) [[unlikely]] {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto line_type_end_pos = line.substr(0, max_type_length + 1).find_first_of(' ');
|
||||
if (line_type_end_pos == std::string::npos) [[unlikely]] {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto line_type = std::string(line.substr(0, line_type_end_pos));
|
||||
|
||||
auto type_processing_iterator = line_processor_map.find(line_type);
|
||||
if (type_processing_iterator == line_processor_map.end()) [[unlikely]] {
|
||||
continue;
|
||||
}
|
||||
|
||||
result.line_data[result.total_lines] = std::string(line);
|
||||
type_processing_iterator->second(line_type, result.total_lines);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user