R/vis_meshes.R

Defines functions sortcoloredmeshes.by.hemi get.rglstyle.shiny get.rglstyle.glass2 get.rglstyle.glass get.rglstyle.edges get.rglstyle.semitransparent get.rglstyle.default get.rglstyle get.rglstyle.parameters vis.coloredmesh view_label3d vis.view vis.rotated.coloredmeshes vis.coloredmeshes.rotating vis.coloredpaths vis.renderable is.Triangles3D fsbrain.renderable vis.coloredmeshes

Documented in fsbrain.renderable get.rglstyle get.rglstyle.default get.rglstyle.edges get.rglstyle.glass get.rglstyle.glass2 get.rglstyle.parameters get.rglstyle.semitransparent get.rglstyle.shiny is.Triangles3D sortcoloredmeshes.by.hemi view_label3d vis.coloredmesh vis.coloredmeshes vis.coloredmeshes.rotating vis.coloredpaths vis.renderable vis.rotated.coloredmeshes vis.view

# Low-level visualization function for meshes.


#' @title Visualize a list of colored meshes in a single scene.
#'
#' @param coloredmeshes list of coloredmesh. A coloredmesh is a named list as returned by the coloredmesh.from.* functions. It has the entries 'mesh' of type tmesh3d, a 'col', which is a color specification for such a mesh.
#'
#' @param background string, background color passed to rgl::bg3d()
#'
#' @param skip_all_na logical, whether to skip (i.e., not render) meshes in the list that have the property 'render' set to FALSE. Defaults to TRUE. Practically, this means that a hemisphere for which the data was not given is not rendered, instead of being rendered in a single color.
#'
#' @param style a named list of style parameters or a string specifying an available style by name (e.g., 'shiny'). Defaults to 'default', the default style. Use the magic word 'from_mesh' to use the 'style' field of each coloredmesh instead of a single, global style: this allows you to give individual meshes in the scene their own look, e.g., to render a cortex mesh semi-transparently behind colored data meshes. Meshes which have no 'style' field fall back to the 'default' style. See \code{\link[fsbrain]{vis.subcortical.region.values}} for an example, and note that the scimesh renderer backend currently only supports the alpha channel of a style, not other material properties.
#'
#' @param rgloptions option list passed to \code{\link[rgl]{par3d}}. Example: \code{rgloptions = list("windowRect"=c(50,50,1000,1000))};
#'
#' @param rglactions named list. A list in which the names are from a set of pre-defined actions. Defaults to the empty list.
#'
#' @param draw_colorbar logical. Whether to draw a colorbar. WARNING: Will only show up if there is enough space in the plot area and does not resize properly. Defaults to FALSE. See \code{\link[fsbrain]{coloredmesh.plot.colorbar.separate}} for an alternative.
#'
#' @note To change or adapt the colorbar, you should use the makecmap_options parameter when constructing them in a vis function. See the example.
#'
#' @return the list of visualized coloredmeshes
#'
#' @examples
#' \dontrun{
#'    fsbrain::download_optional_data();
#'    subjects_dir = fsbrain::get_optional_data_filepath("subjects_dir");
#'    cm = vis.subject.morph.native(subjects_dir, 'subject1', 'thickness',
#'     makecmap_options=list('n'=100, 'colFn'=viridis::viridis));
#'    # You could mess with the meshes here.
#'    vis.coloredmeshes(cm);
#' }
#'
#' @importFrom rgl open3d bg3d wire3d par3d
#' @export
vis.coloredmeshes <- function(coloredmeshes, background="white", skip_all_na=TRUE, style="default", rgloptions = rglo(), rglactions=list(), draw_colorbar=FALSE) {

    if(!is.list(coloredmeshes)) {
        stop("Parameter coloredmeshes must be a list.");
    } else {
        # The check above does not help a lot, since all classes are also derived from 'list', so that will be true in most cases.
        if(length(coloredmeshes) < 1) {
            warning("Nothing to visualize.");
            return(invisible(NULL));
        } else {
            if(! fsbrain.renderable(coloredmeshes[[1]])) {
                stop("Pass a list of 'fs.coloredmesh', 'fs.coloredvoxels', or 'Triangles3D' instances.");
            }
        }
    }

    horizontal = FALSE;
    if(draw_colorbar == TRUE) {
        draw_colorbar = "vertical";
    }

    if(draw_colorbar == "vertical") {
        layout_mat = matrix(c(1, 2), ncol=2, byrow = T);
        layout_column_widths = c(3L, 1L);
        layout_row_heights = rep(1L, nrow(layout_mat));
    } else if(draw_colorbar == "horizontal") {
        horizontal = TRUE;
        layout_mat = matrix(c(1, 2), ncol=1, byrow = T);
        layout_row_heights = c(3L, 1L);
        layout_column_widths = rep(1L, ncol(layout_mat));
    } else if(draw_colorbar == FALSE) {
        # assume FALSE
        layout_mat = NULL;
        layout_column_widths = NULL;
        layout_row_heights = NULL;
    } else {
        stop("Invalid setting for 'draw_colorbar'. Use a logical value or one of 'horizontal' or 'vertical'.");
    }

    rgl::open3d();
    do.call(rgl::par3d, rgloptions);
    Sys.sleep(1);

    rgl::bg3d(background);

    if(is.character(draw_colorbar)) {
        rgl::layout3d(layout_mat, widths=layout_column_widths, height=layout_row_heights);
        rgl::next3d(reuse=TRUE);
    }

    for(cmesh in coloredmeshes) {
        vis.renderable(cmesh, skip_all_na=skip_all_na, style=style);
    }

    if(is.character(draw_colorbar)) {
        rgl::next3d(reuse=FALSE);
        draw.colorbar(coloredmeshes, horizontal=horizontal);
    }

    perform.rglactions(rglactions);

    return(invisible(coloredmeshes));
}


