Search code examples
animation3drgl

Input f into play3d() and movie3d() in the rgl package in R


I don't understand the input f expected by play3d and movie3d in the rgl package.

enter image description here

library(rgl)

nobs<-10
x<-runif(nobs)
y<-runif(nobs)
z<-runif(nobs)
n<-rep(1:nobs)
df<-as.data.frame(cbind(x,y,z,n))
listofobs<-split(df,n)

plot3d(df[,1],df[,2],df[,3], type = "n", radius = .2 )

myplotfunction<-function(x) {
  rgl.spheres(x=x$x,y=x$y,z=x$z, type="s", r=0.025)
}

When executing the 2 lines below, the animation does play but both lines (play3d() and movie3d()) trigger the error displayed below:

play3d(f=lapply(listofobs,myplotfunction), fps=1 )
movie3d(f=lapply(listofobs,myplotfunction), fps=1 , duration=20)

enter image description here

I am hoping someone can correct my code and help me understand the f input to play3d and movie3d.

Question 1: Why is the play3d line above correct enough that the animation does display correctly?

Question 2: Why is the play3d line above incorrect enough that it triggers the error?

Question 3: What is wrong with the movie3d line that it does not produce a video output?


Solution

  • As the docs say, f is "A function returning a list that may be passed to par3d". It's not a list, which is what your usage passes.

    To answer the questions:

    1. R evaluates the lapply call which does the animation, then play3d looks at the result and dies because it's not a function.
    2. f needs to be a function, as described in the help page.
    3. It dies when it looks at f, because it's not a function.

    This looks like it will do what you want:

    library(rgl)
    
    nobs<-10
    x<-runif(nobs)
    y<-runif(nobs)
    z<-runif(nobs)
    df<-data.frame(x,y,z)
    
    plot3d(df, type = "n" ) 
    id <- NA
    myplotfunction<-function(time) {
      index <- round(time)
      # For a 3x faster display, use index <- round(3*time)
      # To cycle through the points several times, use 
      # index <- round(3*time) %% nobs + 1
      if (!is.na(id))
        pop3d(id = id) # Delete previous item
      id <<- spheres3d(df[index,], r=0.025)
      list()
    }
    
    play3d(myplotfunction, startTime = 1, duration = nobs - 1)
    movie3d(myplotfunction, startTime = 1, duration = nobs - 1, fps = 1)
    

    This will leave a GIF in file.path(tempdir(), "movie.gif").

    Some other notes:

    • don't call rgl.spheres. It will cause you immense pain later. Use spheres3d, or never call any *3d function, and never upgrade rgl: you're living in the past using the rgl.* functions. The *3d functions and the rgl.* functions don't play nicely together.
    • to construct a dataframe, just use the data.frame() function, don't convert a matrix.
    • you don't need all those contortions to extract points from the dataframe.
    • Most rgl functions can handle a dataframe with x, y, and z columns.
    • You might notice the plot3d frame move a little: spheres are bigger than points, so it will adjust to accommodate them. You could use xlim, ylim and zlim to set the original frame a little bigger if you don't like this.