diff --git a/src/file.hpp b/src/file.hpp deleted file mode 100644 index ac0e7d5..0000000 --- a/src/file.hpp +++ /dev/null @@ -1,273 +0,0 @@ -#ifndef _WAVEFRONT_PARSER_PARSER_HPP_ -#define _WAVEFRONT_PARSER_PARSER_HPP_ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "numset.hpp" -#include "settings.hpp" - -namespace wavefront::parser { - template - class File { - std::vector> context_position_data_; - std::vector> context_normal_data_; - std::vector< - std::variant< - std::monostate, - FloatType, - std::array, - std::array - > - > context_texcoord_data_; - numset_t output_floatlist_set_; - std::set> output_position_set_; - std::optional>> output_normal_set_; - std::optional< - std::variant< - std::set, - std::set>, - std::set> - > - > output_texcoord_set_; - std::variant< - std::set, - std::set>, - std::set> - > output_vertex_set_; - std::set> output_triangle_set_; - - public: - File(); - - // Stage 1: Read the file contents and fill in context data with traingulized faces. - void parse(const Settings &settings); - - // Stage 2: Create float list (from vectorized set). - void create_floatlist(); - }; - - namespace { - template - std::array repeat_value(ValueType value) { - std::array result; - result.fill(value); - return result; - } - } - - template - File::File() - { - context_position_data_.push_back(repeat_value(std::numeric_limits::quiet_NaN())); - context_normal_data_.push_back(repeat_value(std::numeric_limits::quiet_NaN())); - context_texcoord_data_.push_back(std::monostate{}); - } - - namespace { - inline std::string trim(const std::string &source) { - static const auto if_space = [](auto source_char){ - return std::isspace(source_char); - }; - auto left = std::find_if_not(source.begin(), source.end(), if_space); - auto right = std::find_if_not(source.rbegin(), source.rend(), if_space).base(); - return std::string(left, right); - } - - inline void assign_if_not_empty(std::optional &context, const std::string &name) { - auto trimmed_name = trim(name); - if (trimmed_name.empty()) { - context.reset(); - } else { - context = name; - } - } - } - - template - void File::parse(const Settings &settings) { - // Prepare to filter by object and group by the settings. - using check_filter_func_t = std::function; - using line_processor_func_t = std::function; - using vec3_inserter_factory_func_t = std::function> &)>; - - static const auto return_true = []()->bool { return true; }; - - std::size_t current_line_number = 0; - - std::vector> position_data; - std::vector> normal_data; - std::vector< - std::variant< - std::monostate, - FloatType, - std::array, - std::array - > - > texcoord_data; - - position_data.push_back(repeat_value(std::numeric_limits::quiet_NaN())); - normal_data.push_back(repeat_value(std::numeric_limits::quiet_NaN())); - texcoord_data.push_back(std::monostate{}); - - vec3_inserter_factory_func_t create_vec3_inserter = [&](std::vector> &target_vector) { - return [&](const std::string &type, const std::string &content) { - std::istringstream content_parser(content); - std::array vec3; - auto line_size = content.size(); - std::size_t content_begin_pos = 0; - auto line_begin = content.data(); - auto content_begin = line_begin; - auto line_end = line_begin + line_size; - std::size_t component_count = 0; - while (component_count < 3 && content_begin_pos < line_size) { - decltype(content_begin_pos) content_end_pos = content.find(' ', content_begin_pos); - decltype(content_begin) content_end; - if (content_end_pos == std::string::npos) { - content_end_pos = line_size; - content_end = line_end; - } else { - content_end = line_begin + content_end_pos; - } - auto [number_end, conversion_error] = std::from_chars( - content_begin, - content_end, - vec3[component_count], - std::chars_format::fixed - ); - if (conversion_error != std::errc() || number_end != content_end) { - std::stringstream error_message; - error_message << "Line [" << current_line_number << "]" << ": " << "Invalid float data"; - throw std::runtime_error(error_message.str()); - } - content_begin_pos = content_end_pos + 1; - content_begin = content.data() + content_begin_pos; - ++component_count; - } - if (component_count < 3) { - std::stringstream error_message; - error_message << "Line [" << current_line_number << "]" << ": " << "Insufficient number of components for \"" << type << "\" line"; - throw std::runtime_error(error_message.str()); - } - target_vector.push_back(vec3); - }; - }; - - std::optional current_object, current_group; - check_filter_func_t check_object = return_true; - check_filter_func_t check_group = return_true; - if (settings.selected_objects().size() > 0) { - check_object = [&]()->bool { - auto selected = settings.selected_objects(); - auto begin = selected.begin(); - auto end = selected.end(); - return std::find(begin, end, current_object.value_or("")) == end; - }; - } - if (settings.selected_groups().size() > 0) { - check_group = [&]()->bool { - auto selected = settings.selected_groups(); - auto begin = selected.begin(); - auto end = selected.end(); - return std::find(begin, end, current_group.value_or("")) == end; - }; - } - - static const auto process_vertex_line = create_vec3_inserter(position_data); - static const auto process_normal_line = create_vec3_inserter(normal_data); - static const line_processor_func_t process_texcoord_line = [&](const std::string &type, const std::string &content) { - std::array data_vec3; - - using insert_data_func_t = std::function; - - static const std::array insert_data = { - [&](void) { texcoord_data.push_back(data_vec3[0]); }, - [&](void) { texcoord_data.push_back(std::array{data_vec3[0], data_vec3[1]}); }, - [&](void) { texcoord_data.push_back(data_vec3); }, - }; - - auto line_size = content.size(); - auto line_begin = content.data(); - auto line_end = line_begin + line_size; - auto content_begin = line_begin; - decltype(line_size) content_begin_pos = 0; - size_t component_count = 0; - - while(component_count < 3 && content_begin_pos < line_size) { - auto content_end_pos = content.find(' ', content_begin_pos); - decltype(content_begin) content_end; - if (content_end_pos == std::string::npos) { - content_end_pos = line_size; - content_end = line_end; - } else { - content_end = line_begin + content_end_pos; - } - auto [number_end, conversion_error] = std::from_chars( - content_begin, - content_end, - data_vec3[component_count], - std::chars_format::fixed - ); - if (conversion_error != std::errc() || number_end != content_end) { - throw std::runtime_error("Invalid float data: " + std::string(content_begin, content_end)); - } - - content_begin_pos = content_end_pos + 1; - content_begin = line_begin + content_begin_pos; - ++component_count; - } - - insert_data[component_count - 1](); - }; - - - std::string line; - - static const std::map line_post_processor_map{ - { "o", [&](const std::string &type, const std::string &content) { - assign_if_not_empty(current_object, content); - }}, - { "g", [&](const std::string &type, const std::string &content) { - assign_if_not_empty(current_group, content); - }}, - { "f", [&](const std::string &type, const std::string &content) { - if (!check_object() || !check_group()) { - return; - } - // TODO: Implement face triangulation and context data population. - }}, - { "v", process_vertex_line }, - { "vt", process_texcoord_line }, - { "vn", process_normal_line }, - }; - - while (std::getline(settings.input(), line)) { - ++current_line_number; - - if (line.empty() || line[0] == '#') { - continue; // Skip empty lines and comments - } - - std::istringstream line_parser(line); - std::string line_type; - - std::getline(line_parser, line_type, ' '); - if (line_type.empty() || !line_post_processor_map.contains(line_type)) { - continue; - } - - line_post_processor_map.at(line_type)(line_type, line.substr(line.find(' ') + 1)); - } - } -} - -#endif /* _WAVEFRONT_PARSER_PARSER_HPP_ */ \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 12aef5a..7db1718 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -12,7 +12,8 @@ #include #include -#include "file.hpp" +#include "parse.hpp" +#include "scan.hpp" #include "settings.hpp" static void usage(const char* prog) { @@ -27,8 +28,17 @@ static void usage(const char* prog) { " -- end of options\n"; } +#define CATCH_AND_RETURN(variable, exception, return_value, initializer) \ + decltype(initializer) variable; \ + try { \ + variable = initializer; \ + } catch (exception __ex__) { \ + std::cerr << __ex__.what() << std::endl; \ + return return_value; \ + } + int main(int argc, char** argv) { - using namespace wavefront::parser; + using namespace wavefront; if (argc < 3) { usage(argv[0]); @@ -108,17 +118,19 @@ int main(int argc, char** argv) { Settings settings = settings_builder.build(); - std::variant, File> file = [&]() -> decltype(file) { - if (settings.use_float64()) { - return File(); - } else { - return File(); - } - }(); + CATCH_AND_RETURN(scan_data, wavefront::scan_error, 1, wavefront::scan( + settings.input(), + settings.selected_objects(), + settings.selected_groups()) + ); - std::visit([&](auto& file) { - file.parse(settings); - }, file); + CATCH_AND_RETURN(triangle_data, wavefront::parse_error, 1, wavefront::parse_face_data(scan_data)); + + std::cerr << "Scanned " << scan_data.total_lines << " lines\n"; + std::cerr << "Found " << scan_data.category_map["v"].size() << " vertices\n"; + std::cerr << "Found " << scan_data.category_map["vn"].size() << " normals\n"; + std::cerr << "Found " << scan_data.category_map["vt"].size() << " texture coordinates\n"; + std::cerr << "Found " << scan_data.category_map["f"].size() << " faces\n"; return 0; } diff --git a/src/parse.cpp b/src/parse.cpp new file mode 100644 index 0000000..3202045 --- /dev/null +++ b/src/parse.cpp @@ -0,0 +1,179 @@ +#include +#include +#include +#include +#include + +#include "parse.hpp" +#include "trim.hpp" + +namespace wavefront { + namespace { + void parse_index_value( + vertex_index_t &output_index, + const char *category, + const std::string_view &line, + const std::size_t &position_begin, + const std::size_t &optional_position_end, + const file_line_t &line_number + ){ + auto position_end = std::min(optional_position_end, line.size()); + auto data_begin = line.data() + position_begin; + auto data_end = line.data() + position_end; + auto [number_end, conversion_error] = std::from_chars( + data_begin, + data_end, + output_index, + 10 + ); + if (conversion_error != std::errc() || number_end != data_end || output_index == 0) [[unlikely]] { + throw parse_error(std::format( + "[{}]: {}", + line_number, + std::format("Unable to parse {} index value", category) + )); + } + } + + triangle_vertex_indices parse_vertex( + const std::string_view &vertex_string, + const file_line_t &line_number + ) { + triangle_vertex_indices result = { + .face_line_number = line_number, + .position_index = 0, + .texcoord_index = 0, + .normal_index = 0 + }; + auto trimmed_vertex_string = trim(vertex_string); + auto first_slash_position = trimmed_vertex_string.find_first_of('/'); + auto has_first_slash = first_slash_position != std::string_view::npos; + parse_index_value( + result.position_index, + "position", + trimmed_vertex_string, + 0, + first_slash_position, + line_number + ); + if (has_first_slash) { + auto second_slash_position = trimmed_vertex_string.find_first_of('/', first_slash_position + 1); + auto has_second_slash = second_slash_position != std::string_view::npos; + parse_index_value( + result.texcoord_index, + "texcoord", + trimmed_vertex_string, + first_slash_position + 1, + second_slash_position, + line_number + ); + if (has_second_slash) { + parse_index_value( + result.normal_index, + "normal", + trimmed_vertex_string, + second_slash_position + 1, + trimmed_vertex_string.size(), + line_number + ); + } + } + + if (result.position_index == 0) { + throw parse_error(std::format( + "[{}]: {}", + line_number, + "Detected face vertex with empty/invalid position index" + )); + } + + return result; + } + } + + parse_face_data_result parse_face_data(const scan_result &scan_data) { + using register_vertex_func_t = std::function; + using register_index_func_t = std::function &, const std::size_t &)>; + + parse_face_data_result result; + std::array current_triangle; + std::size_t current_triangle_index; + std::size_t triangles_added; + + static const std::array register_index{ + [](std::set &, const std::size_t &){}, + [](std::set &target, const std::size_t &source) { + target.emplace(source); + } + }; + + const register_vertex_func_t add_vertex_to_triangle = [&](triangle_vertex_indices &vertex) { + register_index[1](result.index_position_set, vertex.position_index); + register_index[!!vertex.normal_index](result.index_normal_set, vertex.normal_index); + register_index[!!vertex.texcoord_index](result.index_texcoord_set, vertex.texcoord_index); + current_triangle[current_triangle_index++] = vertex; + }; + + const auto add_triangle = [&]() { + assert(current_triangle_index >= 3); + result.triangle_list.push_back(current_triangle); + ++triangles_added; + }; + + const register_vertex_func_t register_vertex[]{ + add_vertex_to_triangle, + add_vertex_to_triangle, + [&](triangle_vertex_indices &vertex) { + add_vertex_to_triangle(vertex); + add_triangle(); + }, + [&](triangle_vertex_indices &vertex) { + current_triangle[1] = current_triangle[2]; + current_triangle[2] = vertex; + add_triangle(); + } + }; + + for (const auto &face_index : scan_data.category_map.at("f")) { + const auto &face_line = scan_data.line_data.at(face_index); + if (face_line[0] != 'f' || face_line[1] != ' ') [[unlikely]] { + throw parse_error(std::format( + "[{}]: {}", + face_index, + "Invalid face line" + )); + } + current_triangle_index = 0; + triangles_added = 0; + auto trimmed_face_content = trim( + std::string_view( + face_line.data() + 2, + face_line.size() - 2 + ) + ); + std::size_t parsed_length = 0; + while (parsed_length < trimmed_face_content.size()) { + auto space_pos = trimmed_face_content.find_first_of(' ', parsed_length); + space_pos = std::min(space_pos, trimmed_face_content.size()); + std::string_view vertex_string( + trimmed_face_content.begin() + parsed_length, + trimmed_face_content.begin() + space_pos + ); + if (!vertex_string.empty()) [[likely]] { + auto vertex = parse_vertex(vertex_string, face_index); + register_vertex[current_triangle_index](vertex); + } + parsed_length = trimmed_face_content.find_first_not_of(' ', space_pos); + } + if (triangles_added == 0) [[unlikely]] { + throw parse_error(std::format( + "[{}]: {}", + face_index, + "Insufficinet number of vertices for a face: at least one triangle (3 vertices) required" + )); + } + } + + return result; + } +} diff --git a/src/parse.hpp b/src/parse.hpp new file mode 100644 index 0000000..124401f --- /dev/null +++ b/src/parse.hpp @@ -0,0 +1,38 @@ +#ifndef __WAVEFRONT_PARSE_HPP__ +#define __WAVEFRONT_PARSE_HPP__ + +#include +#include +#include +#include +#include +#include +#include + +#include "scan.hpp" + +namespace wavefront { + class parse_error : public std::runtime_error { + public: + parse_error(const std::string &message) : std::runtime_error(message) {} + }; + + using vertex_index_t = std::int32_t; + using file_line_t = typename decltype(std::declval().line_data)::key_type; + + struct triangle_vertex_indices { + file_line_t face_line_number; + vertex_index_t position_index; + vertex_index_t texcoord_index; + vertex_index_t normal_index; + }; + + struct parse_face_data_result { + std::vector> triangle_list; + std::set index_position_set, index_normal_set, index_texcoord_set; + }; + + parse_face_data_result parse_face_data(const scan_result &scan_data); +} + +#endif // __WAVEFRONT_PARSE_HPP__ \ No newline at end of file diff --git a/src/parser.hpp b/src/parser.hpp new file mode 100644 index 0000000..d8758d4 --- /dev/null +++ b/src/parser.hpp @@ -0,0 +1,306 @@ +#ifndef _WAVEFRONT_PARSER_FILE_HPP_ +#define _WAVEFRONT_PARSER_FILE_HPP_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "settings.hpp" + +namespace wavefront::parser { + class parse_error : public std::runtime_error { + public: + parse_error(const std::string &message) : std::runtime_error(message) {} + }; + + template + class File { + + public: + File() = default; + + void parse(const Settings &settings); + }; + + namespace { + template + std::array repeat_value(ValueType value) { + std::array result; + result.fill(value); + return result; + } + + inline std::string_view trim(const std::string_view &source) { + static const auto if_space = [](auto source_char){ + return std::isspace(source_char); + }; + auto left = std::find_if_not(source.begin(), source.end(), if_space); + auto right = std::find_if_not(source.rbegin(), source.rend(), if_space).base(); + return std::string_view(left, right - left); + } + } + + template + void File::parse(const Settings &settings) { + using line_processor_func_t = std::function< + void(const std::string_view &type, const std::string_view &content) + >; + using store_vec3_factory_func_t = std::function< + line_processor_func_t(std::vector> &target_vector) + >; + using store_filter_factory_func_t = std::function< + line_processor_func_t(std::optional &target_filter) + >; + using check_filter_func_t = std::function; + + static const auto return_true = []()->bool { return true; }; + + std::size_t current_line_number = 0; + + const auto process_float = [&](FloatType &target_value, const char *data_begin, const char *data_end) { + auto [number_end, conversion_error] = std::from_chars( + data_begin, + data_end, + target_value, + std::chars_format::fixed + ); + if (conversion_error != std::errc() || number_end != data_end) { + std::stringstream error_message; + error_message + << "[" + << current_line_number + << "]" + << ": " + << "Unable to parse the expected float data"; + if (conversion_error != std::errc()) { + auto error_condition = std::make_error_condition(conversion_error); + error_message + << ": " + << error_condition.message(); + } else { + error_message + << ": " + << "Unexpected trailing characters"; + } + throw parse_error(error_message.str()); + } + }; + + std::vector> wavefront_data_position; + std::vector> wavefront_data_normal; + std::vector< + std::variant< + std::monostate, + FloatType, + std::array, + std::array + > + > wavefront_data_texcoord; + std::vector, 3>> wavefront_data_triangle; + + wavefront_data_position.push_back(repeat_value(std::numeric_limits::quiet_NaN())); + wavefront_data_normal.push_back(repeat_value(std::numeric_limits::quiet_NaN())); + wavefront_data_texcoord.push_back(std::monostate{}); + + const store_vec3_factory_func_t create_vec3_store_func = [&](std::vector> &target_vector) { + return line_processor_func_t([&](const std::string_view &type, const std::string_view &content) { + std::array vec3{}; + std::size_t component_count = 0; + decltype(content.size()) content_processed = 0; + + // 4th component (weight) is ignored if present. + while (component_count < 3 && content_processed < content.size()) { + auto chunk_end_pos = content.find_first_of(' ', content_processed); + if (chunk_end_pos == std::string_view::npos) { + chunk_end_pos = content.size(); + } + process_float( + vec3[component_count], + content.data() + content_processed, + content.data() + chunk_end_pos + ); + + ++component_count; + content_processed = chunk_end_pos + 1; + auto next_non_space_pos = content.find_first_not_of(' ', content_processed); + if (next_non_space_pos == std::string_view::npos) { + break; + } + content_processed = next_non_space_pos; + } + + if (component_count < 3) { + std::stringstream error_message; + error_message + << "[" + << current_line_number + << "]" + << ": " + << "Insufficient number of components for " + << '"' + << type + << '"' + << " line"; + throw parse_error(error_message.str()); + } + + target_vector.push_back(vec3); + }); + }; + + const line_processor_func_t store_texcoords = [&](const std::string_view &type, const std::string_view &content) { + using insert_data_func_t = std::function; + + std::array data_vec3{}; + decltype(content.size()) content_processed = 0; + std::size_t component_count = 0; + + const std::array insert_data = { + [](){}, + [&](void) { wavefront_data_texcoord.push_back(data_vec3[0]); }, + [&](void) { wavefront_data_texcoord.push_back(std::array{data_vec3[0], data_vec3[1]}); }, + [&](void) { wavefront_data_texcoord.push_back(data_vec3); } + }; + + while (content_processed < content.size()) { + if (component_count >= 3) { + std::stringstream error_message; + error_message + << "[" + << current_line_number + << "]" + << ": " + << "Too many components for " + << '"' + << type + << '"' + << " line"; + throw parse_error(error_message.str()); + } + + auto chunk_end_pos = content.find_first_of(' ', content_processed); + if (chunk_end_pos == std::string_view::npos) { + chunk_end_pos = content.size(); + } + process_float( + data_vec3[component_count], + content.data() + content_processed, + content.data() + chunk_end_pos + ); + + ++component_count; + content_processed = chunk_end_pos + 1; + auto next_non_space_pos = content.find_first_not_of(' ', content_processed); + if (next_non_space_pos == std::string_view::npos) { + break; + } + content_processed = next_non_space_pos; + } + + if (component_count < 1) { + std::stringstream error_message; + error_message + << "[" + << current_line_number + << "]" + << ": " + << "Insufficient number of components for " + << '"' + << type + << '"' + << " line"; + throw parse_error(error_message.str()); + } + + insert_data[component_count](); + }; + + static const store_filter_factory_func_t create_filter_store = [](std::optional &target_filter) { + return [&](const std::string_view &type, const std::string_view &content) { + auto trimmed_name = trim(content); + if (trimmed_name.empty()) { + target_filter.reset(); + } else { + target_filter = std::string(trimmed_name); + } + }; + }; + + std::optional current_object, current_group; + check_filter_func_t check_object = return_true; + check_filter_func_t check_group = return_true; + if (settings.selected_objects().size() > 0) { + check_object = [&]()->bool { + auto selected = settings.selected_objects(); + auto begin = selected.begin(); + auto end = selected.end(); + return std::find(begin, end, current_object.value_or("")) != end; + }; + } + if (settings.selected_groups().size() > 0) { + check_group = [&]()->bool { + auto selected = settings.selected_groups(); + auto begin = selected.begin(); + auto end = selected.end(); + return std::find(begin, end, current_group.value_or("")) != end; + }; + } + + const std::map line_processor_map{ + { "v", create_vec3_store_func(wavefront_data_position) }, + { "vn", create_vec3_store_func(wavefront_data_normal) }, + { "vt", store_texcoords }, + { "o", create_filter_store(current_object) }, + { "g", create_filter_store(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; + }(); + + std::string line; + + while (std::getline(settings.input(), line)) { + ++current_line_number; + + if (line.empty() || line[0] == '#') { + continue; + } + + auto first_space_pos = line.find_first_of(' '); + + if (first_space_pos == std::string::npos || first_space_pos > max_type_length || first_space_pos + 1 >= line.size()) { + continue; + } + + auto type = std::string_view(line.data(), first_space_pos); + auto content = std::string_view(line.data() + first_space_pos + 1, line.size() - first_space_pos - 1); + + auto processor_func = line_processor_map.find(type); + if (processor_func == line_processor_map.end()) { + continue; + } + + processor_func->second(type, content); + } + } + +} + +#endif // _WAVEFRONT_PARSER_FILE_HPP_ diff --git a/src/scan.cpp b/src/scan.cpp new file mode 100644 index 0000000..b862a7d --- /dev/null +++ b/src/scan.cpp @@ -0,0 +1,144 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#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 &selected_objects, + const std::vector &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 &line_data, + std::optional &target_filter + ) + >; + using check_filter_func_t = std::function; + + static const auto return_true = []()->bool { return true; }; + std::optional 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 &line_data, + std::optional &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 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 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; + } +} \ No newline at end of file diff --git a/src/scan.hpp b/src/scan.hpp new file mode 100644 index 0000000..77f9d96 --- /dev/null +++ b/src/scan.hpp @@ -0,0 +1,33 @@ +#ifndef __WAVEFRONT_FILE_HPP__ +#define __WAVEFRONT_FILE_HPP__ + +#include +#include +#include +#include +#include + +#include "settings.hpp" + +namespace wavefront { + class scan_error : public std::runtime_error { + public: + explicit scan_error(const std::string &message); + }; + + struct scan_result { + std::size_t total_lines; + std::map line_data; + std::map> category_map; + + scan_result(); + }; + + scan_result scan( + std::istream &input, + const std::vector &selected_objects = {}, + const std::vector &selected_groups = {} + ); +} + +#endif // __WAVEFRONT_PARSER_FILE_HPP__ \ No newline at end of file diff --git a/src/settings.cpp b/src/settings.cpp index 6199a93..5a0d420 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -1,7 +1,7 @@ #include "settings.hpp" #include -namespace wavefront::parser { +namespace wavefront { std::istream &Settings::input() const { return *input_; } @@ -33,6 +33,8 @@ namespace wavefront::parser { Settings::Settings( std::istream *input, std::ostream *output, + std::unique_ptr &&input_handle, + std::unique_ptr &&output_handle, std::vector selected_objects, std::vector selected_groups, bool extract_normals, @@ -41,6 +43,8 @@ namespace wavefront::parser { ) : input_(input), output_(output), + input_handle_(std::move(input_handle)), + output_handle_(std::move(output_handle)), selected_objects_(std::move(selected_objects)), selected_groups_(std::move(selected_groups)), extract_normals_(extract_normals), @@ -48,6 +52,8 @@ namespace wavefront::parser { use_float64_(use_float64) {} + SettingsBuilder::SettingsBuilder() : input_(&std::cin), output_(&std::cout) {} + std::istream &SettingsBuilder::input() { return *input_; } @@ -127,15 +133,20 @@ namespace wavefront::parser { return selected_groups_; } - Settings SettingsBuilder::build() const { - return Settings( + Settings SettingsBuilder::build() { + auto result = Settings( input_, output_, + std::move(input_handle_), + std::move(output_handle_), std::move(selected_objects_), std::move(selected_groups_), with_normals_, with_texcoords_, use_float64_ ); + input_ = nullptr; + output_ = nullptr; + return result; } } diff --git a/src/settings.hpp b/src/settings.hpp index 93d6d74..9b1c7c0 100644 --- a/src/settings.hpp +++ b/src/settings.hpp @@ -1,11 +1,11 @@ -#ifndef _WAVEFRONT_PARSER_SETTINGS_HPP_ -#define _WAVEFRONT_PARSER_SETTINGS_HPP_ +#ifndef _WAVEFRONT_SETTINGS_HPP_ +#define _WAVEFRONT_SETTINGS_HPP_ #include #include #include -namespace wavefront::parser { +namespace wavefront { class Settings { friend class SettingsBuilder; private: @@ -17,9 +17,14 @@ namespace wavefront::parser { bool extract_texcoords_; bool use_float64_; + std::unique_ptr input_handle_; + std::unique_ptr output_handle_; + Settings( std::istream *input, std::ostream *output, + std::unique_ptr &&input_handle, + std::unique_ptr &&output_handle, std::vector selected_objects, std::vector selected_groups, bool extract_normals, @@ -52,7 +57,7 @@ namespace wavefront::parser { std::unique_ptr output_handle_; public: - SettingsBuilder() : input_(&std::cin), output_(&std::cout) {} + SettingsBuilder(); std::istream &input(); SettingsBuilder &input(std::istream &); @@ -76,8 +81,8 @@ namespace wavefront::parser { bool use_float64() const; SettingsBuilder &use_float64(bool value); - Settings build() const; + Settings build(); }; } -#endif /* _WAVEFRONT_PARSER_SETTINGS_HPP_ */ +#endif /* _WAVEFRONT_SETTINGS_HPP_ */ diff --git a/src/trim.cpp b/src/trim.cpp new file mode 100644 index 0000000..9762dd3 --- /dev/null +++ b/src/trim.cpp @@ -0,0 +1,16 @@ +#include "trim.hpp" + +namespace wavefront { + std::string_view trim(const std::string_view &source) { + auto start = source.data(); + // size is the size of buffer, data() + size() is the first available byte after the string. + auto end = source.data() + source.size() - 1; + while (start < end && std::isspace(*start)) { + ++start; + } + while (start < end && (std::isspace(*end) || *end == 0)) { + --end; + } + return std::string_view(start, end - start + 1); + } +} diff --git a/src/trim.hpp b/src/trim.hpp new file mode 100644 index 0000000..db871a8 --- /dev/null +++ b/src/trim.hpp @@ -0,0 +1,11 @@ +#ifndef __WAVEFRONT_TRIM_HPP__ +#define __WAVEFRONT_TRIM_HPP__ + +#include +#include + +namespace wavefront { + std::string_view trim(const std::string_view &source); +} + +#endif // __WAVEFRONT_TRIM_HPP__ \ No newline at end of file