#' @title Check whether object can be rendered by fsbrain
#'
#' @param x any `R` object
#'
#' @return TRUE if *x* is an instance of a class that can be rendered by fsbrain visualization functions, and FALSE otherwise. Currently, the following types are renderable: `fs.coloredvoxels`, `fs.coloredmesh`, `fs.coloredpaths`, `Triangles3D`.
#'
#' @seealso \code{\link[fsbrain]{is.Triangles3D}}
#'
#' @keywords internal
fsbrain.renderable <- function(x) {
    return(is.fs.coloredvoxels(x) | is.fs.coloredmesh(x) | is.fs.coloredpaths(x) | is.Triangles3D(x));
}


#' @title Check whether object is a Triangles3D instance
#'
#' @param x any `R` object
#'
#' @return TRUE if its argument is a Triangles3D instance (that is, has "Triangles3D" amongst its classes) and FALSE otherwise.
#'
#' @keywords internal
is.Triangles3D <- function(x) inherits(x, "Triangles3D")


#' @title Visualize a renderable object
#'
#' @description Renders instances of `coloredmesh`, `coloredvoxels`, `coloredpaths` and `Triangles3D`.
#'
#' @param cmesh an instance of one of the supported renderable classes
#'
#' @param skip_all_na logical, whether to skip rendering hidden instances
#'
#' @param style a rendering style, can be a style name or a list defining an rgl material style
#'
#' @seealso \code{\link[fsbrain]{fsbrain.renderable}}
#'
#' @keywords internal
#' @importFrom utils modifyList
#' @importFrom rgl triangles3d
vis.renderable <- function(cmesh, skip_all_na=TRUE, style="default") {
    if(is.fs.coloredmesh(cmesh)) {
        if(!(skip_all_na && !cmesh$render)) {
            vis.coloredmesh(cmesh, style = style);
        }
    } else if (is.fs.coloredpaths(cmesh)) {
        if(!(skip_all_na && !cmesh$render)) {
            vis.coloredpaths(cmesh, style = style);
        }
    } else if (is.fs.coloredvoxels(cmesh)) {
        style_params = get.rglstyle.parameters(cmesh, style);
        if(hasIn(cmesh, 'color')) {
            style_params = modifyList(style_params, list("color"=cmesh$color));
        }
        do.call(rgl::triangles3d, c(list(cmesh$voxeltris), style_params));
    } else if(is.Triangles3D(cmesh)) {
        if (requireNamespace("misc3d", quietly = TRUE)) {
            style_params = get.rglstyle.parameters(cmesh, style);
            style_params = modifyList(style_params, list("add"=TRUE)); # Add image to existing scene, otherwise only the last one will be visible.
            do.call(misc3d::drawScene.rgl, c(list(cmesh), style_params));
        } else {
            warning("The 'misc3d' package must be installed to render 'Triangles3D' instances. Skipping visualization."); # nocov
        }
    } else {
        stop(sprintf("Received object with classes '%s', cannot render this. Pass an 'fs.coloredmesh', 'fs.coloredvoxels', 'fs.coloredpaths', or 'Triangles3D' instance.\n", paste(class(cmesh), collapse=" ")));  # nocov
    }
}


