Search code examples
r3dshapesrgl

How to use point shapes other than spheres in rgl?


I'm trying to illustrate the idea of projections using points on a cube, shown with rgl and what that looks like in various 2D projections.

My goal is to use different colors and shapes to distinguish the points, both in 2D plots and 3D, but I can't figure out how to plot the points in rgl using other than spheres.

My current figure looks like this:

enter image description here

I generate the points on a 10^3 cube and two projections as:

vals <- c(0, 10)
X <- expand.grid(x1 = vals, x2=vals, x3=vals) |> as.matrix()

# project on just x1, x2 plane
P1 <- rbind(diag(2), c(0,0))
Y1 <- X %*% P1
# oblique projection
P2 <- matrix(c(0.71, 0.71, 0, -0.42, .42, 0.84), ncol=2)
Y2 <- X %*% P2

The 2D plots in (b) and (c) use 4 different colors and 4 pch shapes (square, circle, triangle, diamond)

pch <- rep(15:18, 2)
colors <- c("red", "blue", "darkgreen", "brown")
col <- rep(colors, each = 2)

plot(Y1, cex = 3, 
     pch = pch, col = col,
     xlab = "Y[,1]", ylab = "Y[,2]",
     xlim = c(-1, 11), ylim = c(-1, 11))

plot(Y2, cex = 3, 
     pch = pch, col = col,
     xlab = "Y[,1]", ylab = "Y[,2]",
     xlim = c(-1, 15), ylim = c(-5, 14)
     )

Here's what I tried using rgl. The colors are correct, but obviously, pch has no effect using type = "s". What I'd like are the shapes: cube, sphere, pyramid, diamond (or anything else)

library(rgl)
open3d()
plot3d(X, type = "s", size = 2,  pch = pch, col = col)

Solution

  • You can use type = "n" to set up the plotting region, then pch3d() to plot symbols at the points. For example,

    library(rgl)
    open3d()
    plot3d(X, type = "n")
    pch3d(X, pch = pch, col = col)
    

    enter image description here

    If you want to use 3d shapes, it's a bit harder. The sprites3d() function can do it, but it plots the same shape at each location, so you'll need to call it several times to plot different shapes. The example below creates 3 shapes, and then plots each of them a few times:

    open3d()
    plot3d(X, type = "n")
    shapes <- c(shade3d(cube3d(), col = "red"),
                shade3d(tetrahedron3d(), col = "blue"),
                shade3d(octahedron3d(), col = "darkgreen"),
                shade3d(sphere3d(), col = "brown")
    # sprites3d can only plot the same shapes at each location,
    # so we'll call it 3 times
    indx <- 1:8
    for (i in 1:3) {
      j <- indx[indx %% 3 == i %% 3]
      sprites3d(X[j,,drop=FALSE], shape = shapes[i])
    }
    

    enter image description here

    EDITED TO ADD:

    Finally, if you're willing and able to use the development version of rgl, sprites3d() now handles cases like yours more easily. For rgl version 1.2.13 or higher, the code above could be written as

    # spheres3d() doesn't work well as a sprite, so make a sphere mesh:
    
    sphere3d <- addNormals(subdivision3d(icosahedron3d(), 2))
    
    open3d()
    plot3d(X, type = "n")
    shapes <- list(shade3d(cube3d(), col = "red"),
                shade3d(tetrahedron3d(), col = "blue"),
                shade3d(octahedron3d(), col = "darkgreen"),
                shade3d(sphere3d, col = "brown"))
    
    shapes <- rep(shapes, each = 2)
    
    sprites3d(X, shape = shapes)
    
    

    enter image description here