Search code examples
htmlrplot3drgl

Export 3D surface with wireframe from R to HTML


The writeWebGL function from the {rgl} package can be used to export interactive 3D plots to HTML (see example below).

    require(rgl)
    jet.colors <-colorRampPalette(c("#00007F", "blue", "#007FFF", "cyan",
                 "#7FFF7F", "yellow", "#FF7F00", "red", "#7F0000"))
    colorzjet <- jet.colors(100)
    data(volcano)
    z <- 5 * volcano        # Exaggerate the relief
    x <- 10 * (1:nrow(z))   # 10 meter spacing (S to N)
    y <- 10 * (1:ncol(z))  

    open3d()
    bg3d("white")
    material3d(col="black")
    persp3d(x, y, z, col = colorzjet[ findInterval(z, seq(min(z), max(z), length=100))], aspect="iso",axes = TRUE, box = FALSE, smooth=FALSE,lit=FALSE,front="fill")
    surface3d(x, y, z, front = "lines",col="black", lit=FALSE)

    browseURL(paste("file://", writeWebGL(dir=file.path("C:/", "webGL"), width=700), sep=""))

It does not, however, currently support wireframe or point rendering, meaning that 3D surfaces will be exported as polygons without visible edges.

Does anyone know a way around that? I'd like my HTML file to show the edges of each facet on the 3D surface.


Solution

  • You'll need to draw the points or edges using low level functions. It's fairly easy to do points:

    id <- surface3d(x, y, z, front = "lines",col="black", lit=FALSE)
    vertices <- rgl.attrib(id, "vertices")
    points3d(vertices)
    

    Doing the wireframe is more complicated, because you need to join the right vertices. Here are a couple of functions that can do it.

    surfaceData <- function(id) {
      vertices <- rgl.attrib(id, "vertices")
      dim <- rgl.attrib(id, "dim")
      array(vertices, c(dim, 3))
    }
    
    surface2lines <- function(data, ...) {
      vertices <- NULL
      for (i in seq_len(dim(data)[1]))
        vertices <- rbind(vertices, data[i,,], c(NA, NA, NA)) 
      for (j in seq_len(dim(data)[2]))
        vertices <- rbind(vertices, data[,j,], c(NA, NA, NA))
      lines3d(vertices, ...) 
    }
    

    You call surfaceData(id) to extract the vertices of a surface, then surface2lines() on that array to plot the lines. For example,

    id <- surface3d(x, y, z, front = "lines",col="black", lit=FALSE)
    d <- surfaceData(id)
    open3d()
    surface2lines(d)
    

    which gives this image from your data:

    wireframe image

    You could overplot this on the surface; you'll likely need to play with the depth_test property to get it to show up on top of the surface.