1
0

wavefront/parse: processing v, vn, vt, o, g

This commit is contained in:
2025-10-08 00:44:02 +03:00
parent 753c21ef11
commit aadd9d8661
9 changed files with 621 additions and 312 deletions

View File

@@ -4,5 +4,8 @@ project(wavefront_parser LANGUAGES CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-g -O3")
set(CMAKE_CXX_FLAGS "-fno-threadsafe-statics")
file(GLOB SRC_FILES src/*.cpp)
add_executable(wavefront_parser ${SRC_FILES})

273
src/file.hpp Normal file
View File

@@ -0,0 +1,273 @@
#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_ */

View File

@@ -1,25 +0,0 @@
#ifndef APP_HELPER_H
#define APP_HELPER_H
#include <array>
#include <functional>
#include <variant>
template<std::size_t N, typename T>
constexpr std::array<T, N> array_fill(const T& value) {
std::array<T, N> array{};
array.fill(value);
return array;
}
template<typename F, std::size_t... I>
constexpr void _static_for_impl(F &&f, std::index_sequence<I...>) {
(f(std::integral_constant<std::size_t, I>{}), ...);
}
template<std::size_t N, typename F>
constexpr void static_for(F &&f) {
_static_for_impl(std::forward<F>(f), std::make_index_sequence<N>{});
}
#endif // APP_HELPER_H

View File

@@ -1,129 +1,124 @@
#include <algorithm>
#include <array>
#include <filesystem>
#include <fstream>
#include <initializer_list>
#include <iostream>
#include <istream>
#include <limits>
#include <optional>
#include <set>
#include <tuple>
#include <variant>
#include <string>
#include <vector>
#include "wavefront.hpp"
#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <string>
#include <memory>
#include <stdexcept>
#include <variant>
namespace fs = std::filesystem;
#include "file.hpp"
#include "settings.hpp"
void printUsage() {
std::cout
<< "Usage: parse-wavefront [options] <input.obj>" << std::endl
<< "Options:" << std::endl
<< " --filter-object / -O <name> Filter objects by name (can be used multiple times)" << std::endl
<< " --filter-group / -G <name> Filter groups by name (can be used multiple times)" << std::endl
<< " --output-float-set / -f <file> Output float set to file" << std::endl
<< " --output-position / -p <file> Output positions to file" << std::endl
<< " --output-vertex / -v <file> Output vertices to file" << std::endl
<< " --output-mesh / -m <file> Output mesh to file" << std::endl
<< " --output-normal-file / -n <file> Output normals to file" << std::endl
<< " --output-texcoord-file / -t <file> Output texture coordinates to file" << std::endl
<< " -- Stop processing flags" << std::endl
;
std::cout
<< " <input.obj> Input Wavefront OBJ file" << std::endl
<< "Examples:" << std::endl
<< " parse-wavefront --float64 --output-float-set output.fset --output-position output.pos --filter-object Car,Wheel --filter-group Metal -- model.obj" << std::endl
;
static void usage(const char* prog) {
std::cerr << "Usage: " << prog
<< " [options] <input_file|- for stdin> <output_file|- for stdout>\n"
"Options:\n"
" --float64 output double instead of float\n"
" --object <name> select an object (can be given multiple times)\n"
" --group <name> select a group (can be given multiple times)\n"
" --with-normals extract normal vectors\n"
" --with-texcoords extract texture coordinates\n"
" -- end of options\n";
}
[[noreturn]]
void exitWithError(const std::string& message) {
std::cerr << "Error: " << message << std::endl;
std::exit(1);
}
int main(int argc, char** argv) {
using namespace wavefront::parser;
int main(int argc, char *argv[]) {
std::vector<std::string> selected_objects, selected_groups;
bool process_flags = true;
bool has_input_file = false;
bool has_output_float_set_file = false;
bool has_output_position_file = false;
bool has_output_mesh_file = false;
bool has_output_vertex_file = false;
std::string input_file_path;
for (decltype(argc) i = 1; i < argc; ++i) {
std::string arg = argv[i];
if (!process_flags) {
if (has_input_file) {
std::cerr << "Unexpected argument after the input file: " << arg << std::endl;
printUsage();
if (argc < 3) {
usage(argv[0]);
return 1;
}
has_input_file = true;
input_file_path = arg;
SettingsBuilder settings_builder;
int arg_index = 1;
bool end_of_options = false;
std::vector<std::string> positional;
while (arg_index < argc) {
std::string arg = argv[arg_index];
if (!end_of_options && arg == "--") {
end_of_options = true;
++arg_index;
continue;
}
if (arg == "--filter-object" || arg == "-O") {
if (i + 1 < argc) {
selected_groups.push_back(argv[++i]);
if (!end_of_options && arg.size() > 0 && arg[0] == '-' && arg != "-") {
if (arg == "--float64") {
settings_builder.use_float64(true);
++arg_index;
continue;
} else if (arg == "--with-normals") {
settings_builder.with_normals(true);
++arg_index;
continue;
} else if (arg == "--with-texcoords") {
settings_builder.with_texcoords(true);
++arg_index;
continue;
} else if (arg == "--object") {
if (arg_index + 1 >= argc) {
std::cerr << "--object requires an argument\n";
return 2;
}
} else if (arg == "--filter-group" || arg == "-G") {
if (i + 1 < argc) {
selected_groups.push_back(argv[++i]);
settings_builder.selected_objects().emplace_back(argv[arg_index + 1]);
arg_index += 2;
continue;
} else if (arg == "--group") {
if (arg_index + 1 >= argc) {
std::cerr << "--group requires an argument\n";
return 2;
}
} else if (arg == "--") {
process_flags = false;
} else if (arg.starts_with("-")) {
std::cerr << "Unknown option: " << arg << std::endl;
printUsage();
return 1;
settings_builder.selected_groups().emplace_back(argv[arg_index + 1]);
arg_index += 2;
continue;
} else {
input_file_path = arg;
has_input_file = true;
std::cerr << "Unknown option: " << arg << "\n";
usage(argv[0]);
return 2;
}
} else {
positional.emplace_back(arg);
++arg_index;
}
if (!has_input_file) {
std::cerr << "No input file specified." << std::endl;
printUsage();
return 1;
}
// if (!has_output_float_set_file) {
// std::cerr << "No output float set file specified." << std::endl;
// printUsage();
// return 1;
// }
// if (!has_output_vertex_file) {
// std::cerr << "No output vertex file specified." << std::endl;
// printUsage();
// return 1;
// }
// if (!has_output_mesh_file) {
// std::cerr << "No output mesh file specified." << std::endl;
// printUsage();
// return 1;
// }
// if (!has_output_position_file) {
// std::cerr << "No output position file specified." << std::endl;
// printUsage();
// return 1;
// }
// WaveFrontFilter wavefront_filter(
// selected_objects.empty() ? std::nullopt : std::make_optional(selected_objects),
// selected_groups.empty() ? std::nullopt : std::make_optional(selected_groups)
// );
std::ifstream input_file;
std::istream &input_stream = (input_file_path == "")
? std::cin
: (input_file.open(input_file_path), input_file);
if (!input_stream) {
std::cerr << "Failed to open file: " << input_file_path << std::endl;
std::exit(1);
}
auto wavefront_lines = parse_wavefront(input_stream);
if (positional.size() != 2) {
std::cerr << "Expected exactly two positional arguments: input_file output_file\n";
usage(argv[0]);
return 3;
}
const std::string& input_file = positional[0];
if (input_file != "-") {
settings_builder.input(input_file);
}
// Open output
const std::string& output_file = positional[1];
if (output_file != "-") {
settings_builder.output(output_file);
}
Settings settings = settings_builder.build();
std::variant<File<float, uint32_t>, File<double, uint32_t>> file = [&]() -> decltype(file) {
if (settings.use_float64()) {
return File<double, uint32_t>();
} else {
return File<float, uint32_t>();
}
}();
std::visit([&](auto& file) {
file.parse(settings);
}, file);
return 0;
}

View File

@@ -6,12 +6,12 @@
#include <limits>
#include <set>
template <typename T>
template <typename FloatType>
struct float_is_equal {
bool operator()(T a, T b) const {
static const auto epsilon = std::numeric_limits<T>::epsilon();
static const auto min_value = std::numeric_limits<T>::min();
static const auto max_value = std::numeric_limits<T>::max();
bool operator()(FloatType a, FloatType b) const {
static const auto epsilon = std::numeric_limits<FloatType>::epsilon();
static const auto min_value = std::numeric_limits<FloatType>::min();
static const auto max_value = std::numeric_limits<FloatType>::max();
// Nothing is equal to NaN (not even NaN)
if (std::isnan(a) || std::isnan(b)) {
return false;
@@ -27,10 +27,10 @@ struct float_is_equal {
}
};
template <typename T>
template <typename FloatType>
struct float_compare_less {
bool operator()(T a, T b) const {
static const auto is_equal_function = float_is_equal<T>();
bool operator()(FloatType a, FloatType b) const {
static const auto is_equal_function = float_is_equal<FloatType>();
if (!is_equal_function(a, b)) {
return a < b;
}
@@ -38,12 +38,12 @@ struct float_compare_less {
}
};
template <typename T>
template <typename FloatType>
struct less_with_nan_first_and_nearly_equal {
bool operator()(T a, T b) const {
static const auto epsilon = std::numeric_limits<T>::epsilon();
static const auto min_value = std::numeric_limits<T>::min();
static const auto max_value = std::numeric_limits<T>::max();
bool operator()(FloatType a, FloatType b) const {
static const auto epsilon = std::numeric_limits<FloatType>::epsilon();
static const auto min_value = std::numeric_limits<FloatType>::min();
static const auto max_value = std::numeric_limits<FloatType>::max();
if (std::isnan(a)) {
return !std::isnan(b);
@@ -64,7 +64,7 @@ struct less_with_nan_first_and_nearly_equal {
}
};
template<typename T>
using numset_t = std::set<T, less_with_nan_first_and_nearly_equal<T>>;
template<typename FloatType>
using numset_t = std::set<FloatType, less_with_nan_first_and_nearly_equal<FloatType>>;
#endif // APP_NUMSET_H

141
src/settings.cpp Normal file
View File

@@ -0,0 +1,141 @@
#include "settings.hpp"
#include <fstream>
namespace wavefront::parser {
std::istream &Settings::input() const {
return *input_;
}
std::ostream &Settings::output() const {
return *output_;
}
const std::vector<std::string> &Settings::selected_objects() const {
return selected_objects_;
}
const std::vector<std::string> &Settings::selected_groups() const {
return selected_groups_;
}
bool Settings::extract_normals() const {
return extract_normals_;
}
bool Settings::extract_texcoords() const {
return extract_texcoords_;
}
bool Settings::use_float64() const {
return use_float64_;
}
Settings::Settings(
std::istream *input,
std::ostream *output,
std::vector<std::string> selected_objects,
std::vector<std::string> selected_groups,
bool extract_normals,
bool extract_texcoords,
bool use_float64
) :
input_(input),
output_(output),
selected_objects_(std::move(selected_objects)),
selected_groups_(std::move(selected_groups)),
extract_normals_(extract_normals),
extract_texcoords_(extract_texcoords),
use_float64_(use_float64)
{}
std::istream &SettingsBuilder::input() {
return *input_;
}
SettingsBuilder &SettingsBuilder::input(std::istream &in) {
input_ = &in;
input_handle_.reset();
return *this;
}
SettingsBuilder &SettingsBuilder::input(const std::string &filename) {
input_handle_ = std::make_unique<std::ifstream>(filename);
input_ = reinterpret_cast<std::istream *>(input_handle_.get());
return *this;
}
SettingsBuilder &SettingsBuilder::input(std::ifstream &&file) {
input_handle_ = std::make_unique<std::ifstream>(std::move(file));
input_ = reinterpret_cast<std::istream *>(input_handle_.get());
return *this;
}
std::ostream &SettingsBuilder::output() {
return *output_;
}
SettingsBuilder &SettingsBuilder::output(std::ostream &out) {
output_ = &out;
output_handle_.reset();
return *this;
}
SettingsBuilder &SettingsBuilder::output(const std::string &filename) {
output_handle_ = std::make_unique<std::ofstream>(filename);
output_ = reinterpret_cast<std::ostream *>(output_handle_.get());
return *this;
}
SettingsBuilder &SettingsBuilder::output(std::ofstream &&file) {
output_handle_ = std::make_unique<std::ofstream>(std::move(file));
output_ = reinterpret_cast<std::ostream *>(output_handle_.get());
return *this;
}
bool SettingsBuilder::with_normals() const {
return with_normals_;
}
SettingsBuilder &SettingsBuilder::with_normals(bool value) {
with_normals_ = value;
return *this;
}
bool SettingsBuilder::with_texcoords() const {
return with_texcoords_;
}
SettingsBuilder &SettingsBuilder::with_texcoords(bool value) {
with_texcoords_ = value;
return *this;
}
bool SettingsBuilder::use_float64() const {
return use_float64_;
}
SettingsBuilder &SettingsBuilder::use_float64(bool value) {
use_float64_ = value;
return *this;
}
std::vector<std::string> &SettingsBuilder::selected_objects() {
return selected_objects_;
}
std::vector<std::string> &SettingsBuilder::selected_groups() {
return selected_groups_;
}
Settings SettingsBuilder::build() const {
return Settings(
input_,
output_,
std::move(selected_objects_),
std::move(selected_groups_),
with_normals_,
with_texcoords_,
use_float64_
);
}
}

83
src/settings.hpp Normal file
View File

@@ -0,0 +1,83 @@
#ifndef _WAVEFRONT_PARSER_SETTINGS_HPP_
#define _WAVEFRONT_PARSER_SETTINGS_HPP_
#include <iostream>
#include <memory>
#include <vector>
namespace wavefront::parser {
class Settings {
friend class SettingsBuilder;
private:
std::istream *input_;
std::ostream *output_;
std::vector<std::string> selected_objects_;
std::vector<std::string> selected_groups_;
bool extract_normals_;
bool extract_texcoords_;
bool use_float64_;
Settings(
std::istream *input,
std::ostream *output,
std::vector<std::string> selected_objects,
std::vector<std::string> selected_groups,
bool extract_normals,
bool extract_texcoords,
bool use_float64
);
public:
std::istream &input() const;
std::ostream &output() const;
const std::vector<std::string> &selected_objects() const;
const std::vector<std::string> &selected_groups() const;
bool extract_normals() const;
bool extract_texcoords() const;
bool use_float64() const;
};
class SettingsBuilder {
std::istream *input_;
std::ostream *output_;
std::vector<std::string> selected_objects_;
std::vector<std::string> selected_groups_;
bool with_normals_ = false;
bool with_texcoords_ = false;
bool use_float64_ = false;
// Keep ownership of opened file streams so pointers remain valid
std::unique_ptr<std::ifstream> input_handle_;
std::unique_ptr<std::ofstream> output_handle_;
public:
SettingsBuilder() : input_(&std::cin), output_(&std::cout) {}
std::istream &input();
SettingsBuilder &input(std::istream &);
SettingsBuilder &input(const std::string &filename);
SettingsBuilder &input(std::ifstream &&file);
std::ostream &output();
SettingsBuilder &output(std::ostream &);
SettingsBuilder &output(const std::string &filename);
SettingsBuilder &output(std::ofstream &&file);
std::vector<std::string> &selected_objects();
std::vector<std::string> &selected_groups();
bool with_normals() const;
SettingsBuilder &with_normals(bool value);
bool with_texcoords() const;
SettingsBuilder &with_texcoords(bool value);
bool use_float64() const;
SettingsBuilder &use_float64(bool value);
Settings build() const;
};
}
#endif /* _WAVEFRONT_PARSER_SETTINGS_HPP_ */

View File

@@ -1,120 +0,0 @@
#include <algorithm>
#include "wavefront.hpp"
WaveFrontLine::WaveFrontLine(const std::size_t &line_number, const std::string &type, const std::string &content)
: line_number_(line_number), type_(type), content_(content) {
}
const std::size_t &WaveFrontLine::line_number() {
return line_number_;
}
const std::string &WaveFrontLine::type() {
return type_;
}
const std::string &WaveFrontLine::content() {
return content_;
}
const WaveFrontFilter WaveFrontFilter::no_filter{std::nullopt, std::nullopt};
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;
}
}
std::map<std::string, std::vector<WaveFrontLine>> parse_wavefront(
std::istream &input_stream,
const WaveFrontFilter &filter
) {
std::map<std::string, std::vector<WaveFrontLine>> line_data;
std::optional<std::string> current_object, current_group;
std::size_t current_line_number = 0;
bool select_face = true;
using check_filter_func_t = std::function<bool()>;
static const auto return_true = []()->bool { return true; };
check_filter_func_t check_object = return_true;
check_filter_func_t check_group = return_true;
if (filter.selected_objects.has_value()) {
check_object = [&]()->bool {
auto selected = filter.selected_objects.value();
auto begin = selected.begin();
auto end = selected.end();
return std::find(begin, end, current_object.value_or("")) == end;
};
}
if (filter.selected_groups.has_value()) {
check_group = [&]()->bool {
auto selected = filter.selected_objects.value();
auto begin = selected.begin();
auto end = selected.end();
return std::find(begin, end, current_object.value_or("")) == end;
};
}
using line_processor_func_t = std::function<void(const std::string &type, const std::string &content)>;
line_processor_func_t insert_line = [&](const std::string &type, const std::string &content) {
line_data[type].push_back(WaveFrontLine(
current_line_number,
type,
content
));
};
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;
}
insert_line(type, content);
}},
{ "v", insert_line },
{ "vt", insert_line },
{ "vn", insert_line },
};
{
std::string line;
while(std::getline(input_stream, 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()) {
continue;
}
auto preprocessor_iterator = line_post_processor_map.find(line_type);
if (preprocessor_iterator != line_post_processor_map.end()) {
std::ostringstream line_content;
line_content << line_parser.rdbuf();
(preprocessor_iterator->second)(line_type, line_content.str());
}
}
}
return line_data;
}

View File

@@ -1,41 +0,0 @@
#ifndef APP_WAVEFRONT_HPP
#define APP_WAVEFRONT_HPP
#include <cstdint>
#include <functional>
#include <map>
#include <optional>
#include <sstream>
#include <string>
class WaveFrontLine {
std::size_t line_number_;
std::string type_;
std::string content_;
public:
WaveFrontLine(const std::size_t &line_number, const std::string &type, const std::string &content);
~WaveFrontLine() = default;
WaveFrontLine(const WaveFrontLine &) = default;
WaveFrontLine(WaveFrontLine &&) = default;
public:
const std::size_t &line_number();
const std::string &type();
const std::string &content();
};
class WaveFrontFilter {
public:
std::optional<std::vector<std::string>> selected_objects;
std::optional<std::vector<std::string>> selected_groups;
public:
static const WaveFrontFilter no_filter;
};
std::map<std::string, std::vector<WaveFrontLine>> parse_wavefront(
std::istream &input_stream,
const WaveFrontFilter &filter = WaveFrontFilter::no_filter
);
#endif // APP_WAVEFRONT_HPP