#' @title Draw the segments of an fs.coloredpaths instance with rgl.
#'
#' @description Uses rgl::segments3d, i.e., hardware lines, so that a line is always one pixel wide (times the requested width) no matter how far away it is from the camera. Segments which share a line width are drawn in a single call, because the line width is a material property (like the color, which can be set per segment).
#'
#' @param cpaths an fs.coloredpaths instance.
#'
#' @param style a rendering style, see \code{\link[fsbrain]{get.rglstyle}}.
#'
#' @return invisible NULL.
#'
#' @keywords internal
#' @importFrom utils modifyList
#' @importFrom rgl segments3d
vis.coloredpaths <- function(cpaths, style="default") {
    if(! is.fs.coloredpaths(cpaths)) {
        stop("Parameter 'cpaths' must be an 'fs.coloredpaths' instance.");
    }
    if(nrow(cpaths$from) < 1L) {
        return(invisible(NULL));
    }

    style_params = get.rglstyle.parameters(cpaths, style);

    for(line_width in unique(cpaths$width)) {
        sel = which(cpaths$width == line_width);
        # segments3d expects the coordinates of the segment endpoints interleaved in a single vector per axis.
        x = as.vector(rbind(cpaths$from[sel, 1], cpaths$to[sel, 1]));
        y = as.vector(rbind(cpaths$from[sel, 2], cpaths$to[sel, 2]));
        z = as.vector(rbind(cpaths$from[sel, 3], cpaths$to[sel, 3]));
        # The entries after the style parameters take precedence over the style, so that the
        # per-segment color and width win over the material defaults of the style.
        line_params = modifyList(style_params, list("color"=cpaths$col[sel], "lwd"=line_width, "lit"=cpaths$lit, "point_antialias"=TRUE, "line_antialias"=TRUE));
        do.call(rgl::segments3d, c(list(x, y, z), line_params));
    }

    return(invisible(NULL));
}


