Compare commits
1 Commits
main
...
dbfd4eb19d
| Author | SHA1 | Date | |
|---|---|---|---|
| dbfd4eb19d |
@@ -7,51 +7,5 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
|||||||
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-g -O3")
|
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-g -O3")
|
||||||
set(CMAKE_CXX_FLAGS "-fno-threadsafe-statics")
|
set(CMAKE_CXX_FLAGS "-fno-threadsafe-statics")
|
||||||
|
|
||||||
option(ENABLE_COVERAGE "Enable coverage instrumentation for tests" OFF)
|
file(GLOB SRC_FILES src/*.cpp)
|
||||||
|
|
||||||
file(GLOB SRC_FILES CONFIGURE_DEPENDS src/*.cpp)
|
|
||||||
|
|
||||||
set(APP_SOURCES ${SRC_FILES})
|
|
||||||
list(REMOVE_ITEM APP_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp)
|
|
||||||
|
|
||||||
add_executable(wavefront_parser ${SRC_FILES})
|
add_executable(wavefront_parser ${SRC_FILES})
|
||||||
target_include_directories(wavefront_parser PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
|
||||||
|
|
||||||
find_package(GTest REQUIRED)
|
|
||||||
|
|
||||||
enable_testing()
|
|
||||||
|
|
||||||
add_executable(wavefront_tests
|
|
||||||
tests/unit/trim_test.cpp
|
|
||||||
tests/unit/settings_test.cpp
|
|
||||||
tests/unit/scan_test.cpp
|
|
||||||
tests/unit/parse_test.cpp
|
|
||||||
${APP_SOURCES}
|
|
||||||
)
|
|
||||||
target_link_libraries(wavefront_tests PRIVATE
|
|
||||||
GTest::gtest
|
|
||||||
GTest::gmock
|
|
||||||
GTest::gtest_main
|
|
||||||
)
|
|
||||||
target_include_directories(wavefront_tests PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
|
||||||
|
|
||||||
if(ENABLE_COVERAGE AND CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
|
|
||||||
target_compile_options(wavefront_tests PRIVATE --coverage -O0 -g)
|
|
||||||
target_link_options(wavefront_tests PRIVATE --coverage)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
include(GoogleTest)
|
|
||||||
gtest_discover_tests(wavefront_tests)
|
|
||||||
|
|
||||||
find_program(LCOV_EXECUTABLE lcov)
|
|
||||||
find_program(GENHTML_EXECUTABLE genhtml)
|
|
||||||
if(LCOV_EXECUTABLE AND GENHTML_EXECUTABLE)
|
|
||||||
add_custom_target(coverage
|
|
||||||
COMMAND ${LCOV_EXECUTABLE} --directory "${CMAKE_BINARY_DIR}" --capture --rc geninfo_unexecuted_blocks=1 --demangle-cpp --ignore-errors inconsistent --keep-going --output-file coverage.info
|
|
||||||
COMMAND ${LCOV_EXECUTABLE} --extract coverage.info "${CMAKE_SOURCE_DIR}/src/*" --output-file coverage.info
|
|
||||||
COMMAND ${GENHTML_EXECUTABLE} coverage.info --output-directory coverage
|
|
||||||
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}"
|
|
||||||
COMMENT "Generating lcov report at \"${CMAKE_BINARY_DIR}/coverage/index.html\""
|
|
||||||
VERBATIM
|
|
||||||
)
|
|
||||||
endif()
|
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
cmake -S . -B build/Coverage -DCMAKE_BUILD_TYPE=Debug -DENABLE_COVERAGE=ON
|
|
||||||
cmake --build build/Coverage --config Debug
|
|
||||||
ctest --test-dir build/Coverage --output-on-failure
|
|
||||||
cmake --build build/Coverage --target coverage
|
|
||||||
|
|
||||||
echo "Coverage report: file://$(pwd)/build/Coverage/coverage/index.html"
|
|
||||||
@@ -88,12 +88,12 @@ namespace wavefront {
|
|||||||
} else {
|
} else {
|
||||||
target.push_back(0);
|
target.push_back(0);
|
||||||
}
|
}
|
||||||
std::copy(
|
|
||||||
source.begin(),
|
|
||||||
source.end(),
|
|
||||||
std::back_inserter(target)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
std::copy(
|
||||||
|
source.begin(),
|
||||||
|
source.end(),
|
||||||
|
std::back_inserter(target)
|
||||||
|
);
|
||||||
},
|
},
|
||||||
std::index_sequence<1, 2, 3>{}
|
std::index_sequence<1, 2, 3>{}
|
||||||
);
|
);
|
||||||
@@ -227,28 +227,20 @@ namespace wavefront {
|
|||||||
std::array<IndexType, 3> triangle_data;
|
std::array<IndexType, 3> triangle_data;
|
||||||
std::size_t triangle_vertex_index = 0;
|
std::size_t triangle_vertex_index = 0;
|
||||||
for (const auto &vertex : triangle) {
|
for (const auto &vertex : triangle) {
|
||||||
if constexpr (VertexDimCount > 1) {
|
std::array<IndexType, VertexDimCount> coordinate_indices;
|
||||||
std::array<IndexType, VertexDimCount> coordinate_indices;
|
for (std::size_t i = 0; i < VertexDimCount; ++i) {
|
||||||
for (std::size_t i = 0; i < VertexDimCount; ++i) {
|
const auto &wavefront_index = mapper[i].get_vertex_index(vertex);
|
||||||
const auto &wavefront_index = mapper[i].get_vertex_index(vertex);
|
const auto &line_index = mapper[i].attribute_line_map.at(wavefront_index);
|
||||||
const auto &line_index = mapper[i].attribute_line_map.at(wavefront_index);
|
const auto &coordinate_index = mapper[i].line_coordinate_map.at(line_index);
|
||||||
const auto &coordinate_index = mapper[i].line_coordinate_map.at(line_index);
|
|
||||||
assert(coordinate_index != 0);
|
|
||||||
coordinate_indices[i] = coordinate_index;
|
|
||||||
}
|
|
||||||
auto iterator = std::lower_bound(vertex_data.begin(), vertex_data.end(), coordinate_indices);
|
|
||||||
assert(iterator != vertex_data.end());
|
|
||||||
auto index = std::distance(vertex_data.begin(), iterator);
|
|
||||||
assert(index != 0);
|
|
||||||
assert(triangle_vertex_index < 3);
|
|
||||||
triangle_data[triangle_vertex_index++] = index;
|
|
||||||
} else {
|
|
||||||
const auto &wavefront_index = mapper[0].get_vertex_index(vertex);
|
|
||||||
const auto &line_index = mapper[0].attribute_line_map.at(wavefront_index);
|
|
||||||
const auto &coordinate_index = mapper[0].line_coordinate_map.at(line_index);
|
|
||||||
assert(coordinate_index != 0);
|
assert(coordinate_index != 0);
|
||||||
triangle_data[triangle_vertex_index++] = coordinate_index;
|
coordinate_indices[i] = coordinate_index;
|
||||||
}
|
}
|
||||||
|
auto iterator = std::lower_bound(vertex_data.begin(), vertex_data.end(), coordinate_indices);
|
||||||
|
assert(iterator != vertex_data.end());
|
||||||
|
auto index = std::distance(vertex_data.begin(), iterator);
|
||||||
|
assert(index != 0);
|
||||||
|
assert(triangle_vertex_index < 3);
|
||||||
|
triangle_data[triangle_vertex_index++] = index;
|
||||||
}
|
}
|
||||||
assert(triangle_vertex_index == 3);
|
assert(triangle_vertex_index == 3);
|
||||||
triangle_data_set.emplace(triangle_data);
|
triangle_data_set.emplace(triangle_data);
|
||||||
|
|||||||
73
src/main.cpp
73
src/main.cpp
@@ -124,10 +124,10 @@ int main(int argc, char** argv) {
|
|||||||
using FloatType = float;
|
using FloatType = float;
|
||||||
using IndexType = std::uint32_t;
|
using IndexType = std::uint32_t;
|
||||||
|
|
||||||
auto scan_data = wavefront::scan(
|
CATCH_AND_RETURN(scan_data, wavefront::scan_error, 1, wavefront::scan(
|
||||||
settings.input(),
|
settings.input(),
|
||||||
settings.selected_objects(),
|
settings.selected_objects(),
|
||||||
settings.selected_groups()
|
settings.selected_groups())
|
||||||
);
|
);
|
||||||
|
|
||||||
std::cerr << "Scanned " << scan_data.total_lines << " lines" << std::endl;
|
std::cerr << "Scanned " << scan_data.total_lines << " lines" << std::endl;
|
||||||
@@ -177,10 +177,9 @@ int main(int argc, char** argv) {
|
|||||||
|
|
||||||
auto &output = settings.output();
|
auto &output = settings.output();
|
||||||
|
|
||||||
// 0:4: Signature
|
// 0:8
|
||||||
settings.output().write("\x7F" "3DG", 4);
|
settings.output().write("\x7F" "3DG", 4);
|
||||||
// 4:8: Version
|
wavefront::write_number<std::uint32_t>(output, 0);
|
||||||
wavefront::write_number<std::uint32_t>(output, 1);
|
|
||||||
|
|
||||||
std::uint32_t flags = 0;
|
std::uint32_t flags = 0;
|
||||||
|
|
||||||
@@ -199,27 +198,37 @@ int main(int argc, char** argv) {
|
|||||||
}
|
}
|
||||||
}, coordinate_data.texture_coordinates);
|
}, coordinate_data.texture_coordinates);
|
||||||
|
|
||||||
// 8:12: flags<0: float_size, 1: has_normals, 2&3: texcoord_ndims (0 = no),
|
|
||||||
wavefront::write_number<std::uint32_t>(output, flags);
|
|
||||||
|
|
||||||
// 12:16: number_list item count
|
|
||||||
wavefront::write_number<std::uint32_t>(output, number_list.size());
|
|
||||||
|
|
||||||
// 16:20: 2D storage item count
|
|
||||||
wavefront::write_number<std::uint32_t>(output, std::get<2>(coordinate_storage).size());
|
|
||||||
// 20:24: 3D storage item count
|
|
||||||
wavefront::write_number<std::uint32_t>(output, std::get<3>(coordinate_storage).size());
|
|
||||||
|
|
||||||
// 24:28: Vertex storage item count
|
|
||||||
std::visit([&](const auto &vertex_data) {
|
std::visit([&](const auto &vertex_data) {
|
||||||
using value_t = std::decay_t<decltype(vertex_data)>::value_type;
|
if constexpr (std::ranges::range<std::decay_t<decltype(vertex_data)>>) {
|
||||||
if constexpr (std::tuple_size_v<value_t> > 1) {
|
using mapped_type = typename std::decay_t<decltype(vertex_data)>::value_type;
|
||||||
wavefront::write_number<std::uint32_t>(output, vertex_data.size());
|
if constexpr (std::ranges::range<mapped_type>) {
|
||||||
} else {
|
flags |= std::tuple_size_v<mapped_type> << 4;
|
||||||
wavefront::write_number<std::uint32_t>(output, 0);
|
} else {
|
||||||
|
flags |= 1 << 4;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, index_storage.vertices);
|
}, index_storage.vertices);
|
||||||
|
|
||||||
|
// 8:12
|
||||||
|
wavefront::write_number<std::uint32_t>(output, flags);
|
||||||
|
|
||||||
|
// 12:16
|
||||||
|
wavefront::write_number<std::uint32_t>(output, number_list.size());
|
||||||
|
|
||||||
|
// 16:28
|
||||||
|
wavefront::write_number<std::uint32_t>(output, std::get<1>(coordinate_storage).size());
|
||||||
|
wavefront::write_number<std::uint32_t>(output, std::get<2>(coordinate_storage).size());
|
||||||
|
wavefront::write_number<std::uint32_t>(output, std::get<3>(coordinate_storage).size());
|
||||||
|
|
||||||
|
// 28:36
|
||||||
|
std::visit([&](const auto &vertex_data){
|
||||||
|
wavefront::write_number<std::uint32_t>(output, vertex_data.size());
|
||||||
|
}, index_storage.vertices);
|
||||||
wavefront::write_number<std::uint32_t>(output, index_storage.triangles.size());
|
wavefront::write_number<std::uint32_t>(output, index_storage.triangles.size());
|
||||||
|
|
||||||
|
// 36:48
|
||||||
|
wavefront::write_number<std::uint32_t>(output, 0);
|
||||||
|
wavefront::write_number<std::uint64_t>(output, 0);
|
||||||
|
|
||||||
output.write(reinterpret_cast<const char *>(number_list.data()), number_list.size() * sizeof(decltype(number_list)::value_type));
|
output.write(reinterpret_cast<const char *>(number_list.data()), number_list.size() * sizeof(decltype(number_list)::value_type));
|
||||||
|
|
||||||
@@ -230,6 +239,16 @@ int main(int argc, char** argv) {
|
|||||||
required_padding = (16 - (number_list.size() * sizeof(decltype(number_list)::value_type) % 16)) % 16;
|
required_padding = (16 - (number_list.size() * sizeof(decltype(number_list)::value_type) % 16)) % 16;
|
||||||
output.write(padding.data(), required_padding);
|
output.write(padding.data(), required_padding);
|
||||||
|
|
||||||
|
if (std::get<1>(coordinate_storage).size() > 0) {
|
||||||
|
const auto &value_list = std::get<1>(coordinate_storage);
|
||||||
|
using value_t = std::decay_t<decltype(value_list)>::value_type;
|
||||||
|
output.write(reinterpret_cast<const char *>(value_list.data()), value_list.size() * sizeof(value_t));
|
||||||
|
required_padding = (16 - (value_list.size() * sizeof(value_t) % 16)) % 16;
|
||||||
|
if (required_padding > 0) {
|
||||||
|
output.write(padding.data(), required_padding);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (std::get<2>(coordinate_storage).size() > 0) {
|
if (std::get<2>(coordinate_storage).size() > 0) {
|
||||||
const auto &value_list = std::get<2>(coordinate_storage);
|
const auto &value_list = std::get<2>(coordinate_storage);
|
||||||
using value_t = std::decay_t<decltype(value_list)>::value_type;
|
using value_t = std::decay_t<decltype(value_list)>::value_type;
|
||||||
@@ -252,12 +271,10 @@ int main(int argc, char** argv) {
|
|||||||
|
|
||||||
std::visit([&](const auto &vertex_data) {
|
std::visit([&](const auto &vertex_data) {
|
||||||
using value_t = std::decay_t<decltype(vertex_data)>::value_type;
|
using value_t = std::decay_t<decltype(vertex_data)>::value_type;
|
||||||
if constexpr (std::tuple_size_v<value_t> > 1) {
|
output.write(reinterpret_cast<const char *>(vertex_data.data()), vertex_data.size() * sizeof(value_t));
|
||||||
output.write(reinterpret_cast<const char *>(vertex_data.data()), vertex_data.size() * sizeof(value_t));
|
required_padding = (16 - (vertex_data.size() * sizeof(value_t) % 16)) % 16;
|
||||||
required_padding = (16 - (vertex_data.size() * sizeof(value_t) % 16)) % 16;
|
if (required_padding > 0) {
|
||||||
if (required_padding > 0) {
|
output.write(padding.data(), required_padding);
|
||||||
output.write(padding.data(), required_padding);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}, index_storage.vertices);
|
}, index_storage.vertices);
|
||||||
|
|
||||||
|
|||||||
308
src/parse.hpp
308
src/parse.hpp
@@ -55,15 +55,313 @@ 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__
|
||||||
323
src/parse.tpp
323
src/parse.tpp
@@ -1,323 +0,0 @@
|
|||||||
#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;
|
|
||||||
|
|
||||||
if (!scan_data.category_map.contains("v"s)) {
|
|
||||||
throw parse_error("Missing required \"v\" data");
|
|
||||||
}
|
|
||||||
|
|
||||||
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__
|
|
||||||
@@ -11,6 +11,8 @@
|
|||||||
#include "scan.hpp"
|
#include "scan.hpp"
|
||||||
|
|
||||||
namespace wavefront {
|
namespace wavefront {
|
||||||
|
scan_error::scan_error(const std::string &message) : std::runtime_error(message) {}
|
||||||
|
|
||||||
scan_result::scan_result() :
|
scan_result::scan_result() :
|
||||||
total_lines(0),
|
total_lines(0),
|
||||||
line_data(),
|
line_data(),
|
||||||
|
|||||||
@@ -10,6 +10,11 @@
|
|||||||
#include "settings.hpp"
|
#include "settings.hpp"
|
||||||
|
|
||||||
namespace wavefront {
|
namespace wavefront {
|
||||||
|
class scan_error : public std::runtime_error {
|
||||||
|
public:
|
||||||
|
explicit scan_error(const std::string &message);
|
||||||
|
};
|
||||||
|
|
||||||
struct scan_result {
|
struct scan_result {
|
||||||
std::size_t total_lines;
|
std::size_t total_lines;
|
||||||
std::map<std::size_t, std::string> line_data;
|
std::map<std::size_t, std::string> line_data;
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ namespace wavefront {
|
|||||||
auto start = source.data();
|
auto start = source.data();
|
||||||
// size is the size of buffer, data() + size() is the first available byte after the string.
|
// size is the size of buffer, data() + size() is the first available byte after the string.
|
||||||
auto end = source.data() + source.size() - 1;
|
auto end = source.data() + source.size() - 1;
|
||||||
while (start <= end && std::isspace(*start)) {
|
while (start < end && std::isspace(*start)) {
|
||||||
++start;
|
++start;
|
||||||
}
|
}
|
||||||
while (start <= end && (std::isspace(*end) || *end == 0)) {
|
while (start < end && (std::isspace(*end) || *end == 0)) {
|
||||||
--end;
|
--end;
|
||||||
}
|
}
|
||||||
return std::string_view(start, end - start + 1);
|
return std::string_view(start, end - start + 1);
|
||||||
|
|||||||
6
test.sh
6
test.sh
@@ -1,6 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
cmake -S . -B build/Debug -DCMAKE_BUILD_TYPE=Debug
|
|
||||||
cmake --build build/Debug --config Debug
|
|
||||||
ctest --test-dir build/Debug --output-on-failure
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
# Command
|
|
||||||
|
|
||||||
v 0.1 0.2 0.3
|
|
||||||
v 0.2 0.3 0.4
|
|
||||||
v 0.3 0.4 0.5
|
|
||||||
v 1.1 1.2 1.3
|
|
||||||
v 1.2 1.3 1.4
|
|
||||||
v 1.3 1.4 1.5
|
|
||||||
v 2.1 2.2 2.3
|
|
||||||
v 2.2 2.3 2.4
|
|
||||||
v 2.3 2.4 2.5
|
|
||||||
vn 0.15 0.25 0.35
|
|
||||||
vn 0.25 0.35 0.45
|
|
||||||
vn 0.35 0.45 0.55
|
|
||||||
vt 0.9 0.8
|
|
||||||
vt 0.8 0.7
|
|
||||||
f 1/1/1 2/2/2 3/3/2
|
|
||||||
o test
|
|
||||||
f 4/3/2 5/2/1 6/1/2
|
|
||||||
g test
|
|
||||||
f 7/2/2 8/3/2 9/1/1
|
|
||||||
o
|
|
||||||
f 8/1/1 5/2/2 3/3/2
|
|
||||||
g
|
|
||||||
f 2/1/1 6/2/2 4/3/2
|
|
||||||
h this line is skipped
|
|
||||||
@@ -1,315 +0,0 @@
|
|||||||
#include <string>
|
|
||||||
#include <variant>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include <gtest/gtest.h>
|
|
||||||
|
|
||||||
#include "parse.hpp"
|
|
||||||
#include "scan.hpp"
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
#define ASSERT_COORDINATE_3D(container, line, x, y, z) \
|
|
||||||
{ \
|
|
||||||
const auto &coord = (container).at(line); \
|
|
||||||
EXPECT_FLOAT_EQ(coord[0], (x)); \
|
|
||||||
EXPECT_FLOAT_EQ(coord[1], (y)); \
|
|
||||||
EXPECT_FLOAT_EQ(coord[2], (z)); \
|
|
||||||
}
|
|
||||||
|
|
||||||
#define ASSERT_COORDINATE_2D(container, line, x, y) \
|
|
||||||
{ \
|
|
||||||
const auto &coord = (container).at(line); \
|
|
||||||
EXPECT_FLOAT_EQ(coord[0], (x)); \
|
|
||||||
EXPECT_FLOAT_EQ(coord[1], (y)); \
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
ASSERT_COORDINATE_3D(coordinate_data.position_coordinates, 3u, 0.1f, 0.2f, 0.3f);
|
|
||||||
ASSERT_COORDINATE_3D(coordinate_data.position_coordinates, 4u, 0.2f, 0.3f, 0.4f);
|
|
||||||
ASSERT_COORDINATE_3D(coordinate_data.position_coordinates, 5u, 0.3f, 0.4f, 0.5f);
|
|
||||||
ASSERT_COORDINATE_3D(coordinate_data.position_coordinates, 6u, 1.1f, 1.2f, 1.3f);
|
|
||||||
ASSERT_COORDINATE_3D(coordinate_data.position_coordinates, 7u, 1.2f, 1.3f, 1.4f);
|
|
||||||
ASSERT_COORDINATE_3D(coordinate_data.position_coordinates, 8u, 1.3f, 1.4f, 1.5f);
|
|
||||||
ASSERT_COORDINATE_3D(coordinate_data.position_coordinates, 9u, 2.1f, 2.2f, 2.3f);
|
|
||||||
ASSERT_COORDINATE_3D(coordinate_data.position_coordinates, 10u, 2.2f, 2.3f, 2.4f);
|
|
||||||
ASSERT_COORDINATE_3D(coordinate_data.position_coordinates, 11u, 2.3f, 2.4f, 2.5f);
|
|
||||||
|
|
||||||
ASSERT_EQ(coordinate_data.normal_coordinates.size(), 3u);
|
|
||||||
ASSERT_COORDINATE_3D(coordinate_data.normal_coordinates, 12u, 0.15f, 0.25f, 0.35f);
|
|
||||||
ASSERT_COORDINATE_3D(coordinate_data.normal_coordinates, 13u, 0.25f, 0.35f, 0.45f);
|
|
||||||
ASSERT_COORDINATE_3D(coordinate_data.normal_coordinates, 14u, 0.35f, 0.45f, 0.55f);
|
|
||||||
|
|
||||||
ASSERT_EQ(coordinate_data.texture_coordinates.index(), 2u);
|
|
||||||
ASSERT_EQ(std::get<2>(coordinate_data.texture_coordinates).size(), 2u);
|
|
||||||
ASSERT_COORDINATE_2D(std::get<2>(coordinate_data.texture_coordinates), 15u, 0.9f, 0.8f);
|
|
||||||
ASSERT_COORDINATE_2D(std::get<2>(coordinate_data.texture_coordinates), 16u, 0.8f, 0.7f);
|
|
||||||
}
|
|
||||||
|
|
||||||
struct InvalidNumberCase {
|
|
||||||
std::string line_type;
|
|
||||||
std::size_t invalid_index;
|
|
||||||
std::string expected_message;
|
|
||||||
};
|
|
||||||
|
|
||||||
class ParseCoordinateDataInvalidNumberTest
|
|
||||||
: public ::testing::TestWithParam<InvalidNumberCase> {
|
|
||||||
};
|
|
||||||
|
|
||||||
static std::string build_line_with_invalid_number(
|
|
||||||
const std::string &line_type,
|
|
||||||
std::size_t invalid_index
|
|
||||||
) {
|
|
||||||
std::vector<std::string> values;
|
|
||||||
if (line_type == "vt") {
|
|
||||||
values = {"0.9", "0.8", "0.7"};
|
|
||||||
} else {
|
|
||||||
values = {"0.1", "0.2", "0.3"};
|
|
||||||
}
|
|
||||||
if (invalid_index < values.size()) {
|
|
||||||
values[invalid_index] = "x";
|
|
||||||
}
|
|
||||||
std::string line = line_type;
|
|
||||||
for (const auto &value : values) {
|
|
||||||
line += " ";
|
|
||||||
line += value;
|
|
||||||
}
|
|
||||||
return line;
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_P(ParseCoordinateDataInvalidNumberTest, ThrowsOnInvalidNumber) {
|
|
||||||
const auto ¶m = GetParam();
|
|
||||||
|
|
||||||
wavefront::scan_result scan_result;
|
|
||||||
scan_result.total_lines = 3;
|
|
||||||
scan_result.line_data = {
|
|
||||||
{1u, "v 0.1 0.2 0.3"},
|
|
||||||
{3u, build_line_with_invalid_number(param.line_type, param.invalid_index)}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (param.line_type == "v") {
|
|
||||||
scan_result.category_map = {
|
|
||||||
{"v", {0u, 1u, 3u}}
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
scan_result.category_map = {
|
|
||||||
{"v", {0u, 1u}},
|
|
||||||
{param.line_type, {0u, 3u}}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
wavefront::wavefront_face_data_result_t face_data;
|
|
||||||
if (param.line_type == "v") {
|
|
||||||
face_data.index_position_set = {2};
|
|
||||||
} else if (param.line_type == "vn") {
|
|
||||||
face_data.index_normal_set = {1};
|
|
||||||
} else {
|
|
||||||
face_data.index_texcoord_set = {1};
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
(void)wavefront::parse_coordinate_data<float>(scan_result, face_data);
|
|
||||||
FAIL() << "Expected parse_error";
|
|
||||||
} catch (const wavefront::parse_error &ex) {
|
|
||||||
EXPECT_EQ(std::string(ex.what()), param.expected_message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
INSTANTIATE_TEST_SUITE_P(
|
|
||||||
ParseCoordinateData,
|
|
||||||
ParseCoordinateDataInvalidNumberTest,
|
|
||||||
::testing::Values(
|
|
||||||
InvalidNumberCase{"v", 0u, "[3]: Unable to parse \"v\" floating point value"},
|
|
||||||
InvalidNumberCase{"v", 1u, "[3]: Unable to parse \"v\" floating point value"},
|
|
||||||
InvalidNumberCase{"v", 2u, "[3]: Unable to parse \"v\" floating point value"},
|
|
||||||
InvalidNumberCase{"vn", 0u, "[3]: Unable to parse \"vn\" floating point value"},
|
|
||||||
InvalidNumberCase{"vn", 1u, "[3]: Unable to parse \"vn\" floating point value"},
|
|
||||||
InvalidNumberCase{"vn", 2u, "[3]: Unable to parse \"vn\" floating point value"},
|
|
||||||
InvalidNumberCase{"vt", 0u, "[3]: Unable to parse \"vt\" floating point value"},
|
|
||||||
InvalidNumberCase{"vt", 1u, "[3]: Unable to parse \"vt\" floating point value"},
|
|
||||||
InvalidNumberCase{"vt", 2u, "[3]: Unable to parse \"vt\" floating point value"}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
TEST(ParseCoordinateData, ThrowsWhenMissingVertexData) {
|
|
||||||
wavefront::scan_result scan_result;
|
|
||||||
scan_result.total_lines = 3;
|
|
||||||
scan_result.line_data = {
|
|
||||||
{3u, "vn 0.1 0.2 0.3"}
|
|
||||||
};
|
|
||||||
scan_result.category_map = {
|
|
||||||
{"vn", {0u, 3u}}
|
|
||||||
};
|
|
||||||
|
|
||||||
wavefront::wavefront_face_data_result_t face_data;
|
|
||||||
face_data.index_position_set = {1};
|
|
||||||
|
|
||||||
try {
|
|
||||||
(void)wavefront::parse_coordinate_data<float>(scan_result, face_data);
|
|
||||||
FAIL() << "Expected parse_error";
|
|
||||||
} catch (const wavefront::parse_error &ex) {
|
|
||||||
EXPECT_EQ(std::string(ex.what()), "Missing required \"v\" data");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct UnderflowCase {
|
|
||||||
std::string line_type;
|
|
||||||
std::size_t number_count;
|
|
||||||
std::string expected_message;
|
|
||||||
};
|
|
||||||
|
|
||||||
class ParseCoordinateDataUnderflowTest
|
|
||||||
: public ::testing::TestWithParam<UnderflowCase> {
|
|
||||||
};
|
|
||||||
|
|
||||||
static std::string build_line_with_count(
|
|
||||||
const std::string &line_type,
|
|
||||||
std::size_t number_count
|
|
||||||
) {
|
|
||||||
std::vector<std::string> values;
|
|
||||||
if (line_type == "vt") {
|
|
||||||
values = {"0.9", "0.8", "0.7"};
|
|
||||||
} else {
|
|
||||||
values = {"0.1", "0.2", "0.3"};
|
|
||||||
}
|
|
||||||
std::string line = line_type;
|
|
||||||
for (std::size_t i = 0; i < number_count && i < values.size(); ++i) {
|
|
||||||
line += " ";
|
|
||||||
line += values[i];
|
|
||||||
}
|
|
||||||
return line;
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST_P(ParseCoordinateDataUnderflowTest, ThrowsOnInsufficientNumbers) {
|
|
||||||
const auto ¶m = GetParam();
|
|
||||||
|
|
||||||
wavefront::scan_result scan_result;
|
|
||||||
scan_result.total_lines = 3;
|
|
||||||
scan_result.line_data = {
|
|
||||||
{1u, "v 0.1 0.2 0.3"},
|
|
||||||
{3u, build_line_with_count(param.line_type, param.number_count)}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (param.line_type == "v") {
|
|
||||||
scan_result.category_map = {
|
|
||||||
{"v", {0u, 1u, 3u}}
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
scan_result.category_map = {
|
|
||||||
{"v", {0u, 1u}},
|
|
||||||
{param.line_type, {0u, 3u}}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
wavefront::wavefront_face_data_result_t face_data;
|
|
||||||
if (param.line_type == "v") {
|
|
||||||
face_data.index_position_set = {2};
|
|
||||||
} else if (param.line_type == "vn") {
|
|
||||||
face_data.index_normal_set = {1};
|
|
||||||
} else {
|
|
||||||
face_data.index_texcoord_set = {1};
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
(void)wavefront::parse_coordinate_data<float>(scan_result, face_data);
|
|
||||||
FAIL() << "Expected parse_error";
|
|
||||||
} catch (const wavefront::parse_error &ex) {
|
|
||||||
EXPECT_EQ(std::string(ex.what()), param.expected_message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
INSTANTIATE_TEST_SUITE_P(
|
|
||||||
ParseCoordinateData,
|
|
||||||
ParseCoordinateDataUnderflowTest,
|
|
||||||
::testing::Values(
|
|
||||||
UnderflowCase{"v", 0u, "[3]: Line \"v\" must contain at least 3 numbers"},
|
|
||||||
UnderflowCase{"v", 1u, "[3]: Line \"v\" must contain at least 3 numbers"},
|
|
||||||
UnderflowCase{"v", 2u, "[3]: Line \"v\" must contain at least 3 numbers"},
|
|
||||||
UnderflowCase{"vn", 0u, "[3]: Line \"vn\" must contain exactly 3 numbers"},
|
|
||||||
UnderflowCase{"vn", 1u, "[3]: Line \"vn\" must contain exactly 3 numbers"},
|
|
||||||
UnderflowCase{"vn", 2u, "[3]: Line \"vn\" must contain exactly 3 numbers"},
|
|
||||||
UnderflowCase{"vt", 0u, "[3]: Unable to parse \"vt\" line: expected minimum one number"}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
#include <filesystem>
|
|
||||||
#include <fstream>
|
|
||||||
|
|
||||||
#include <gtest/gtest.h>
|
|
||||||
|
|
||||||
#include "scan.hpp"
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
std::filesystem::path test_data_path(const std::string &filename) {
|
|
||||||
const auto here = std::filesystem::path(__FILE__).parent_path();
|
|
||||||
return here.parent_path() / "data" / filename;
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(Scan, ReadsAllSupportedTypes) {
|
|
||||||
std::ifstream input(test_data_path("test-scan-1.txt"));
|
|
||||||
ASSERT_TRUE(input.is_open());
|
|
||||||
|
|
||||||
const auto result = wavefront::scan(input);
|
|
||||||
|
|
||||||
EXPECT_EQ(result.total_lines, 26u);
|
|
||||||
EXPECT_EQ(result.line_data.size(), 21u);
|
|
||||||
|
|
||||||
EXPECT_TRUE(result.category_map.contains("v"));
|
|
||||||
EXPECT_TRUE(result.category_map.contains("vn"));
|
|
||||||
EXPECT_TRUE(result.category_map.contains("vt"));
|
|
||||||
EXPECT_TRUE(result.category_map.contains("f"));
|
|
||||||
|
|
||||||
EXPECT_EQ(result.category_map.at("v").size(), 10u);
|
|
||||||
EXPECT_EQ(result.category_map.at("vn").size(), 4u);
|
|
||||||
EXPECT_EQ(result.category_map.at("vt").size(), 3u);
|
|
||||||
EXPECT_EQ(result.category_map.at("f").size(), 5u);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(Scan, FiltersByObject) {
|
|
||||||
std::ifstream input(test_data_path("test-scan-1.txt"));
|
|
||||||
ASSERT_TRUE(input.is_open());
|
|
||||||
|
|
||||||
const auto result = wavefront::scan(input, {"test"});
|
|
||||||
|
|
||||||
EXPECT_EQ(result.category_map.at("f").size(), 4u);
|
|
||||||
EXPECT_EQ(result.category_map.at("f")[0], 19u);
|
|
||||||
EXPECT_EQ(result.category_map.at("f")[1], 21u);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(Scan, FiltersByGroup) {
|
|
||||||
std::ifstream input(test_data_path("test-scan-1.txt"));
|
|
||||||
ASSERT_TRUE(input.is_open());
|
|
||||||
|
|
||||||
const auto result = wavefront::scan(input, {}, {"test"});
|
|
||||||
|
|
||||||
EXPECT_EQ(result.category_map.at("f").size(), 3u);
|
|
||||||
EXPECT_EQ(result.category_map.at("f")[0], 21u);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(Scan, FiltersByObjectAndGroup) {
|
|
||||||
std::ifstream input(test_data_path("test-scan-1.txt"));
|
|
||||||
ASSERT_TRUE(input.is_open());
|
|
||||||
|
|
||||||
const auto result = wavefront::scan(input, {"test"}, {"test"});
|
|
||||||
|
|
||||||
EXPECT_EQ(result.category_map.at("f").size(), 3u);
|
|
||||||
EXPECT_EQ(result.category_map.at("f")[0], 21u);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,186 +0,0 @@
|
|||||||
#include <filesystem>
|
|
||||||
#include <fstream>
|
|
||||||
#include <random>
|
|
||||||
#include <sstream>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
#include <gtest/gtest.h>
|
|
||||||
|
|
||||||
#include "settings.hpp"
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
class ScopedFileCleanup {
|
|
||||||
public:
|
|
||||||
explicit ScopedFileCleanup(std::filesystem::path path) : path_(std::move(path)) {}
|
|
||||||
ScopedFileCleanup(const ScopedFileCleanup &) = delete;
|
|
||||||
ScopedFileCleanup &operator=(const ScopedFileCleanup &) = delete;
|
|
||||||
ScopedFileCleanup(ScopedFileCleanup &&) = delete;
|
|
||||||
ScopedFileCleanup &operator=(ScopedFileCleanup &&) = delete;
|
|
||||||
~ScopedFileCleanup() {
|
|
||||||
if (!path_.empty()) {
|
|
||||||
std::error_code error;
|
|
||||||
std::filesystem::remove(path_, error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
std::filesystem::path path_;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::string random_token(std::size_t length) {
|
|
||||||
static const char charset[] =
|
|
||||||
"0123456789"
|
|
||||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
|
||||||
"abcdefghijklmnopqrstuvwxyz";
|
|
||||||
std::string result;
|
|
||||||
result.reserve(length);
|
|
||||||
|
|
||||||
std::mt19937 engine{std::random_device{}()};
|
|
||||||
std::uniform_int_distribution<std::size_t> dist(0, sizeof(charset) - 2);
|
|
||||||
for (std::size_t i = 0; i < length; ++i) {
|
|
||||||
result.push_back(charset[dist(engine)]);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(SettingsBuilder, DefaultsToStdStreamsAndFlagsOff) {
|
|
||||||
wavefront::SettingsBuilder builder;
|
|
||||||
|
|
||||||
EXPECT_EQ(&builder.input(), &std::cin);
|
|
||||||
EXPECT_EQ(&builder.output(), &std::cout);
|
|
||||||
EXPECT_FALSE(builder.with_normals());
|
|
||||||
EXPECT_FALSE(builder.with_texcoords());
|
|
||||||
EXPECT_FALSE(builder.use_float64());
|
|
||||||
|
|
||||||
wavefront::Settings settings = builder.build();
|
|
||||||
|
|
||||||
EXPECT_EQ(&settings.input(), &std::cin);
|
|
||||||
EXPECT_EQ(&settings.output(), &std::cout);
|
|
||||||
EXPECT_FALSE(settings.extract_normals());
|
|
||||||
EXPECT_FALSE(settings.extract_texcoords());
|
|
||||||
EXPECT_FALSE(settings.use_float64());
|
|
||||||
EXPECT_TRUE(settings.selected_objects().empty());
|
|
||||||
EXPECT_TRUE(settings.selected_groups().empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(SettingsBuilder, AppliesConfigurationAndSelection) {
|
|
||||||
std::istringstream input("dummy");
|
|
||||||
std::ostringstream output;
|
|
||||||
|
|
||||||
wavefront::SettingsBuilder builder;
|
|
||||||
builder.input(input)
|
|
||||||
.output(output)
|
|
||||||
.with_normals(true)
|
|
||||||
.with_texcoords(true)
|
|
||||||
.use_float64(true);
|
|
||||||
builder.selected_objects().push_back("object_a");
|
|
||||||
builder.selected_groups().push_back("group_a");
|
|
||||||
|
|
||||||
EXPECT_EQ(&builder.input(), &input);
|
|
||||||
EXPECT_EQ(&builder.output(), &output);
|
|
||||||
EXPECT_TRUE(builder.with_normals());
|
|
||||||
EXPECT_TRUE(builder.with_texcoords());
|
|
||||||
EXPECT_TRUE(builder.use_float64());
|
|
||||||
|
|
||||||
wavefront::Settings settings = builder.build();
|
|
||||||
|
|
||||||
EXPECT_EQ(&settings.input(), &input);
|
|
||||||
EXPECT_EQ(&settings.output(), &output);
|
|
||||||
EXPECT_TRUE(settings.extract_normals());
|
|
||||||
EXPECT_TRUE(settings.extract_texcoords());
|
|
||||||
EXPECT_TRUE(settings.use_float64());
|
|
||||||
ASSERT_EQ(settings.selected_objects().size(), 1u);
|
|
||||||
EXPECT_EQ(settings.selected_objects()[0], "object_a");
|
|
||||||
ASSERT_EQ(settings.selected_groups().size(), 1u);
|
|
||||||
EXPECT_EQ(settings.selected_groups()[0], "group_a");
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(SettingsBuilder, CanOpenFileFromString) {
|
|
||||||
const auto temp_dir = std::filesystem::temp_directory_path();
|
|
||||||
const auto token = random_token(12);
|
|
||||||
const auto input_path = temp_dir / std::filesystem::path("dragiyski-wavefront-parser-test-settings-input-" + token + ".txt");
|
|
||||||
const auto output_path = temp_dir / std::filesystem::path("dragiyski-wavefront-parser-test-settings-output-" + token + ".txt");
|
|
||||||
const ScopedFileCleanup input_cleanup(input_path);
|
|
||||||
const ScopedFileCleanup output_cleanup(output_path);
|
|
||||||
|
|
||||||
{
|
|
||||||
std::ofstream seed(input_path);
|
|
||||||
seed << token;
|
|
||||||
}
|
|
||||||
|
|
||||||
wavefront::SettingsBuilder builder;
|
|
||||||
builder.input(input_path.string())
|
|
||||||
.output(output_path.string());
|
|
||||||
|
|
||||||
wavefront::Settings settings = builder.build();
|
|
||||||
|
|
||||||
EXPECT_NE(&settings.input(), &std::cin);
|
|
||||||
EXPECT_NE(&settings.output(), &std::cout);
|
|
||||||
|
|
||||||
auto *input_stream = dynamic_cast<std::ifstream *>(&settings.input());
|
|
||||||
ASSERT_NE(input_stream, nullptr);
|
|
||||||
EXPECT_TRUE(input_stream->is_open());
|
|
||||||
|
|
||||||
std::string input_contents;
|
|
||||||
*input_stream >> input_contents;
|
|
||||||
EXPECT_EQ(input_contents, token);
|
|
||||||
|
|
||||||
auto *output_stream = dynamic_cast<std::ofstream *>(&settings.output());
|
|
||||||
ASSERT_NE(output_stream, nullptr);
|
|
||||||
EXPECT_TRUE(output_stream->is_open());
|
|
||||||
|
|
||||||
*output_stream << token;
|
|
||||||
output_stream->flush();
|
|
||||||
|
|
||||||
std::ifstream verify(output_path);
|
|
||||||
std::string output_contents;
|
|
||||||
verify >> output_contents;
|
|
||||||
EXPECT_EQ(output_contents, token);
|
|
||||||
}
|
|
||||||
|
|
||||||
TEST(SettingsBuilder, CanOpenFileFromMovedStreams) {
|
|
||||||
const auto temp_dir = std::filesystem::temp_directory_path();
|
|
||||||
const auto token = random_token(12);
|
|
||||||
const auto input_path = temp_dir / std::filesystem::path("dragiyski-wavefront-parser-test-settings-input-move-" + token + ".txt");
|
|
||||||
const auto output_path = temp_dir / std::filesystem::path("dragiyski-wavefront-parser-test-settings-output-move-" + token + ".txt");
|
|
||||||
const ScopedFileCleanup input_cleanup(input_path);
|
|
||||||
const ScopedFileCleanup output_cleanup(output_path);
|
|
||||||
|
|
||||||
{
|
|
||||||
std::ofstream seed(input_path);
|
|
||||||
seed << token;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::ifstream input_stream(input_path);
|
|
||||||
std::ofstream output_stream(output_path);
|
|
||||||
|
|
||||||
wavefront::SettingsBuilder builder;
|
|
||||||
builder.input(std::move(input_stream))
|
|
||||||
.output(std::move(output_stream));
|
|
||||||
|
|
||||||
wavefront::Settings settings = builder.build();
|
|
||||||
|
|
||||||
EXPECT_NE(&settings.input(), &std::cin);
|
|
||||||
EXPECT_NE(&settings.output(), &std::cout);
|
|
||||||
|
|
||||||
auto *input_file = dynamic_cast<std::ifstream *>(&settings.input());
|
|
||||||
ASSERT_NE(input_file, nullptr);
|
|
||||||
EXPECT_TRUE(input_file->is_open());
|
|
||||||
|
|
||||||
std::string input_contents;
|
|
||||||
*input_file >> input_contents;
|
|
||||||
EXPECT_EQ(input_contents, token);
|
|
||||||
|
|
||||||
auto *output_file = dynamic_cast<std::ofstream *>(&settings.output());
|
|
||||||
ASSERT_NE(output_file, nullptr);
|
|
||||||
EXPECT_TRUE(output_file->is_open());
|
|
||||||
|
|
||||||
*output_file << token;
|
|
||||||
output_file->flush();
|
|
||||||
|
|
||||||
std::ifstream verify(output_path);
|
|
||||||
std::string output_contents;
|
|
||||||
verify >> output_contents;
|
|
||||||
EXPECT_EQ(output_contents, token);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
#include <string>
|
|
||||||
|
|
||||||
#include <gmock/gmock.h>
|
|
||||||
#include <gtest/gtest.h>
|
|
||||||
|
|
||||||
#include "trim.hpp"
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
using ::testing::StrEq;
|
|
||||||
|
|
||||||
struct TrimCase {
|
|
||||||
const char *source;
|
|
||||||
const char *expected;
|
|
||||||
};
|
|
||||||
|
|
||||||
class TrimTest : public ::testing::TestWithParam<TrimCase> {
|
|
||||||
};
|
|
||||||
|
|
||||||
TEST_P(TrimTest, ReturnsTrimmedString) {
|
|
||||||
const auto ¶m = GetParam();
|
|
||||||
std::string_view result = wavefront::trim(param.source);
|
|
||||||
EXPECT_THAT(std::string(result), StrEq(param.expected));
|
|
||||||
}
|
|
||||||
|
|
||||||
INSTANTIATE_TEST_SUITE_P(
|
|
||||||
TrimCases,
|
|
||||||
TrimTest,
|
|
||||||
::testing::Values(
|
|
||||||
TrimCase{" hello world ", "hello world"},
|
|
||||||
TrimCase{"\t\n spaced\t\n", "spaced"},
|
|
||||||
TrimCase{"trimmed", "trimmed"},
|
|
||||||
TrimCase{" ", ""},
|
|
||||||
TrimCase{"", ""}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user