Nothing
#'@title Render Movie
#'
#'@description Renders a movie using the \pkg{av} or \pkg{gifski} packages. Moves the camera around a 3D visualization
#'using either a standard orbit, or accepts vectors listing user-defined values for each camera parameter. If the latter,
#'the values must be equal in length to `frames` (or of length `1`, in which the value will be fixed).
#'
#'@param filename Filename. If not appended with `.mp4`, it will be appended automatically. If the file extension is `gif`,
#'the \pkg{gifski} package will be used to generate the animation.
#'@param type Default `orbit`, which orbits the 3D object at the user-set camera settings `phi`, `zoom`, and `fov`.
#'Other options are `oscillate` (sine wave around `theta` value, covering 90 degrees), or `custom` (which uses the values from the
#'`theta`, `phi`, `zoom`, and `fov` vectors passed in by the user).
#'@param frames Default `360`. Number of frames to render.
#'@param fps Default `30`. Frames per second. Recommmend either 30 or 60 for web.
#'@param phi Defaults to current view. Azimuth values, in degrees.
#'@param theta Default to current view. Theta values, in degrees.
#'@param zoom Defaults to the current view. Zoom value, between `0` and `1`.
#'@param fov Defaults to the current view. Field of view values, in degrees.
#'@param width Default `NULL`, uses the window size by default. Width of the movie. Note that the frames will still
#'be captured at the resolution (and aspect ratio) of the rgl window.
#'@param height Default `NULL`, uses the window size by default. Height of the movie. Note that the frames will still
#'be captured at the resolution (and aspect ratio) of the rgl window.
#'@param title_text Default `NULL`. Text. Adds a title to the movie, using magick::image_annotate.
#'@param title_offset Default `c(20,20)`. Distance from the top-left (default, `gravity` direction in
#'image_annotate) corner to offset the title.
#'@param title_size Default `30`. Font size in pixels.
#'@param title_color Default `black`. Font color.
#'@param title_font Default `sans`. String with font family such as "sans", "mono", "serif", "Times", "Helvetica",
#'"Trebuchet", "Georgia", "Palatino" or "Comic Sans".
#'@param title_bar_color Default `NULL`. If a color, this will create a colored bar under the title.
#'@param title_bar_alpha Default `0.5`. Transparency of the title bar.
#'@param title_position Default `northwest`. Position of the title.
#'@param image_overlay Default `NULL`. Either a string indicating the location of a png image to overlay
#'over the whole movie (transparency included), or a 4-layer RGBA array. This image will be resized to the
#'dimension of the movie if it does not match exactly.
#'@param vignette Default `FALSE`. If `TRUE` or numeric, a camera vignetting effect will be added to the image.
#'`1` is the darkest vignetting, while `0` is no vignetting. If vignette is a length-2 vector, the second entry will
#'control the blurriness of the vignette effect.
#'@param vignette_color Default `"black"`. Color of the vignette.
#'@param vignette_radius Default `1.3`. Radius of the vignette, as a porportion of the image dimensions.
#'@param audio Default `NULL`. Optional file with audio to add to the video.
#'@param progbar Default `TRUE` if interactive, `FALSE` otherwise. If `FALSE`, turns off progress bar.
#'Will display a progress bar when adding an overlay or title.
#'@param ... Additional parameters to pass to magick::image_annotate.
#'@export
#'@examples
#'if(interactive()) {
#'filename_movie = tempfile()
#'
#'#By default, the function produces a 12 second orbit at 30 frames per second, at 30 degrees azimuth.
#'\donttest{
#'montereybay %>%
#' sphere_shade(texture="imhof1") %>%
#' plot_3d(montereybay, zscale=50, water = TRUE, watercolor="imhof1",
#' waterlinecolor="white", waterlinealpha=0.5)
#'#Un-comment the following to run:
#'#render_movie(filename = filename_movie)
#'}
#'filename_movie = tempfile()
#'
#'#You can change to an oscillating orbit. The magnification is increased and azimuth angle set to 30.
#'#A title has also been added using the title_text argument.
#'\donttest{
#'#Un-comment the following to run:
#'#render_movie(filename = filename_movie, type = "oscillate",
#'# frames = 60, phi = 30, zoom = 0.8, theta = -90,
#'# title_text = "Monterey Bay: Oscillating")
#'}
#'filename_movie = tempfile()
#'
#'#Finally, you can pass your own set of values to the
#'#camera parameters as a vector with type = "custom".
#'
#'phivechalf = 30 + 60 * 1/(1 + exp(seq(-7, 20, length.out = 180)/2))
#'phivecfull = c(phivechalf, rev(phivechalf))
#'thetavec = -90 + 45 * sin(seq(0,359,length.out = 360) * pi/180)
#'zoomvec = 0.45 + 0.2 * 1/(1 + exp(seq(-5, 20, length.out = 180)))
#'zoomvecfull = c(zoomvec, rev(zoomvec))
#'\donttest{
#'#Un-comment the following to run
#'#render_movie(filename = filename_movie, type = "custom",
#'# frames = 360, phi = phivecfull, zoom = zoomvecfull, theta = thetavec)
#'}
#'}
render_movie = function(filename, type = "orbit", frames = 360, fps = 30,
phi = 30, theta = 0, zoom = NULL, fov = NULL,
width = NULL, height = NULL,
title_text = NULL, title_offset = c(20,20),
title_color = "black", title_size = 30, title_font = "sans",
title_bar_color = NULL, title_bar_alpha = 0.5,
image_overlay = NULL,
vignette = FALSE, vignette_color = "black", vignette_radius = 1.3,
title_position = "northwest",
audio=NULL, progbar = interactive(), ...) {
if(rgl::cur3d() == 0) {
stop("No rgl window currently open.")
}
if(is.null(filename)) {
stop("render_movie requires a filename")
}
movie_type = tools::file_ext(filename)
use_av = TRUE
if(movie_type %in% c("mp4","mkv", "mov","flv","")) {
if(movie_type == "") {
filename = paste0(filename,".mp4")
}
if(!(length(find.package("av", quiet = TRUE)) > 0)) {
stop("`av` package required for render_movie()")
}
} else if (movie_type == "gif") {
if(!(length(find.package("gifski", quiet = TRUE)) > 0)) {
stop("`gifski` package required for render_movie() gifs")
}
use_av = FALSE
}
if(!is.null(title_text)) {
has_title = TRUE
} else {
has_title = FALSE
}
if(length(title_offset) != 2) {
stop("`title_offset` needs to be length-2 vector")
}
if(!is.null(image_overlay)) {
if("character" %in% class(image_overlay)) {
image_overlay_file = image_overlay
has_overlay = TRUE
} else if("array" %in% class(image_overlay)) {
image_overlay_file = tempfile()
png::writePNG(image_overlay_file)
has_overlay = TRUE
}
} else {
has_overlay = FALSE
}
windowsize = rgl::par3d()$viewport
if(is.null(fov)) {
fov = rgl::par3d()$FOV
}
if(is.null(zoom)) {
zoom = rgl::par3d()$zoom
}
if(is.null(phi) || is.null(theta)) {
rotmat = rot_to_euler(rgl::par3d()$userMatrix)
if(is.null(phi)) {
phi = rotmat[1]
}
if(is.null(theta)) {
if(0.001 > abs(abs(rotmat[3]) - 180)) {
theta = -rotmat[2] + 180
} else {
theta = rotmat[2]
}
}
}
png_files = file.path(tempdir(), sprintf("image%d.png", seq_len(frames)))
on.exit(unlink(png_files))
if(type == "orbit") {
theta_vector = seq(0,360,length.out = frames+1)[-(frames+1)]
for(i in seq_len(frames)) {
render_camera(theta = theta_vector[i], phi = phi, zoom = zoom, fov = fov)
rgl::snapshot3d(filename = png_files[i], top = FALSE, webshot = FALSE)
}
} else if (type == "oscillate") {
theta_vector = theta + 45 * sin(seq(0,360,length.out = frames+1)[-(frames+1)]*pi/180)
for(i in seq_len(frames)) {
render_camera(theta = theta_vector[i], phi = phi, zoom = zoom, fov = fov)
rgl::snapshot3d(filename = png_files[i], top = FALSE, webshot = FALSE)
}
} else if (type == "custom") {
if(length(theta) == 1) theta = rep(theta, frames)
if(length(phi) == 1) phi = rep(phi, frames)
if(length(zoom) == 1) zoom = rep(zoom, frames)
if(length(fov) == 1) fov = rep(fov, frames)
if(!all(c(length(theta), length(phi), length(zoom),length(fov)) == frames)) {
stop("All camera vectors must be the same length (or fixed values)")
}
for(i in seq_len(frames)) {
render_camera(theta = theta[i], phi = phi[i], zoom = zoom[i], fov = fov[i])
rgl::snapshot3d(filename = png_files[i], top = FALSE, webshot = FALSE)
}
} else {
stop("Unknown type: ", type)
}
temp = png::readPNG(png_files[1])
dimensions = dim(temp)
if(!is.null(width)) {
dimensions[1] = width
}
if(!is.null(height)) {
dimensions[2] = height
}
if(dimensions[1] %% 2 != 0) {
dimensions[1] = dimensions[1] - 1
}
if(has_overlay) {
if(!(length(find.package("magick", quiet = TRUE)) > 0)) {
stop("`magick` package required for adding overlay")
}
if(progbar) {
pb = progress::progress_bar$new(
format = " Adding overlay image [:bar] :percent eta: :eta",
total = frames, width= 60)
}
for(i in seq_len(frames)) {
if(progbar) {
pb$tick()
}
rayimage::add_image_overlay(png_files[i], image_overlay = image_overlay_file,
filename = png_files[i])
}
}
if(vignette || is.numeric(vignette)) {
if(!(length(find.package("magick", quiet = TRUE)) > 0)) {
stop("`magick` package required for adding overlay")
}
if(progbar) {
pb = progress::progress_bar$new(
format = " Adding vignetting [:bar] :percent eta: :eta",
total = frames, width = 60)
}
for(i in seq_len(frames)) {
if(progbar) {
pb$tick()
}
rayimage::add_vignette(png_files[i], vignette = vignette, filename = png_files[i],
color = vignette_color,
radius = vignette_radius)
}
}
if(has_title) {
if(!(length(find.package("magick", quiet = TRUE)) > 0)) {
stop("`magick` package required for adding title")
}
if(progbar) {
pb = progress::progress_bar$new(
format = " Adding title text [:bar] :percent eta: :eta",
total = frames, width= 60)
}
for(i in seq_len(frames)) {
if(progbar) {
pb$tick()
}
rayimage::add_title(png_files[i], filename = png_files[i], title_text = title_text,
title_bar_color = title_bar_color,title_bar_alpha = title_bar_alpha,
title_offset = title_offset, title_color = title_color,
title_position = title_position,
title_size = title_size, title_font = title_font)
}
}
if(use_av) {
av::av_encode_video(png_files, output = filename, framerate = fps,
vfilter = paste0("scale=",dimensions[2],":-2"), audio=audio)
} else {
gifski::gifski(png_files=png_files, gif_file = filename, delay = 1/fps,
width = dimensions[1],height= dimensions[2])
}
}
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.