// Copyright 2017-2023, Nicholas Sharp and the Polyscope contributors. https://polyscope.run #include "gtest/gtest.h" #include "glm/glm.hpp" #include #include #include #include #include #define POLYSCOPE_NO_STANDARDIZE_FALLTHROUGH #include "polyscope/standardize_data_array.h" // ============================================================ // =============== Array adaptor tests // ============================================================ // A few examples to test namespace { // std arrays std::vector arr_vecdouble{0.1, 0.2, 0.3, 0.4, 0.5}; std::vector arr_vecfloat{0.1, 0.2, 0.3, 0.4, 0.5}; std::vector arr_vecint{1, 2, 3, 4, 5}; std::array arr_arrdouble{0.1, 0.2, 0.3, 0.4, 0.5}; std::list arr_listdouble{0.1, 0.2, 0.3, 0.4, 0.5}; // == A custom array which needs all the adaptors struct UserArray { std::vector myData; size_t bigness() const { return myData.size(); } }; UserArray userArray_sizeFunc{{0.1, 0.2, 0.3, 0.4, 0.5}}; // Size function for custom array size_t adaptorF_custom_size(const UserArray& c) { return c.bigness(); } // == A type that we access with callable (paren) // (Eigen works this way, but don't want to depend on Eigen) struct UserArrayCallable { std::vector myData; size_t size() const { return myData.size(); } double operator()(size_t i) const { return myData[i]; } }; UserArrayCallable userArray_callableAccess{{0.1, 0.2, 0.3, 0.4, 0.5}}; // == A type that we access with callable (paren), and needs an int (rather than a size_t) struct UserArrayCallableInt { std::vector myData; size_t size() const { return myData.size(); } double operator()(int i) const { return myData[i]; } }; UserArrayCallableInt userArray_callableAccessInt{{0.1, 0.2, 0.3, 0.4, 0.5}}; // == A type that requires a custom access function struct UserArrayFuncAccess { std::vector myData; size_t size() const { return myData.size(); } }; UserArrayFuncAccess userArray_funcAccess{{0.1, 0.2, 0.3, 0.4, 0.5}}; std::vector adaptorF_custom_convertToStdVector(const UserArrayFuncAccess& c) { std::vector out; for (auto x : c.myData) { out.push_back(x); } return out; } // A vector type with x-y access struct UserVector2XY { double x; double y; }; UserVector2XY userVec2_xy{0.1, 0.2}; // A vector type with u-v access struct UserVector2UV { double u; double v; }; UserVector2UV userVec2_uv{0.1, 0.2}; // A vector2 type with crazy custom access struct UserVector2Custom { double foo; double bar; }; UserVector2Custom userVec2_custom{0.1, 0.2}; double adaptorF_custom_accessVector2Value(const UserVector2Custom& v, unsigned int ind) { if (ind == 0) return v.foo; if (ind == 1) return v.bar; throw std::logic_error("bad access"); return -1.; } // A vector type with x y z access struct UserVector3XYZ { double x; double y; double z; }; UserVector3XYZ userVec3_xyz{0.1, 0.2, 0.3}; // A vector3 type with crazy custom access struct UserVector3Custom { double foo; double bar; double baz; }; UserVector3Custom userVec3_custom{0.1, 0.2, 0.3}; double adaptorF_custom_accessVector3Value(const UserVector3Custom& v, unsigned int ind) { if (ind == 0) return v.foo; if (ind == 1) return v.bar; if (ind == 2) return v.baz; throw std::logic_error("bad access"); return -1.; } // A array-vector type with double-callable access struct UserArrayVectorCallable { std::vector> vals; size_t size() const { return vals.size(); } double operator()(int i, int j) const { return vals[i][j]; } }; UserArrayVectorCallable userArrayVector_doubleCallable{{{0.1, 0.2, 0.3}}}; // A array-vector type with custom access struct UserArrayVectorCustom { std::list vals; size_t size() const { return vals.size(); } }; std::vector> adaptorF_custom_convertArrayOfVectorToStdVector(const UserArrayVectorCustom& inputData) { std::vector> out; for (auto v : inputData.vals) { out.push_back({v.x, v.y, v.z}); } return out; } UserArrayVectorCustom userArrayVector_custom{{{0.1, 0.2, 0.3}}}; // A wannabe Eigen matrix struct FakeMatrix { std::vector> myData; long long int rows() const { return myData.size(); } long long int cols() const { return 3; } double operator()(int i, int j) const { return myData[i][j]; } }; FakeMatrix fakeMatrix_int{{{1, 2, 3}, {4, 5, 6}}}; // Nested list access with paren-vector struct UserArrayParenBracketCustom { std::vector> myData; size_t size() const { return myData.size(); } std::vector operator()(int i) const { return myData[i]; } }; UserArrayParenBracketCustom userArray_parentBracketCustom{{{1, 2, 3}, {4, 5, 6, 7}}}; // A nested list type with custom access struct UserNestedListCustom { std::list> vals; size_t size() const { return vals.size(); } }; std::tuple, std::vector> adaptorF_custom_convertNestedArrayToStdVector(const UserNestedListCustom& inputData) { std::tuple, std::vector> outTuple; std::vector& outData = std::get<0>(outTuple); std::vector& outStarts = std::get<1>(outTuple); outStarts.push_back(0); for (auto v : inputData.vals) { for (auto x : v) { outData.push_back(x); } outStarts.push_back(outData.size()); } return outTuple; } UserNestedListCustom userArray_nestedListCustom{{{1, 2, 3}, {4, 5, 6, 7}}}; } // namespace // Test that validateSize works when the type has a .size() member TEST(ArrayAdaptorTests, validateSize_MemberMethod) { polyscope::validateSize(arr_vecdouble, 5, "test"); polyscope::validateSize(arr_vecfloat, 5, "test"); polyscope::validateSize(arr_vecint, 5, "test"); polyscope::validateSize(arr_arrdouble, 5, "test"); polyscope::validateSize(arr_listdouble, 5, "test"); polyscope::validateSize(userArray_callableAccess, 5, "test"); } // Test that validateSize works with a custom overload TEST(ArrayAdaptorTests, validateSize_Custom) { polyscope::validateSize(userArray_sizeFunc, 5, "test"); } // Test that standardizeArray works with bracket access TEST(ArrayAdaptorTests, access_BracketOperator) { EXPECT_EQ(polyscope::standardizeArray(arr_vecdouble)[0], .1); EXPECT_NEAR(polyscope::standardizeArray(arr_vecfloat)[0], .1, 1e-5); EXPECT_NEAR(polyscope::standardizeArray(arr_vecint)[0], 1, 1e-5); EXPECT_EQ(polyscope::standardizeArray(arr_arrdouble)[0], .1); } // Test that standardizeArray works with callable (paren) access TEST(ArrayAdaptorTests, access_CallableOperator) { EXPECT_EQ(polyscope::standardizeArray(userArray_callableAccess)[0], .1); EXPECT_EQ(polyscope::standardizeArray(userArray_callableAccessInt)[0], .1); } // Test that standardizeArray works with iterable TEST(ArrayAdaptorTests, access_Iterable) { EXPECT_EQ(polyscope::standardizeArray(arr_listdouble)[0], .1); } // Test that standardizeArray works with ptr/size // (note this also tests type convesrion) TEST(ArrayAdaptorTests, access_ptr) { EXPECT_NEAR(polyscope::standardizeArray(std::make_tuple(&arr_vecfloat[0], arr_vecfloat.size()))[0], 0.1, 1e-5); } // Test that standardizeArray works with a custom accessor function TEST(ArrayAdaptorTests, access_FuncAccess) { EXPECT_EQ(polyscope::standardizeArray(userArray_funcAccess)[0], .1); // ensures the conversion code path works EXPECT_NEAR(polyscope::standardizeArray(userArray_funcAccess)[0], .1, 1e-5); } // Test that accessVector2 works. TEST(ArrayAdaptorTests, adaptor_vector2) { // Shouldn't compile // EXPECT_EQ((polyscope::adaptorF_accessVector2Value(std::array{0.1, 0.2})), 0.1); // bracket access EXPECT_EQ((polyscope::adaptorF_accessVector2Value(std::array{0.1, 0.2})), 0.1); EXPECT_EQ((polyscope::adaptorF_accessVector2Value(std::array{0.1, 0.2})), 0.2); // x-y access EXPECT_EQ((polyscope::adaptorF_accessVector2Value(userVec2_xy)), 0.1); EXPECT_EQ((polyscope::adaptorF_accessVector2Value(userVec2_xy)), 0.2); // u-v access EXPECT_EQ((polyscope::adaptorF_accessVector2Value(userVec2_uv)), 0.1); EXPECT_EQ((polyscope::adaptorF_accessVector2Value(userVec2_uv)), 0.2); // real() imag() access EXPECT_EQ((polyscope::adaptorF_accessVector2Value(std::complex{0.1, 0.2})), 0.1); EXPECT_EQ((polyscope::adaptorF_accessVector2Value(std::complex{0.1, 0.2})), 0.2); // custom function access EXPECT_EQ((polyscope::adaptorF_accessVector2Value(userVec2_custom)), 0.1); EXPECT_EQ((polyscope::adaptorF_accessVector2Value(userVec2_custom)), 0.2); } // Test that accessVector3 works. TEST(ArrayAdaptorTests, adaptor_vector3) { // Shouldn't compile // EXPECT_EQ((polyscope::adaptorF_accessVector3Value(std::array{0.1, 0.2, 0.3})), 0.1); // bracket access EXPECT_EQ((polyscope::adaptorF_accessVector3Value(std::array{0.1, 0.2, 0.3})), 0.1); EXPECT_EQ((polyscope::adaptorF_accessVector3Value(std::array{0.1, 0.2, 0.3})), 0.2); EXPECT_EQ((polyscope::adaptorF_accessVector3Value(std::array{0.1, 0.2, 0.3})), 0.3); // x-y access EXPECT_EQ((polyscope::adaptorF_accessVector3Value(userVec3_xyz)), 0.1); EXPECT_EQ((polyscope::adaptorF_accessVector3Value(userVec3_xyz)), 0.2); EXPECT_EQ((polyscope::adaptorF_accessVector3Value(userVec3_xyz)), 0.3); // custom function access EXPECT_EQ((polyscope::adaptorF_accessVector3Value(userVec3_custom)), 0.1); EXPECT_EQ((polyscope::adaptorF_accessVector3Value(userVec3_custom)), 0.2); EXPECT_EQ((polyscope::adaptorF_accessVector3Value(userVec3_custom)), 0.3); } // Test that access array of vectors works. TEST(ArrayAdaptorTests, adaptor_array_vectors) { // {ptr, size} access std::vector> data{{0.1, 0.2, 0.3}, {0.4, 0.5, 0.6}}; EXPECT_NEAR((polyscope::standardizeVectorArray(std::make_tuple(&data[0][0], 2))[1][1]), 0.5, 1e-5); // bracket-bracket access EXPECT_NEAR( (polyscope::standardizeVectorArray(std::vector>{{0.1, 0.2, 0.3}}))[0][0], 0.1, 1e-5); // double callable access EXPECT_NEAR((polyscope::standardizeVectorArray(userArrayVector_doubleCallable))[0][0], 0.1, 1e-5); // bracket-vector2 access (xy) EXPECT_NEAR((polyscope::standardizeVectorArray(std::vector{userVec2_xy}))[0][0], 0.1, 1e-5); // bracket-vector2 access (uv) EXPECT_NEAR((polyscope::standardizeVectorArray(std::vector{userVec2_uv}))[0][0], 0.1, 1e-5); // bracket-vector2 access (real/imag) EXPECT_NEAR((polyscope::standardizeVectorArray(std::vector{userVec2_uv}))[0][0], 0.1, 1e-5); // list bracket access std::list> arr_listdouble{{0.1, 0.2, 0.3}, {0.4, 0.5, 0.6}}; EXPECT_NEAR((polyscope::standardizeVectorArray(arr_listdouble))[0][0], 0.1, 1e-5); // bracket-vector3 access EXPECT_NEAR((polyscope::standardizeVectorArray(std::vector{userVec3_xyz}))[0][0], 0.1, 1e-5); EXPECT_NEAR((polyscope::standardizeVectorArray(std::vector{userVec3_xyz}))[0][2], 0.3, 1e-5); // custom function access EXPECT_NEAR((polyscope::standardizeVectorArray(userArrayVector_custom))[0][0], 0.1, 1e-5); EXPECT_NEAR((polyscope::standardizeVectorArray(userArrayVector_custom))[0][2], 0.3, 1e-5); // custom inner type (bracketed) std::vector userVec3sArr{userVec3_custom, userVec3_custom}; EXPECT_NEAR((polyscope::standardizeVectorArray(userVec3sArr))[0][0], 0.1, 1e-5); std::vector userVec2sArr{userVec2_custom, userVec2_custom}; EXPECT_NEAR((polyscope::standardizeVectorArray(userVec2sArr))[0][0], 0.1, 1e-5); // custom inner type (iterable) std::list userVec3sList{userVec3_custom, userVec3_custom}; EXPECT_NEAR((polyscope::standardizeVectorArray(userVec3sList))[0][0], 0.1, 1e-5); std::list userVec2sList{userVec2_custom, userVec2_custom}; EXPECT_NEAR((polyscope::standardizeVectorArray(userVec2sList))[0][0], 0.1, 1e-5); } // Test that nested access works TEST(ArrayAdaptorTests, adaptor_nested_array) { std::tuple, std::vector> nestedListTup; std::vector& dataEntries = std::get<0>(nestedListTup); std::vector& dataStarts = std::get<1>(nestedListTup); // Test {ptr, outer, inner} access std::vector> ptrData{{1, 2, 3}, {4, 5, 6}}; nestedListTup = polyscope::standardizeNestedList(std::make_tuple(&ptrData[0][0], 2, 3)); EXPECT_EQ(dataEntries[4], 5); EXPECT_EQ(dataStarts[1], 3); // Test matrix-style access nestedListTup = polyscope::standardizeNestedList(fakeMatrix_int); EXPECT_EQ(dataEntries[4], 5); EXPECT_EQ(dataStarts[1], 3); // Test bracket-bracket access std::vector> testVecBracket{{1, 2, 3}, {4, 5, 6}}; nestedListTup = polyscope::standardizeNestedList(testVecBracket); EXPECT_EQ(dataEntries[4], 5); EXPECT_EQ(dataStarts[1], 3); // Test paren-braket access nestedListTup = polyscope::standardizeNestedList(userArray_parentBracketCustom); EXPECT_EQ(dataEntries[6], 7); EXPECT_EQ(dataStarts[2], 7); // Test iterable-bracket access std::list> testVecList{{1, 2, 3}, {4, 5, 6, 7}}; nestedListTup = polyscope::standardizeNestedList(testVecList); EXPECT_EQ(dataEntries[6], 7); EXPECT_EQ(dataStarts[2], 7); // Test user-specified nestedListTup = polyscope::standardizeNestedList(userArray_nestedListCustom); EXPECT_EQ(dataEntries[6], 7); EXPECT_EQ(dataStarts[2], 7); }