Splitting RCGAL... and the connected components
I published two posts here about my package RCGAL, the first one about surface reconstruction, and the second one about Boolean operations on 3D meshes.
Now I decided to split this package into two packages: SurfaceReconstruction, and MeshesOperations.
I think MeshesOperations is almost ready for submission to CRAN. I have just started SurfaceReconstruction today, but it already works.
Dealing with a ‘R CMD check’ issue
A technical note before playing with the MeshesOperations package. Feel free to skip this section.
I faced a ‘R CMD check’ issue on ‘win-builder’ with MeshesOperations. Namely, this so-called significant warning:
C:/.../BH/include/boost/container/detail/copy_move_algo.hpp:184:19: warning:
'void* memmove(void*, const void*, size_t)' writing to an object of type 'value_type'
{aka 'struct std::pair, std::allocator >, long long unsigned int> > >,
std::allocator, std::allocator >, long long unsigned int> > > > >,
CGAL::internal::In_place_list_iterator, std::allocator >,
long long unsigned int> > >, std::allocator, std::allocator >,
long long unsigned int> > > > > >'}
with no trivial copy-assignment; use copy-assignment or copy-initialization
instead [-Wclass-memaccess]
This is a warning from the BH package (which allows to use the C++ library Boost with Rcpp). On Linux, the ‘R CMD check’ report was clean.
I found a solution to this issue. I describe it here, in case it could help someone.
In order to use the BH package, I included it in the LinkingTo field of the DESCRIPTION file. Then I discovered that this was not necessary with R-4.2.0, and that’s because Rtools42 contains the Boost library.
So this solved the problem for Windows, but created a new problem for the other OSs. Then I solved this new problem by:
putting BH in the Suggests field of DESCRIPTION (and only in this field)
adding the following lines in the Makevars file in the src folder of the package (breaking the first line for display here):
BH_PATH = `echo 'cat(system.file("include", package = "BH", mustWork=TRUE))' \
| "${R_HOME}/bin/R" --vanilla --no-echo`
PKG_CXXFLAGS = -I$(BH_PATH)
The first line allows to call R to get the path of the include folder of the BH package, and this path is set to the variable BH_PATH.
Of course I didn’t change the Makevars.win file. This works, but I don’t know yet whether this is acceptable for CRAN. However I am optimistic, since such a technique can be found in ‘Writing R extensions’.
Connected components
There’s something new in MeshesOperations as compared to RCGAL: the computation of the connected components of a mesh. I’m going to show you an example. Note that the mesh in this example is triangle (i.e. it only has triangular faces), and then one could alternatively use my package MeshesOperations (not submitted to CRAN yet) to get the connected components of this mesh.
So let’s try this feature. Our example is an isosurface called the C8 surface. Unfortunately I do not remember where I found it. I’m using the rmarchingcubes to construct a mesh of this isosurface.
<- function(x, y, z){
f 64*x**8 - 128*x**6 + 80*x**4 - 16*x**2 + 2 + 64*y**8 - 128*y**6 +
80*y**4 - 16*y**2 + 64*z**8 - 128*z**6 + 80*z**4 - 16*z**2
}
<- 150L
ngrid <- y <- z <- seq(-1.1, 1.1, len = ngrid)
x <- expand.grid(X = x, Y = y, Z = z)
Grid <- array(
voxel with(Grid, f(X, Y, Z)), dim = c(ngrid, ngrid, ngrid)
)
library(rmarchingcubes)
<- contour3d(
contour_shape griddata = voxel, level = -0.1,
x = x, y = y, z = z
)
Now let’s plot it with rgl:
library(rgl)
<- tmesh3d(
tmesh vertices = t(contour_shape[["vertices"]]),
indices = t(contour_shape[["triangles"]]),
normals = contour_shape[["normals"]],
homogeneous = FALSE
)open3d(windowRect = c(50, 50, 562, 562))
shade3d(tmesh, color = "darkred")
As you can see, it has many isolated parts, the so-called connected components.
Now we use the MeshesOperations package to extract these connected components, while requesting the vertex normals of each component:
library(MeshesOperations)
<- connectedComponents(
meshes vertices = contour_shape[["vertices"]],
faces = contour_shape[["triangles"]],
normals = TRUE
)# Found 64 components.
Now let’s plot all theses meshes, one color per mesh:
<- length(meshes)
ncc library(randomcoloR)
<- randomColor(ncc, hue = "random", luminosity = "dark")
colors
open3d(windowRect = c(50, 50, 562, 562))
for(i in 1L:ncc){
<- meshes[[i]]
cc <- tmesh3d(
tmesh vertices = t(cc[["vertices"]]),
indices = t(cc[["faces"]]),
normals = cc[["normals"]],
homogeneous = FALSE
)shade3d(tmesh, color = colors[i])
}
Nice. However there is one problem: compare the smoothness of these meshes to the smoothness of our first plot. It is not as smooth.
I think I know why. The smoothness depends on the vertex normals. Here
we computed the normals with CGAL, then each vertex normal is obtained
by averaging the normals of the surrounding faces of this vertex. We
could have used rgl::addNormals
instead, the result would be the same.
But there is a convenient way to get the “true” vertex normals of an
isosurface: the normal associated to a vertex is the gradient of the
isosurface function evaluated at the coordinates of this vertex. So my
guess is that rmarchingcubes uses the gradient. I am still thinking
of having the possibility to preserve the original normals when
computing the connected components, I didn’t have the time yet to find
how to do that. This is not a problem, we will compute the true normals
with R. In fact we will probably do better than rmarchingcubes:
surely this package numerically computes the gradient, while we will use
the exact gradient. How? Look at the isosurface function f
: this is
a multivariate polynomial. So it is easy to calculate its derivatives.
This would be tedious to calculate them by hand however, so let’s use
the spray package:
library(spray)
# define the polynomial corresponding to f:
<- f(lone(1,3), lone(2,3), lone(3,3))
P # and its derivatives:
<- as.function(deriv(P, 1L))
dfx <- as.function(deriv(P, 2L))
dfy <- as.function(deriv(P, 3L))
dfz # the gradient is:
<- function(xyz){
gradient cbind(dfx(xyz), dfy(xyz), dfz(xyz))
}
Just a couple of lines of code… Now let’s do the plot with the true normals:
open3d(windowRect = c(50, 50, 562, 562))
for(i in 1:ncc){
<- meshes[[i]]
cc <- tmesh3d(
tmesh vertices = t(cc$vertices),
indices = t(cc$faces),
normals = gradient(cc$vertices),
homogeneous = FALSE
)shade3d(tmesh, color = colors[i])
}
A perfect smoothness!