Deprecating the <code>rgl.*</code> interface

# Deprecating the `rgl.*` interface

## Duncan Murdoch

### 2025-01-17

# Introduction

Since at least 2004, `rgl` has had two interfaces for many of the primitive functions: `rgl.*` and `*3d`. For example, to draw points you could use `rgl.points()` or `points3d()`. With the upcoming version 1.0.0 release of `rgl`, most of the duplication will be removed. The first step will be to deprecate a large number of `rgl.*` functions so they give warnings when they are called, and a few months later they will be removed from the package exports.

This document describes the differences and changes needed by users of the `rgl.*` interface.

# Differences between the interfaces

## Opening a window

The `rgl.open()` function has a single argument, `useNULL`. If set to `TRUE`, the NULL `rgl` device will be used. The `par3d()` settings will be set to their defaults.

The `open3d()` function has arguments

    function(...,
             params = getr3dDefaults(),
             useNULL = rgl.useNULL(),
             silent = FALSE	)

and allows `par3d()` values to be specified, and uses the `r3dDefaults` variable to set default values for `par3d()`, `material3d()`, and `bg3d()`. Initially `r3dDefaults` is defined as

    list(userMatrix = rotationMatrix(290*pi/180, 1, 0, 0),
         mouseMode = c("none", "trackball", "zoom", "fov", "pull"),
         FOV = 30,
         family = "sans",
         bg = list(color="white",
                   fogtype = "none"),
         material = list(color="black", fog = TRUE)
    )

Users can create their own default lists; e.g. to get the same result as `rgl.open()` would give, use

    open3d(params = list())

or

    r3dDefaults <- list()
    open3d()

## Material properties

The `rgl.material()` function has a large number of parameters. The pre-deprecation arguments were:

    function(
      color        = "white",
      alpha        = 1.0,
      lit          = TRUE,
      ambient      = "black",
      specular     = "white",
      emission     = "black",
      shininess    = 50.0,
      smooth       = TRUE,
      texture      = NULL,
      textype      = "rgb",
      texmipmap    = FALSE,
      texminfilter = "linear",
      texmagfilter = "linear",
      texenvmap    = FALSE,
      front        = "filled",
      back         = "filled",
      size         = 3.0,
      lwd          = 1.0,
      fog          = TRUE,
      point_antialias = FALSE,
      line_antialias = FALSE,
      depth_mask   = TRUE,
      depth_test   = "less",
      polygon_offset = c(0.0, 0.0),
      margin = "",
      floating = FALSE,
      tag = "",
      blend = c("src_alpha", "one_minus_src_alpha"),
      col,
      ...
    )

Thus a call like `rgl.material(color = "black")` will set the color to black, and will also set all of the other parameters to the default values listed above.

On the other hand, the arguments to `material3d()` are

    function (..., id = NULL)

Calling `material3d(color = "black")` will set the color to black and leave all other parameters unchanged.

## Primitive shapes

The primitive shapes (points etc.) can be set using calls like `rgl.points(x, y, z, color = "black")` or `points3d(x, y, z, color = "black")`.

The first difference is that `rgl.*` primitives will call `rgl.material()` to set the material properties: in this example `color` will be set to `black`, and all other parameters will be set to their defaults. The `*3d` versions of the primitives use `material3d()` to set material properties, so only those that were specified will be changed, and the original values will be restored afterwards.

The second difference is what happens if there is no window already open. The `rgl.*` functions will call `rgl.open()` (ignoring `r3dDefaults`), whereas the `*3d` functions will call `open3d()`.

# Why deprecate `rgl.*`?

Both of the systems worked, but they do not work together. For example, calling `rgl.points()` will have carry-on effects on later `points3d()` calls, whereas each `points3d()` call will just draw the points, it won’t affect future calls.

Users have found this confusing, and it makes their code hard to debug, and the `rgl` package hard to maintain. The `*3d` interface is more flexible, and more similar to the base graphics interface in R, so I’ve decided it will be the only one available going forward.

## Some `rgl.*` functions are not deprecated

There will still be some `rgl.*` functions in the package. These are functions that are mainly intended for programming, such as `rgl.attrib()` and `rgl.user2window()`, and a few legacy functions like `rgl.Sweave()` supporting older approaches of using `rgl`.

In a few cases both function versions are identical (`rgl.cur`, `rgl.ids`, and `rgl.pop` are identical to `cur3d`, `ids3d` and `pop3d` respectively), and for those the `rgl.*` versions will be kept, but the documentation will concentrate on the `*3d` functions.

# My package uses `rgl.*`. What do I need to do?

If your package is using `rgl.*` functions, the first step is to just make the substitutions suggested by the deprecation warning message. For example, if you use `rgl.points(rnorm(10), rnorm(10), rnorm(10))` try using `points3d(rnorm(10), rnorm(10), rnorm(10))` instead. In most cases this will give you what you want. In some cases more changes will be needed.

## `rgl.open` and `rgl.material`

See above if you were using these.

## Textures

The default color after `rgl.open()` was white, whereas with `open3d()` the default color is black, with a white background. Textures multiplicatively modify the color of the object, so after `open3d()`, a texture on an object will still appear black. Explicitly specifying `color = "white"` when a texture is used will fix this.

## `rgl.surface()`

The arguments to `rgl.surface()` and `surface3d()` functions are different. The argument lists are

    rgl.surface( x, z, y,
                 coords = 1:3,  ...,
                 normal_x = NULL, normal_y = NULL, normal_z = NULL,
                 texture_s = NULL, texture_t = NULL)
    surface3d(x, y = NULL, z = NULL,
              ...,
              normal_x = NULL, normal_y = NULL,
              normal_z = NULL,
              texture_s = NULL, texture_t=NULL)

Notice that the arguments are in a different order. Another difference is that `rgl.surface()` expects the surface to be defined in the `y` coordinate and viewed in the orientation produced by `rgl.open()`, not the one produced by `open3d()`. Up until very recently, `surface3d()` didn’t allow both `x` and `z` to be vectors.

The excellent `rayshader` package used the convention that the `y` argument held the surface, so the `y` direction should point up. Using `view3d(theta = 45, phi = 45)` (which it was already doing) gives a reasonable view.

## Lists of material names and par3d properties

Many functions in `rgl` and other packages use `...` to set material or `par3d` properties in a call, and for some, `...` will contain other optional arguments. Some packages used the argument list of `rgl.material()` to identify the material property names. Going forward, packages should use the variables

    rgl.material.names
    rgl.material.readonly

These are character variables holding all the material property names. (`rgl.material.names` contains all names; `rgl.material.readonly` is the read-only subset of that list.) There are also variables

    rgl.par3d.names
    rgl.par3d.readonly

which give the same information for `par3d`.

Since these variables are recently added, you will need to add a dependence on `rgl (>= 0.111.5)` if you use them.

## Others

If you have particular problems adapting other `rgl.*` to the `*3d` interface, please post them as issues on https://github.com/dmurdoch/rgl/issues . I’ll explain how to get what you want or fix things in `rgl` so you can do it.