rpdfsvgrgl

how to fix incorrect image saving as vector graphics in "rgl" package?


Executing the sample code results in valid .png images, but incorrect .pdf and .svg images. The 3D points are invisible. These points and the ellipsoid are likely incompatible. The appearance of the points in vector graphics disrupts the ellipsoid's surface.

if (requireNamespace("MASS", quietly = TRUE)) {
  Sigma <- matrix(c(10, 3, 0, 3, 2, 0, 0, 0, 1), 3, 3)
  Mean <- 1:3
  x <- MASS::mvrnorm(10, Mean, Sigma)
  library(rgl)
  open3d()
  plot3d(x, type="s", lit=FALSE, box = FALSE)
  ellipse <- ellipse3d(Sigma, centre = Mean)
  plot3d(ellipse , col = "darkcyan", alpha = 0.3, add = TRUE)
  shade3d(ellipse, col = "Gray90", alpha = 0.3, lit = FALSE)
  rgl.snapshot(filename = '3dellipse.png', fmt = 'png')
  rgl.postscript('3dellipse.pdf', fmt = 'pdf')
  # alternative way to svg file
  require(x3ptools)
  x3p_snapshot("3dellipse_x3p.svg")
}

Changing the line of code to

plot3d(ellipse, col = "darkcyan", alpha = 0.3, add = TRUE, type="shade")

generates PDF and SVG images with the correct ellipsoid, but the points inside it are invisible.

The resulting .png image: enter image description here

The resulting .pdf or .svg image: enter image description here


Solution

  • The docs for rgl.postscript warn that not all OpenGL features are supported, so you shouldn't be surprised that not everything works. However, you should be able to do better than your PDF output. It looks to me as though that image is showing "z-fighting": you are displaying both a gray ellipsoid and a darkcyan one at the same location, and rounding error means that sometimes one is shown, sometimes the other.

    The easiest solution to z-fighting is to avoid displaying two things at the same location. In your code, you have

    plot3d(ellipse , col = "darkcyan", alpha = 0.3, add = TRUE)
    shade3d(ellipse, col = "Gray90", alpha = 0.3, lit = FALSE)
    

    Include only one of those lines and things should look better. Another possible solution is to use a different polygon_offset in one of the displays so that depth testing doesn't see ties. See ?material3d for the details on that.

    The reason you're seeing z-fighting in one display but not the other is that the rgl.postscript code doesn't support alpha transparency. You'd probably see z-fighting in both displays if you left out the alpha = 0.3 settings. It's possible a newer version of the GL2PS library would do a better job of rendering; rgl uses GL2PS 1.4.0 from 7 years ago.

    EDITED to add:

    The current development version 1.2.12 of rgl does a much better job of rendering your image, after deleting the shade3d(...) call above. It now supports transparency in PDF output.

    This modified version of your code:

    library(rgl)
    library(MASS)
    
    Sigma <- matrix(c(10, 3, 0, 3, 2, 0, 0, 0, 1), 3, 3)
    Mean <- 1:3
    
    set.seed(123)
    x <- MASS::mvrnorm(10, Mean, Sigma)
    
    open3d()
    plot3d(x, type="s", box = FALSE, col = "white")
    ellipse <- ellipse3d(Sigma/2, centre = Mean)
    plot3d(ellipse , col = "darkcyan", alpha = 0.3, add = TRUE)
    rgl.postscript('3dellipse.pdf', fmt = 'pdf')
    

    produces this output: screenshot

    You should be able to install this devel version using

    remotes::install_github("dmurdoch/rgl")
    

    if you have the necessary tools and libraries.