r3drgl

Is it possible to put 3D mesh and arc3d objects in the same rgl window while allowing for separate rotation?


This post is an extension of my question on how to draw polar dendrogram in 3D in rgl. The answer from user2554330 solved this question. Now, I want to further add 3D meshes to the dendrogram at their tips.

This constructs the dendrogram:

a <- list()  # initialize empty object
# define merging pattern: 
#    negative numbers are leaves, 
#    positive are merged clusters (defined by row number in $merge)
a$merge <- matrix(c(-1, -2,
                    -3, -4,
                    1,  2), nc=2, byrow=TRUE ) 
a$height <- c(1, 1.5, 3)    # define merge heights
a$order <- 1:4              # order of leaves(trivial if hand-entered)
a$labels <- LETTERS[1:4]    # labels of leaves
class(a) <- "hclust"        # make it an hclust object
plot(a)                     # show it

# Convert to a dendrogram object.
ad <- as.dendrogram(a)

# dend_data contains segment information
library(ggdendro)
dend_data <- dendro_data(ad, type = "rectangle")

This code from user2554330 plotted the dendrogram in 3D:

nodes <- dend_data$segments

# Set the gap between the ends of the tree
gap <- 0
# Set the offset from the center.  
offset <- 0

radius <- with(nodes, max(c(y, yend)) + offset)
circ <- with(nodes, max(c(x, xend)) + gap)

# Convert to polar coordinates
nodes$theta <- with(nodes, 2*pi*x/circ)
nodes$thetaend <- with(nodes, 2*pi*xend/circ)
nodes$r     <- with(nodes, (radius - y)/radius)
nodes$rend  <- with(nodes, (radius - yend)/radius)

# Extract the horizontal and vertical segments
horiz <- subset(nodes, y == yend)
vert <- subset(nodes, x == xend)

library(rgl)

open3d()
#> glX 
#>   1

# Draw the vertical segments, which are still segments
x     <- with(vert, as.numeric(rbind(r*cos(theta), rend*cos(theta))))
y     <- with(vert, as.numeric(rbind(r*sin(theta), rend*sin(theta))))
segments3d(x, y, z = 0)

# Draw the horizontal segments, which are now arcs.  Zero
# radius arcs are dropped
horiz <- subset(horiz, r > 0)
with(horiz, arc3d(from = cbind(r*cos(theta), r*sin(theta), 0),
                  to = cbind(r*cos(thetaend), r*sin(thetaend), 0),
                  center = c(0, 0, 0)))

# Draw the labels
labels <- dend_data$labels
labels$theta <- with(labels, 2*pi*x/circ)
# Add a bit to the y so the label doesn't overlap the segment
labels$r     <- with(labels, (radius - y)/radius + 0.1)
with(labels, text3d(r*cos(theta), r*sin(theta), 0, label))

Now I add spheres at the tips of the dendrogram:

with(labels, spheres3d(r*cos(theta), r*sin(theta), 0, radius = 0.1, color = c("lightblue", "pink", "lightyellow", "lightgrey")))

When I rotate the rgl scene, all four spheres rotate together with the 3D dendrogram. I want to ask if there is a way to allow for separate rotation of each spehere interactively in rgl window such that when I rotate the pink sphere, the 3D dendrogram is not rotated, nor is any of the other 3 spheres rotated. Thank you.


Solution

  • There are really 3 tasks involved in doing what you want to do. All of them are possible, but they vary in difficulty:

    1. Allow some objects to rotate independently of others. This is fairly easy. One way is to put the different objects in different "subscenes". Subscenes can share the same apparent volume of space, but not share the same mouse controls. Another way would be to use 3d sprites, which can have their own associated matrix to rotate them differently from other objects.

    2. Allow selection of particular objects. You didn't say how you wanted the user to specify which object should rotate or how you would control the rotation, but the usual way would be to point at an object and "grab" it to rotate it with the mouse. This is possible in rgl (see select3d()), but it's a little tricky. A much simpler choice would be to require some function to be called; that function would set some variables that would be used later.

    3. Specify how the selected object responds to the mouse. You can set a user mouse handler after the object is selected, but you need to figure out what that handler should do. The rgl.setMouseCallbacks() function gives one example.