This project voxelizes the meshes in STL file without the condition of watertight. It supports many formats only now, since we are using assimp
library to load the file. Basically, the project can be summarized into two steps:
-
Surface voxelization
For each piece of mesh (triangle) , we check the collided voxels in either way:- Get the minimal bounding box of each triangle, check each voxel in this box with the triangle;
- Start at any voxel collided with the triangle, and do bfs search to check neighboring voxels.
The first way is lightweight, but may become worse when the ratio of (triangle's volume/bounding box's volume) is small. While the second way has quite large constant overhead. For each thread in thread pool, it will pick a triangle to voxelize. The time complexity is O(m*c), where m is the triangle number and c is some factor such as the voxel number in the bounding box or constant overhead of bfs.
-
Solid voxelization
When equipped with surface voxelization, the solid voxelization can be simple: flood fill. We try to flood fill the outer space of the meshes like carving the wood, since it is more simple and doesn't requires watertight property. However, the basic flood fill with bfs is too heavy and time-consuming, optimizations are proposed here (see below). The time complexity is O(n), where n is the voxel number in the bounding box of whole mesh.
-
Use thread pool.
-
Use bit compression to record and order the voxel. First, I store voxel (x, y, z) to index=x*size_y*size_z + y*size_z + z, where size_{x,y,z} are the voxel grid size, and I call the compressed format as index. For instance, with size=4, index=1 indicates voxel (0,0,1), index=4 indicates voxel (0,1,0). With indexes, all voxels can be represented with a binary string. For instance, '010010' means the voxels with indexes of 1 and 4 are collided with the mesh, while others are not. The binary string is further compressed with 64-bit unsigned int array.
-
Use atomic on the int array (the conceptional binary string) to guarantee the correctness when multi threads write the results, i.e., set the '0' or '1' to the int array. This design gives better parallel performance.
-
Estimate the bounding box size of a triangle to guide which method is more suitable for surface voxelization.
-
Randomly permutate the triangles' order to reduce the possibility of lock, thus get better parallel performance.
-
Use filter-and-refine strategy to optimize the plain flood fill algorithm as follows:
- Filter
We fill the voxels along each axis, and stop when meet the mesh. For instance, fix (x,y), enumerate z in [0, maxz], if a voxel (x,y,z) is occupied, stop. The intuition is to mark all voxels which are visible along that axis. This idea is similar to flood fill but is much more efficient benefit from the lightweight operations. - Refine
And it's worthy noting, after filtering, there are some holes, because they are not visible along any axis. But these are very few holes, so we can use flood fill again on them efficiently.
Although the time complexity is still O(n), it runs much faster than basic flood fill search.
- Filter
-
Compress the output file, for your convenience, I choose the simple version of output for default. But optimized version is prepared. Recall that the voxels are stored in binary string, such as '1110000'. To reduce the output file, please be noted only voxels in the minimal bounding box for mesh are output. It is further compressed as
$value$ and$count$ . For '1110000', it is compressed to (1)(2)(0)(3), (1)(2) means 3 consecutive 1, and (0)(3) means 4 consecutive 0. I use two bytes to record$value$ and$count$ . Specifically,$value$ can be 0 or 1, and$count$ can be [0,255] which corresponds to [1,256]. To retrieve the coordination of a voxel in original space, let$(x,y,z)$ denote the voxel coordinate extracted from the output binary string, and the coordinate in voxelized space is$(x+x_{vox_lb},y+y_{vox_lb},z+z_{vox_lb})$ . -
[TODO] Combine the coarse and fine strategy, i.e., do coarse grid size first to filter more unnecessary voxels.
-
[TODO] Use gpu
-
[TODO] Try to fill the inside voxels, such that the time complexity is proportional to size of the result voxels.
This project requires libraries (dependencies) as follows:
- absl (https://github.com/abseil/abseil-cpp), which is installed as git submodule
- boost
- libccd
- libfcl
for collision checking (https://github.com/flexible-collision-library/fcl, version: tags/0.3.3) - assimp
for loading STL file (https://github.com/assimp/assimp) - cmake & make
make sure
pkg-config
is installed.
CMakeLists.txt is used to generate makefiles. To build this project, in command line, run
mkdir build
cd build
cmake ..
Next, in linux, use make
in 'build' directory to compile the code.
voxelizer --help
will give all the parameters.
--clipping_size (clipping size (x,y,z) to clip the voxelized result. The
clipping size is grid size based. If only one integter is specified,
clipping size is initialized as (x,x,x). If not set, the result is not
clipped.); default: ;
--format (output format, can be binvox or rawvox); default: "binvox";
--grid_size (grid size, the granularity of voxelizer. if only one integer is
set, assuming the X,Y,Z are the same.); default: ;
--input (input file to be voxelized, file type will be inferred from file
suffix); default: "";
--mesh_index (mesh index to be voxelized); default: 0;
--mode (voxelizer mode, surface or solid); default: "solid";
--num_thread (number of thread to run voxelizer); default: 4;
--output (output file to store voxelized result); default: "";
--tight (If true, check whether the center point of each voxel is inside the
mesh or outside it, and if it is outside, remove it. Caveat: this option
could be time consuming.); default: false;
--verbose (print debug info); default: false;
--voxel_size (voxel size, which determines the size of each voxel);
default: ;
--with_meta (write voxel meta info to .meta file if set to true);
default: false;
When you are in 'build' directory, one running example is:
./bin/voxelizer --input=../data/sphere.obj --output=../playground/sphere.binvox --grid_size=256 --verbose=true
It outputs a binvox
file with grid size 256 x 256 x 256
.
If you specify --voxel_size=0.1
, instead, it will output a file with each voxel size is 0.1 x 0.1 x 0.1
. Note that the dimensions can be different.
To view the sphere.binbox
file, you may use the viewvox here: https://www.patrickmin.com/viewvox/ or https://raahii.github.io/simple_voxel_viewer/index.html.
This project has folders and files as follows:
- src
the source files - test
the test files - data
testing data (.stl files) - CMakeLists.txt
for cmake
For simple testing, in build
directory, run ./test/collision_checker
.
It is a benchmark of voxel collision checking and mesh collision checking (using the libfcl), with three testing files (kawada-hironx.stl, racecar.stl, and bike.stl). The grid size varies from 128 to 512, and the random cube (with random translate and random rotation) has size of 0.005^3 to 0.08^3 of the total scene size. The accuracy is defined based on the ground truth of mesh collision checking. As you can see, when the grid size becomes larger, the accuracy will increase. The collision checking performance is not specially optimized yet.
MacOS 10.9.3
ubuntu 14.04