#' @title Visualize a list of colored meshes in a single scene and rotate them, movie-style.
#'
#' @param coloredmeshes list of coloredmesh. A coloredmesh is a named list as returned by the coloredmesh.from.* functions. It has the entries 'mesh' of type tmesh3d, a 'col', which is a color specification for such a mesh.
#'
#' @param background string, background color passed to rgl::bg3d()
#'
#' @param skip_all_na logical, whether to skip (i.e., not render) meshes in the list that have the property 'rendner' set to FALSE. Defaults to TRUE. Practically, this means that a hemisphere for which the data was not given is not rendered, instead of being rendered in a single color.
#'
#' @param style a named list of style parameters or a string specifying an available style by name (e.g., 'shiny'). Defaults to 'default', the default style.
#'
#' @param x rotation x axis value, passed to \code{\link[rgl]{spin3d}}. Defaults to 0.
#'
#' @param y rotation y axis value, passed to \code{\link[rgl]{spin3d}}. Defaults to 1.
#'
#' @param z rotation z axis value, passed to \code{\link[rgl]{spin3d}}. Defaults to 0.
#'
#' @param rpm rotation rpm value, passed to \code{\link[rgl]{spin3d}}. Defaults to 15.
#'
#' @param duration rotation duration value, passed to \code{\link[rgl]{spin3d}}. Defaults to 20.
#'
#' @param rgloptions option list passed to \code{\link[rgl]{par3d}}. Example: rgloptions = list("windowRect"=c(50,50,1000,1000));
#'
#' @param rglactions named list. A list in which the names are from a set of pre-defined actions. Defaults to the empty list.
#'
#' @return the list of visualized coloredmeshes
#'
#' @export
#' @importFrom rgl open3d bg3d wire3d play3d spin3d
vis.coloredmeshes.rotating <- function(coloredmeshes, background="white", skip_all_na=TRUE, style="default", x=0, y=0, z=1, rpm=6, duration=10, rgloptions = rglo(), rglactions = list()) {

    if(!is.list(coloredmeshes)) {
        stop("Parameter coloredmeshes must be a list.");
    }

    rgl::open3d();
    do.call(rgl::par3d, rgloptions);
    Sys.sleep(1);
    rgl::bg3d(background);
    for(cmesh in coloredmeshes) {
        vis.renderable(cmesh, skip_all_na=TRUE, style=style);
    }
    rgl::view3d(-90, 0);

    if (!rgl::rgl.useNULL()) {
        if(rglactions.has.key(rglactions, 'movie')) {
            movie = rglactions$movie;
            rgl::movie3d(rgl::spin3d(axis = c(x, y, z), rpm = rpm), duration = duration, movie = movie, fps = 20, dir=path.expand("~"));
            expected_movie_path = file.path(path.expand("~"), sprintf("%s.gif", movie));
            message(sprintf("Tried to write gif movie to user home, check file '%s'.\n", expected_movie_path));
        } else {
            rgl::play3d(rgl::spin3d(axis = c(x, y, z), rpm = rpm), duration = duration);
        }
    } else {
        message("Cannot show rotating scene with NULL device.");    # nocov
    }

    perform.rglactions(rglactions);
    invisible(coloredmeshes);
}



#' @title Rotate and visualize coloredmeshes, applying a style.
#'
#' @param coloredmeshes list of renderables. A coloredmesh is a named list as returned by the coloredmesh.from.* functions. It has the entries 'mesh' of type tmesh3d, a 'col', which is a color specification for such a mesh.
#'
#' @param rotation_angle angle in radians. Passed to \code{\link[rgl]{rotate3d}}.
#'
#' @param x x value passed to \code{\link[rgl]{rotate3d}}.
#'
#' @param y y value passed to \code{\link[rgl]{rotate3d}}.
#'
#' @param z z value passed to \code{\link[rgl]{rotate3d}}.
#'
#' @param style a named list of style parameters or a string specifying an available style by name (e.g., 'shiny'). Defaults to 'default', the default style.
#'
#' @param draw_colorbar logical. Whether to draw a colorbar.
#'
#' @keywords internal
vis.rotated.coloredmeshes <- function(renderables, rotation_angle, x, y, z, style="default", draw_colorbar=FALSE) {
    if(is.null(renderables)) {
        return(invisible(NULL));
    }
    rot_matrix = rotation.matrix(rotation_angle, x, y, z);
    for (mesh_idx in seq_len(length(renderables))) {     # usually this will only run once for the single mesh of a hemisphere.
        orig_renderable = renderables[[mesh_idx]];
        rotated_renderable = tryCatch(transform_renderable(orig_renderable, rot_matrix), error = function(e) e);
        if(inherits(rotated_renderable, "error")) {
            warning(sprintf("Rotation not supported for object of type '%s'. Not rendering object.\n", paste(class(orig_renderable), collapse = " ")));
        } else {
            vis.renderable(rotated_renderable, style=style);
        }
    }

    if(draw_colorbar) {
        draw.colorbar(renderables);
    }
    return(invisible(NULL));
}


