Display multiple cylinders efficiently in RGL Plot

I am trying to plot cylinder models of trees in R, which consist of several thousand cylinders. Currently, I am trying to use the rgl package for this task. Until now, I looped through all cylinders and added them separately to the 3D Plot.

Here an example with dummy data:

# dummy data
cylinder <- data.frame(start_X = rep(0:2, each = 2),
                       start_Y = rep(0:1, 3),
                       start_Z = 0,
                       end_X = rep(0:2, each = 2),
                       end_Y = rep(0:1, 3),
                       end_Z = 1)
radii <- seq(0.1, 0.5, length.out = 6)

# plotting
open3d()
for (i in 1:nrow(cylinder)) {
  cyl <- cylinder3d(
    center = cbind(
      c(cylinder$start_X[i], cylinder$end_X[i]),
      c(cylinder$start_Y[i], cylinder$end_Y[i]),
      c(cylinder$start_Z[i], cylinder$end_Z[i])),
    radius = radii[i],
    closed = -2)
  shade3d(cyl, col = "steelblue")
}
axes3d(edges = c("x", "y", "z"))

example output

However, this approach takes ages when trying to plot several thousand cylinders. Is there a way to hand over several cylinder objects at once to the shade3d() function? Can I create several separate cylinders in one call of cylinder3d()? Or am I possibly using wrong functions or an inappropriate R package?


Solution 1:

There are quite a few possibilities.

The easiest change is to tell rgl not to update the display until you are finished updating it using the par3d(skipRedraw=TRUE) option. You need to run par3d(skipRedraw=FALSE) when you want to see the updates. That often makes enough of a speedup.

If all of your cylinders were the same shape (but maybe rescaled), you could use sprites3d; but you are varying the radius separately from the height, so that's not possible.

To do what you asked for (put them all into one shade3d call) you could make a shape list. For example, after editing to allow separate colors,

cylinder <- data.frame(start_X = rep(0:2, each = 2),
                       start_Y = rep(0:1, 3),
                       start_Z = 0,
                       end_X = rep(0:2, each = 2),
                       end_Y = rep(0:1, 3),
                       end_Z = 1)
radii <- seq(0.1, 0.5, length.out = 6)
colors <- rainbow(6)

library(rgl)
thelist <- lapply(1:6, function(i) {
  cyl <- cylinder3d(
    center = cbind(
      c(cylinder$start_X[i], cylinder$end_X[i]),
      c(cylinder$start_Y[i], cylinder$end_Y[i]),
      c(cylinder$start_Z[i], cylinder$end_Z[i])),
    radius = radii[i],
    closed = -2)
  cyl$material$color <- colors[i]
  cyl
})

# plotting
open3d()
shade3d(shapelist3d(thelist, plot = FALSE))
axes3d(edges = c("x", "y", "z"))

The shapelist3d() function can take a list of meshes as above, or take a single mesh and with other arguments reshape and duplicate it. There probably won't be much difference in the speed.