Nothing
#' Render a 3D mesh to an image
#'
#' Renders a single mesh using the scimesh software renderer.
#' The mesh can be specified either as separate
#' \code{vertices}/\code{triangles} matrices, as a scimesh mesh
#' descriptor list, or as an rgl \code{tmesh3d}-style list (with
#' \code{vb}/\code{it} components). rgl meshes are transparently
#' converted via \code{\link{mesh_from_rgl}()}.
#'
#' @param vertices Either an Nx3 numeric matrix of vertex positions,
#' or a scimesh mesh descriptor list (with \code{vertices} and
#' \code{triangles} components), or an rgl-style list (with
#' \code{vb} and \code{it} components).
#' @param triangles Mx3 integer matrix of triangle indices (1-based).
#' Ignored when \code{vertices} is a list.
#' @param colors Optional Nx4 numeric matrix of RGBA vertex colors (0-1).
#' Use \code{face_colors} (Mx4) for per-triangle colours instead.
#' @param face_colors Optional Mx4 numeric matrix of per-face RGBA colors,
#' one row per triangle. When present, all three vertices of a triangle
#' use the same colour. Takes precedence over vertex \code{colors}.
#' @param normals Optional Nx3 numeric matrix of vertex normals.
#' @param uv Optional Nx2 numeric matrix of texture coordinates (0-1).
#' @param texture Optional texture image as a 3D array (H x W x 3 or 4)
#' with values in \code{[0, 1]}, e.g. from \code{png::readPNG()}.
#' @param camera A camera list from \code{camera()} or \code{camera_auto()}.
#' @param options A render options list from \code{render_options()}.
#' @return A list with components \code{width}, \code{height}, and
#' \code{pixels} (raw vector of RGBA values).
#'
#' @examples
#' # Render a simple colored triangle
#' verts <- matrix(c(0, 0, 0, 1, 0, 0, 0.5, 1, 0), ncol = 3, byrow = TRUE)
#' tris <- matrix(1L, nrow = 1, ncol = 3)
#' cols <- matrix(c(1, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1), ncol = 4, byrow = TRUE)
#' img <- render_mesh(verts, tris, colors = cols)
#' tmp_file <- tempfile(fileext = ".png")
#' write_png(img, tmp_file)
#'
#' # Render from a mesh descriptor list (scimesh format)
#' mesh_desc <- list(vertices = verts, triangles = tris, colors = cols)
#' img <- render_mesh(mesh_desc)
#'
#' @export
render_mesh <- function(vertices, triangles = NULL, colors = NULL,
face_colors = NULL, normals = NULL, uv = NULL,
texture = NULL,
camera = NULL,
options = render_options()) {
# Transparently accept rgl or scimesh mesh descriptors
if (is.list(vertices) && !is.matrix(vertices)) {
converted <- as_scimesh_mesh(vertices)
if (!is.null(converted$colors) && is.null(colors)) {
colors <- converted$colors
}
if (!is.null(converted$face_colors) && is.null(face_colors)) {
face_colors <- converted$face_colors
}
if (!is.null(converted$normals) && is.null(normals)) {
normals <- converted$normals
}
vertices <- converted$vertices
triangles <- converted$triangles
}
if (!is.matrix(vertices) || ncol(vertices) != 3L) {
stop("vertices must be an Nx3 numeric matrix")
}
if (!is.matrix(triangles) || ncol(triangles) != 3L) {
stop("triangles must be an Mx3 integer matrix")
}
if (is.null(camera)) {
camera <- camera_auto(vertices)
}
mesh <- list(
vertices = vertices,
triangles = triangles
)
if (!is.null(colors)) {
if (!is.matrix(colors) || ncol(colors) < 3L || ncol(colors) > 4L) {
stop("colors must be an Nx3 or Nx4 numeric matrix")
}
mesh$colors <- colors
}
if (!is.null(face_colors)) {
if (!is.matrix(face_colors) || ncol(face_colors) < 3L || ncol(face_colors) > 4L) {
stop("face_colors must be an Mx3 or Mx4 numeric matrix")
}
mesh$face_colors <- face_colors
}
if (!is.null(normals)) {
if (!is.matrix(normals) || ncol(normals) != 3L) {
stop("normals must be an Nx3 numeric matrix")
}
mesh$normals <- normals
}
if (!is.null(uv)) {
if (!is.matrix(uv) || ncol(uv) != 2L) {
stop("uv must be an Nx2 numeric matrix")
}
mesh$uv <- uv
}
if (!is.null(texture)) {
mesh$texture <- texture
}
scimesh_render_mesh(mesh, camera, options)
}
#' Render multiple meshes to an image
#'
#' Renders a list of meshes as a single scene using the scimesh
#' software renderer. Each element can be a scimesh mesh descriptor
#' or an rgl-style mesh (with \code{vb}/\code{it}); rgl meshes are
#' transparently converted.
#'
#' @param meshes A list of mesh descriptors. Each element is a list
#' with components \code{vertices} (Nx3 matrix), \code{triangles}
#' (Mx3 integer matrix), and optionally \code{colors}, \code{face_colors},
#' \code{normals}, and \code{default_color}.
#' Elements may also be rgl-style lists (with \code{vb} and \code{it}),
#' which are converted automatically.
#' @param camera A camera list from \code{camera()} or \code{camera_auto()}.
#' @param options A render options list from \code{render_options()}.
#' @return A list with components \code{width}, \code{height}, and
#' \code{pixels} (raw vector of RGBA values).
#'
#' @examples
#' # Render two cubes side by side
#' cube1 <- generate_cuboid(c(-1.5, 0, 0), c(0.8, 0.8, 0.8), c(1, 0, 0, 1))
#' cube2 <- generate_cuboid(c( 1.5, 0, 0), c(0.8, 0.8, 0.8), c(0, 0, 1, 1))
#' cam <- camera_auto(list(cube1, cube2), direction = c(1, 1, 1))
#' img <- render_scene(list(cube1, cube2), cam,
#' render_options(width = 800, height = 600, background_color = c(1, 1, 1, 1)))
#' tmp_file <- tempfile(fileext = ".png")
#' write_png(img, tmp_file)
#'
#' # Render multiple meshes together
#' scimesh_cube <- generate_cuboid(c(-1, 0, 0), c(0.5, 0.5, 0.5))
#' sphere <- generate_sphere(c(1, 0, 0), radius = 0.5, color = c(0, 1, 0, 1))
#' cam <- camera_auto(list(scimesh_cube, sphere), direction = c(1, 1, 1))
#' img <- render_scene(list(scimesh_cube, sphere), cam,
#' render_options(width = 400, height = 300, background_color = c(1, 1, 1, 1)))
#'
#' @export
render_scene <- function(meshes, camera, options = render_options()) {
if (!is.list(meshes)) {
stop("meshes must be a list of mesh descriptors")
}
for (i in seq_along(meshes)) {
meshes[[i]] <- as_scimesh_mesh(meshes[[i]])
}
for (i in seq_along(meshes)) {
m <- meshes[[i]]
if (!is.list(m) || is.null(m$vertices) || is.null(m$triangles)) {
stop("each mesh must be a list with 'vertices' and 'triangles'")
}
}
scimesh_render_scene(meshes, camera, options)
}
#' Create render options
#'
#' @param width Output image width in pixels.
#' @param height Output image height in pixels.
#' @param shading Shading mode: \code{"smooth"} or \code{"flat"}.
#' @param backface_culling Whether to cull back-facing triangles.
#' @param background_color Background RGBA color as numeric vector of
#' length 4 (values 0-1).
#' @param default_color Default vertex color when no colors are provided.
#' @param invert_normals Whether to invert surface normals.
#' @param wireframe Whether to render in wireframe mode.
#' @param wireframe_color RGBA color for wireframe edges (0-1
#' scale). Default \code{c(0, 0, 0, 1)} (black).
#' @param projection Projection type: \code{"perspective"} (default)
#' or \code{"orthographic"}. Orthographic gives a parallel
#' projection (no perspective foreshortening), matching rgl's
#' \code{view3d(fov=0)} convention.
#' @param specular_color Specular highlight color (0-1 scale). When
#' \code{shininess > 0}, a Blinn-Phong highlight in this colour is
#' added where the surface faces the camera. Default
#' \code{c(0, 0, 0, 0)} (off).
#' @param shininess Specular exponent controlling highlight sharpness.
#' Higher values produce a tighter spot. Typical values: 32 (soft
#' plastic), 64 (shiny), 128 (glass). Default \code{0} (off).
#' @param lights A list of light descriptors, each a list with
#' \code{position} (length-3 direction vector or point position),
#' \code{color} (length-4 RGBA, 0-1 scale), \code{intensity}
#' (numeric, default 1), and \code{directional} (logical, default
#' \code{TRUE}). When empty or \code{NULL}, a single headlight at
#' \code{c(0, 0, 1)} is used (the original behaviour).
#' @param ambient Ambient light contribution (0-1). Default 0.3.
#' @param contrast Contrast adjustment applied after shading, before
#' uint8_t conversion. Default 1.0 (no change). Values > 1.0 produce
#' darker darks and lighter highlights (S-curve). Formula:
#' \code{(value - 0.5) * contrast + 0.5}, clamped to \code{[0, 1]}.
#' @param fog_enabled Enable depth cueing (fog). Default \code{FALSE}.
#' @param fog_start Z-depth where fog begins (0 = near plane, 1 = far
#' plane). Default 0.
#' @param fog_end Z-depth where fog is fully opaque. Default 1.
#' @param fog_color RGBA fog colour (0-1 scale). Defaults to
#' \code{background_color}.
#' @param threads Number of render threads. 0 = auto-detect (use all
#' cores), 1 = single-threaded (deterministic). Default 0.
#' Requires OpenMP at compile time.
#' @param clip_planes A list of clip plane descriptors, each a list
#' with \code{normal} (length-3 vector) and \code{offset} (numeric).
#' Points satisfying \code{dot(normal, position) + offset >= 0} are
#' kept. Default \code{NULL} (no clipping).
#' @param ssao_enabled Enable screen-space ambient occlusion.
#' Default \code{FALSE}.
#' @param ssao_radius Screen-space sample radius in pixels. Default 16.
#' @param ssao_intensity Occlusion strength (0-1). Default 0.8.
#' @param aa_samples Anti-aliasing supersampling factor. Renders
#' internally at \code{width * aa_samples} x
#' \code{height * aa_samples}, then downsamples to the requested
#' size via box averaging. Default \code{1} (no AA), \code{2} for
#' 2x2 SSAA, \code{4} for 4x4.
#' @return A render options list for use with \code{render_mesh()} or
#' \code{render_scene()}.
#'
#' @examples
#' # Default options
#' opts <- render_options()
#'
#' # High-resolution with anti-aliasing and specular highlights
#' opts <- render_options(width = 1200, height = 900,
#' aa_samples = 2L,
#' specular_color = c(0.4, 0.4, 0.4, 1),
#' shininess = 64)
#'
#' # Wireframe with transparent background
#' opts <- render_options(wireframe = TRUE,
#' wireframe_color = c(0, 0, 0, 1),
#' background_color = c(0, 0, 0, 0))
#'
#' @export
render_options <- function(width = 800L, height = 600L,
shading = c("smooth", "flat"),
backface_culling = TRUE,
background_color = c(0, 0, 0, 0),
default_color = c(0.7, 0.7, 0.7, 1),
invert_normals = FALSE,
wireframe = FALSE,
wireframe_color = c(0, 0, 0, 1),
projection = c("perspective", "orthographic"),
specular_color = c(0, 0, 0, 0),
shininess = 0,
lights = NULL,
ambient = 0.3,
contrast = 1.0,
fog_enabled = FALSE,
fog_start = 0,
fog_end = 1,
fog_color = c(0, 0, 0, 0),
threads = 0L,
clip_planes = NULL,
ssao_enabled = FALSE,
ssao_radius = 16,
ssao_intensity = 0.8,
aa_samples = 1L) {
shading <- match.arg(shading)
structure(list(
width = as.integer(width),
height = as.integer(height),
shading = shading,
backface_culling = isTRUE(backface_culling),
background_color = as.numeric(background_color),
default_color = as.numeric(default_color),
invert_normals = isTRUE(invert_normals),
wireframe = isTRUE(wireframe),
wireframe_color = as.numeric(wireframe_color),
projection = match.arg(projection),
specular_color = as.numeric(specular_color),
shininess = as.numeric(shininess),
lights = lights,
ambient = as.numeric(ambient),
contrast = as.numeric(contrast),
fog_enabled = isTRUE(fog_enabled),
fog_start = as.numeric(fog_start),
fog_end = as.numeric(fog_end),
fog_color = as.numeric(fog_color),
threads = as.integer(threads),
clip_planes = clip_planes,
ssao_enabled = isTRUE(ssao_enabled),
ssao_radius = as.numeric(ssao_radius),
ssao_intensity = as.numeric(ssao_intensity),
aa_samples = as.integer(aa_samples)
), class = "scimesh_options")
}
#' Render raw triangles without index buffer
#'
#' Renders triangle geometry where positions and colours are given
#' as flat arrays with 3 vertices per triangle (no index buffer).
#' Useful for voxel renderings, misc3d isosurfaces, and other
#' dynamically generated geometry.
#'
#' @param positions Nx3 numeric matrix of vertex positions, where N
#' is a multiple of 3 (3 per triangle).
#' @param colors Nx4 numeric matrix of RGBA colours (0-1 scale).
#' @param camera A camera list from \code{camera()} or
#' \code{camera_auto()}.
#' @param options Render options from \code{render_options()}.
#' @return An image list with \code{width}, \code{height},
#' \code{pixels}.
#'
#' @examples
#' # Render a single red triangle from raw vertices
#' positions <- matrix(c(0, 0, 0, 1, 0, 0, 0.5, 1, 0), ncol = 3, byrow = TRUE)
#' colors <- matrix(c(1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1), ncol = 4, byrow = TRUE)
#' cam <- camera_auto(positions)
#' img <- render_triangles(positions, colors, cam)
#'
#' @export
render_triangles <- function(positions, colors, camera,
options = render_options()) {
if (!is.matrix(positions) || ncol(positions) != 3L) {
stop("positions must be an Nx3 numeric matrix")
}
n <- nrow(positions)
if (n %% 3L != 0L) {
stop("positions must have a multiple of 3 rows")
}
if (!is.matrix(colors) || nrow(colors) != n || ncol(colors) < 3L) {
stop("colors must be an Nx4 numeric matrix matching positions")
}
scimesh_render_triangles_raw(positions, colors, camera, options)
}
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.