#' @title Render renderables for a static view and orient the camera (camera-based).
#'
#' @description Renders the given renderables UNROTATED and orients the rgl
#' camera so the scene appears exactly as if the renderables had been rotated by
#' (rotation_angle, x, y, z) and then viewed from (theta, phi, fov=0). This is
#' the camera-based replacement for the legacy mesh-rotation approach
#' (\code{vis.rotated.coloredmeshes}); it is pixel-identical for the fsbrain
#' view angles (see tests/testthat/test-camera_unification.R).
#'
#' @param renderables list of renderables (coloredmesh, coloredvoxels,
#'   Triangles3D) to render into the current scene.
#'
#' @param style a rendering style, see \code{\link[fsbrain]{get.rglstyle}}.
#'
#' @param rotation_angle, x, y, z the mesh rotation that the camera must
#'   reproduce (passed to \code{\link[fsbrain]{rotation.matrix}}).
#'
#' @param theta, phi the fixed-camera angles (passed to \code{\link[rgl]{view3d}}).
#'
#' @keywords internal
vis.view <- function(renderables, style="default", rotation_angle=0, x=1, y=0, z=0, theta=0, phi=0) {
    if(is.null(renderables)) {
        return(invisible(NULL));
    }
    R_mesh = rotation.matrix(rotation_angle, x, y, z);
    for(renderable in renderables) {
        vis.renderable(renderable, style=style);
    }
    # Compose the fixed-camera orientation (view3d(theta, phi)) with the camera
    # rotation equivalent to rotating the mesh by R_mesh. transform_renderable
    # rotates meshes as t(R) %*% vb (column convention), hence the transpose.
    rgl::view3d(theta, phi, fov=0, interactive=FALSE);
    um_view = rgl::par3d("userMatrix");
    rgl::view3d(userMatrix = um_view %*% t(R_mesh), fov=0, interactive=FALSE);
    return(invisible(NULL));
}


#' @title Draw a view label at a position, compensating for the camera rotation.
#'
#' @description With camera-based views, geometry (and text) stays in world
#'   coordinates while the camera applies the view rotation. To keep a label at
#'   the same screen position relative to the (now unrotated) renderables as in
#'   the legacy mesh-rotation views, the label offset is pre-rotated by the
#'   view's mesh rotation matrix R_mesh.
#'
#' @param text character string, the label text.
#'
#' @param x, y, z numeric, the label offset in the rotated (view) frame.
#'
#' @param R_mesh 4x4 numeric rotation matrix, the view's mesh rotation (use
#'   \code{\link[fsbrain]{rotation.matrix}}), or NULL for no rotation (e.g.,
#'   the dorsal view).
#'
#' @keywords internal
view_label3d <- function(text, x, y, z, R_mesh=NULL) {
    coords = c(x, y, z);
    if(! is.null(R_mesh)) {
        coords = transform_coords(rbind(coords), R_mesh)[1, ];
    }
    rgl::text3d(coords[1], coords[2], coords[3], text);
    return(invisible(NULL));
}




#' @title Draw a coloredmesh using a style.
#'
#' @param cmesh a coloredmesh. A coloredmesh is a named list as returned by the coloredmesh.from.* functions. It has the entries 'mesh' of type tmesh3d, a 'col', which is a color specification for such a mesh.
#'
#' @param style a named list of style parameters or a string specifying an available style by name (e.g., 'shiny'). Defaults to 'default', the default style. Pass the magic word 'from_mesh' to try to retrieve a style (as a name or a style list) from the field `style` of the mesh, or default to "default" if the mesh has no such field.
#'
#' @seealso \code{\link[fsbrain]{vis.renderable}}
#'
#' @keywords internal
#' @importFrom rgl shade3d
vis.coloredmesh <- function(cmesh, style="default") {
    if(! is.fs.coloredmesh(cmesh)) {
        stop("Parameter cmesh must be an 'fs.coloredmesh' instance.");
    }
    style_params = get.rglstyle.parameters(cmesh, style);
    do.call(rgl::shade3d, c(list(cmesh$mesh, col=cmesh$col), style_params));
}


