refactor parse + unit test
This commit is contained in:
@@ -25,6 +25,7 @@ add_executable(wavefront_tests
|
|||||||
tests/unit/trim_test.cpp
|
tests/unit/trim_test.cpp
|
||||||
tests/unit/settings_test.cpp
|
tests/unit/settings_test.cpp
|
||||||
tests/unit/scan_test.cpp
|
tests/unit/scan_test.cpp
|
||||||
|
tests/unit/parse_test.cpp
|
||||||
${APP_SOURCES}
|
${APP_SOURCES}
|
||||||
)
|
)
|
||||||
target_link_libraries(wavefront_tests PRIVATE
|
target_link_libraries(wavefront_tests PRIVATE
|
||||||
|
|||||||
308
src/parse.hpp
308
src/parse.hpp
@@ -55,313 +55,15 @@ namespace wavefront {
|
|||||||
wavefront_face_data_result_t parse_face_data(const scan_result &scan_data);
|
wavefront_face_data_result_t parse_face_data(const scan_result &scan_data);
|
||||||
|
|
||||||
template<typename FloatType>
|
template<typename FloatType>
|
||||||
coordinate_data_t<FloatType> parse_coordinate_data(const scan_result &scan_data, const wavefront_face_data_result_t &face_data) {
|
coordinate_data_t<FloatType> parse_coordinate_data(const scan_result &scan_data, const wavefront_face_data_result_t &face_data);
|
||||||
using namespace std::string_literals;
|
|
||||||
coordinate_data_t<FloatType> result;
|
|
||||||
|
|
||||||
const auto &position_data = scan_data.category_map.at("v"s);
|
|
||||||
for (const auto &position_index : face_data.index_position_set) {
|
|
||||||
std::array<FloatType, 3> position_coordinates;
|
|
||||||
const auto &position_line_index = position_data.at(position_index);
|
|
||||||
const auto &position_line = scan_data.line_data.at(position_line_index);
|
|
||||||
|
|
||||||
decltype(position_line.find_first_of(' ')) number_begin_pos = 2, number_end_pos;
|
|
||||||
for (std::size_t position_coordinate_index = 0; position_coordinate_index < 3; position_coordinate_index++) {
|
|
||||||
if (number_begin_pos >= position_line.size()) {
|
|
||||||
throw parse_error(std::format(
|
|
||||||
"[{}]: {}",
|
|
||||||
position_line_index,
|
|
||||||
std::format("Line \"{}\" must contain at least {} numbers", "v", 3)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
number_end_pos = std::min(position_line.find_first_of(' ', number_begin_pos), position_line.size());
|
|
||||||
auto [number_end, conversion_error] = std::from_chars(
|
|
||||||
position_line.data() + number_begin_pos,
|
|
||||||
position_line.data() + number_end_pos,
|
|
||||||
position_coordinates[position_coordinate_index],
|
|
||||||
std::chars_format::fixed
|
|
||||||
);
|
|
||||||
if (conversion_error != std::errc() || position_line.data() + number_end_pos != number_end) {
|
|
||||||
throw parse_error(std::format(
|
|
||||||
"[{}]: {}",
|
|
||||||
position_line_index,
|
|
||||||
std::format("Unable to parse \"{}\" floating point value", "v")
|
|
||||||
));
|
|
||||||
}
|
|
||||||
number_begin_pos = std::min(position_line.find_first_not_of(' ', number_end_pos), position_line.size());
|
|
||||||
}
|
|
||||||
if (number_begin_pos < position_line.size()) {
|
|
||||||
if (position_line.find_first_not_of("0123456789.-", number_begin_pos)) {
|
|
||||||
throw parse_error(std::format(
|
|
||||||
"[{}]: {}",
|
|
||||||
position_line_index,
|
|
||||||
std::format("Additional data in \"{}\" line: expected {} only", "v", "<x> <y> <z> [weight]")
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result.position_coordinates[position_line_index] = position_coordinates;
|
|
||||||
}
|
|
||||||
if (face_data.index_normal_set.size() > 0 && scan_data.category_map.contains("vn"s)) {
|
|
||||||
const auto &normal_data = scan_data.category_map.at("vn"s);
|
|
||||||
for (const auto &normal_index : face_data.index_normal_set) {
|
|
||||||
std::array<FloatType, 3> normal_coordinates;
|
|
||||||
const auto &normal_line_index = normal_data.at(normal_index);
|
|
||||||
const auto &normal_line = scan_data.line_data.at(normal_line_index);
|
|
||||||
|
|
||||||
decltype(normal_line.find_first_of(' ')) number_begin_pos = 3, number_end_pos;
|
|
||||||
for (std::size_t normal_coordinate_index = 0; normal_coordinate_index < 3; normal_coordinate_index++) {
|
|
||||||
if (number_begin_pos >= normal_line.size()) {
|
|
||||||
throw parse_error(std::format(
|
|
||||||
"[{}]: {}",
|
|
||||||
normal_line_index,
|
|
||||||
std::format("Line \"{}\" must contain exactly {} numbers", "vn", 3)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
number_end_pos = std::min(normal_line.find_first_of(' ', number_begin_pos), normal_line.size());
|
|
||||||
auto [number_end, conversion_error] = std::from_chars(
|
|
||||||
normal_line.data() + number_begin_pos,
|
|
||||||
normal_line.data() + number_end_pos,
|
|
||||||
normal_coordinates[normal_coordinate_index],
|
|
||||||
std::chars_format::fixed
|
|
||||||
);
|
|
||||||
if (conversion_error != std::errc() || normal_line.data() + number_end_pos != number_end) {
|
|
||||||
throw parse_error(std::format(
|
|
||||||
"[{}]: {}",
|
|
||||||
normal_line_index,
|
|
||||||
std::format("Unable to parse \"{}\" floating point value", "vn")
|
|
||||||
));
|
|
||||||
}
|
|
||||||
number_begin_pos = std::min(normal_line.find_first_not_of(' ', number_end_pos), normal_line.size());
|
|
||||||
}
|
|
||||||
if (number_end_pos != normal_line.size()) {
|
|
||||||
throw parse_error(std::format(
|
|
||||||
"[{}]: {}",
|
|
||||||
normal_line_index,
|
|
||||||
std::format("Additional data in \"{}\" line: expected {} only", "vn", "<x> <y> <z>")
|
|
||||||
));
|
|
||||||
}
|
|
||||||
result.normal_coordinates[normal_line_index] = normal_coordinates;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (face_data.index_texcoord_set.size() > 0 && scan_data.category_map.contains("vt"s)) {
|
|
||||||
using insert_texcoord_data_t = std::function<void()>;
|
|
||||||
|
|
||||||
const auto &texcoord_data = scan_data.category_map.at("vt"s);
|
|
||||||
for (const auto &texcoord_index : face_data.index_texcoord_set) {
|
|
||||||
std::array<FloatType, 3> texcoord_coordinates;
|
|
||||||
const auto &texcoord_line_index = texcoord_data.at(texcoord_index);
|
|
||||||
const auto &texcoord_line = scan_data.line_data.at(texcoord_line_index);
|
|
||||||
|
|
||||||
decltype(texcoord_line.find_first_of(' ')) number_begin_pos = 3, number_end_pos;
|
|
||||||
std::size_t texcoord_coordinate_index = 0;
|
|
||||||
while (number_begin_pos < texcoord_line.size()) {
|
|
||||||
if (texcoord_coordinate_index >= 3) {
|
|
||||||
throw parse_error(std::format(
|
|
||||||
"[{}]: {}",
|
|
||||||
texcoord_line_index,
|
|
||||||
std::format("Line \"{}\" must contain maximum {} numbers", "vt", 3)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
number_end_pos = std::min(texcoord_line.find_first_of(' ', number_begin_pos), texcoord_line.size());
|
|
||||||
auto [number_end, conversion_error] = std::from_chars(
|
|
||||||
texcoord_line.data() + number_begin_pos,
|
|
||||||
texcoord_line.data() + number_end_pos,
|
|
||||||
texcoord_coordinates[texcoord_coordinate_index++],
|
|
||||||
std::chars_format::fixed
|
|
||||||
);
|
|
||||||
if (conversion_error != std::errc() || texcoord_line.data() + number_end_pos != number_end) {
|
|
||||||
throw parse_error(std::format(
|
|
||||||
"[{}]: {}",
|
|
||||||
texcoord_line_index,
|
|
||||||
std::format("Unable to parse \"{}\" floating point value", "vt")
|
|
||||||
));
|
|
||||||
}
|
|
||||||
number_begin_pos = std::min(texcoord_line.find_first_not_of(' ', number_end_pos), texcoord_line.size());
|
|
||||||
}
|
|
||||||
if (texcoord_coordinate_index == 0) [[unlikely]] {
|
|
||||||
throw parse_error(std::format(
|
|
||||||
"[{}]: {}",
|
|
||||||
texcoord_line_index,
|
|
||||||
std::format("Unable to parse \"{}\" line: expected minimum one number", "vt")
|
|
||||||
));
|
|
||||||
}
|
|
||||||
if (result.texture_coordinates.valueless_by_exception() || result.texture_coordinates.index() == 0) [[unlikely]] {
|
|
||||||
if (texcoord_coordinate_index == 1) {
|
|
||||||
result.texture_coordinates = std::map<file_line_t, FloatType>{};
|
|
||||||
} else if (texcoord_coordinate_index == 2) {
|
|
||||||
result.texture_coordinates = std::map<file_line_t, std::array<FloatType, 2>>{};
|
|
||||||
} else if (texcoord_coordinate_index == 3) {
|
|
||||||
result.texture_coordinates = std::map<file_line_t, std::array<FloatType, 3>>{};
|
|
||||||
} else {
|
|
||||||
throw parse_error(std::format(
|
|
||||||
"[{}]: {}",
|
|
||||||
texcoord_line_index,
|
|
||||||
std::format("Line \"{}\" must contain maximum {} numbers", "vt", 3)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (texcoord_coordinate_index == 0) [[unlikely]] {
|
|
||||||
throw parse_error(std::format(
|
|
||||||
"[{}]: {}",
|
|
||||||
texcoord_line_index,
|
|
||||||
std::format("Unable to parse \"{}\" line: expected minimum one number", "vt")
|
|
||||||
));
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
if (texcoord_coordinate_index == 1) {
|
|
||||||
std::get<1>(result.texture_coordinates)[texcoord_line_index] = texcoord_coordinates[0];
|
|
||||||
} else if (texcoord_coordinate_index == 2) {
|
|
||||||
std::get<2>(result.texture_coordinates)[texcoord_line_index] = std::array<FloatType, 2>{texcoord_coordinates[0], texcoord_coordinates[1]};
|
|
||||||
} else if (texcoord_coordinate_index == 3) {
|
|
||||||
std::get<3>(result.texture_coordinates)[texcoord_line_index] = texcoord_coordinates;
|
|
||||||
}
|
|
||||||
} catch (std::bad_variant_access&) {
|
|
||||||
throw parse_error(std::format(
|
|
||||||
"[{}]: {}",
|
|
||||||
texcoord_line_index,
|
|
||||||
std::format("Line \"{}\": {}D texture coordinates expected, got {}D texture coordinates", "vt", result.texture_coordinates.index(), texcoord_coordinate_index)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename FloatType>
|
template<typename FloatType>
|
||||||
std::vector<FloatType> create_number_list(const coordinate_data_t<FloatType> &coordinate_data) {
|
std::vector<FloatType> create_number_list(const coordinate_data_t<FloatType> &coordinate_data);
|
||||||
numset_t<FloatType> number_set;
|
|
||||||
|
|
||||||
for (const auto& [line_number, coordinates] : coordinate_data.position_coordinates) {
|
|
||||||
for (const auto &coordinate : coordinates) {
|
|
||||||
number_set.emplace(coordinate);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (const auto& [line_number, coordinates] : coordinate_data.normal_coordinates) {
|
|
||||||
for (const auto &coordinate : coordinates) {
|
|
||||||
number_set.emplace(coordinate);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::visit([&](const auto &texture_coordinate_map) {
|
|
||||||
if constexpr (std::ranges::range<std::decay_t<decltype(texture_coordinate_map)>>) {
|
|
||||||
for (const auto& [line_number, coordinates] : texture_coordinate_map) {
|
|
||||||
if constexpr (std::ranges::range<std::decay_t<decltype(coordinates)>>) {
|
|
||||||
for (const auto &coordinate : coordinates) {
|
|
||||||
number_set.emplace(coordinate);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
number_set.emplace(coordinates);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, coordinate_data.texture_coordinates);
|
|
||||||
|
|
||||||
number_set.emplace(std::numeric_limits<FloatType>::quiet_NaN());
|
|
||||||
|
|
||||||
std::vector<FloatType> number_list;
|
|
||||||
number_list.reserve(number_set.size());
|
|
||||||
std::copy(number_set.begin(), number_set.end(), std::back_inserter(number_list));
|
|
||||||
|
|
||||||
return number_list;
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename FloatType, typename IndexType>
|
template<typename FloatType, typename IndexType>
|
||||||
coordinate_data_t<IndexType> create_coordinate_index(const coordinate_data_t<FloatType> &coordinate_data, const std::vector<FloatType> &float_list) {
|
coordinate_data_t<IndexType> create_coordinate_index(const coordinate_data_t<FloatType> &coordinate_data, const std::vector<FloatType> &float_list);
|
||||||
coordinate_data_t<IndexType> coordinate_index_data;
|
|
||||||
|
|
||||||
static const auto float_compare = float_is_equal<FloatType>{};
|
|
||||||
static const auto float_less = less_with_nan_first_and_nearly_equal<FloatType>{};
|
|
||||||
|
|
||||||
for (const auto& [line_number, coordinates] : coordinate_data.position_coordinates) {
|
|
||||||
std::array<IndexType, coordinates.size()> position_coordinate_indices;
|
|
||||||
for (decltype(coordinates.size()) dim = 0; dim < coordinates.size(); ++dim) {
|
|
||||||
auto iterator = std::lower_bound(float_list.begin(), float_list.end(), coordinates[dim], float_less);
|
|
||||||
assert(iterator != float_list.end());
|
|
||||||
assert(float_compare(*iterator, coordinates[dim]));
|
|
||||||
auto index = std::distance(float_list.begin(), iterator);
|
|
||||||
assert(index != 0);
|
|
||||||
position_coordinate_indices[dim] = index;
|
|
||||||
}
|
|
||||||
coordinate_index_data.position_coordinates[line_number] = position_coordinate_indices;
|
|
||||||
}
|
|
||||||
for (const auto& [line_number, coordinates] : coordinate_data.normal_coordinates) {
|
|
||||||
std::array<IndexType, coordinates.size()> normal_coordinate_indices;
|
|
||||||
for (decltype(coordinates.size()) dim = 0; dim < coordinates.size(); ++dim) {
|
|
||||||
auto iterator = std::lower_bound(float_list.begin(), float_list.end(), coordinates[dim], float_less);
|
|
||||||
assert(iterator != float_list.end());
|
|
||||||
assert(float_compare(*iterator, coordinates[dim]));
|
|
||||||
auto index = std::distance(float_list.begin(), iterator);
|
|
||||||
assert(index != 0);
|
|
||||||
normal_coordinate_indices[dim] = index;
|
|
||||||
}
|
|
||||||
coordinate_index_data.normal_coordinates[line_number] = normal_coordinate_indices;
|
|
||||||
}
|
|
||||||
if (!coordinate_data.texture_coordinates.valueless_by_exception() && coordinate_data.texture_coordinates.index() > 0) {
|
|
||||||
std::visit([&](const auto &texture_coordinate_map) {
|
|
||||||
if constexpr (std::ranges::range<std::decay_t<decltype(texture_coordinate_map)>>) {
|
|
||||||
if constexpr (std::ranges::range<typename std::decay_t<decltype(texture_coordinate_map)>::mapped_type>) {
|
|
||||||
coordinate_index_data.texture_coordinates = std::map<
|
|
||||||
typename std::decay_t<decltype(texture_coordinate_map)>::key_type,
|
|
||||||
std::array<
|
|
||||||
IndexType,
|
|
||||||
std::tuple_size_v<typename std::decay_t<decltype(texture_coordinate_map)>::mapped_type>
|
|
||||||
>
|
|
||||||
>{};
|
|
||||||
} else {
|
|
||||||
coordinate_index_data.texture_coordinates = std::map<
|
|
||||||
typename std::decay_t<decltype(texture_coordinate_map)>::key_type,
|
|
||||||
IndexType
|
|
||||||
>{};
|
|
||||||
}
|
|
||||||
for (const auto& [line_number, coordinates] : texture_coordinate_map) {
|
|
||||||
if constexpr (std::ranges::range<std::decay_t<decltype(coordinates)>>) {
|
|
||||||
std::array<IndexType, coordinates.size()> texcoord_coordinate_indices;
|
|
||||||
for (std::size_t dim = 0; dim < coordinates.size(); ++dim) {
|
|
||||||
auto iterator = std::lower_bound(float_list.begin(), float_list.end(), coordinates[dim], float_less);
|
|
||||||
assert(iterator != float_list.end());
|
|
||||||
assert(float_compare(*iterator, coordinates[dim]));
|
|
||||||
auto index = std::distance(float_list.begin(), iterator);
|
|
||||||
assert(index != 0);
|
|
||||||
texcoord_coordinate_indices[dim] = index;
|
|
||||||
}
|
|
||||||
std::visit([&](auto &target_data) {
|
|
||||||
if constexpr (std::is_same_v<
|
|
||||||
std::map<
|
|
||||||
file_line_t,
|
|
||||||
std::array<
|
|
||||||
IndexType,
|
|
||||||
std::tuple_size_v<typename std::decay_t<decltype(texture_coordinate_map)>::mapped_type>
|
|
||||||
>
|
|
||||||
>,
|
|
||||||
typename std::decay_t<decltype(target_data)>
|
|
||||||
>) {
|
|
||||||
target_data[line_number] = texcoord_coordinate_indices;
|
|
||||||
}
|
|
||||||
}, coordinate_index_data.texture_coordinates);
|
|
||||||
} else {
|
|
||||||
auto iterator = std::lower_bound(float_list.begin(), float_list.end(), coordinates, float_less);
|
|
||||||
assert(iterator != float_list.end());
|
|
||||||
assert(float_compare(*iterator, coordinates));
|
|
||||||
auto coordinate = std::distance(float_list.begin(), iterator);
|
|
||||||
assert(coordinate != 0);
|
|
||||||
std::visit([&](auto &target_data) {
|
|
||||||
if constexpr (std::is_same_v<
|
|
||||||
std::map<file_line_t, IndexType>,
|
|
||||||
typename std::decay_t<decltype(target_data)>
|
|
||||||
>) {
|
|
||||||
target_data[line_number] = coordinate;
|
|
||||||
}
|
|
||||||
}, coordinate_index_data.texture_coordinates);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
int j = 0;
|
|
||||||
}, coordinate_data.texture_coordinates);
|
|
||||||
}
|
|
||||||
|
|
||||||
return coordinate_index_data;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#include "parse.tpp"
|
||||||
|
|
||||||
#endif // __WAVEFRONT_PARSE_HPP__
|
#endif // __WAVEFRONT_PARSE_HPP__
|
||||||
319
src/parse.tpp
Normal file
319
src/parse.tpp
Normal file
@@ -0,0 +1,319 @@
|
|||||||
|
#ifndef __WAVEFRONT_PARSE_TPP__
|
||||||
|
#define __WAVEFRONT_PARSE_TPP__
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <limits>
|
||||||
|
#include <ranges>
|
||||||
|
|
||||||
|
namespace wavefront {
|
||||||
|
template<typename FloatType>
|
||||||
|
coordinate_data_t<FloatType> parse_coordinate_data(const scan_result &scan_data, const wavefront_face_data_result_t &face_data) {
|
||||||
|
using namespace std::string_literals;
|
||||||
|
coordinate_data_t<FloatType> result;
|
||||||
|
|
||||||
|
const auto &position_data = scan_data.category_map.at("v"s);
|
||||||
|
for (const auto &position_index : face_data.index_position_set) {
|
||||||
|
std::array<FloatType, 3> position_coordinates;
|
||||||
|
const auto &position_line_index = position_data.at(position_index);
|
||||||
|
const auto &position_line = scan_data.line_data.at(position_line_index);
|
||||||
|
|
||||||
|
decltype(position_line.find_first_of(' ')) number_begin_pos = 2, number_end_pos;
|
||||||
|
for (std::size_t position_coordinate_index = 0; position_coordinate_index < 3; position_coordinate_index++) {
|
||||||
|
if (number_begin_pos >= position_line.size()) {
|
||||||
|
throw parse_error(std::format(
|
||||||
|
"[{}]: {}",
|
||||||
|
position_line_index,
|
||||||
|
std::format("Line \"{}\" must contain at least {} numbers", "v", 3)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
number_end_pos = std::min(position_line.find_first_of(' ', number_begin_pos), position_line.size());
|
||||||
|
auto [number_end, conversion_error] = std::from_chars(
|
||||||
|
position_line.data() + number_begin_pos,
|
||||||
|
position_line.data() + number_end_pos,
|
||||||
|
position_coordinates[position_coordinate_index],
|
||||||
|
std::chars_format::fixed
|
||||||
|
);
|
||||||
|
if (conversion_error != std::errc() || position_line.data() + number_end_pos != number_end) {
|
||||||
|
throw parse_error(std::format(
|
||||||
|
"[{}]: {}",
|
||||||
|
position_line_index,
|
||||||
|
std::format("Unable to parse \"{}\" floating point value", "v")
|
||||||
|
));
|
||||||
|
}
|
||||||
|
number_begin_pos = std::min(position_line.find_first_not_of(' ', number_end_pos), position_line.size());
|
||||||
|
}
|
||||||
|
if (number_begin_pos < position_line.size()) {
|
||||||
|
if (position_line.find_first_not_of("0123456789.-", number_begin_pos)) {
|
||||||
|
throw parse_error(std::format(
|
||||||
|
"[{}]: {}",
|
||||||
|
position_line_index,
|
||||||
|
std::format("Additional data in \"{}\" line: expected {} only", "v", "<x> <y> <z> [weight]")
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.position_coordinates[position_line_index] = position_coordinates;
|
||||||
|
}
|
||||||
|
if (face_data.index_normal_set.size() > 0 && scan_data.category_map.contains("vn"s)) {
|
||||||
|
const auto &normal_data = scan_data.category_map.at("vn"s);
|
||||||
|
for (const auto &normal_index : face_data.index_normal_set) {
|
||||||
|
std::array<FloatType, 3> normal_coordinates;
|
||||||
|
const auto &normal_line_index = normal_data.at(normal_index);
|
||||||
|
const auto &normal_line = scan_data.line_data.at(normal_line_index);
|
||||||
|
|
||||||
|
decltype(normal_line.find_first_of(' ')) number_begin_pos = 3, number_end_pos;
|
||||||
|
for (std::size_t normal_coordinate_index = 0; normal_coordinate_index < 3; normal_coordinate_index++) {
|
||||||
|
if (number_begin_pos >= normal_line.size()) {
|
||||||
|
throw parse_error(std::format(
|
||||||
|
"[{}]: {}",
|
||||||
|
normal_line_index,
|
||||||
|
std::format("Line \"{}\" must contain exactly {} numbers", "vn", 3)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
number_end_pos = std::min(normal_line.find_first_of(' ', number_begin_pos), normal_line.size());
|
||||||
|
auto [number_end, conversion_error] = std::from_chars(
|
||||||
|
normal_line.data() + number_begin_pos,
|
||||||
|
normal_line.data() + number_end_pos,
|
||||||
|
normal_coordinates[normal_coordinate_index],
|
||||||
|
std::chars_format::fixed
|
||||||
|
);
|
||||||
|
if (conversion_error != std::errc() || normal_line.data() + number_end_pos != number_end) {
|
||||||
|
throw parse_error(std::format(
|
||||||
|
"[{}]: {}",
|
||||||
|
normal_line_index,
|
||||||
|
std::format("Unable to parse \"{}\" floating point value", "vn")
|
||||||
|
));
|
||||||
|
}
|
||||||
|
number_begin_pos = std::min(normal_line.find_first_not_of(' ', number_end_pos), normal_line.size());
|
||||||
|
}
|
||||||
|
if (number_end_pos != normal_line.size()) {
|
||||||
|
throw parse_error(std::format(
|
||||||
|
"[{}]: {}",
|
||||||
|
normal_line_index,
|
||||||
|
std::format("Additional data in \"{}\" line: expected {} only", "vn", "<x> <y> <z>")
|
||||||
|
));
|
||||||
|
}
|
||||||
|
result.normal_coordinates[normal_line_index] = normal_coordinates;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (face_data.index_texcoord_set.size() > 0 && scan_data.category_map.contains("vt"s)) {
|
||||||
|
using insert_texcoord_data_t = std::function<void()>;
|
||||||
|
|
||||||
|
const auto &texcoord_data = scan_data.category_map.at("vt"s);
|
||||||
|
for (const auto &texcoord_index : face_data.index_texcoord_set) {
|
||||||
|
std::array<FloatType, 3> texcoord_coordinates;
|
||||||
|
const auto &texcoord_line_index = texcoord_data.at(texcoord_index);
|
||||||
|
const auto &texcoord_line = scan_data.line_data.at(texcoord_line_index);
|
||||||
|
|
||||||
|
decltype(texcoord_line.find_first_of(' ')) number_begin_pos = 3, number_end_pos;
|
||||||
|
std::size_t texcoord_coordinate_index = 0;
|
||||||
|
while (number_begin_pos < texcoord_line.size()) {
|
||||||
|
if (texcoord_coordinate_index >= 3) {
|
||||||
|
throw parse_error(std::format(
|
||||||
|
"[{}]: {}",
|
||||||
|
texcoord_line_index,
|
||||||
|
std::format("Line \"{}\" must contain maximum {} numbers", "vt", 3)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
number_end_pos = std::min(texcoord_line.find_first_of(' ', number_begin_pos), texcoord_line.size());
|
||||||
|
auto [number_end, conversion_error] = std::from_chars(
|
||||||
|
texcoord_line.data() + number_begin_pos,
|
||||||
|
texcoord_line.data() + number_end_pos,
|
||||||
|
texcoord_coordinates[texcoord_coordinate_index++],
|
||||||
|
std::chars_format::fixed
|
||||||
|
);
|
||||||
|
if (conversion_error != std::errc() || texcoord_line.data() + number_end_pos != number_end) {
|
||||||
|
throw parse_error(std::format(
|
||||||
|
"[{}]: {}",
|
||||||
|
texcoord_line_index,
|
||||||
|
std::format("Unable to parse \"{}\" floating point value", "vt")
|
||||||
|
));
|
||||||
|
}
|
||||||
|
number_begin_pos = std::min(texcoord_line.find_first_not_of(' ', number_end_pos), texcoord_line.size());
|
||||||
|
}
|
||||||
|
if (texcoord_coordinate_index == 0) [[unlikely]] {
|
||||||
|
throw parse_error(std::format(
|
||||||
|
"[{}]: {}",
|
||||||
|
texcoord_line_index,
|
||||||
|
std::format("Unable to parse \"{}\" line: expected minimum one number", "vt")
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if (result.texture_coordinates.valueless_by_exception() || result.texture_coordinates.index() == 0) [[unlikely]] {
|
||||||
|
if (texcoord_coordinate_index == 1) {
|
||||||
|
result.texture_coordinates = std::map<file_line_t, FloatType>{};
|
||||||
|
} else if (texcoord_coordinate_index == 2) {
|
||||||
|
result.texture_coordinates = std::map<file_line_t, std::array<FloatType, 2>>{};
|
||||||
|
} else if (texcoord_coordinate_index == 3) {
|
||||||
|
result.texture_coordinates = std::map<file_line_t, std::array<FloatType, 3>>{};
|
||||||
|
} else {
|
||||||
|
throw parse_error(std::format(
|
||||||
|
"[{}]: {}",
|
||||||
|
texcoord_line_index,
|
||||||
|
std::format("Line \"{}\" must contain maximum {} numbers", "vt", 3)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (texcoord_coordinate_index == 0) [[unlikely]] {
|
||||||
|
throw parse_error(std::format(
|
||||||
|
"[{}]: {}",
|
||||||
|
texcoord_line_index,
|
||||||
|
std::format("Unable to parse \"{}\" line: expected minimum one number", "vt")
|
||||||
|
));
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
if (texcoord_coordinate_index == 1) {
|
||||||
|
std::get<1>(result.texture_coordinates)[texcoord_line_index] = texcoord_coordinates[0];
|
||||||
|
} else if (texcoord_coordinate_index == 2) {
|
||||||
|
std::get<2>(result.texture_coordinates)[texcoord_line_index] = std::array<FloatType, 2>{texcoord_coordinates[0], texcoord_coordinates[1]};
|
||||||
|
} else if (texcoord_coordinate_index == 3) {
|
||||||
|
std::get<3>(result.texture_coordinates)[texcoord_line_index] = texcoord_coordinates;
|
||||||
|
}
|
||||||
|
} catch (std::bad_variant_access&) {
|
||||||
|
throw parse_error(std::format(
|
||||||
|
"[{}]: {}",
|
||||||
|
texcoord_line_index,
|
||||||
|
std::format("Line \"{}\": {}D texture coordinates expected, got {}D texture coordinates", "vt", result.texture_coordinates.index(), texcoord_coordinate_index)
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename FloatType>
|
||||||
|
std::vector<FloatType> create_number_list(const coordinate_data_t<FloatType> &coordinate_data) {
|
||||||
|
numset_t<FloatType> number_set;
|
||||||
|
|
||||||
|
for (const auto& [line_number, coordinates] : coordinate_data.position_coordinates) {
|
||||||
|
for (const auto &coordinate : coordinates) {
|
||||||
|
number_set.emplace(coordinate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const auto& [line_number, coordinates] : coordinate_data.normal_coordinates) {
|
||||||
|
for (const auto &coordinate : coordinates) {
|
||||||
|
number_set.emplace(coordinate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::visit([&](const auto &texture_coordinate_map) {
|
||||||
|
if constexpr (std::ranges::range<std::decay_t<decltype(texture_coordinate_map)>>) {
|
||||||
|
for (const auto& [line_number, coordinates] : texture_coordinate_map) {
|
||||||
|
if constexpr (std::ranges::range<std::decay_t<decltype(coordinates)>>) {
|
||||||
|
for (const auto &coordinate : coordinates) {
|
||||||
|
number_set.emplace(coordinate);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
number_set.emplace(coordinates);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, coordinate_data.texture_coordinates);
|
||||||
|
|
||||||
|
number_set.emplace(std::numeric_limits<FloatType>::quiet_NaN());
|
||||||
|
|
||||||
|
std::vector<FloatType> number_list;
|
||||||
|
number_list.reserve(number_set.size());
|
||||||
|
std::copy(number_set.begin(), number_set.end(), std::back_inserter(number_list));
|
||||||
|
|
||||||
|
return number_list;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename FloatType, typename IndexType>
|
||||||
|
coordinate_data_t<IndexType> create_coordinate_index(const coordinate_data_t<FloatType> &coordinate_data, const std::vector<FloatType> &float_list) {
|
||||||
|
coordinate_data_t<IndexType> coordinate_index_data;
|
||||||
|
|
||||||
|
static const auto float_compare = float_is_equal<FloatType>{};
|
||||||
|
static const auto float_less = less_with_nan_first_and_nearly_equal<FloatType>{};
|
||||||
|
|
||||||
|
for (const auto& [line_number, coordinates] : coordinate_data.position_coordinates) {
|
||||||
|
std::array<IndexType, coordinates.size()> position_coordinate_indices;
|
||||||
|
for (decltype(coordinates.size()) dim = 0; dim < coordinates.size(); ++dim) {
|
||||||
|
auto iterator = std::lower_bound(float_list.begin(), float_list.end(), coordinates[dim], float_less);
|
||||||
|
assert(iterator != float_list.end());
|
||||||
|
assert(float_compare(*iterator, coordinates[dim]));
|
||||||
|
auto index = std::distance(float_list.begin(), iterator);
|
||||||
|
assert(index != 0);
|
||||||
|
position_coordinate_indices[dim] = index;
|
||||||
|
}
|
||||||
|
coordinate_index_data.position_coordinates[line_number] = position_coordinate_indices;
|
||||||
|
}
|
||||||
|
for (const auto& [line_number, coordinates] : coordinate_data.normal_coordinates) {
|
||||||
|
std::array<IndexType, coordinates.size()> normal_coordinate_indices;
|
||||||
|
for (decltype(coordinates.size()) dim = 0; dim < coordinates.size(); ++dim) {
|
||||||
|
auto iterator = std::lower_bound(float_list.begin(), float_list.end(), coordinates[dim], float_less);
|
||||||
|
assert(iterator != float_list.end());
|
||||||
|
assert(float_compare(*iterator, coordinates[dim]));
|
||||||
|
auto index = std::distance(float_list.begin(), iterator);
|
||||||
|
assert(index != 0);
|
||||||
|
normal_coordinate_indices[dim] = index;
|
||||||
|
}
|
||||||
|
coordinate_index_data.normal_coordinates[line_number] = normal_coordinate_indices;
|
||||||
|
}
|
||||||
|
if (!coordinate_data.texture_coordinates.valueless_by_exception() && coordinate_data.texture_coordinates.index() > 0) {
|
||||||
|
std::visit([&](const auto &texture_coordinate_map) {
|
||||||
|
if constexpr (std::ranges::range<std::decay_t<decltype(texture_coordinate_map)>>) {
|
||||||
|
if constexpr (std::ranges::range<typename std::decay_t<decltype(texture_coordinate_map)>::mapped_type>) {
|
||||||
|
coordinate_index_data.texture_coordinates = std::map<
|
||||||
|
typename std::decay_t<decltype(texture_coordinate_map)>::key_type,
|
||||||
|
std::array<
|
||||||
|
IndexType,
|
||||||
|
std::tuple_size_v<typename std::decay_t<decltype(texture_coordinate_map)>::mapped_type>
|
||||||
|
>
|
||||||
|
>{};
|
||||||
|
} else {
|
||||||
|
coordinate_index_data.texture_coordinates = std::map<
|
||||||
|
typename std::decay_t<decltype(texture_coordinate_map)>::key_type,
|
||||||
|
IndexType
|
||||||
|
>{};
|
||||||
|
}
|
||||||
|
for (const auto& [line_number, coordinates] : texture_coordinate_map) {
|
||||||
|
if constexpr (std::ranges::range<std::decay_t<decltype(coordinates)>>) {
|
||||||
|
std::array<IndexType, coordinates.size()> texcoord_coordinate_indices;
|
||||||
|
for (std::size_t dim = 0; dim < coordinates.size(); ++dim) {
|
||||||
|
auto iterator = std::lower_bound(float_list.begin(), float_list.end(), coordinates[dim], float_less);
|
||||||
|
assert(iterator != float_list.end());
|
||||||
|
assert(float_compare(*iterator, coordinates[dim]));
|
||||||
|
auto index = std::distance(float_list.begin(), iterator);
|
||||||
|
assert(index != 0);
|
||||||
|
texcoord_coordinate_indices[dim] = index;
|
||||||
|
}
|
||||||
|
std::visit([&](auto &target_data) {
|
||||||
|
if constexpr (std::is_same_v<
|
||||||
|
std::map<
|
||||||
|
file_line_t,
|
||||||
|
std::array<
|
||||||
|
IndexType,
|
||||||
|
std::tuple_size_v<typename std::decay_t<decltype(texture_coordinate_map)>::mapped_type>
|
||||||
|
>
|
||||||
|
>,
|
||||||
|
typename std::decay_t<decltype(target_data)>
|
||||||
|
>) {
|
||||||
|
target_data[line_number] = texcoord_coordinate_indices;
|
||||||
|
}
|
||||||
|
}, coordinate_index_data.texture_coordinates);
|
||||||
|
} else {
|
||||||
|
auto iterator = std::lower_bound(float_list.begin(), float_list.end(), coordinates, float_less);
|
||||||
|
assert(iterator != float_list.end());
|
||||||
|
assert(float_compare(*iterator, coordinates));
|
||||||
|
auto coordinate = std::distance(float_list.begin(), iterator);
|
||||||
|
assert(coordinate != 0);
|
||||||
|
std::visit([&](auto &target_data) {
|
||||||
|
if constexpr (std::is_same_v<
|
||||||
|
std::map<file_line_t, IndexType>,
|
||||||
|
typename std::decay_t<decltype(target_data)>
|
||||||
|
>) {
|
||||||
|
target_data[line_number] = coordinate;
|
||||||
|
}
|
||||||
|
}, coordinate_index_data.texture_coordinates);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int j = 0;
|
||||||
|
}, coordinate_data.texture_coordinates);
|
||||||
|
}
|
||||||
|
|
||||||
|
return coordinate_index_data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // __WAVEFRONT_PARSE_TPP__
|
||||||
129
tests/unit/parse_test.cpp
Normal file
129
tests/unit/parse_test.cpp
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
#include <variant>
|
||||||
|
|
||||||
|
#include <gtest/gtest.h>
|
||||||
|
|
||||||
|
#include "parse.hpp"
|
||||||
|
#include "scan.hpp"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
TEST(ParseFaceData, BuildsTrianglesAndIndexSets) {
|
||||||
|
wavefront::scan_result scan_result;
|
||||||
|
scan_result.total_lines = 26;
|
||||||
|
scan_result.line_data = {
|
||||||
|
{17u, "f 1/1/1 2/2/2 3/3/2"},
|
||||||
|
{19u, "f 4/3/2 5/2/1 6/1/2"},
|
||||||
|
{21u, "f 7/2/2 8/3/2 9/1/1"},
|
||||||
|
{23u, "f 8/1/1 5/2/2 3/3/2"},
|
||||||
|
{25u, "f 2/1/1 6/2/2 4/3/2"}
|
||||||
|
};
|
||||||
|
scan_result.category_map = {
|
||||||
|
{"f", {17u, 19u, 21u, 23u, 25u}}
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto face_data = wavefront::parse_face_data(scan_result);
|
||||||
|
|
||||||
|
EXPECT_EQ(face_data.triangle_list.size(), 5u);
|
||||||
|
|
||||||
|
EXPECT_EQ(face_data.index_position_set.size(), 9u);
|
||||||
|
EXPECT_TRUE(face_data.index_position_set.contains(1));
|
||||||
|
EXPECT_TRUE(face_data.index_position_set.contains(9));
|
||||||
|
|
||||||
|
EXPECT_EQ(face_data.index_normal_set.size(), 2u);
|
||||||
|
EXPECT_TRUE(face_data.index_normal_set.contains(1));
|
||||||
|
EXPECT_TRUE(face_data.index_normal_set.contains(2));
|
||||||
|
|
||||||
|
EXPECT_EQ(face_data.index_texcoord_set.size(), 3u);
|
||||||
|
EXPECT_TRUE(face_data.index_texcoord_set.contains(1));
|
||||||
|
EXPECT_TRUE(face_data.index_texcoord_set.contains(3));
|
||||||
|
|
||||||
|
const auto &first_triangle = face_data.triangle_list.at(0);
|
||||||
|
EXPECT_EQ(first_triangle[0].face_line_number, 17u);
|
||||||
|
EXPECT_EQ(first_triangle[0].position_index, 1);
|
||||||
|
EXPECT_EQ(first_triangle[0].texcoord_index, 1);
|
||||||
|
EXPECT_EQ(first_triangle[0].normal_index, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(ParseCoordinateData, MapsCoordinatesWithoutParsingFaces) {
|
||||||
|
wavefront::scan_result scan_result;
|
||||||
|
scan_result.total_lines = 26;
|
||||||
|
scan_result.line_data = {
|
||||||
|
{3u, "v 0.1 0.2 0.3"},
|
||||||
|
{4u, "v 0.2 0.3 0.4"},
|
||||||
|
{5u, "v 0.3 0.4 0.5"},
|
||||||
|
{6u, "v 1.1 1.2 1.3"},
|
||||||
|
{7u, "v 1.2 1.3 1.4"},
|
||||||
|
{8u, "v 1.3 1.4 1.5"},
|
||||||
|
{9u, "v 2.1 2.2 2.3"},
|
||||||
|
{10u, "v 2.2 2.3 2.4"},
|
||||||
|
{11u, "v 2.3 2.4 2.5"},
|
||||||
|
{12u, "vn 0.15 0.25 0.35"},
|
||||||
|
{13u, "vn 0.25 0.35 0.45"},
|
||||||
|
{14u, "vn 0.35 0.45 0.55"},
|
||||||
|
{15u, "vt 0.9 0.8"},
|
||||||
|
{16u, "vt 0.8 0.7"},
|
||||||
|
{17u, "f 1/1/1 2/2/2 3/3/2"},
|
||||||
|
{19u, "f 4/3/2 5/2/1 6/1/2"},
|
||||||
|
{21u, "f 7/2/2 8/3/2 9/1/1"},
|
||||||
|
{23u, "f 8/1/1 5/2/2 3/3/2"},
|
||||||
|
{25u, "f 2/1/1 6/2/2 4/3/2"}
|
||||||
|
};
|
||||||
|
scan_result.category_map = {
|
||||||
|
{"v", {0u, 3u, 4u, 5u, 6u, 7u, 8u, 9u, 10u, 11u}},
|
||||||
|
{"vn", {0u, 12u, 13u, 14u}},
|
||||||
|
{"vt", {0u, 15u, 16u}},
|
||||||
|
{"f", {17u, 19u, 21u, 23u, 25u}}
|
||||||
|
};
|
||||||
|
|
||||||
|
wavefront::wavefront_face_data_result_t face_data;
|
||||||
|
face_data.index_position_set = {1, 2, 3, 4, 5, 6, 7, 8, 9};
|
||||||
|
face_data.index_normal_set = {1, 2, 3};
|
||||||
|
face_data.index_texcoord_set = {1, 2};
|
||||||
|
face_data.triangle_list = {
|
||||||
|
{{{17u, 1, 1, 1}, {17u, 2, 2, 2}, {17u, 3, 3, 2}}},
|
||||||
|
{{{19u, 4, 3, 2}, {19u, 5, 2, 1}, {19u, 6, 1, 2}}},
|
||||||
|
{{{21u, 7, 2, 2}, {21u, 8, 3, 2}, {21u, 9, 1, 1}}},
|
||||||
|
{{{23u, 8, 1, 1}, {23u, 5, 2, 2}, {23u, 3, 3, 2}}},
|
||||||
|
{{{25u, 2, 1, 1}, {25u, 6, 2, 2}, {25u, 4, 3, 2}}}
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto coordinate_data = wavefront::parse_coordinate_data<float>(scan_result, face_data);
|
||||||
|
|
||||||
|
ASSERT_EQ(coordinate_data.position_coordinates.size(), 9u);
|
||||||
|
const auto &pos_line_2 = coordinate_data.position_coordinates.at(3u);
|
||||||
|
EXPECT_FLOAT_EQ(pos_line_2[0], 0.1f);
|
||||||
|
EXPECT_FLOAT_EQ(pos_line_2[1], 0.2f);
|
||||||
|
EXPECT_FLOAT_EQ(pos_line_2[2], 0.3f);
|
||||||
|
|
||||||
|
const auto &pos_line_5 = coordinate_data.position_coordinates.at(6u);
|
||||||
|
EXPECT_FLOAT_EQ(pos_line_5[0], 1.1f);
|
||||||
|
EXPECT_FLOAT_EQ(pos_line_5[1], 1.2f);
|
||||||
|
EXPECT_FLOAT_EQ(pos_line_5[2], 1.3f);
|
||||||
|
|
||||||
|
const auto &pos_line_10 = coordinate_data.position_coordinates.at(11u);
|
||||||
|
EXPECT_FLOAT_EQ(pos_line_10[0], 2.3f);
|
||||||
|
EXPECT_FLOAT_EQ(pos_line_10[1], 2.4f);
|
||||||
|
EXPECT_FLOAT_EQ(pos_line_10[2], 2.5f);
|
||||||
|
|
||||||
|
ASSERT_EQ(coordinate_data.normal_coordinates.size(), 3u);
|
||||||
|
const auto &normal_line_11 = coordinate_data.normal_coordinates.at(12u);
|
||||||
|
EXPECT_FLOAT_EQ(normal_line_11[0], 0.15f);
|
||||||
|
EXPECT_FLOAT_EQ(normal_line_11[1], 0.25f);
|
||||||
|
EXPECT_FLOAT_EQ(normal_line_11[2], 0.35f);
|
||||||
|
|
||||||
|
const auto &normal_line_13 = coordinate_data.normal_coordinates.at(14u);
|
||||||
|
EXPECT_FLOAT_EQ(normal_line_13[0], 0.35f);
|
||||||
|
EXPECT_FLOAT_EQ(normal_line_13[1], 0.45f);
|
||||||
|
EXPECT_FLOAT_EQ(normal_line_13[2], 0.55f);
|
||||||
|
|
||||||
|
ASSERT_EQ(coordinate_data.texture_coordinates.index(), 2u);
|
||||||
|
const auto &texcoords = std::get<2>(coordinate_data.texture_coordinates);
|
||||||
|
ASSERT_EQ(texcoords.size(), 2u);
|
||||||
|
const auto &tex_line_14 = texcoords.at(15u);
|
||||||
|
EXPECT_FLOAT_EQ(tex_line_14[0], 0.9f);
|
||||||
|
EXPECT_FLOAT_EQ(tex_line_14[1], 0.8f);
|
||||||
|
|
||||||
|
const auto &tex_line_15 = texcoords.at(16u);
|
||||||
|
EXPECT_FLOAT_EQ(tex_line_15[0], 0.8f);
|
||||||
|
EXPECT_FLOAT_EQ(tex_line_15[1], 0.7f);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user