How to increase smoothness of spheres3d in rgl

Although rgl::spheres3d() can't do this, an alternative is to write your own function to draw spheres. Here is a function that renders a sphere as a grid of quadrilaterals spaced at equal degrees of latitude and longitude.

drawSphere = function(xc=0, yc=0, zc=0, r=1, lats=50L, longs=50L, ...) {
  #xc,yc,zc give centre of sphere, r is radius, lats/longs for resolution
  vertices = vector(mode = "numeric", length = 12L * lats * longs)
  vi = 1L
  for(i in 1:lats) {
    lat0 = pi * (-0.5 + (i - 1) / lats)
    z0   = sin(lat0)*r
    zr0  = cos(lat0)*r
    lat1 = pi * (-0.5 + i / lats)
    z1   = sin(lat1)*r
    zr1  = cos(lat1)*r
    for(j in 1:longs) {
      lng1 = 2 * pi *  (j - 1) / longs
      lng2 = 2 * pi *  (j) / longs
      x1 = cos(lng1)
      y1 = sin(lng1)
      x2 = cos(lng2)
      y2 = sin(lng2)
      vertices[vi] = x1 * zr0 + xc;    vi = vi + 1L
      vertices[vi] = y1 * zr0 + yc;    vi = vi + 1L 
      vertices[vi] = z0 + zc;          vi = vi + 1L
      vertices[vi] = x1 * zr1 + xc;    vi = vi + 1L
      vertices[vi] = y1 * zr1 + yc;    vi = vi + 1L
      vertices[vi] = z1 + zc;          vi = vi + 1L
      vertices[vi] = x2 * zr1 + xc;    vi = vi + 1L
      vertices[vi] = y2 * zr1 + yc;    vi = vi + 1L
      vertices[vi] = z1 + zc;          vi = vi + 1L
      vertices[vi] = x2 * zr0 + xc;    vi = vi + 1L
      vertices[vi] = y2 * zr0 + yc;    vi = vi + 1L
      vertices[vi] = z0 + zc;          vi = vi + 1L
    }
  }
  indices = 1:(length(vertices)/3)
  shade3d(qmesh3d(vertices, indices, homogeneous=F), ...)
}

It should be possible to improve on this, for example using icospheres (i.e. drawing the sphere as a stretched icosohedron). But this version already draws pretty good spheres if you make lats and longs high enough.

An example of the function in action:

spheres = data.frame(x = c(1,2,3), y = c(1,3,1), z=c(0,0,0), color = c("#992222" , "#222299", "#229922"))
open3d() 
material3d(ambient = "black", specular = "grey60", emission = "black", shininess = 30.0)
rgl.clear(type = "lights")
rgl.light(theta = -30, phi = 60, viewpoint.rel = TRUE, ambient = "#FFFFFF", diffuse = "#FFFFFF", specular = "#FFFFFF", x = NULL, y = NULL, z = NULL)
rgl.light(theta = -0, phi = 0, viewpoint.rel = TRUE,  diffuse = "gray20", specular = "gray25", ambient = "gray80", x = NULL, y = NULL, z = NULL)
sapply(1:NROW(spheres), function(i) 
  drawSphere(spheres$x[i], spheres$y[i], spheres$z[i], r=1, lats = 400, longs = 400, color=spheres$color[i]))

enter image description here


A much simpler approach is to use subdivision3d(). Here, depth=4 isn't all that smooth, but you could increase that.

library(rgl)
sphere <- subdivision3d(cube3d(),depth=4)
sphere$vb[4,] <- apply(sphere$vb[1:3,], 2, function(x) sqrt(sum(x^2)))
open3d()
shade3d(sphere, col="red")

enter image description here


It's not easy; I think if you want to do this you're going to have to

  • download the rgl source from CRAN
  • unpack it and modify line 24 of src/sphereSet.cpp, which is currently
sphereMesh.setGlobe(16,16);

to call the function with some larger values (this function is defined on line 25 of src/SphereMesh.cpp; the arguments are in_segments and in_sections ...)

  • build/install the package from source; this will require not only the standard compilation tools, but also the relevant OpenGL libraries (on a Debian Linux OS you could use sudo apt-get build-dep r-cran-rgl to get them, I think ...)

I haven't tried this. Good luck ... alternately, you could ask the package maintainer to make this a settable parameter via materials3d or in some other way ...

Tags:

R

Rgl