#' @title Produce the named list of style parameters from style definition.
#'
#' @description A style definition can be a character string like "shiny", already a parameter list, or a command like 'from_mesh' that tells us to get the style from the renderable. This function creates the final parameters from the definition and the renderable. Note that a mesh which carries its own style (in its 'style' field, see \code{\link[fsbrain]{coloredmesh.from.color}}) is rendered with that style whenever the requested style is the 'default' style: this way, scenes that assign individual styles to their meshes look as intended even if the rendering or export function is called without an explicit style.
#'
#' @param renderable A renderable (or any list) which includes a 'style' key. If it does not include such a key, the 'default' style will be used.
#'
#' @param style A style definition. Can be a character string like 'shiny' or 'from_mesh', or already a named list of material properties (which will be returned as-is).
#'
#' @return a style, resolved to a parameter list compatible with \code{\link[rgl]{material3d}}.
#'
#' @keywords internal
get.rglstyle.parameters <- function(renderable, style) {
    if(is.list(style)) {
        return(style);
    }
    if(! is.character(style)) {
        stop("Parameter 'style' must be a named list of style parameters or a string specifying an available style by name (e.g., 'default' or 'shiny').");
    }
    if(style == 'from_mesh') {
        style = if(! is.null(renderable$style)) renderable$style else 'default';
        return(get.rglstyle.parameters(renderable, style));
    }
    # If the caller asked for the default style but the mesh has its own style, use the mesh style.
    # (Guarded against 'default' in the mesh to avoid infinite recursion.)
    if(style == 'default' && ! is.null(renderable$style) && ! identical(renderable$style, 'default')) {
        return(get.rglstyle.parameters(renderable, renderable$style));
    }
    return(get.rglstyle(style));
}


#' @title Get the default visualization style parameters as a named list.
#'
#' @description Run \code{\link[rgl]{material3d}} without arguments to see valid style keywords to create new styles.
#'
#' @param style string. A style name. Available styles are one of: "default", "shiny", "semitransparent", "glass", "edges".
#'
#' @return a style, resolved to a parameter list compatible with \code{\link[rgl]{material3d}}.
#'
#' @note In addition to the style names listed above, the magic word 'from_mesh' can be passed as parameter 'style' to the rendering functions: it makes them use the style stored in the 'style' field of each individual mesh (see \code{\link[fsbrain]{coloredmesh.from.color}}). This allows you to give individual meshes of a scene their own look, e.g., to render a cortex mesh semi-transparently behind colored data meshes, see \code{\link[fsbrain]{vis.subcortical.region.values}}. Note that the scimesh renderer backend currently only supports the alpha channel of a style, not other material properties.
#'
#' @seealso \code{\link[rgl]{shade3d}} can use the returned style
#'
#' @export
get.rglstyle <- function(style) {
    if(style == "default") {
        return(get.rglstyle.default());
    } else if (style == "shiny") {
        return(get.rglstyle.shiny());
    } else if (style == "edges") {
        return(get.rglstyle.edges());
    } else if (style == "semitransparent") {
        return(get.rglstyle.semitransparent());
    } else if (style == "glass") {
        return(get.rglstyle.glass());
    } else if (style == "glass2") {
        return(get.rglstyle.glass2());
    } else {
        stop(sprintf("No such rendering style: '%s'. Try something like 'default', 'shiny', 'edges' or 'semitransparent'.\n", style));
    }
}


#' @title Get the default visualization style parameters as a named list.
#'
#' @description The default rendering style, which is is rather plain. Does not look super fancy, but allows for clear data visualization without distractions. Hint: Run \code{\link[rgl]{material3d}} without arguments to see valid style keywords to create new styles.
#'
#' @return named list, style parameters that can be passed to \code{\link[rgl]{shade3d}} via \code{\link{do.call}}.
#'
#' @keywords internal
get.rglstyle.default <- function() {
    return(list("shininess"=50, specular="black"));
}


#' @title Get the semi-transparent visualization style parameters as a named list.
#'
#' @description Semitransparent rendering style. This style has a very negative impact on rendering performance (in interactive mode). Hint: Run \code{\link[rgl]{material3d}} without arguments to see valid style keywords to create new styles.
#'
#' @return named list, style parameters that can be passed to \code{\link[rgl]{shade3d}} via \code{\link{do.call}}.
#'
#' @keywords internal
get.rglstyle.semitransparent <- function() {
    return(list("shininess"=50, specular="black", alpha=0.5, front="filled", back="lines"));
}


