rmacosrgl

axes change when using overlay with next3d() in rgl


Here is a reproducible example of the problem. I want to create side by side plot3d in rgl with shared mouse.

#use an example from rgl's manual
set.seed(1234)
x <- sort(rnorm(100))
y <- rnorm(100)
z <- rnorm(100) + atan2(x, y)

#create side by side plot
open3d()
mfrow3d(1, 2, sharedMouse=TRUE)
plot3d(x, y, z, col=rainbow(100))
arrow3d(c(0, 0, 0), c(2, 2, 2), type="lines")

next3d()
plot3d(x, y, z, col=rainbow(100))

All good so far.

resulting side by side plot

Now overlay the arrow3d to the plot on the right side

arrow3d(c(0, 0, 0), c(2, 2, 2), type="lines")

and the axes change with the arrow not placed correctly

overlay arrow3d

Can't figure out what is happening. If I plot each of these two plots with arrow3d() in separate open3d() calls they look identical. I tried other approaches such as combineWidgets in the manipulateWidget package, which works correctly but I can't figure out how to share the mouse across subscenes. plotly will be too tricky because my application require many 3d arrows along a path to create a trajectory and 3d arrows apparently are not easy to implement in plotly outside of annotations.

Running rgl 1.1.3 on R 4.2.2 on a mac (x86_64-apple-darwin17.0).


Solution

  • This is a bug in the rgl.window2user() function that is used by arrow3d(). It has been fixed in rgl 1.1.10, but that's only available on Github, and installing rgl from source isn't always easy. Here's a bit of a hack to install just the bug fix in the CRAN version 1.1.3:

    # This is the fixed version of rgl.window2user:
    
    rgl.window2user <- function( x, y = NULL, z = 0, projection = rgl.projection()) {
      xyz <- xyz.coords(x,y,z,recycle=TRUE)
        
      viewport <- projection$view
        
      normalized <- rbind( 2*(xyz$x - viewport[1]/viewport[3]) - 1,
                           2*(xyz$y - viewport[2]/viewport[4]) - 1,
                           2*xyz$z - 1,
                           1 )
      asEuclidean(with(projection, t(solve(proj %*% model, normalized)))) 
    }
    environment(rgl.window2user) <- environment(rgl::rgl.window2user)
    assignInNamespace("rgl.window2user", rgl.window2user, "rgl")
    

    Code like this is not accepted by CRAN in a package, but for your own use it should be okay. The change it makes will only last for the current R session.