rr-markdownknitrrgl

How can I get the "smooth=FALSE" argument in rgl to work in RMarkdown?


When I make a checkered surface by setting smooth=FALSE in the rgl package in R, it appears correctly in my computer's rgl window, but not in knitted R Markdown documents nor in the RStudio viewer, where it appears as the default smooth=TRUE.

This appears as intended in an XQuartz window:

library(rgl)

# Create x, y, and z points
set.seed(123)
x <- sort(rnorm(10))
y <- sort(rnorm(10))
f <- function(x,y) {r <- x+y}
z <- outer(x,y,f)

# Create a grid on the x and y axes
x.grid <- seq(min(x), max(x), length.out =10)
y.grid <- seq(min(y), max(y), length.out =10)

# Create a checkered surface 
persp3d(x = x.grid,
        y = y.grid,
        z = z,
        xlab="x", ylab="y", zlab="z",
        col=rainbow(9),
        lit = FALSE,
        smooth = FALSE) # This works

RGL window

However, when try to recreate this in an RMarkdown file, the checkering blurs. It appears that smooth=FALSE is not recognized:

    ```{r echo=F}
    library(rgl)

    # Create x, y, and z points
    set.seed(123)
    x <- sort(rnorm(10))
    y <- sort(rnorm(10))
    f <- function(x,y) {r <- x+y}
    z <- outer(x,y,f)

    # Create a grid on the x and y axes
    x.grid <- seq(min(x), max(x), length.out =10)
    y.grid <- seq(min(y), max(y), length.out =10)

    # Create a checkered surface 
    persp3d(x = x.grid,
        y = y.grid,
        z = z,
        xlab="x", ylab="y", zlab="z",
        col=rainbow(9),
        lit = F,
        smooth = F) # Now this does not work

    rglwidget()
    ```

RMarkdown output

Is this a bug in rgl? How can I display this interactive checkered surface in R Markdown?


Solution

  • It's a bug, or at least a limitation of the WebGL display. A workaround has been added to the latest release (1.3.14), but I'll leave the rest of this answer here for users of older versions.

    When you choose smooth = FALSE in the built-in display, old OpenGL 1.2 semantics are used. They allow the colour at one vertex of a triangle or quad to be used for the whole polygon.

    However, in an Rmarkdown document, you're using WebGL, which is based on a more modern OpenGL version that doesn't support that mode. To display a triangle or quad with all one colour, you would need to set all vertices of the triangle to that colour.

    You can fake this yourself by drawing the facets separately.

    There's a way to do it semi-automatically by drawing the surface, converting it to a mesh, then fiddling with the mesh to change how colours are applied. You want the mesh to end up with meshColor = "faces", but you also need to change the colour vector to give one colour per face.

    Here's a version of your code that does that:

    library(rgl)
    
    # Create x, y, and z points
    set.seed(123)
    x <- sort(rnorm(10))
    y <- sort(rnorm(10))
    f <- function(x,y) {r <- x+y}
    z <- outer(x,y,f)
    
    # Create a grid on the x and y axes
    x.grid <- seq(min(x), max(x), length.out =10)
    y.grid <- seq(min(y), max(y), length.out =10)
    
    # Create a checkered surface 
    open3d()
    ids <- persp3d(x = x.grid,
            y = y.grid,
            z = z,
            xlab="x", ylab="y", zlab="z",
            col=rainbow(9),
            lit = FALSE,
            smooth = FALSE)
    m <- as.mesh3d(rglId(ids["surface"]))
    m$meshColor <- "faces"
    oldcolor <- m$material$color
    indices <- setdiff(1:90, (0:8)*10 + 1)
    m$material$color <- oldcolor[indices]
    open3d()
    plot3d(m)
    

    It would be possible to make this completely automatic. If you want to do that, you should submit it as a PR on the rgl Github site.