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:
273
src/file.hpp
273
src/file.hpp
@@ -1,273 +0,0 @@
|
|||||||
#ifndef _WAVEFRONT_PARSER_PARSER_HPP_
|
|
||||||
#define _WAVEFRONT_PARSER_PARSER_HPP_
|
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <array>
|
|
||||||
#include <charconv>
|
|
||||||
#include <cstdint>
|
|
||||||
#include <functional>
|
|
||||||
#include <map>
|
|
||||||
#include <optional>
|
|
||||||
#include <set>
|
|
||||||
#include <string>
|
|
||||||
#include <variant>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "numset.hpp"
|
|
||||||
#include "settings.hpp"
|
|
||||||
|
|
||||||
namespace wavefront::parser {
|
|
||||||
template<typename FloatType, typename IndexType = std::uint32_t>
|
|
||||||
class File {
|
|
||||||
std::vector<std::array<FloatType, 3>> context_position_data_;
|
|
||||||
std::vector<std::array<FloatType, 3>> context_normal_data_;
|
|
||||||
std::vector<
|
|
||||||
std::variant<
|
|
||||||
std::monostate,
|
|
||||||
FloatType,
|
|
||||||
std::array<FloatType, 2>,
|
|
||||||
std::array<FloatType, 3>
|
|
||||||
>
|
|
||||||
> context_texcoord_data_;
|
|
||||||
numset_t<FloatType> output_floatlist_set_;
|
|
||||||
std::set<std::array<IndexType, 3>> output_position_set_;
|
|
||||||
std::optional<std::set<std::array<IndexType, 3>>> output_normal_set_;
|
|
||||||
std::optional<
|
|
||||||
std::variant<
|
|
||||||
std::set<IndexType>,
|
|
||||||
std::set<std::array<IndexType, 2>>,
|
|
||||||
std::set<std::array<IndexType, 3>>
|
|
||||||
>
|
|
||||||
> output_texcoord_set_;
|
|
||||||
std::variant<
|
|
||||||
std::set<IndexType>,
|
|
||||||
std::set<std::array<IndexType, 2>>,
|
|
||||||
std::set<std::array<IndexType, 3>>
|
|
||||||
> output_vertex_set_;
|
|
||||||
std::set<std::array<IndexType, 3>> 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<typename ValueType, size_t Length>
|
|
||||||
std::array<ValueType, Length> repeat_value(ValueType value) {
|
|
||||||
std::array<ValueType, Length> result;
|
|
||||||
result.fill(value);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename FloatType, typename IndexType>
|
|
||||||
File<FloatType, IndexType>::File()
|
|
||||||
{
|
|
||||||
context_position_data_.push_back(repeat_value<FloatType, 3>(std::numeric_limits<FloatType>::quiet_NaN()));
|
|
||||||
context_normal_data_.push_back(repeat_value<FloatType, 3>(std::numeric_limits<FloatType>::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<std::string> &context, const std::string &name) {
|
|
||||||
auto trimmed_name = trim(name);
|
|
||||||
if (trimmed_name.empty()) {
|
|
||||||
context.reset();
|
|
||||||
} else {
|
|
||||||
context = name;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename FloatType, typename IndexType>
|
|
||||||
void File<FloatType, IndexType>::parse(const Settings &settings) {
|
|
||||||
// Prepare to filter by object and group by the settings.
|
|
||||||
using check_filter_func_t = std::function<bool()>;
|
|
||||||
using line_processor_func_t = std::function<void(const std::string &type, const std::string &content)>;
|
|
||||||
using vec3_inserter_factory_func_t = std::function<line_processor_func_t(std::vector<std::array<FloatType, 3>> &)>;
|
|
||||||
|
|
||||||
static const auto return_true = []()->bool { return true; };
|
|
||||||
|
|
||||||
std::size_t current_line_number = 0;
|
|
||||||
|
|
||||||
std::vector<std::array<FloatType, 3>> position_data;
|
|
||||||
std::vector<std::array<FloatType, 3>> normal_data;
|
|
||||||
std::vector<
|
|
||||||
std::variant<
|
|
||||||
std::monostate,
|
|
||||||
FloatType,
|
|
||||||
std::array<FloatType, 2>,
|
|
||||||
std::array<FloatType, 3>
|
|
||||||
>
|
|
||||||
> texcoord_data;
|
|
||||||
|
|
||||||
position_data.push_back(repeat_value<FloatType, 3>(std::numeric_limits<FloatType>::quiet_NaN()));
|
|
||||||
normal_data.push_back(repeat_value<FloatType, 3>(std::numeric_limits<FloatType>::quiet_NaN()));
|
|
||||||
texcoord_data.push_back(std::monostate{});
|
|
||||||
|
|
||||||
vec3_inserter_factory_func_t create_vec3_inserter = [&](std::vector<std::array<FloatType, 3>> &target_vector) {
|
|
||||||
return [&](const std::string &type, const std::string &content) {
|
|
||||||
std::istringstream content_parser(content);
|
|
||||||
std::array<FloatType, 3> 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<std::string> 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<FloatType, 3> data_vec3;
|
|
||||||
|
|
||||||
using insert_data_func_t = std::function<void(void)>;
|
|
||||||
|
|
||||||
static const std::array<insert_data_func_t, 3> insert_data = {
|
|
||||||
[&](void) { texcoord_data.push_back(data_vec3[0]); },
|
|
||||||
[&](void) { texcoord_data.push_back(std::array<FloatType, 2>{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<std::string, line_processor_func_t> 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_ */
|
|
||||||
36
src/main.cpp
36
src/main.cpp
@@ -12,7 +12,8 @@
|
|||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <variant>
|
#include <variant>
|
||||||
|
|
||||||
#include "file.hpp"
|
#include "parse.hpp"
|
||||||
|
#include "scan.hpp"
|
||||||
#include "settings.hpp"
|
#include "settings.hpp"
|
||||||
|
|
||||||
static void usage(const char* prog) {
|
static void usage(const char* prog) {
|
||||||
@@ -27,8 +28,17 @@ static void usage(const char* prog) {
|
|||||||
" -- end of options\n";
|
" -- 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) {
|
int main(int argc, char** argv) {
|
||||||
using namespace wavefront::parser;
|
using namespace wavefront;
|
||||||
|
|
||||||
if (argc < 3) {
|
if (argc < 3) {
|
||||||
usage(argv[0]);
|
usage(argv[0]);
|
||||||
@@ -108,17 +118,19 @@ int main(int argc, char** argv) {
|
|||||||
|
|
||||||
Settings settings = settings_builder.build();
|
Settings settings = settings_builder.build();
|
||||||
|
|
||||||
std::variant<File<float, uint32_t>, File<double, uint32_t>> file = [&]() -> decltype(file) {
|
CATCH_AND_RETURN(scan_data, wavefront::scan_error, 1, wavefront::scan(
|
||||||
if (settings.use_float64()) {
|
settings.input(),
|
||||||
return File<double, uint32_t>();
|
settings.selected_objects(),
|
||||||
} else {
|
settings.selected_groups())
|
||||||
return File<float, uint32_t>();
|
);
|
||||||
}
|
|
||||||
}();
|
|
||||||
|
|
||||||
std::visit([&](auto& file) {
|
CATCH_AND_RETURN(triangle_data, wavefront::parse_error, 1, wavefront::parse_face_data(scan_data));
|
||||||
file.parse(settings);
|
|
||||||
}, file);
|
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;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|||||||
179
src/parse.cpp
Normal file
179
src/parse.cpp
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
#include <cassert>
|
||||||
|
#include <charconv>
|
||||||
|
#include <format>
|
||||||
|
#include <functional>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
#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<void(triangle_vertex_indices &)>;
|
||||||
|
using register_index_func_t = std::function<void(std::set<vertex_index_t> &, const std::size_t &)>;
|
||||||
|
|
||||||
|
parse_face_data_result result;
|
||||||
|
std::array<triangle_vertex_indices, 3> current_triangle;
|
||||||
|
std::size_t current_triangle_index;
|
||||||
|
std::size_t triangles_added;
|
||||||
|
|
||||||
|
static const std::array<register_index_func_t, 2> register_index{
|
||||||
|
[](std::set<vertex_index_t> &, const std::size_t &){},
|
||||||
|
[](std::set<vertex_index_t> &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;
|
||||||
|
}
|
||||||
|
}
|
||||||
38
src/parse.hpp
Normal file
38
src/parse.hpp
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
#ifndef __WAVEFRONT_PARSE_HPP__
|
||||||
|
#define __WAVEFRONT_PARSE_HPP__
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <set>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <string>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#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<scan_result>().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<std::array<triangle_vertex_indices, 3>> triangle_list;
|
||||||
|
std::set<vertex_index_t> 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__
|
||||||
306
src/parser.hpp
Normal file
306
src/parser.hpp
Normal file
@@ -0,0 +1,306 @@
|
|||||||
|
#ifndef _WAVEFRONT_PARSER_FILE_HPP_
|
||||||
|
#define _WAVEFRONT_PARSER_FILE_HPP_
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <array>
|
||||||
|
#include <charconv>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <functional>
|
||||||
|
#include <limits>
|
||||||
|
#include <map>
|
||||||
|
#include <optional>
|
||||||
|
#include <set>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <variant>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#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<typename FloatType, typename IndexType>
|
||||||
|
class File {
|
||||||
|
|
||||||
|
public:
|
||||||
|
File() = default;
|
||||||
|
|
||||||
|
void parse(const Settings &settings);
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
template<typename ValueType, size_t Length>
|
||||||
|
std::array<ValueType, Length> repeat_value(ValueType value) {
|
||||||
|
std::array<ValueType, Length> 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<typename FloatType, typename IndexType>
|
||||||
|
void File<FloatType, IndexType>::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<std::array<FloatType, 3>> &target_vector)
|
||||||
|
>;
|
||||||
|
using store_filter_factory_func_t = std::function<
|
||||||
|
line_processor_func_t(std::optional<std::string> &target_filter)
|
||||||
|
>;
|
||||||
|
using check_filter_func_t = std::function<bool()>;
|
||||||
|
|
||||||
|
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<std::array<FloatType, 3>> wavefront_data_position;
|
||||||
|
std::vector<std::array<FloatType, 3>> wavefront_data_normal;
|
||||||
|
std::vector<
|
||||||
|
std::variant<
|
||||||
|
std::monostate,
|
||||||
|
FloatType,
|
||||||
|
std::array<FloatType, 2>,
|
||||||
|
std::array<FloatType, 3>
|
||||||
|
>
|
||||||
|
> wavefront_data_texcoord;
|
||||||
|
std::vector<std::array<std::array<IndexType, 3>, 3>> wavefront_data_triangle;
|
||||||
|
|
||||||
|
wavefront_data_position.push_back(repeat_value<FloatType, 3>(std::numeric_limits<FloatType>::quiet_NaN()));
|
||||||
|
wavefront_data_normal.push_back(repeat_value<FloatType, 3>(std::numeric_limits<FloatType>::quiet_NaN()));
|
||||||
|
wavefront_data_texcoord.push_back(std::monostate{});
|
||||||
|
|
||||||
|
const store_vec3_factory_func_t create_vec3_store_func = [&](std::vector<std::array<FloatType, 3>> &target_vector) {
|
||||||
|
return line_processor_func_t([&](const std::string_view &type, const std::string_view &content) {
|
||||||
|
std::array<FloatType, 3> 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<void(void)>;
|
||||||
|
|
||||||
|
std::array<FloatType, 3> data_vec3{};
|
||||||
|
decltype(content.size()) content_processed = 0;
|
||||||
|
std::size_t component_count = 0;
|
||||||
|
|
||||||
|
const std::array<insert_data_func_t, 4> insert_data = {
|
||||||
|
[](){},
|
||||||
|
[&](void) { wavefront_data_texcoord.push_back(data_vec3[0]); },
|
||||||
|
[&](void) { wavefront_data_texcoord.push_back(std::array<FloatType, 2>{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<std::string> &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<std::string> 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<std::string_view, line_processor_func_t> 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_
|
||||||
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
33
src/scan.hpp
Normal file
33
src/scan.hpp
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
#ifndef __WAVEFRONT_FILE_HPP__
|
||||||
|
#define __WAVEFRONT_FILE_HPP__
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#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<std::size_t, std::string> line_data;
|
||||||
|
std::map<std::string, std::vector<std::size_t>> category_map;
|
||||||
|
|
||||||
|
scan_result();
|
||||||
|
};
|
||||||
|
|
||||||
|
scan_result scan(
|
||||||
|
std::istream &input,
|
||||||
|
const std::vector<std::string> &selected_objects = {},
|
||||||
|
const std::vector<std::string> &selected_groups = {}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // __WAVEFRONT_PARSER_FILE_HPP__
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
#include "settings.hpp"
|
#include "settings.hpp"
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
|
||||||
namespace wavefront::parser {
|
namespace wavefront {
|
||||||
std::istream &Settings::input() const {
|
std::istream &Settings::input() const {
|
||||||
return *input_;
|
return *input_;
|
||||||
}
|
}
|
||||||
@@ -33,6 +33,8 @@ namespace wavefront::parser {
|
|||||||
Settings::Settings(
|
Settings::Settings(
|
||||||
std::istream *input,
|
std::istream *input,
|
||||||
std::ostream *output,
|
std::ostream *output,
|
||||||
|
std::unique_ptr<std::ifstream> &&input_handle,
|
||||||
|
std::unique_ptr<std::ofstream> &&output_handle,
|
||||||
std::vector<std::string> selected_objects,
|
std::vector<std::string> selected_objects,
|
||||||
std::vector<std::string> selected_groups,
|
std::vector<std::string> selected_groups,
|
||||||
bool extract_normals,
|
bool extract_normals,
|
||||||
@@ -41,6 +43,8 @@ namespace wavefront::parser {
|
|||||||
) :
|
) :
|
||||||
input_(input),
|
input_(input),
|
||||||
output_(output),
|
output_(output),
|
||||||
|
input_handle_(std::move(input_handle)),
|
||||||
|
output_handle_(std::move(output_handle)),
|
||||||
selected_objects_(std::move(selected_objects)),
|
selected_objects_(std::move(selected_objects)),
|
||||||
selected_groups_(std::move(selected_groups)),
|
selected_groups_(std::move(selected_groups)),
|
||||||
extract_normals_(extract_normals),
|
extract_normals_(extract_normals),
|
||||||
@@ -48,6 +52,8 @@ namespace wavefront::parser {
|
|||||||
use_float64_(use_float64)
|
use_float64_(use_float64)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
|
SettingsBuilder::SettingsBuilder() : input_(&std::cin), output_(&std::cout) {}
|
||||||
|
|
||||||
std::istream &SettingsBuilder::input() {
|
std::istream &SettingsBuilder::input() {
|
||||||
return *input_;
|
return *input_;
|
||||||
}
|
}
|
||||||
@@ -127,15 +133,20 @@ namespace wavefront::parser {
|
|||||||
return selected_groups_;
|
return selected_groups_;
|
||||||
}
|
}
|
||||||
|
|
||||||
Settings SettingsBuilder::build() const {
|
Settings SettingsBuilder::build() {
|
||||||
return Settings(
|
auto result = Settings(
|
||||||
input_,
|
input_,
|
||||||
output_,
|
output_,
|
||||||
|
std::move(input_handle_),
|
||||||
|
std::move(output_handle_),
|
||||||
std::move(selected_objects_),
|
std::move(selected_objects_),
|
||||||
std::move(selected_groups_),
|
std::move(selected_groups_),
|
||||||
with_normals_,
|
with_normals_,
|
||||||
with_texcoords_,
|
with_texcoords_,
|
||||||
use_float64_
|
use_float64_
|
||||||
);
|
);
|
||||||
|
input_ = nullptr;
|
||||||
|
output_ = nullptr;
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
#ifndef _WAVEFRONT_PARSER_SETTINGS_HPP_
|
#ifndef _WAVEFRONT_SETTINGS_HPP_
|
||||||
#define _WAVEFRONT_PARSER_SETTINGS_HPP_
|
#define _WAVEFRONT_SETTINGS_HPP_
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace wavefront::parser {
|
namespace wavefront {
|
||||||
class Settings {
|
class Settings {
|
||||||
friend class SettingsBuilder;
|
friend class SettingsBuilder;
|
||||||
private:
|
private:
|
||||||
@@ -17,9 +17,14 @@ namespace wavefront::parser {
|
|||||||
bool extract_texcoords_;
|
bool extract_texcoords_;
|
||||||
bool use_float64_;
|
bool use_float64_;
|
||||||
|
|
||||||
|
std::unique_ptr<std::ifstream> input_handle_;
|
||||||
|
std::unique_ptr<std::ofstream> output_handle_;
|
||||||
|
|
||||||
Settings(
|
Settings(
|
||||||
std::istream *input,
|
std::istream *input,
|
||||||
std::ostream *output,
|
std::ostream *output,
|
||||||
|
std::unique_ptr<std::ifstream> &&input_handle,
|
||||||
|
std::unique_ptr<std::ofstream> &&output_handle,
|
||||||
std::vector<std::string> selected_objects,
|
std::vector<std::string> selected_objects,
|
||||||
std::vector<std::string> selected_groups,
|
std::vector<std::string> selected_groups,
|
||||||
bool extract_normals,
|
bool extract_normals,
|
||||||
@@ -52,7 +57,7 @@ namespace wavefront::parser {
|
|||||||
std::unique_ptr<std::ofstream> output_handle_;
|
std::unique_ptr<std::ofstream> output_handle_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
SettingsBuilder() : input_(&std::cin), output_(&std::cout) {}
|
SettingsBuilder();
|
||||||
|
|
||||||
std::istream &input();
|
std::istream &input();
|
||||||
SettingsBuilder &input(std::istream &);
|
SettingsBuilder &input(std::istream &);
|
||||||
@@ -76,8 +81,8 @@ namespace wavefront::parser {
|
|||||||
bool use_float64() const;
|
bool use_float64() const;
|
||||||
SettingsBuilder &use_float64(bool value);
|
SettingsBuilder &use_float64(bool value);
|
||||||
|
|
||||||
Settings build() const;
|
Settings build();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif /* _WAVEFRONT_PARSER_SETTINGS_HPP_ */
|
#endif /* _WAVEFRONT_SETTINGS_HPP_ */
|
||||||
|
|||||||
16
src/trim.cpp
Normal file
16
src/trim.cpp
Normal file
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
11
src/trim.hpp
Normal file
11
src/trim.hpp
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
#ifndef __WAVEFRONT_TRIM_HPP__
|
||||||
|
#define __WAVEFRONT_TRIM_HPP__
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
namespace wavefront {
|
||||||
|
std::string_view trim(const std::string_view &source);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // __WAVEFRONT_TRIM_HPP__
|
||||||
Reference in New Issue
Block a user