#' @title Get the mesh edges visualization style parameters as a named list.
#'
#' @description Mesh edges rendering style. Zoom in enough to see them.
#'
#' @return named list, style parameters that can be passed to \code{\link[rgl]{shade3d}} via \code{\link{do.call}}.
#'
#' @keywords internal
get.rglstyle.edges <- function() {
    return(list(front="lines", back="lines", lwd=2.0, size=5.0));
}


#' @title Get the glass visualization style parameters as a named list.
#'
#' @description Glass-brain rendering style. This style has a very negative impact on rendering performance (especially in interactive mode). Hint: Run \code{\link[rgl]{material3d}} without arguments to see valid style keywords to create new styles.
#'
#' @return named list, style parameters that can be passed to \code{\link[rgl]{shade3d}} via \code{\link{do.call}}.
#'
#' @keywords internal
get.rglstyle.glass <- function() {
    return(list("shininess"=50, specular="black", alpha=0.4, front="filled", back="culled"));
}


#' @title Get the glass2 visualization style parameters as a named list.
#'
#' @description Glass-brain rendering style. This style has a very negative impact on rendering performance (especially in interactive mode). Hint: Run \code{\link[rgl]{material3d}} without arguments to see valid style keywords to create new styles.
#'
#' @return named list, style parameters that can be passed to \code{\link[rgl]{shade3d}} via \code{\link{do.call}}.
#'
#' @keywords internal
get.rglstyle.glass2 <- function() {
    return(list("shininess"=80, specular="white", alpha=0.4, front="filled", back="culled"));
}


#' @title Get a shiny visualization style.
#'
#' @description A shiny or glossy rendering style. Looks a bit more modern, but the resulting highlights may make the interpretation of the plotted data a bit harder in some areas. Hint: Run \code{\link[rgl]{material3d}} without arguments to see valid style keywords to create new styles.
#'
#' @return named list, style parameters that can be passed to to \code{\link[rgl]{shade3d}} via \code{\link{do.call}}.
#'
#' @keywords internal
get.rglstyle.shiny <- function() {
    return(list("shininess"=50, specular="white"));
}


#' @title Sort coloredmeshes into 2 lists by their 'hemi' property.
#'
#' @param coloredmeshes list of coloredmeshes or other renderables
#'
#' @return named list with two entries: "lh": list of coloredmeshes that have property hemi set to 'lh'. "rh": list of coloredmeshes that have property hemi set to 'rh'. The rest is ignored.
#'
#' @keywords internal
sortcoloredmeshes.by.hemi <- function(coloredmeshes) {
    lh_meshes = list();
    rh_meshes = list();
    for (mesh_idx in seq_len(length(coloredmeshes))) {
        cmesh = coloredmeshes[[mesh_idx]];
        mesh_name = sprintf("mesh%d", mesh_idx);
        # Note that a hemi value of NULL is the documented default for meshes which are not
        # hemisphere-specific, e.g., volume iso-surfaces created with Triangles3D.to.coloredmesh().
        # Such meshes are shown in the views of both hemispheres, just like hemi='both'.
        if(! ('hemi' %in% names(cmesh)) || is.null(cmesh$hemi)) {
            if(! ('hemi' %in% names(cmesh)) && is.fs.coloredmesh(cmesh)) {
                warning(sprintf("Assigning coloredmesh # %d which has no hemi value at all to both hemispheres.\n", mesh_idx));
            }
            lh_meshes[[mesh_name]] = cmesh;
            rh_meshes[[mesh_name]] = cmesh;
        } else {
            if(cmesh$hemi == 'lh') {
                lh_meshes[[mesh_name]] = cmesh;
            } else if(cmesh$hemi == 'rh') {
                rh_meshes[[mesh_name]] = cmesh;
            } else if(cmesh$hemi == 'both') {
                lh_meshes[[mesh_name]] = cmesh;
                rh_meshes[[mesh_name]] = cmesh;
            } else {
                warning(sprintf("Ignoring mesh # %d with invalid hemi value '%s'.\n", mesh_idx, cmesh$hemi));
            }
        }
    }
    return(list("lh"=lh_meshes, "rh"=rh_meshes));
}

Try the fsbrain package in your browser

Any scripts or data that you put into this service are public.

fsbrain documentation built on Sept. 27, 2026, 1:07 a.m.