diff --git a/.github/workflows/ax.yml b/.github/workflows/ax.yml index 83ac4500e7..be512fbc2c 100644 --- a/.github/workflows/ax.yml +++ b/.github/workflows/ax.yml @@ -76,6 +76,8 @@ jobs: - name: nanobind #if: contains(matrix.config.image, '2023') == false run: ./ci/install_nanobind.sh 2.0.0 + - name: install_gtest + run: ./ci/install_gtest.sh 1.15.2 - name: timestamp id: timestamp run: echo "timestamp=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3e7798e8fb..6d479e57a5 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -89,6 +89,8 @@ jobs: - name: glfw if: contains(matrix.config.image, '2023') == true run: ./ci/install_glfw.sh 3.3.10 + - name: install_gtest + run: ./ci/install_gtest.sh 1.15.2 - name: timestamp id: timestamp run: echo "timestamp=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 1fff84b892..617ae20a12 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -40,12 +40,9 @@ jobs: container: # @note We can't update this as epydoc doesn't support python3. We'll # need to re-write the python docs to use sphinx - image: aswf/ci-openvdb:2022 + image: aswf/ci-openvdb:2024 steps: - - name: Enable Node 16 - run: | - echo "ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION=true" >> $GITHUB_ENV - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: install_doxygen run: ./ci/install_doxygen.sh 1_8_11 - name: nanobind @@ -55,10 +52,6 @@ jobs: # run: pip install epydoc - name: install_latex run: | - # Fix error: Cannot prepare internal mirrorlist: No URLs in mirrorlist. CentOS 8 reached EOL means need to replace the official mirror to vault.centos.org - # Comment out mirrorlist and replace #baseurl=...mirror.centos.org with baseurl=...vault.centos.org in files starting with CentOS- in /etc/yum.repos.d folder - sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-* - sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-* yum -y install texlive-latex-bin texlive-dvips texlive-collection-fontsrecommended texlive-collection-latexrecommended - name: build run: > diff --git a/.github/workflows/houdini.yml b/.github/workflows/houdini.yml index 68348e3351..28efc703b8 100644 --- a/.github/workflows/houdini.yml +++ b/.github/workflows/houdini.yml @@ -93,6 +93,8 @@ jobs: run: ./ci/install_glfw.sh 3.3.10 - name: cppunit run: ./ci/install_cppunit.sh 1.15.1 + - name: install_gtest + run: ./ci/install_gtest.sh 1.15.2 - name: timestamp id: timestamp run: echo "timestamp=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT diff --git a/.github/workflows/nanovdb.yml b/.github/workflows/nanovdb.yml index 43b2c233d9..0efc4580a8 100644 --- a/.github/workflows/nanovdb.yml +++ b/.github/workflows/nanovdb.yml @@ -170,12 +170,12 @@ jobs: github.event.inputs.type == 'all' || github.event.inputs.type == 'linux' runs-on: ${{ (github.repository_owner == 'AcademySoftwareFoundation' && 'ubuntu-20.04-8c-32g-300h') || 'ubuntu-latest' }} + container: + image: aswf/ci-openvdb:2024 steps: - uses: actions/checkout@v3 - name: install_gtest - run: | - sudo apt-get update - sudo apt-get -q install -y libgtest-dev + run: ./ci/install_gtest.sh 1.15.2 - name: build_and_test run: | cd nanovdb/nanovdb diff --git a/ci/build.sh b/ci/build.sh index b3b318d093..110455c881 100755 --- a/ci/build.sh +++ b/ci/build.sh @@ -189,6 +189,7 @@ cmake \ -DOPENVDB_BUILD_VDB_PRINT=ON \ -DOPENVDB_BUILD_VDB_LOD=ON \ -DOPENVDB_BUILD_VDB_TOOL=ON \ + -DOPENVDB_BUILD_VDB_TOOL_UNITTESTS=ON \ -DOPENVDB_TOOL_USE_NANO=OFF \ -DOPENVDB_BUILD_PYTHON_UNITTESTS=ON \ -DMSVC_MP_THREAD_COUNT=${PARMS[-j]} \ diff --git a/ci/install_gtest.sh b/ci/install_gtest.sh new file mode 100755 index 0000000000..71d7fbd7bb --- /dev/null +++ b/ci/install_gtest.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +set -ex + +GTEST_VERSION="$1" + + +git clone https://github.com/google/googletest.git -b v${GTEST_VERSION} +cd googletest +mkdir build +cd build +cmake .. + +make -j$(nproc) + +sudo make install diff --git a/nanovdb/nanovdb/unittest/TestNanoVDB.cc b/nanovdb/nanovdb/unittest/TestNanoVDB.cc index 7eacd07915..33e120c063 100644 --- a/nanovdb/nanovdb/unittest/TestNanoVDB.cc +++ b/nanovdb/nanovdb/unittest/TestNanoVDB.cc @@ -4,6 +4,7 @@ // Uncomment to temporarily disable testing of PNanoVDB //#define DISABLE_PNANOVDB +#include #include #include #include // for std::stringstream diff --git a/openvdb_cmd/vdb_tool/CMakeLists.txt b/openvdb_cmd/vdb_tool/CMakeLists.txt index da1ade24ca..c0a2d64277 100644 --- a/openvdb_cmd/vdb_tool/CMakeLists.txt +++ b/openvdb_cmd/vdb_tool/CMakeLists.txt @@ -26,7 +26,7 @@ list(APPEND CMAKE_MODULE_PATH ${OPENVDB_CMAKE_PATH}) add_library(vdb_tool_common INTERFACE) # Optional components -option(BUILD_TEST "Build unit tests" OFF) +option(OPENVDB_BUILD_VDB_TOOL_UNITTESTS "Build unit tests" OFF) option(OPENVDB_TOOL_USE_NANO "Compile with NanoVDB support" OFF) option(OPENVDB_TOOL_NANO_USE_ZIP "Compile NanoVDB with zip compression support. Requires OPENVDB_TOOL_USE_NANO=ON to have effect" ON) @@ -185,10 +185,11 @@ install(TARGETS vdb_tool RUNTIME DESTINATION ${OPENVDB_INSTALL_BINDIR}) # unit test -if(BUILD_TEST) +if(OPENVDB_BUILD_VDB_TOOL_UNITTESTS) find_package(GTest ${MINIMUM_GOOGLETEST_VERSION} CONFIG REQUIRED) add_executable(vdb_tool_test src/unittest.cpp) target_include_directories(vdb_tool_test PRIVATE vdb_tool_common) target_link_libraries(vdb_tool_test PRIVATE vdb_tool_common GTest::gmock GTest::gtest GTest::gmock_main GTest::gtest_main) + add_test(vdb_tool_unit_test vdb_tool_test) endif() diff --git a/openvdb_cmd/vdb_tool/README.md b/openvdb_cmd/vdb_tool/README.md index f64ebb6bea..23bb29066f 100644 --- a/openvdb_cmd/vdb_tool/README.md +++ b/openvdb_cmd/vdb_tool/README.md @@ -10,8 +10,8 @@ This command-line tool, dubbed vdb_tool, can combine any number of the of high-l | **eval** | Evaluate an expression written in our Reverse Polish Notation (see below) | | **config** | Load a configuration file and add the actions for processing | | **default** | Set default values used by all subsequent actions | -| **read** | Read mesh, points and level sets as obj, ply, abc, stl, pts, vdb or nvdb files | -| **write** | Write a polygon mesh, points or level set as a obj, ply, stl, abc or vdb file | +| **read** | Read mesh, points and level sets as obj, ply, abc, stl, off, pts, vdb or nvdb files | +| **write** | Write a polygon mesh, points or level set as a obj, ply, stl, off, abc or vdb file | | **vdb2points** | Extracts points from a VDB grid | | **mesh2ls** | Convert a polygon mesh to a narrow-band level set | | **points2ls** | Convert points into a narrow-band level set | @@ -59,9 +59,10 @@ For support, bug-reports or ideas for improvements please contact ken.museth@gma | Extension | Actions | Description | |-------|-------|-------| | vdb | read and write | OpenVDB sparse volume files with float, Vec3f and points | -| obj | read and write | ASCII OBJ mesh files with triangle, quad or points | -| ply | read and write | Binary and ASCII PLY mesh files with triangle, quad or points | +| obj | read and write | ASCII OBJ mesh files with triangles, quads or points | +| ply | read and write | Binary and ASCII PLY mesh files with triangles, quads or points | | stl | read and write | Binary STL mesh files with triangles | +| off | read and write | ASCI OFF mesh files with triangles, quads or points | | pts | read | ASCII PTS points files with one or more point clouds | | abc | optional read and write | Alembic binary mesh files | | nvdb| optional read and write | NanoVDB file with voxels or points | @@ -73,7 +74,7 @@ For support, bug-reports or ideas for improvements please contact ken.museth@gma # Terminology -We introduce terms: **actions**, **options**, **expressions**, and **instructions**. Actions are high-level openvdb tools, which each have unique options, e.g. -mesh2ls geo=1 voxel=0.1, where "-mesh2ls" is an action with two options "geo" and "voxel". Expressions are strings of code with one or more low-level instructions in our stack-based programming language (see below). These expressions start with "{" and ends with "}", and ":" is used to separate values and instructions. E.g. {1:2:+} is an expression with two values (1 and 2) and one instruction "+", and it reduces to the string value "3". See section on the "Stack-based string expressions" below for more details. +We introduce the following terms: **actions**, **options**, **expressions**, and **instructions**. Actions are high-level openvdb tools, which each have unique options, e.g. -mesh2ls geo=1 voxel=0.1, where "-mesh2ls" is an action with two options "geo" and "voxel". Expressions are strings of code with one or more low-level instructions in our stack-based programming language (see below). These expressions start with "{" and ends with "}", and ":" is used to separate values and instructions. E.g. {1:2:+} is an expression with two values (1 and 2) and one instruction "+", and it reduces to the string value "3". See section on the "Stack-based string expressions" below for more details. Note that **actions** always start with one or more "-" and (except for file names) its associated **options** always contain a "=" and an optional number of leading characters used for identification, e.g. "-erode r=2" is identical to "-erode radius=2.0", but "-erode rr=2" will produce an error since "rr" does not match the first two characters of any option associated with the action "erode". @@ -88,7 +89,7 @@ This tool supports its own light-weight stack-oriented programming language that This tool is using CMake for build on Linux and Windows. The only mandatory dependency of is [OpenVDB](http://www.openvdb.org). Optional dependencies include NanoVDB, libpng, libjpeg, OpenEXR, and Alembic. To enable them use the `-DUSE_=ON` flags. See the CMakeLists.txt for details. -The included unit test are using Gtest. Add `-DBUILD_TEST=ON` to the cmake command line to build it. +The included unit test are using Gtest. Add `-DOPENVDB_BUILD_VDB_TOOL_UNITTESTS=ON` to the cmake command line to build it. ## Building OpenVDB @@ -102,7 +103,7 @@ To generate the makefile, navigate to the cloned directory of vdb_tool, then fol ```bash mkdir build cd build -cmake -DOPENVDB_CMAKE_PATH=/usr/local/lib/cmake/OpenVDB -DUSE_ALL=ON -DBUILD_TEST=ON .. +cmake -DOPENVDB_CMAKE_PATH=/usr/local/lib/cmake/OpenVDB -DUSE_ALL=ON -DOPENVDB_BUILD_VDB_TOOL_UNITTESTS=ON .. ``` Update the OpenVDB cmake path above as needed. diff --git a/openvdb_cmd/vdb_tool/include/Geometry.h b/openvdb_cmd/vdb_tool/include/Geometry.h index 2d6472dcde..ebd497b3bb 100644 --- a/openvdb_cmd/vdb_tool/include/Geometry.h +++ b/openvdb_cmd/vdb_tool/include/Geometry.h @@ -33,7 +33,7 @@ #ifdef VDB_TOOL_USE_NANO #include -#include +#include #endif #ifdef VDB_TOOL_USE_ABC @@ -66,6 +66,8 @@ OPENVDB_USE_VERSION_NAMESPACE namespace OPENVDB_VERSION_NAME { namespace vdb_tool { +#define MY_CLEAN_VERSION + /// @brief Class that encapsulates (explicit) geometry, i.e. vertices/points, /// triangles and quads. It is used to represent points and polygon meshes class Geometry @@ -104,17 +106,20 @@ class Geometry // Reads all the vertices in the file and treats them as Geometry void write(const std::string &fileName) const; void writeOBJ(const std::string &fileName) const; - void writePLY(const std::string &fileName) const; + void writeOFF(const std::string &fileName) const; + void writePLY(const std::string &fileName, bool binary = true) const; void writeSTL(const std::string &fileName) const; void writeGEO(const std::string &fileName) const; void writeABC(const std::string &fileName) const; void writeOBJ(std::ostream &os) const; - void writePLY(std::ostream &os) const; + void writeOFF(std::ostream &os) const; + void writePLY(std::ostream &os, bool binary = true) const; void writeSTL(std::ostream &os) const; void read(const std::string &fileName); void readOBJ(const std::string &fileName); + void readOFF(const std::string &fileName); void readPLY(const std::string &fileName); void readSTL(const std::string &fileName); void readPTS(const std::string &fileName); @@ -125,6 +130,7 @@ class Geometry void readNVDB(const std::string &fileName); void readOBJ(std::istream &is); + void readOFF(std::istream &is); void readPLY(std::istream &is); size_t vtxCount() const { return mVtx.size(); } @@ -229,7 +235,7 @@ const math::BBox& Geometry::bbox() const void Geometry::write(const std::string &fileName) const { - switch (findFileExt(fileName, {"geo", "obj", "ply", "stl", "abc"})) { + switch (findFileExt(fileName, {"geo", "obj", "ply", "stl", "abc", "off"})) { case 1: this->writeGEO(fileName); break; @@ -245,30 +251,33 @@ void Geometry::write(const std::string &fileName) const case 5: this->writeABC(fileName); break; + case 6: + this->writeOFF(fileName); + break; default: throw std::invalid_argument("Geometry file \"" + fileName + "\" has an invalid extension"); } }// Geometry::write -void Geometry::writePLY(const std::string &fileName) const +void Geometry::writePLY(const std::string &fileName, bool binary) const { if (fileName == "stdout.ply") { //if (isatty(fileno(stdout))) throw std::invalid_argument("writePLY: stdout is not connected to the terminal!"); - this->writePLY(std::cout); + this->writePLY(std::cout, binary); } else { std::ofstream outfile(fileName, std::ios_base::binary); if (!outfile.is_open()) throw std::invalid_argument("Error writing to ply file \""+fileName+"\""); - this->writePLY(outfile);; + this->writePLY(outfile, binary); } }// Geometry::writePLY -void Geometry::writePLY(std::ostream &os) const +void Geometry::writePLY(std::ostream &os, bool binary) const { - os << "ply\n"; - if (isLittleEndian()) { - os << "format binary_little_endian 1.0\n"; + os << "ply\nformat "; + if (binary) { + os << "binary_" << (isLittleEndian() ? "little" : "big") << "_endian 1.0\n"; } else { - os << "format binary_big_endian 1.0\n"; + os << "ascii 1.0\n"; } os << "comment created by vdb_tool" << std::endl; os << "element vertex " << mVtx.size() << std::endl; @@ -279,32 +288,26 @@ void Geometry::writePLY(std::ostream &os) const os << "property list uchar int vertex_index\n"; os << "end_header\n"; static_assert(sizeof(Vec3s) == 3 * sizeof(float), "Unexpected sizeof(Vec3s)"); - os.write((const char *)mVtx.data(), mVtx.size() * 3 * sizeof(float)); - if (mTri.size()>0) { - const size_t size = sizeof(char) + 3*sizeof(uint32_t); - char *buffer = static_cast(std::malloc(mTri.size()*size)), *p = buffer;// uninitialized - if (buffer==nullptr) throw std::invalid_argument("Geometry::writePLY: failed to allocate buffer"); - static_assert(sizeof(Vec3I) == 3 * sizeof(uint32_t), "Unexpected sizeof(Vec3I)"); - for (const Vec3I *t = mTri.data(), *e = t + mTri.size(); t!=e; ++t) { - *p = 3; - std::memcpy(p + 1, t, 3*sizeof(uint32_t)); - p += size; - } - os.write(buffer, mTri.size()*size); - std::free(buffer); - } - if (mQuad.size()>0) { - const size_t size = sizeof(char) + 4*sizeof(uint32_t); - char *buffer = static_cast(std::malloc(mQuad.size()*size)), *p = buffer;// uninitialized - if (buffer==nullptr) throw std::invalid_argument("Geometry::writePLY: failed to allocate buffer"); - static_assert(sizeof(Vec4I) == 4 * sizeof(uint32_t), "Unexpected sizeof(Vec4I)"); - for (const Vec4I *q = mQuad.data(), *e = q + mQuad.size(); q!=e; ++q) { - *p = 4; - std::memcpy(p + 1, q, 4*sizeof(uint32_t)); - p += size; - } - os.write(buffer, mQuad.size()*size); - std::free(buffer); + if (binary) { + os.write((const char *)mVtx.data(), mVtx.size() * 3 * sizeof(float));// write x,y,z vertex coordinates + auto writeFaces = [](std::ostream &os, const uint32_t *faces, size_t count, uint8_t n) { + if (count==0) return; + const int size = 1 + 4*n; + char *buffer = (char*)std::malloc(count*size), *p = buffer;// uninitialized + if (buffer==nullptr) throw std::invalid_argument("Geometry::writePLY: failed to allocate buffer"); + for (const uint32_t *f = faces, *e = f + n*count; f!=e; f+=n, p += size) { + *p = (char)n; + std::memcpy(p + 1, f, 4*n); + } + os.write(buffer, count*size); + std::free(buffer); + }; + writeFaces(os, (const uint32_t*)mTri.data(), mTri.size(), 3); + writeFaces(os, (const uint32_t*)mQuad.data(), mQuad.size(), 4); + } else {// ascii + for (auto &v : mVtx) os << v[0] << " " << v[1] << " " << v[2] << "\n"; + for (auto &t : mTri) os << "3 " << t[0] << " " << t[1] << " " << t[2] << "\n"; + for (auto &q : mQuad) os << "4 " << q[0] << " " << q[1] << " " << q[2] << " " << q[3] << "\n"; } }// Geometry::writePLY @@ -316,24 +319,38 @@ void Geometry::writeOBJ(const std::string &fileName) const } else { std::ofstream outfile(fileName); if (!outfile.is_open()) throw std::invalid_argument("Error writing to obj file \""+fileName+"\""); - this->writeOBJ(outfile);; + this->writeOBJ(outfile); } }// Geometry::writeOBJ void Geometry::writeOBJ(std::ostream &os) const { - os << "# Created by vdb_tool\n"; - for (auto &v : mVtx) { - os << "v " << v[0] << " " << v[1] << " " << v[2] << "\n"; - } - for (auto &t : mTri) { - os << "f " << t[0]+1 << " " << t[1]+1 << " " << t[2]+1 << "\n";// obj is 1-based - } - for (auto &q : mQuad) { - os << "f " << q[0]+1 << " " << q[1]+1 << " " << q[2]+1 << " " << q[3]+1 << "\n";// obj is 1-based - } + os << "# obj file created by vdb_tool\n"; + for (auto &v : mVtx) os << "v " << v[0] << " " << v[1] << " " << v[2] << "\n"; + for (auto &t : mTri) os << "f " << t[0]+1 << " " << t[1]+1 << " " << t[2]+1 << "\n";// obj is 1-based + for (auto &q : mQuad) os << "f " << q[0]+1 << " " << q[1]+1 << " " << q[2]+1 << " " << q[3]+1 << "\n";// obj is 1-based }// Geometry::writeOBJ +void Geometry::writeOFF(const std::string &fileName) const +{ + if (fileName=="stdout.off") { + this->writeOFF(std::cout); + } else { + std::ofstream outfile(fileName); + if (!outfile.is_open()) throw std::invalid_argument("Error writing to off file \""+fileName+"\""); + this->writeOFF(outfile); + } +}// Geometry::writeOFF + +void Geometry::writeOFF(std::ostream &os) const +{ + os << "OFF\n# Created by vdb_tool\n"; + os << mVtx.size() << " " << (mTri.size() + mQuad.size()) << " " << 0 << "\n"; + for (auto &v : mVtx) os << v[0] << " " << v[1] << " " << v[2] << "\n"; + for (auto &t : mTri) os << "3 " << t[0] << " " << t[1] << " " << t[2] << "\n"; + for (auto &q : mQuad) os << "4 " << q[0] << " " << q[1] << " " << q[2] << " " << q[3] << "\n"; +}// Geometry::writeOFF + void Geometry::writeSTL(const std::string &fileName) const { if (fileName == "stdout.stl") { @@ -342,7 +359,7 @@ void Geometry::writeSTL(const std::string &fileName) const } else { std::ofstream outfile(fileName, std::ios::out | std::ios_base::binary); if (!outfile.is_open()) throw std::invalid_argument("Error writing to stl file \""+fileName+"\""); - this->writeSTL(outfile);; + this->writeSTL(outfile); } }// Geometry::writeSTL @@ -381,7 +398,7 @@ void Geometry::writeGEO(const std::string &fileName) const void Geometry::read(const std::string &fileName) { - switch (findFileExt(fileName, {"obj", "ply", "pts", "stl", "abc", "vdb", "nvdb", "geo"})) { + switch (findFileExt(fileName, {"obj", "ply", "pts", "stl", "abc", "vdb", "nvdb", "geo", "off"})) { case 1: this->readOBJ(fileName); break; @@ -406,6 +423,9 @@ void Geometry::read(const std::string &fileName) case 8: this->readGEO(fileName); break; + case 9: + this->readOFF(fileName); + break; default: #if VDB_TOOL_USE_PDAL pdal::StageFactory factory; @@ -519,6 +539,63 @@ void Geometry::readPDAL(const std::string &fileName) mBBox = BBoxT(); //invalidate BBox }// Geometry::readPDAL +void Geometry::readOFF(const std::string &fileName) +{ + if (fileName == "stdin.off") { + this->readOFF(std::cin); + } else { + std::ifstream infile(fileName); + if (!infile.is_open()) throw std::invalid_argument("Error opening Geometry file \""+fileName+"\""); + this->readOFF(infile); + } +}// Geometry::readOFF + +void Geometry::readOFF(std::istream &is) +{ + // read header + std::string line; + if (!std::getline(is, line) || line != "OFF") { + throw std::invalid_argument("Geometry::readOFF: expected header \"OFF\" but read \"" + line + "\""); + } + + // read vertex and face counts + size_t vtxCount=0, faceCount=0, edgeCount=0, nGon=0; + while (vtxCount == 0 && std::getline(is, line)) { + if (line.empty() || line[0] == '#') continue; + std::istringstream iss(line); + iss >> vtxCount >> faceCount >> edgeCount; + } + + // read vertices + Vec3f p; + vtxCount += mVtx.size(); + while (mVtx.size() < vtxCount && std::getline(is, line)) { + if (line.empty() || line[0] == '#') continue; + std::istringstream iss(line); + iss >> p[0] >> p[1] >> p[2]; + mVtx.push_back(p); + } + + // read faces + int f[4]; + faceCount += mTri.size() + mQuad.size(); + while (mTri.size() + mQuad.size() < faceCount && std::getline(is, line)) { + if (line.empty() || line[0] == '#') continue; + std::istringstream iss(line); + iss >> nGon; + if (nGon == 3) { + iss >> f[0] >> f[1] >> f[2]; + mTri.emplace_back(f[0],f[1],f[2]); + } else if (nGon == 4) { + iss >> f[0] >> f[1] >> f[2] >> f[3]; + mQuad.emplace_back(f[0],f[1],f[2],f[3]); + } else { + throw std::invalid_argument("Geometry::readOFF: " + std::to_string(nGon) + "-gons are not supported"); + } + } + mBBox = BBoxT();//invalidate BBox +}// Geometry::readOFF + void Geometry::readPLY(const std::string &fileName) { if (fileName == "stdin.ply") { @@ -545,9 +622,7 @@ void Geometry::readPLY(std::istream &is) auto tokens = tokenize_line(); auto test = [&tokens](int i, std::vector str) { if (i >= static_cast(tokens.size())) return false; - for (auto &s : str) { - if (tokens[i] == s) return true; - } + for (auto &s : str) if (tokens[i] == s) return true; return false; }; auto error = [&tokens](const std::string &msg){ @@ -556,8 +631,19 @@ void Geometry::readPLY(std::istream &is) std::cerr << "\"\n"; throw std::invalid_argument(msg); }; + auto sizeOf = [test, error](int i){ + if ( test(i, {"float", "float32", "int", "int32"}) ) return 4; + if ( test(i, {"double", "float64"}) ) return 8; + if ( test(i, {"int16", "uint16"}) ) return 2; + if ( test(i, {"uchar", "int8"}) ) return 1; + error("vdb_tool::readPLY: unsupported type"); + return 0; + }; + // check header if (!test(0, {"ply"})) error("vdb_tool::readPLY: not a ply file"); + + // check file format int format = -1;// 0 is ascii, 1 is little endian and 2 is big endian tokens = tokenize_line(); if (!(test(0, {"format"}) && test(2, {"1.0"})) ) { @@ -573,18 +659,20 @@ void Geometry::readPLY(std::istream &is) } const bool reverseBytes = format && format != (isLittleEndian() ? 1 : 2); // header: https://www.mathworks.com/help/vision/ug/the-ply-format.html - size_t vtxCount = 0, polyCount = 0; - struct Skip {int count, bytes;} vtx_skip[2]={{0,0},{0,0}}, ply_skip[2]={{0,0},{0,0}}; + size_t vtxCount = 0, faceCount = 0; + int vtxStride=0, vtxProps=0;// byte size of all vtx properties, number of vertex properties + struct Triplet {int offset, id, size;} xyz[3];// byte offset, id#, byte size + struct Skip {int count, bytes;} faceSkip[2]={{0,0},{0,0}};// head, {faces}, tail + + // parse header with vertex, face and property information tokens = tokenize_line(); bool run = true; while(run) { if ( test(0, {"element"}) ) { if ( test(1, {"vertex"}) ) { vtxCount = std::stoll(tokens[2]); - int n = 0; const std::string axis[3] = {"x", "y", "z"}; while(true) { - const int m = n>0 ? 1 : 0; tokens = tokenize_line(); if ( test(0, {"end_header"}) ) { run = false; @@ -592,35 +680,16 @@ void Geometry::readPLY(std::istream &is) } else if ( test(0, {"element"}) ) { break; } else if ( test(0, {"property"}) ) { - if ( test(1, {"float", "float32"}) ) { - if ( test(2, {"x", "y", "z"}) ) {// nx,ny.nz - if (n>2 || !test(2, {axis[n++]}) ) error("vdb_tool::readPLY: expected x or y or z"); - } else {// e.g. nx, ny, nz, intensity, s, t etc - if (n!=0 && n!=3) error("vdb_tool::readPLY: vertex float property interlaced with coordinates"); - vtx_skip[m].count += 1; - vtx_skip[m].bytes += static_cast(sizeof(float)); - } - } else if ( test(1, {"int16", "uint16"}) ) {// e.g. material_index etc - if (n!=0 && n!=3) error("vdb_tool::readPLY: vertex int16 property interlaced with coordinates is not supported"); - vtx_skip[m].count += 1; - vtx_skip[m].bytes += static_cast(sizeof(int16_t)); - } else if ( test(1, {"int", "int32"}) ) {// e.g. material_index etc - if (n!=0 && n!=3) error("vdb_tool::readPLY: vertex int32 property interlaced with coordinates is not supported"); - vtx_skip[m].count += 1; - vtx_skip[m].bytes += static_cast(sizeof(int32_t)); - } else if ( test(1, {"uchar", "int8"}) ) {// eg red, green, blue, alpha - if (n!=0 && n!=3) error("vdb_tool::readPLY: vertex int8 property interlaced with coordinates is not supported"); - vtx_skip[m].count += 1; - vtx_skip[m].bytes += static_cast(sizeof(unsigned char)); - } else { - error("vdb_tool::readPLY: invalid vertex property"); - } + Triplet t{vtxStride, vtxProps++, sizeOf(1)}; + for (int i=0; i<3; ++i) if (test(2, {axis[i]})) xyz[i] = t; + vtxStride += t.size; } } - if (n!=3) error("vdb_tool::readPLY: missing vertex coordinates"); + for (int i=0; i<3; ++i) if (xyz[i].size!=4 && xyz[i].size!=8) error("vdb_tool::readPLY: missing "+axis[i]+ + " vertex coordinates or unsupported size "+std::to_string(xyz[i].size)); } else if ( test(1, {"face"}) ) { - polyCount = std::stoll(tokens[2]); - int n = 0; + faceCount = std::stoll(tokens[2]); + int n = 0;// 0 is head and 1 is tail while (true) { tokens = tokenize_line(); if ( test(0, {"end_header"}) ) { @@ -628,15 +697,15 @@ void Geometry::readPLY(std::istream &is) break; } else if (test(0, {"element"}) ) { break; - } else if (test(0, {"property"}) ) { - if (test(1, {"list"}) && - test(2, {"uchar", "uint8"}) && - test(3, {"int", "uint", "int32"}) && + } else if (test(0, {"property"}) ) {// eg: "property list uchar int vertex_indices" + if (test(1, {"list"}) &&// list of vertex ID belonging to a polygon + test(2, {"uchar", "uint8"}) &&// size of polygon, e.g. 3 or 4 + test(3, {"int", "uint", "int32"}) &&// type of vertex id test(4, {"vertex_index", "vertex_indices"}) ) { - n = 1; - } else if ( test(1, {"uchar", "uint8"}) ) { - ply_skip[n].count += 1; - ply_skip[n].bytes += 1; + n = 1;// change from head to tail + } else if ( test(1, {"uchar", "uint8"}) ) {// eg: "property uchar intensity" + faceSkip[n].count += 1; + faceSkip[n].bytes += 1; } else { error("vdb_tool::readPLY: invalid face properties"); } @@ -655,7 +724,7 @@ void Geometry::readPLY(std::istream &is) } else { error("vdb_tool::readPLY: invalid element"); } - } else if ( test(0, {"comment", "obj_info"}) ) { + } else if ( test(0, {"comment", "obj_info"}) ) {// eq: "obj_info 3D colored patch boundaries" and "comment author: Paraform" tokens = tokenize_line(); } else { error("vdb_tool::readPLY: unexpected entry in header"); @@ -665,96 +734,69 @@ void Geometry::readPLY(std::istream &is) // read vertex coordinates mVtx.resize(vtxCount); if (format) {// binary - if (vtx_skip[0].count == 0 && vtx_skip[1].count == 0) {//faster + if (xyz[0].offset==0 && xyz[1].offset==4 && xyz[2].offset==8 && vtxStride==12) {// most common case is.read((char *)(mVtx.data()), vtxCount * 3 * sizeof(float)); + if (reverseBytes) for (Vec3f &v : mVtx) swapBytes(&v[0], 3); } else { - const size_t bSize = vtx_skip[0].bytes + 3*sizeof(float) + vtx_skip[1].bytes; - char *buffer = static_cast(std::malloc(vtxCount*bSize));// uninitialized + char *buffer = static_cast(std::malloc(vtxCount*vtxStride)), *p = buffer;// uninitialized if (buffer==nullptr) throw std::invalid_argument("Geometry::readPLY: failed to allocate buffer"); - is.read(buffer, vtxCount*bSize); - for (size_t i=0; i(buffer + i*bSize + vtx_skip[0].bytes); - mVtx[i] = Vec3f(p); + is.read(buffer, vtxCount*vtxStride); + for (Vec3f &vtx : mVtx) { + for (int i=0; i<3; ++i) { + if (xyz[i].size == 4) { + float v = *(float*)(p + xyz[i].offset); + vtx[i] = reverseBytes ? swapBytes(v) : v; + } else { + double v = *(double*)(p + xyz[i].offset); + vtx[i] = float(reverseBytes ? swapBytes(v) : v); + } + } + p += vtxStride; } std::free(buffer); } - if (reverseBytes) { - auto flipBytes = [](float v)->float{ - float tmp; - char *p = (char*)&v, *q = (char*)&tmp; - q[0] = p[3]; - q[1] = p[2]; - q[2] = p[1]; - q[3] = p[0]; - return tmp; - };// flipBytes in float - for (size_t i = 0; i < mVtx.size(); ++i) { - auto &p = mVtx[i]; - p[0] = flipBytes(p[0]); - p[1] = flipBytes(p[1]); - p[2] = flipBytes(p[2]); - } - } - } else {// ascii + + } else {// ascii vertices for (auto &v : mVtx) { tokens = tokenize_line(); - if (static_cast(tokens.size()) != vtx_skip[0].count + 3 + vtx_skip[1].count) { - error("vdb_tool::readPLY: error reading ascii vertex coordinates"); - } - for (int i = 0; i<3; ++i) { - v[i] = std::stof(tokens[i + vtx_skip[0].count]); - } + if (int(tokens.size()) != vtxProps) error("vdb_tool::readPLY: error reading ascii vertex coordinates"); + for (int i = 0; i<3; ++i) v[i] = std::stof(tokens[xyz[0].id]); }// loop over vertices } // read polygon vertex lists uint32_t vtx[4]; if (format) {// binary - auto flipBytes = [&](int n){ - uint32_t tmp; - char *q = (char*)&tmp; - for (int i=0; i(std::malloc(ply_skip[0].bytes + 1));// uninitialized + char *buffer = static_cast(std::malloc(faceSkip[0].bytes + 1));// uninitialized if (buffer==nullptr) throw std::invalid_argument("Geometry::readPLY: failed to allocate buffer"); - for (size_t i=0; i unsigned int switch (n) { case 3: - is.read((char *)(&vtx), 3*sizeof(uint32_t)); - if (reverseBytes) flipBytes(3); + is.read((char*)vtx, 3*sizeof(uint32_t)); + if (reverseBytes) swapBytes(vtx, 3); mTri.emplace_back(vtx); break; case 4: - is.read((char *)(&vtx), 4*sizeof(uint32_t)); - if (reverseBytes) flipBytes(4); + is.read((char*)vtx, 4*sizeof(uint32_t)); + if (reverseBytes) swapBytes(vtx, 4); mQuad.emplace_back(vtx); break; default: throw std::invalid_argument("Geometry::readPLY: binary " + std::to_string(n) + "-gons are not supported"); break; } - is.ignore(ply_skip[1].bytes); + is.ignore(faceSkip[1].bytes); }// loop over polygons std::free(buffer); - } else {// ascii format - for (size_t i=0; i(std::stoll(tokens[i + 1 + ply_skip[0].count])); - } + const std::string polySize = tokens[faceSkip[0].count]; + const int n = std::stoi(polySize); + if (n!=3 && n!=4) throw std::invalid_argument("Geometry::readPLY: ascii " + polySize + "-gons are not supported"); + for (int i = 0, j=1+faceSkip[0].count; i(std::stoll(tokens[j])); if (n==3) { mTri.emplace_back(vtx); } else { diff --git a/openvdb_cmd/vdb_tool/include/Tool.h b/openvdb_cmd/vdb_tool/include/Tool.h index 5e4cae6899..b24e71696e 100644 --- a/openvdb_cmd/vdb_tool/include/Tool.h +++ b/openvdb_cmd/vdb_tool/include/Tool.h @@ -53,9 +53,9 @@ #ifdef VDB_TOOL_USE_NANO #include -#include -#include -#include +#include +#include +#include #endif #ifdef VDB_TOOL_USE_EXR @@ -369,14 +369,14 @@ void Tool::init() mParser.addAction( "read", "i", "Read one or more geometry or VDB files from disk or STDIN.", - {{"files", "", "{file|stdin}.{abc|obj|ply|stl|vdb}", "list of files or the input stream, e.g. file.vdb,stdin.vdb. Note that \"files=\" is optional since any argument without \"=\" is intrepreted as a file and appended to \"files\""}, + {{"files", "", "{file|stdin}.{abc|obj|ply|stl|off|vdb}", "list of files or the input stream, e.g. file.vdb,stdin.vdb. Note that \"files=\" is optional since any argument without \"=\" is intrepreted as a file and appended to \"files\""}, {"grids", "*", "*|grid_name,...", "list of VDB grids name to be imported (defaults to \"*\", i.e. import all available grids)"}, {"delayed", "true", "1|0|true|false", "toggle delayed loading of VDB grids (enabled by default). This option is ignored by other file types"}}, [](){}, [&](){this->read();}, 0);// anonymous options are treated as to the first option,i.e. "files" mParser.addAction( "write", "o", "Write list of geometry, VDB or config files to disk or STDOUT", - {{"files", "", "{file|stdout}.{obj|ply|stl|vdb|nvdb}", "list of files or the output stream, e.g. file.vdb or stdin.vdb. Note that \"files=\" is optional since any argument without the \"=\" character is intrepreted as a file and appended to \"files\"."}, + {{"files", "", "{file|stdout}.{obj|ply|stl|off|vdb|nvdb}", "list of files or the output stream, e.g. file.vdb or stdin.vdb. Note that \"files=\" is optional since any argument without the \"=\" character is intrepreted as a file and appended to \"files\"."}, {"geo", "0", "0|1...", "geometry to write (defaults to \"0\" which is the latest)."}, {"vdb", "*", "0,1,...", "list of VDB grids to write (defaults to \"*\", i.e. all available grids)."}, {"keep", "", "1|0|true|false", "toggle wether to preserved or deleted geometry and grids after they have been written."}, @@ -925,8 +925,8 @@ std::string Tool::examples() const { const int w = 16; std::stringstream ss; - ss << std::left << std::setw(w) << "Surface points:" << mCmdName << " -read points.[obj/ply/stl/pts] -points2ls d=256 r=2.0 w=3 -dilate r=2 -gauss i=1 w=1 -erode r=2 -ls2m a=0.25 -write output.[ply/obj/stl]\n"; - ss << std::left << std::setw(w) << "Convert mesh: " << mCmdName << " -read mesh.[ply/obj] -mesh2ls d=256 -write output.vdb config.txt\n"; + ss << std::left << std::setw(w) << "Surface points:" << mCmdName << " -read points.[obj/ply/stl/off/pts] -points2ls d=256 r=2.0 w=3 -dilate r=2 -gauss i=1 w=1 -erode r=2 -ls2m a=0.25 -write output.[ply/obj/stl]\n"; + ss << std::left << std::setw(w) << "Convert mesh: " << mCmdName << " -read mesh.[ply/obj/off] -mesh2ls d=256 -write output.vdb config.txt\n"; ss << std::left << std::setw(w) << "Config example:" << mCmdName << " -config config.txt\n"; return ss.str(); } @@ -963,7 +963,7 @@ void Tool::read() { OPENVDB_ASSERT(mParser.getAction().name == "read"); for (auto &fileName : mParser.getVec("files")) { - switch (findFileExt(fileName, {"geo,obj,ply,abc,pts,stl", "vdb", "nvdb"})) { + switch (findFileExt(fileName, {"geo,obj,ply,abc,pts,off,stl", "vdb", "nvdb"})) { case 1: this->readGeo(fileName); break; @@ -1062,7 +1062,7 @@ void Tool::readNVDB(const std::string &fileName) const size_t count = mGrid.size(); if (grids.size()) { for (const auto& gridHandle : grids) { - if (gridNames[0]=="*" || findMatch(gridHandle.gridMetaData()->shortGridName(), gridNames)) mGrid.push_back(nanovdb::nanoToOpenVDB(gridHandle)); + if (gridNames[0]=="*" || findMatch(gridHandle.gridMetaData()->shortGridName(), gridNames)) mGrid.push_back(nanovdb::tools::nanoToOpenVDB(gridHandle)); } } else if (mParser.verbose>0) { std::cerr << "readVDB: no vdb grids in \"" << fileName << "\""; @@ -1135,7 +1135,7 @@ void Tool::write() { OPENVDB_ASSERT(mParser.getAction().name == "write"); for (std::string &fileName : mParser.getVec("files")) { - switch (findFileExt(fileName, {"geo,obj,ply,stl,abc", "vdb", "nvdb", "txt"})) { + switch (findFileExt(fileName, {"geo,obj,ply,stl,off,abc", "vdb", "nvdb", "txt"})) { case 1: this->writeGeo(fileName); break; @@ -1262,26 +1262,26 @@ void Tool::writeNVDB(const std::string &fileName) throw std::invalid_argument("writeNVDB: unsupported bits \""+bits+"\""); } - nanovdb::StatsMode sMode = nanovdb::StatsMode::Default; + nanovdb::tools::StatsMode sMode = nanovdb::tools::StatsMode::Default; if (stats == "none") { - sMode = nanovdb::StatsMode::Disable; + sMode = nanovdb::tools::StatsMode::Disable; } else if (stats == "bbox") { - sMode = nanovdb::StatsMode::BBox; + sMode = nanovdb::tools::StatsMode::BBox; } else if (stats == "extrema") { - sMode = nanovdb::StatsMode::MinMax; + sMode = nanovdb::tools::StatsMode::MinMax; } else if (stats == "all") { - sMode = nanovdb::StatsMode::All; + sMode = nanovdb::tools::StatsMode::All; } else if (stats != "") { throw std::invalid_argument("writeNVDB: unsupported stats \""+stats+"\""); } - nanovdb::ChecksumMode cMode = nanovdb::ChecksumMode::Default; + nanovdb::CheckMode cMode = nanovdb::CheckMode::Default; if (checksum == "none") { - cMode = nanovdb::ChecksumMode::Disable; + cMode = nanovdb::CheckMode::Disable; } else if (checksum == "partial") { - cMode = nanovdb::ChecksumMode::Partial; + cMode = nanovdb::CheckMode::Partial; } else if (checksum == "full") { - cMode = nanovdb::ChecksumMode::Full; + cMode = nanovdb::CheckMode::Full; } else if (checksum != "") { throw std::invalid_argument("writeNVDB: unsupported checksum \""+checksum+"\""); } @@ -1302,21 +1302,21 @@ void Tool::writeNVDB(const std::string &fileName) using SrcGridT = openvdb::FloatGrid; switch (qMode){ case nanovdb::GridType::Fp4: - return nanovdb::createNanoGrid(*floatGrid, sMode, cMode, dither, verbose); + return nanovdb::tools::createNanoGrid(*floatGrid, sMode, cMode, dither, verbose); case nanovdb::GridType::Fp8: - return nanovdb::createNanoGrid(*floatGrid, sMode, cMode, dither, verbose); + return nanovdb::tools::createNanoGrid(*floatGrid, sMode, cMode, dither, verbose); case nanovdb::GridType::Fp16: - return nanovdb::createNanoGrid(*floatGrid, sMode, cMode, dither, verbose); + return nanovdb::tools::createNanoGrid(*floatGrid, sMode, cMode, dither, verbose); case nanovdb::GridType::FpN: if (absolute) { - return nanovdb::createNanoGrid(*floatGrid, sMode, cMode, dither, verbose, nanovdb::AbsDiff(tolerance)); + return nanovdb::tools::createNanoGrid(*floatGrid, sMode, cMode, dither, verbose, nanovdb::tools::AbsDiff(tolerance)); } else { - return nanovdb::createNanoGrid(*floatGrid, sMode, cMode, dither, verbose, nanovdb::RelDiff(tolerance)); + return nanovdb::tools::createNanoGrid(*floatGrid, sMode, cMode, dither, verbose, nanovdb::tools::RelDiff(tolerance)); } default: break;// 32 bit float grids are handled below }// end of switch } - return nanovdb::openToNanoVDB(base, sMode, cMode, verbose);// float and other grids + return nanovdb::tools::openToNanoVDB(base, sMode, cMode, verbose);// float and other grids };// openToNano if (fileName=="stdout.nvdb") { diff --git a/openvdb_cmd/vdb_tool/include/Util.h b/openvdb_cmd/vdb_tool/include/Util.h index 96c324711c..9d084fb22e 100644 --- a/openvdb_cmd/vdb_tool/include/Util.h +++ b/openvdb_cmd/vdb_tool/include/Util.h @@ -377,6 +377,31 @@ inline bool isLittleEndian() return (*(char *)&tmp == 1); } +/// @brief invert endianess of a type +/// @tparam T Template type to be inverted +/// @param val value to be inverted +/// @return value with reverse bytes +template +inline T swapBytes(T val) +{ + T tmp; + for (char *src=(char*)&val, *dst=(char*)(&tmp)+sizeof(T)-1, *end=src+sizeof(T);src!=end; *dst-- = *src++); + return tmp; +} + +/// @brief invert endianess of an array of values of a specific type +/// @tparam T Template type to be inverted +/// @param val pointer to array with values to be inverted +/// @param n number of elements in the array +template +inline void swapBytes(T *val, int n) +{ + for (T tmp, *last = val + n; val < last; ++val) { + for (char *src=(char*)val, *dst=(char*)(&tmp)+sizeof(T)-1, *end=src+sizeof(T); src!=end; *dst-- = *src++); + *val = tmp; + } +} + /// @brief return a pseudo random uuid string. /// /// @details this function approximates a uuid version 4, variant 1 as detailed diff --git a/openvdb_cmd/vdb_tool/src/unittest.cpp b/openvdb_cmd/vdb_tool/src/unittest.cpp index 71fc4c9ade..0ddd6fb2cf 100644 --- a/openvdb_cmd/vdb_tool/src/unittest.cpp +++ b/openvdb_cmd/vdb_tool/src/unittest.cpp @@ -301,6 +301,52 @@ TEST_F(Test_vdb_tool, Util) } EXPECT_EQ(size, tmp.size()); } + + {//swapBytes + const int i = 4, j = openvdb::vdb_tool::swapBytes(i); + EXPECT_NE(i, j); + EXPECT_EQ(i, openvdb::vdb_tool::swapBytes(j)); + + const float a = 4, b = openvdb::vdb_tool::swapBytes(a); + EXPECT_NE(a, b); + EXPECT_EQ(a, openvdb::vdb_tool::swapBytes(b)); + + const double x = 4, y = openvdb::vdb_tool::swapBytes(x); + EXPECT_NE(x, y); + EXPECT_EQ(x, openvdb::vdb_tool::swapBytes(y)); + + int vec_i[3]={3,4,5}, vec_j[3]; + for (int n=0; n<3; ++n) { + vec_j[n] = openvdb::vdb_tool::swapBytes(vec_i[n]); + EXPECT_NE(vec_i[n], vec_j[n]); + } + openvdb::vdb_tool::swapBytes(vec_j, 3); + for (int n=0; n<3; ++n) EXPECT_EQ(vec_i[n], vec_j[n]); + + float vec_a[3]={3,4,5}, vec_b[3]; + for (int n=0; n<3; ++n) { + vec_b[n] = openvdb::vdb_tool::swapBytes(vec_a[n]); + EXPECT_NE(vec_a[n], vec_b[n]); + } + openvdb::vdb_tool::swapBytes(vec_b, 3); + for (int n=0; n<3; ++n) EXPECT_EQ(vec_a[n], vec_b[n]); + + double vec_x[3]={3,4,5}, vec_y[3]; + for (int n=0; n<3; ++n) { + vec_y[n] = openvdb::vdb_tool::swapBytes(vec_x[n]); + EXPECT_NE(vec_x[n], vec_y[n]); + } + openvdb::vdb_tool::swapBytes(vec_y, 3); + for (int n=0; n<3; ++n) EXPECT_EQ(vec_x[n], vec_y[n]); + } + {// weird pointer behaviour + float vec[4], *p = vec; + EXPECT_EQ(vec, p);// of course + EXPECT_EQ((char*)(vec), (char*)p);// sure + EXPECT_EQ((char*)(&vec), (char*)p);// wait, what?! + EXPECT_NE((char*)(vec), (char*)(&p));// yep + EXPECT_NE((char*)(&p), (char*)p);// of course + } }// Util TEST_F(Test_vdb_tool, getArgs) @@ -371,18 +417,18 @@ TEST_F(Test_vdb_tool, Geometry) EXPECT_EQ(4, geo2.vtxCount()); EXPECT_EQ(2, geo2.triCount()); EXPECT_EQ(1, geo2.quadCount()); - EXPECT_EQ(openvdb::Vec3f(1,2,3), geo.bbox().min()); - EXPECT_EQ(openvdb::Vec3f(10,11,12), geo.bbox().max()); + EXPECT_EQ(openvdb::Vec3f(1,2,3), geo2.bbox().min()); + EXPECT_EQ(openvdb::Vec3f(10,11,12), geo2.bbox().max()); - EXPECT_EQ(openvdb::Vec3f(1,2,3), geo.vtx()[0]); - EXPECT_EQ(openvdb::Vec3f(4,5,6), geo.vtx()[1]); - EXPECT_EQ(openvdb::Vec3f(7,8,9), geo.vtx()[2]); - EXPECT_EQ(openvdb::Vec3f(10,11,12), geo.vtx()[3]); + EXPECT_EQ(openvdb::Vec3f(1,2,3), geo2.vtx()[0]); + EXPECT_EQ(openvdb::Vec3f(4,5,6), geo2.vtx()[1]); + EXPECT_EQ(openvdb::Vec3f(7,8,9), geo2.vtx()[2]); + EXPECT_EQ(openvdb::Vec3f(10,11,12), geo2.vtx()[3]); - EXPECT_EQ(openvdb::Vec3I(0,1,2), geo.tri()[0]); - EXPECT_EQ(openvdb::Vec3I(1,2,3), geo.tri()[1]); + EXPECT_EQ(openvdb::Vec3I(0,1,2), geo2.tri()[0]); + EXPECT_EQ(openvdb::Vec3I(1,2,3), geo2.tri()[1]); - EXPECT_EQ(openvdb::Vec4I(0,1,2,3), geo.quad()[0]); + EXPECT_EQ(openvdb::Vec4I(0,1,2,3), geo2.quad()[0]); } {// write to file std::ofstream os("data/test.geo", std::ios_base::binary); @@ -395,18 +441,38 @@ TEST_F(Test_vdb_tool, Geometry) EXPECT_EQ(4, geo2.vtxCount()); EXPECT_EQ(2, geo2.triCount()); EXPECT_EQ(1, geo2.quadCount()); - EXPECT_EQ(openvdb::Vec3f(1,2,3), geo.bbox().min()); - EXPECT_EQ(openvdb::Vec3f(10,11,12), geo.bbox().max()); + EXPECT_EQ(openvdb::Vec3f(1,2,3), geo2.bbox().min()); + EXPECT_EQ(openvdb::Vec3f(10,11,12), geo2.bbox().max()); - EXPECT_EQ(openvdb::Vec3f(1,2,3), geo.vtx()[0]); - EXPECT_EQ(openvdb::Vec3f(4,5,6), geo.vtx()[1]); - EXPECT_EQ(openvdb::Vec3f(7,8,9), geo.vtx()[2]); - EXPECT_EQ(openvdb::Vec3f(10,11,12), geo.vtx()[3]); + EXPECT_EQ(openvdb::Vec3f(1,2,3), geo2.vtx()[0]); + EXPECT_EQ(openvdb::Vec3f(4,5,6), geo2.vtx()[1]); + EXPECT_EQ(openvdb::Vec3f(7,8,9), geo2.vtx()[2]); + EXPECT_EQ(openvdb::Vec3f(10,11,12), geo2.vtx()[3]); - EXPECT_EQ(openvdb::Vec3I(0,1,2), geo.tri()[0]); - EXPECT_EQ(openvdb::Vec3I(1,2,3), geo.tri()[1]); + EXPECT_EQ(openvdb::Vec3I(0,1,2), geo2.tri()[0]); + EXPECT_EQ(openvdb::Vec3I(1,2,3), geo2.tri()[1]); - EXPECT_EQ(openvdb::Vec4I(0,1,2,3), geo.quad()[0]); + EXPECT_EQ(openvdb::Vec4I(0,1,2,3), geo2.quad()[0]); + } + {// test readOFF and writeOFF + geo.write("data/test.off"); + openvdb::vdb_tool::Geometry geo2; + geo2.read("data/test.off"); + EXPECT_EQ(4, geo2.vtxCount()); + EXPECT_EQ(2, geo2.triCount()); + EXPECT_EQ(1, geo2.quadCount()); + EXPECT_EQ(openvdb::Vec3f(1,2,3), geo2.bbox().min()); + EXPECT_EQ(openvdb::Vec3f(10,11,12), geo2.bbox().max()); + + EXPECT_EQ(openvdb::Vec3f(1,2,3), geo2.vtx()[0]); + EXPECT_EQ(openvdb::Vec3f(4,5,6), geo2.vtx()[1]); + EXPECT_EQ(openvdb::Vec3f(7,8,9), geo2.vtx()[2]); + EXPECT_EQ(openvdb::Vec3f(10,11,12), geo2.vtx()[3]); + + EXPECT_EQ(openvdb::Vec3I(0,1,2), geo2.tri()[0]); + EXPECT_EQ(openvdb::Vec3I(1,2,3), geo2.tri()[1]); + + EXPECT_EQ(openvdb::Vec4I(0,1,2,3), geo2.quad()[0]); } #ifdef VDB_TOOL_USE_PDAL {// read from PDAL-supported ASCII format file @@ -425,11 +491,10 @@ TEST_F(Test_vdb_tool, Geometry) geo2.read("data/test.txt"); EXPECT_EQ(4, geo2.vtxCount()); - EXPECT_EQ(openvdb::Vec3f(1,2,3), geo.vtx()[0]); - EXPECT_EQ(openvdb::Vec3f(4,5,6), geo.vtx()[1]); - EXPECT_EQ(openvdb::Vec3f(7,8,9), geo.vtx()[2]); - EXPECT_EQ(openvdb::Vec3f(10,11,12), geo.vtx()[3]); - + EXPECT_EQ(openvdb::Vec3f(1,2,3), geo2.vtx()[0]); + EXPECT_EQ(openvdb::Vec3f(4,5,6), geo2.vtx()[1]); + EXPECT_EQ(openvdb::Vec3f(7,8,9), geo2.vtx()[2]); + EXPECT_EQ(openvdb::Vec3f(10,11,12), geo2.vtx()[3]); } #endif }// Geometry @@ -801,7 +866,7 @@ TEST_F(Test_vdb_tool, ToolParser) float beta = 0.0f, beta_sum = 0.0f; std::string path, base, ext; - Parser p({{"alpha", "64"}, {"beta", "4.56"}}); + Parser p({{"alpha", "64", "", ""}, {"beta", "4.56", "", ""}}); p.addAction("process_a", "a", "docs", {{"alpha", "", "", ""},{"beta", "", "", ""}}, [&](){p.setDefaults();}, @@ -824,7 +889,7 @@ TEST_F(Test_vdb_tool, ToolParser) p.finalize(); auto args = getArgs("vdb_tool -quiet -process_a alpha=128 -for v=0.1,0.4,0.1 -b alpha={$#v:++} beta={$v} -end"); - p.parse(args.size(), args.data()); + p.parse(int(args.size()), args.data()); EXPECT_EQ(0, alpha); EXPECT_EQ(0.0f, beta); EXPECT_EQ(0, alpha_sum); @@ -836,7 +901,7 @@ TEST_F(Test_vdb_tool, ToolParser) EXPECT_EQ(0.1f + 0.2f + 0.3f, beta_sum);// derived from loop args = getArgs("vdb_tool -quiet -each file=path1/base1.ext1,path2/base2.ext2 -c alpha={$file:path} beta={$file:name} gamma={$file:ext} -end"); - p.parse(args.size(), args.data()); + p.parse(int(args.size()), args.data()); p.run(); EXPECT_EQ(path, "path1,path2"); EXPECT_EQ(base, "base1,base2"); @@ -856,7 +921,7 @@ TEST_F(Test_vdb_tool, ToolBasic) EXPECT_NO_THROW({ auto args = getArgs("vdb_tool -quiet -sphere r=1.1 -ls2mesh -write data/sphere.ply data/config.txt"); - Tool vdb_tool(args.size(), args.data()); + Tool vdb_tool(int(args.size()), args.data()); vdb_tool.run(); }); @@ -877,7 +942,7 @@ TEST_F(Test_vdb_tool, Counter) EXPECT_NO_THROW({ auto args = getArgs("vdb_tool -quiet -eval {1:@G} -sphere r=1.1 -ls2mesh -write data/sphere_{$G}.ply data/config_{$G:++}.txt"); - Tool vdb_tool(args.size(), args.data()); + Tool vdb_tool(int(args.size()), args.data()); vdb_tool.run(); }); @@ -900,7 +965,7 @@ TEST_F(Test_vdb_tool, ToolForLoop) // test single for-loop EXPECT_NO_THROW({ auto args = getArgs("vdb_tool -quiet -for i=0,3 -sphere r=1.{$i} dim=128 name=sphere_{$i} -ls2mesh -write data/sphere_{$#i:++}.ply -end"); - Tool vdb_tool(args.size(), args.data()); + Tool vdb_tool(int(args.size()), args.data()); vdb_tool.run(); }); @@ -909,7 +974,7 @@ TEST_F(Test_vdb_tool, ToolForLoop) // test two nested for-loops EXPECT_NO_THROW({ auto args = getArgs("vdb_tool -quiet -for v=0.1,0.3,0.1 -each s=sphere_1,sphere_3 -read ./data/{$s}.ply -mesh2ls voxel={$v} -end -end -write data/test.vdb"); - Tool vdb_tool(args.size(), args.data()); + Tool vdb_tool(int(args.size()), args.data()); vdb_tool.run(); }); @@ -929,7 +994,7 @@ TEST_F(Test_vdb_tool, ToolError) EXPECT_THROW({ auto args = getArgs("vdb_tool -sphere bla=3 -ls2mesh -write data/sphere.ply data/config.txt -quiet"); - Tool vdb_tool(args.size(), args.data()); + Tool vdb_tool(int(args.size()), args.data()); vdb_tool.run(); }, std::invalid_argument); @@ -952,7 +1017,7 @@ TEST_F(Test_vdb_tool, ToolKeep) EXPECT_NO_THROW({ auto args = getArgs("vdb_tool -quiet -default keep=1 -sphere r=2 -ls2mesh vdb=0 -write vdb=0 geo=0 data/sphere.vdb data/sphere.ply data/config.txt"); - Tool vdb_tool(args.size(), args.data()); + Tool vdb_tool(int(args.size()), args.data()); vdb_tool.run(); }); @@ -975,7 +1040,7 @@ TEST_F(Test_vdb_tool, ToolConfig) EXPECT_NO_THROW({ auto args = getArgs("vdb_tool -quiet -config data/config.txt"); - Tool vdb_tool(args.size(), args.data()); + Tool vdb_tool(int(args.size()), args.data()); vdb_tool.run(); }); diff --git a/pendingchanges/vdb_tool.txt b/pendingchanges/vdb_tool.txt new file mode 100644 index 0000000000..c68dc25834 --- /dev/null +++ b/pendingchanges/vdb_tool.txt @@ -0,0 +1 @@ +added read and write support for OFF (Object File Format) files to vdb_tool \ No newline at end of file