22

While the view3d(theta, phi,...) function can be used to rotate the viewing point to a suitable location while taking snapshot of 3d charts/objects, it's quite hard to guess which theta and phi values are good.

Once the plot is shown, we can interactively rotate it. But is there anyway to find out the theta and phi parameters of the plot after manual rotation, such that we can use it programmatically (i.e. when creating many plots that should be of the same viewpoint)?

Charles
  • 50,943
  • 13
  • 104
  • 142
Ali
  • 9,440
  • 12
  • 62
  • 92
  • 4
    See 2nd comment in accepted answer http://stackoverflow.com/questions/7977313/is-there-an-interactive-output-device-to-view-3d-graphs-in-r – Julián Urbano Mar 07 '14 at 17:53
  • possible duplicate of [Save the orientation of a RGL plot3d() plot](http://stackoverflow.com/questions/16362381/save-the-orientation-of-a-rgl-plot3d-plot) – Josh O'Brien Mar 07 '14 at 18:02
  • 1
    @JoshO'Brien Still calculating theta and phi from the matrix is not straightforward. I am preparing a complete answer for this question – Ali Mar 07 '14 at 18:32
  • Ali -- Great! I've retracted my close vote, and look forward to seeing how you extract phi and theta from that projection matrix. – Josh O'Brien Mar 07 '14 at 18:38
  • @JoshO'Brien I tried, projMatrix is not changed after manual rotation! – Ali Mar 07 '14 at 19:28
  • @JuliánUrbano projMatrix seems not changing after manual rotation – Ali Mar 07 '14 at 19:29
  • It's called `userMatrix`, not `projMatrix`, and it **does** change when I rotate the plot. Try the example in my answer to the linked question, repeatedly manually rotating the plot and then doing `par3d(no.readonly=TRUE)$userMatrix` to see the (changed) projection matrix. – Josh O'Brien Mar 07 '14 at 19:35
  • 1
    @JoshO'Brien Yes it changes. Every manual rotation can *not* be reconstructed by changing viewpoint, since it takes only two parameters (theta, phi), while 3 are needed – Ali Mar 07 '14 at 20:26
  • @JoshO'Brien I also used simple mathematics to convert the userMatrix to phi and theta, but using view3d on calculated results did not provide a similar view point: prj <- par3d("userMatrix") prjs <- rowSums(prj) theta <- acos(prjs[3]/sqrt(sum(prjs[1:3]^2))) * 180 / pi phi <- atan(prjs[2]/prjs[1]) * 180/pi – Ali Mar 07 '14 at 20:42
  • Ah yes, that's true. I was thinking that because viewpoint w.r.t. a 3-D globe can be described by just two parameters (e.g. latitude and longitude), that meant that only theta and phi would be needed. But I was neglecting the third possible rotation, corresponding to rotation of the viewer around the axis along which they are viewing the object. So I guess now you've got your answer which is that you should be able to extract theta and phi, but they won't generally capture all aspects of the plot's rotation. – Josh O'Brien Mar 07 '14 at 20:43

3 Answers3

16

I've been trying to work this out myself and I think I have the answer for maintaining the perspective of a user modified interactive plot. view3d only affects the perspective within an already open window, the key is to use open3d to set-up your window and perspectives before you actually generate the plot.

In other words, we don't actually need to use the phi and theta angle information (directly). Once you have generated an interactive plot and got the perspective you like (you'll probably want to resize the window or the image grab will be too small), something like the following will extract the required information:

zoom<-par3d()$zoom
userMatrix<-par3d()$userMatrix
windowRect<-par3d()$windowRect

Then the following will open a window at the desired size and perspective (and zoom), generate the plot, and then grab the image.

open3d(zoom = zoom, userMatrix = userMatrix, windowRect=windowRect)
perps3d(x=x,y=y,z=z) # plus whatever arguments you need, but ignoring all perspective arguments
rgl.snapshot( filename, fmt="png", top=TRUE)

That's the basic idea and can be used to automatically generate graphs of the same perspective. You can also mess around with the scale or fov arguments from par3d as you see fit, with the same sort of idea to extract and use the information. I think these are what will be required for Ali above.

It's a little inelegant to call persp3d when automatically generating multiple plots, because that function is really designed for interactive plots. I suspect you can use the information userMatrix, zoom, fov, scale etc from par3d, and some maths (such as Ali's) to determine phi, theta, r and d, and put these into persp directly - rather than dealing with persp3d for every plot, but I haven't tested that.

Mooks
  • 593
  • 4
  • 12
7

There is no need to extract the viewing angles. You can extract userMatrix

um <- par3d()$userMatrix

and then use

view3d(userMatrix = um)

The viewing angles will be restored.

Viktor
  • 472
  • 5
  • 14
  • 2
    Nice. But how to change those numbers to make the box rotate left or right? – Rodrigo Apr 04 '18 at 19:12
  • For me, the problem is two-fold: 1) I want to increment from one view to another view in a sequence of frames, and 2) I don't have a math degree. It gets tedious and often quite difficult to manually set a view using theta, phi, x-y-z rotation, zoom, and observer position (as opposed to moving the plot around in the window). If I could figure out how to increment a userMatrix, this solution would be spot on, but that appears to require some pretty advanced understanding of math. – Andrew Brick Jun 09 '18 at 03:38
1

Yes, once you have rotated the view manually in the RGL device window, you can get all of the orientation information you need to recreate that view, in whatever coordinate system you want to use.

From the base rgl package you can get:

myUserMatrix <- par3d()$userMatrix
myZoom <- par3d()$zoom
myObserver <- par3d()$observer

Next install the orientlib package, as the two functions we will be using rely on that package

install.packages("orientlib")

Then using the userMatrix you extracted, you can get the theta & phi variables using the rglToBase function to extract the theta and phi.

theta <- rglToBase(myUserMatrix)$theta
phi <- rglToBase(myUserMatrix)$phi

Alternatively, you can use the rglToLattice function to get out the x, y & z coordinates

x <- rglToLattice(myUserMatrix)$x
y <- rglToLattice(myUserMatrix)$y
z <- rglToLattice(myUserMatrix)$z
illustro
  • 143
  • 6