wavefront/parse: processing v, vn, vt, o, g
This commit is contained in:
@@ -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
273
src/file.hpp
Normal 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_ */
|
||||
@@ -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
|
||||
217
src/main.cpp
217
src/main.cpp
@@ -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();
|
||||
return 1;
|
||||
}
|
||||
has_input_file = true;
|
||||
input_file_path = arg;
|
||||
continue;
|
||||
}
|
||||
if (arg == "--filter-object" || arg == "-O") {
|
||||
if (i + 1 < argc) {
|
||||
selected_groups.push_back(argv[++i]);
|
||||
}
|
||||
} else if (arg == "--filter-group" || arg == "-G") {
|
||||
if (i + 1 < argc) {
|
||||
selected_groups.push_back(argv[++i]);
|
||||
}
|
||||
} else if (arg == "--") {
|
||||
process_flags = false;
|
||||
} else if (arg.starts_with("-")) {
|
||||
std::cerr << "Unknown option: " << arg << std::endl;
|
||||
printUsage();
|
||||
return 1;
|
||||
} else {
|
||||
input_file_path = arg;
|
||||
has_input_file = true;
|
||||
}
|
||||
}
|
||||
if (!has_input_file) {
|
||||
std::cerr << "No input file specified." << std::endl;
|
||||
printUsage();
|
||||
if (argc < 3) {
|
||||
usage(argv[0]);
|
||||
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);
|
||||
SettingsBuilder settings_builder;
|
||||
int arg_index = 1;
|
||||
bool end_of_options = false;
|
||||
std::vector<std::string> positional;
|
||||
|
||||
if (!input_stream) {
|
||||
std::cerr << "Failed to open file: " << input_file_path << std::endl;
|
||||
std::exit(1);
|
||||
while (arg_index < argc) {
|
||||
std::string arg = argv[arg_index];
|
||||
|
||||
if (!end_of_options && arg == "--") {
|
||||
end_of_options = true;
|
||||
++arg_index;
|
||||
continue;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
settings_builder.selected_groups().emplace_back(argv[arg_index + 1]);
|
||||
arg_index += 2;
|
||||
continue;
|
||||
} else {
|
||||
std::cerr << "Unknown option: " << arg << "\n";
|
||||
usage(argv[0]);
|
||||
return 2;
|
||||
}
|
||||
} else {
|
||||
positional.emplace_back(arg);
|
||||
++arg_index;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -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
141
src/settings.cpp
Normal 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_ = ∈
|
||||
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
83
src/settings.hpp
Normal 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_ */
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user