| Title: | Headless Publication-Quality 3D Mesh Rendering Engine |
|---|---|
| Description: | A fast, GPU-free 3D software renderer written in modern C++17 with native R bindings. Renders triangle meshes to publication-quality images entirely on the CPU, requiring no display server or graphics hardware. Features multi-light Blinn-Phong shading, screen-space ambient occlusion, anti-aliasing, depth fog, transparency, wireframe rendering, texture mapping, screen-space lines and text labels, and procedural geometry generation. Supports standard mesh file formats with PNG and PPM output. Works on high-performance computing clusters, headless servers, containers, and continuous integration pipelines, making it suitable for scientific visualization across neuro-imaging, molecular structures, and general 3D graphics. |
| Authors: | Tim Schäfer [aut, cre], Martin Hořeňovský [ctb] (Author of Catch2 (cpp_tests/catch_amalgamated.{h,cpp})), Christophe Riccio [ctb] (Author of GLM - OpenGL Mathematics (src/third_party/glm/)), Dimitri Diakopoulos [ctb] (Author of tinyply (src/third_party/tinyply.{h,cpp})), Tim Schäfer [ctb] (Author of libfs (src/third_party/libfs.h)), Sebastian Reiter [ctb] (Author of stl_reader (src/third_party/stl_reader.h)), Sean Barrett [ctb] (Author of stb libraries (src/third_party/stb_image*.h, src/third_party/stb_truetype.h)), Rasmus Andersson [ctb] (Author of the Inter font (inst/extdata/Inter-Regular.ttf, SIL OFL 1.1)) |
| Maintainer: | Tim Schäfer <[email protected]> |
| License: | MIT + file LICENSE |
| Version: | 0.4.0 |
| Built: | 2026-09-26 13:32:02 UTC |
| Source: | https://github.com/dfsp-spirit/scimesh |
Maps numeric per-vertex (or per-element) data to RGBA colours using a colormap. Handles multi-dataset data (e.g., two brain hemispheres), NaN values, and optional outlier clipping via winsorizing.
apply_colormap( data, colormap = viridis_colormap(256L), limits = NULL, nan_color = c(0.5, 0.5, 0.5, 1), winsor_percentiles = NULL )apply_colormap( data, colormap = viridis_colormap(256L), limits = NULL, nan_color = c(0.5, 0.5, 0.5, 1), winsor_percentiles = NULL )
data |
A numeric vector, or a list of numeric vectors for multi-dataset mapping (e.g., list(lh_data, rh_data)). |
colormap |
A colormap specification: a function f(n) returning n hex colour strings (e.g., viridis_colormap), a character vector of hex colours, or an Nx3/Nx4 matrix of RGBA values in [0,1]. Default is viridis_colormap(256L). |
limits |
How the data value range is determined. NULL (default) auto-detects from finite values after winsorizing. c(min, max) sets an explicit fixed range. "global" pools all datasets for a shared range. "each" uses independent per-dataset ranges. |
nan_color |
RGBA colour for NaN/NA values as a length-3 (RGB) or length-4 (RGBA) numeric vector in [0,1]. Default mid-grey. |
winsor_percentiles |
Optional c(lower, upper) percentiles for outlier clipping, e.g. c(0.02, 0.98). NULL disables winsorizing. |
If data is a single vector: an Nx4 numeric matrix of RGBA colours. If data is a list: a list of Nx4 matrices.
Attributes on the result provide metadata. Single-dataset: data_min, data_max, raw_min, raw_max, winsor_lo, winsor_hi, nan_count. Multi-dataset: pooled_data_min, pooled_data_max (use for a colourbar), data_ranges, winsor_cutoffs, nan_counts.
data <- c(1.2, 3.4, NA, 2.1, 5.0, 2.8) colors <- apply_colormap(data) noisy <- c(rnorm(95, mean = 50, sd = 10), 200, -50) colors <- apply_colormap(noisy, winsor_percentiles = c(0.02, 0.98)) lh <- c(2.3, 2.1, NA, 3.4) rh <- c(2.5, 2.0, NA, 3.1) colors <- apply_colormap(list(lh, rh), colormap = viridis_colormap(256L), limits = "global", winsor_percentiles = c(0.02, 0.98), nan_color = c(1, 1, 1, 1))data <- c(1.2, 3.4, NA, 2.1, 5.0, 2.8) colors <- apply_colormap(data) noisy <- c(rnorm(95, mean = 50, sd = 10), 200, -50) colors <- apply_colormap(noisy, winsor_percentiles = c(0.02, 0.98)) lh <- c(2.3, 2.1, NA, 3.4) rh <- c(2.5, 2.0, NA, 3.1) colors <- apply_colormap(list(lh, rh), colormap = viridis_colormap(256L), limits = "global", winsor_percentiles = c(0.02, 0.98), nan_color = c(1, 1, 1, 1))
Evaluates a Bezier curve of any degree with de Casteljau's algorithm. Unlike
spline_path, the control points are **not** points on the curve:
only the first and the last one are, and the rest pull the curve like a
magnet. This is the function to use when the input really is a control
polygon (a font outline, a vector graphics path, a designed shape); use
spline_path when your points are positions the curve should
pass through.
bezier_path(control_points, samples = 64L)bezier_path(control_points, samples = 64L)
control_points |
Nx3 numeric matrix of control points (at least 2). |
samples |
Number of points to emit along the curve (default 64, at least 2). Both endpoints are included in the result. |
An Nx3 numeric matrix of path points, or an empty (0-row) matrix if there are fewer than two control points.
# A quadratic Bezier arc, drawn as a tube: arc <- bezier_path(matrix(c(0, 0, 0, 1, 1, 0, 2, 0, 0), ncol = 3, byrow = TRUE)) nrow(arc)# A quadratic Bezier arc, drawn as a tube: arc <- bezier_path(matrix(c(0, 0, 0, 1, 1, 0, 2, 0, 0), ncol = 3, byrow = TRUE)) nrow(arc)
Defines a camera for rendering by specifying the eye position, look-at center, up vector, projection type, and field of view.
camera( eye, center, up = c(0, 1, 0), projection = c("perspective", "orthographic"), fov = 45 )camera( eye, center, up = c(0, 1, 0), projection = c("perspective", "orthographic"), fov = 45 )
eye |
Numeric vector of length 3: camera position. |
center |
Numeric vector of length 3: point the camera looks at. |
up |
Numeric vector of length 3: camera up direction. |
projection |
Projection type: |
fov |
Field of view in degrees (perspective only). |
A camera list suitable for render_mesh() or
render_scene().
cam <- camera(eye = c(0, 0, 5), center = c(0, 0, 0)) cam$eyecam <- camera(eye = c(0, 0, 5), center = c(0, 0, 0)) cam$eye
Computes a camera position that frames the entire mesh in view.
The camera is placed on the side given by direction and looks
back at the mesh, at a distance that ensures the mesh fits within the
field of view.
camera_auto( mesh, direction = c(0, 0, -1), up = c(0, 1, 0), fov = 45, margin = 1.1, rgl_compat = FALSE, projection = c("perspective", "orthographic") )camera_auto( mesh, direction = c(0, 0, -1), up = c(0, 1, 0), fov = 45, margin = 1.1, rgl_compat = FALSE, projection = c("perspective", "orthographic") )
mesh |
Either an Nx3 numeric matrix of vertex positions, or a
mesh descriptor list with a |
direction |
Direction from the mesh towards the camera, as a
length-3 vector: it selects the side you view the mesh from (the
camera is placed at |
up |
The up vector as a length-3 vector. Default |
fov |
Field of view in degrees. Default 45° (30° when
|
margin |
Extra margin factor (1.0 = tight fit, 1.1 = 10% margin). |
rgl_compat |
Logical. If |
projection |
Projection type: |
When rgl_compat = TRUE, the camera mimics rgl's default
auto-framing behaviour: a 30° FOV, 15° elevation, and the distance
is computed from the bounding sphere of the mesh (the
half-diagonal of the axis-aligned bounding box), reproducing the
formula distance = sphere_radius / sin(FOV/2) used by rgl.
A camera list, with S3 class "scimesh_camera".
This function frames a mesh (or a set of vertices). It does not know
about scene contents such as line layers or text labels; use
camera_fit_scene to fit a camera to a whole scene, including
the line layers that contribute to the scene bounds.
verts <- matrix(c(-1,-1,-1, 1,-1,-1, 1,1,-1, -1,1,-1, -1,-1, 1, 1,-1, 1, 1,1, 1, -1,1, 1), ncol = 3, byrow = TRUE) tris <- matrix(c(0L,3L,2L, 0L,2L,1L, 4L,5L,6L, 4L,6L,7L, 0L,1L,5L, 0L,5L,4L, 2L,3L,7L, 2L,7L,6L, 0L,4L,7L, 0L,7L,3L, 1L,2L,6L, 1L,6L,5L), ncol = 3, byrow = TRUE) mesh <- list(vertices = verts, triangles = tris) cam <- camera_auto(mesh, direction = c(1, 1, 1)) cam_rgl <- camera_auto(mesh, rgl_compat = TRUE)verts <- matrix(c(-1,-1,-1, 1,-1,-1, 1,1,-1, -1,1,-1, -1,-1, 1, 1,-1, 1, 1,1, 1, -1,1, 1), ncol = 3, byrow = TRUE) tris <- matrix(c(0L,3L,2L, 0L,2L,1L, 4L,5L,6L, 4L,6L,7L, 0L,1L,5L, 0L,5L,4L, 2L,3L,7L, 2L,7L,6L, 0L,4L,7L, 0L,7L,3L, 1L,2L,6L, 1L,6L,5L), ncol = 3, byrow = TRUE) mesh <- list(vertices = verts, triangles = tris) cam <- camera_auto(mesh, direction = c(1, 1, 1)) cam_rgl <- camera_auto(mesh, rgl_compat = TRUE)
Computes a camera that frames the contents of a scene (see
scene), i.e. its meshes together with the line layers that
contribute to the scene bounds (see line_layer, parameter
affects_bounds). Use this instead of camera_auto when
the camera has to consider something else than a mesh: a scene that contains
only line layers (e.g. a tractogram or a connectome without a brain surface)
is framed by those lines, and decorational layers
(affects_bounds = FALSE) are ignored.
camera_fit_scene( scene, direction = c(0, 0, -1), up = c(0, 1, 0), fov = 45, margin = 1.1, projection = c("perspective", "orthographic") )camera_fit_scene( scene, direction = c(0, 0, -1), up = c(0, 1, 0), fov = 45, margin = 1.1, projection = c("perspective", "orthographic") )
scene |
A scene descriptor list, see |
direction |
Length-3 view direction, from the camera towards the scene
(default |
up |
Length-3 up vector (default |
fov |
Vertical field of view in degrees (default 45). |
margin |
Scale factor applied to the fitted distance; values above 1 leave a margin around the content (default 1.1). |
projection |
Projection type, |
The camera is placed on the line from the center of the bounding box along
direction, at a distance that makes the content fit into the field of
view, exactly like camera_auto does it for a mesh.
A camera list (see camera()) with class
"scimesh_camera".
camera_auto for meshes, scene,
scene_set_line_affects_bounds
# A scene without any mesh is framed by its lines. line <- line_layer(matrix(c(0, 0, 0), ncol = 3), matrix(c(2, 0, 0), ncol = 3)) sc <- scene(list(), lines = line) cam <- camera_fit_scene(sc, direction = c(0, 0, -1))# A scene without any mesh is framed by its lines. line <- line_layer(matrix(c(0, 0, 0), ncol = 3), matrix(c(2, 0, 0), ncol = 3)) sc <- scene(list(), lines = line) cam <- camera_fit_scene(sc, direction = c(0, 0, -1))
Rotates a camera's eye position and up vector around its center by a given angle about a rotation axis. Useful for generating turntable-style frame sequences.
camera_orbit(camera, axis = c(0, 0, 1), angle_degrees)camera_orbit(camera, axis = c(0, 0, 1), angle_degrees)
camera |
A camera list from |
axis |
Rotation axis as a length-3 vector. Default |
angle_degrees |
Rotation angle in degrees. |
A camera list with S3 class "scimesh_camera".
mesh <- generate_torus(c(0, 0, 0)) cam <- camera_auto(mesh, direction = c(1, 1, 1)) cam2 <- camera_orbit(cam, axis = c(0, 0, 1), angle_degrees = 90)mesh <- generate_torus(c(0, 0, 0)) cam <- camera_auto(mesh, direction = c(1, 1, 1)) cam2 <- camera_orbit(cam, axis = c(0, 0, 1), angle_degrees = 90)
Defines a clipping plane for render_options(). Geometry on the
negative side of the plane is removed, i.e. a point p is kept when
dot(normal, p) + offset >= 0.
clip_plane(normal, offset = 0, space = c("world", "eye"))clip_plane(normal, offset = 0, space = c("world", "eye"))
normal |
Numeric vector of length 3: the plane normal. It points
toward the side of the plane that is kept. It does not have to be
unit-length; it is normalized internally and |
offset |
Numeric scalar: the signed distance of the plane from the
origin along |
space |
Character, either
|
By default the plane is defined in world space, so the cut is a
fixed feature of the scene: it does not move when the camera is moved
around, which is what you want for cross-sections and multi-view figures.
Set space = "eye" for a camera-relative plane that travels with the
camera (the classic OpenGL glClipPlane behaviour), e.g. for
cutaway views.
A list with components normal, offset and
space, suitable for the clip_planes argument of
render_options().
# World space (default): keep the half of the scene with x <= 0. # The cut stays at x = 0, no matter where the camera is placed. clip_plane(normal = c(-1, 0, 0), offset = 0) # Keep only the part with z >= -0.5 (remove everything below z = -0.5): clip_plane(normal = c(0, 0, 1), offset = 0.5) # Eye space: remove everything closer than 2 units to the camera # (a camera-attached cutaway). clip_plane(normal = c(0, 0, -1), offset = -2, space = "eye") # A world-space cut through a cuboid; the cut stays at x = 0 for any camera cuboid <- generate_cuboid(c(0, 0, 0), c(1, 1, 1)) cut_opts <- render_options(clip_planes = list( clip_plane(normal = c(-1, 0, 0), offset = 0))) mesh_pixels <- function(img) sum(image_to_array(img)[, , 1] < 1) cam <- camera(eye = c(3, 3, 3), center = c(0, 0, 0)) full <- render_mesh(cuboid$vertices, cuboid$triangles, camera = cam) cut <- render_mesh(cuboid$vertices, cuboid$triangles, camera = cam, options = cut_opts) mesh_pixels(cut) < mesh_pixels(full) # TRUE: half of the cuboid is gone# World space (default): keep the half of the scene with x <= 0. # The cut stays at x = 0, no matter where the camera is placed. clip_plane(normal = c(-1, 0, 0), offset = 0) # Keep only the part with z >= -0.5 (remove everything below z = -0.5): clip_plane(normal = c(0, 0, 1), offset = 0.5) # Eye space: remove everything closer than 2 units to the camera # (a camera-attached cutaway). clip_plane(normal = c(0, 0, -1), offset = -2, space = "eye") # A world-space cut through a cuboid; the cut stays at x = 0 for any camera cuboid <- generate_cuboid(c(0, 0, 0), c(1, 1, 1)) cut_opts <- render_options(clip_planes = list( clip_plane(normal = c(-1, 0, 0), offset = 0))) mesh_pixels <- function(img) sum(image_to_array(img)[, , 1] < 1) cam <- camera(eye = c(3, 3, 3), center = c(0, 0, 0)) full <- render_mesh(cuboid$vertices, cuboid$triangles, camera = cam) cut <- render_mesh(cuboid$vertices, cuboid$triangles, camera = cam, options = cut_opts) mesh_pixels(cut) < mesh_pixels(full) # TRUE: half of the cuboid is gone
Creates a horizontal colorbar as a 4-channel RGBA array. The color
strip and optional tick labels are rendered to PNG using base R
graphics (headless-safe). Returns a 3D array suitable for
image_to_array() or direct composition.
colorbar_horizontal( colormap, n_colors = 256L, width = 600L, height = 80L, ticks = NULL, tick_labels = NULL, data_range = c(0, 1), label_cex = 1, title = NULL, background = c(1, 1, 1, 1) )colorbar_horizontal( colormap, n_colors = 256L, width = 600L, height = 80L, ticks = NULL, tick_labels = NULL, data_range = c(0, 1), label_cex = 1, title = NULL, background = c(1, 1, 1, 1) )
colormap |
A vector of colors or a function returning colors
(e.g. |
n_colors |
Number of discrete color segments in the gradient. |
width |
Output width in pixels. |
height |
Output height in pixels. |
ticks |
Numeric vector of tick positions in data units
(matching |
tick_labels |
Character vector of tick labels. If |
data_range |
The data range that |
label_cex |
Label size multiplier. |
title |
Optional title string drawn above the color strip (horizontal) or to the right (vertical). |
background |
Background RGBA color (0-1 scale). |
A 3D array of dimensions (height, width, 4) with values in
[0, 1].
cbar <- colorbar_horizontal(viridis_colormap, data_range = c(-2, 3), title = "Value") dim(cbar) # height x width x 4cbar <- colorbar_horizontal(viridis_colormap, data_range = c(-2, 3), title = "Value") dim(cbar) # height x width x 4
Creates a vertical colorbar as a 4-channel RGBA array.
colorbar_vertical( colormap, n_colors = 256L, width = 80L, height = 600L, ticks = NULL, tick_labels = NULL, data_range = c(0, 1), label_cex = 1, title = NULL, background = c(1, 1, 1, 1) )colorbar_vertical( colormap, n_colors = 256L, width = 80L, height = 600L, ticks = NULL, tick_labels = NULL, data_range = c(0, 1), label_cex = 1, title = NULL, background = c(1, 1, 1, 1) )
colormap |
A vector of colors or a function returning colors. |
n_colors |
Number of discrete color segments in the gradient. |
width |
Output width in pixels. |
height |
Output height in pixels. |
ticks |
Numeric vector of tick positions in data units.
If |
tick_labels |
Character vector of tick labels. |
data_range |
The data range that |
label_cex |
Label size multiplier. |
title |
Optional title string drawn to the right of the color strip. |
background |
Background RGBA color (0-1 scale). |
A 3D array of dimensions (height, width, 4) with values in
[0, 1].
cbar <- colorbar_vertical(viridis_colormap, data_range = c(0, 100), title = "Count") dim(cbar)cbar <- colorbar_vertical(viridis_colormap, data_range = c(0, 100), title = "Count") dim(cbar)
Arranges rendered images (from render_mesh() or
render_scene()) into a grid layout and optionally appends
a colorbar. All composition is done with pure R array operations.
compose_layout( images, nrow = NULL, ncol = NULL, colorbar = NULL, colorbar_height = 80L, colorbar_width = 80L, background = c(0, 0, 0, 0), colorbar_side = c("right", "left"), crop = FALSE )compose_layout( images, nrow = NULL, ncol = NULL, colorbar = NULL, colorbar_height = 80L, colorbar_width = 80L, background = c(0, 0, 0, 0), colorbar_side = c("right", "left"), crop = FALSE )
images |
A list of images, each a list with |
nrow |
Number of rows in the grid layout. |
ncol |
Number of columns in the grid layout. If both
|
colorbar |
Optional colorbar array (from
|
colorbar_height |
Height of the colorbar row in pixels. Only used when appending a horizontal colorbar. |
colorbar_width |
Width of the colorbar column in pixels. Only used when appending a vertical colorbar. |
background |
Background RGBA color for padding (0-1 scale). |
colorbar_side |
For vertical colorbars, whether to place
the bar on the |
crop |
Logical. If |
A list with width, height, pixels
suitable for write_png() or image_to_array().
mesh1 <- generate_cuboid(c(-1.5, 0, 0), c(0.5, 0.5, 0.5), c(1, 0, 0, 1)) mesh2 <- generate_cuboid(c( 1.5, 0, 0), c(0.5, 0.5, 0.5), c(0, 0, 1, 1)) img1 <- render_mesh(mesh1$vertices, mesh1$triangles) img2 <- render_mesh(mesh2$vertices, mesh2$triangles) result <- compose_layout(list(img1, img2), nrow = 1L) tmp_file <- tempfile(fileext = ".png") write_png(result, tmp_file)mesh1 <- generate_cuboid(c(-1.5, 0, 0), c(0.5, 0.5, 0.5), c(1, 0, 0, 1)) mesh2 <- generate_cuboid(c( 1.5, 0, 0), c(0.5, 0.5, 0.5), c(0, 0, 1, 1)) img1 <- render_mesh(mesh1$vertices, mesh1$triangles) img2 <- render_mesh(mesh2$vertices, mesh2$triangles) result <- compose_layout(list(img1, img2), nrow = 1L) tmp_file <- tempfile(fileext = ".png") write_png(result, tmp_file)
Computes smooth vertex normals by averaging face normals. Returns
the same mesh with a normals component (Nx3 numeric matrix).
Useful for imported meshes that lack pre-computed normals.
compute_vertex_normals(mesh)compute_vertex_normals(mesh)
mesh |
A mesh descriptor list with |
The mesh with a normals component added.
mesh <- generate_cuboid(c(0, 0, 0), c(1, 1, 1)) mesh <- compute_vertex_normals(mesh) nrow(mesh$normals)mesh <- generate_cuboid(c(0, 0, 0), c(1, 1, 1)) mesh <- compute_vertex_normals(mesh) nrow(mesh$normals)
Text labels are drawn with a TrueType font that ships with the package
(inst/extdata/Inter-Regular.ttf, SIL Open Font License 1.1), so labels
look the same everywhere and no system font is required. This function
returns the path of the font that will be used by default.
default_font()default_font()
It can be overridden without touching any code by setting the
SCIMESH_FONT environment variable to the path of another .ttf
file, or per call by passing font_file to
text_layer(), text_extent() and friends.
A character scalar: the path to an existing font file.
default_font()default_font()
Returns a blue-white-red diverging colormap suitable for displaying signed morphometry data (e.g. cortical thickness Z-scores).
diverging_colormap(n)diverging_colormap(n)
n |
Number of colors. |
A character vector of hex color strings.
cols <- diverging_colormap(256) plot(1:256, pch = 15, col = cols, cex = 2, axes = FALSE, xlab = "", ylab = "")cols <- diverging_colormap(256) plot(1:256, pch = 15, col = cols, cex = 2, axes = FALSE, xlab = "", ylab = "")
scimesh stores texture coordinates in image space, with v = 0
at the top edge of the texture image — the same rule as every other
coordinate in scimesh (c(0, 0) addresses the top-left pixel of the
texture image, c(1, 1) the bottom-right one). OBJ and PLY files,
OpenGL, rgl and tools like Blender and MeshLab use the opposite convention
(v = 0 at the bottom), so UVs taken from those sources have to be
converted once; this function does that, instead of you having to rewrite the
second column by hand.
flip_uvs(mesh)flip_uvs(mesh)
mesh |
A mesh descriptor (scimesh or rgl format, see
|
Geometry, colors and normals are untouched. A mesh without texture coordinates is returned unchanged, so calling this is safe either way.
The mesh with flipped UVs.
render_mesh (the uv and texture
arguments)
quad <- list(vertices = matrix(c(-1, -1, 0, 1, -1, 0, 1, 1, 0, -1, -1, 0, 1, 1, 0, -1, 1, 0), ncol = 3, byrow = TRUE), triangles = matrix(c(1, 2, 3, 1, 3, 4), ncol = 3, byrow = TRUE), # UVs with v = 0 at the bottom (OBJ/OpenGL convention) uv = matrix(c(0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0), ncol = 2, byrow = TRUE)) flipped <- flip_uvs(quad) flipped$uv[, 2] # v is now measured from the top of the texturequad <- list(vertices = matrix(c(-1, -1, 0, 1, -1, 0, 1, 1, 0, -1, -1, 0, 1, 1, 0, -1, 1, 0), ncol = 3, byrow = TRUE), triangles = matrix(c(1, 2, 3, 1, 3, 4), ncol = 3, byrow = TRUE), # UVs with v = 0 at the bottom (OBJ/OpenGL convention) uv = matrix(c(0, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0), ncol = 2, byrow = TRUE)) flipped <- flip_uvs(quad) flipped$uv[, 2] # v is now measured from the top of the texture
Reports which font file is used (and where it lives), its family name and
its vertical metrics. Useful to check that the bundled font was found and to
see what a custom .ttf file contains.
font_info(font_file = NULL, size = 18)font_info(font_file = NULL, size = 18)
font_file |
Path to a |
size |
Text height in output pixels (default 18). |
A list with components family, path, size,
ascent, descent and line_gap.
font_info()font_info()
Creates a 3D arrow from from to to, with a cylindrical
shaft and a conical head.
generate_arrow( from, to, shaft_radius = 0.1, head_radius = 0.3, head_length = 0.6, segments = 32, color = c(1, 1, 1, 1) )generate_arrow( from, to, shaft_radius = 0.1, head_radius = 0.3, head_length = 0.6, segments = 32, color = c(1, 1, 1, 1) )
from |
Length-3 start point. |
to |
Length-3 end point (tip of the arrowhead). |
shaft_radius |
Radius of the shaft cylinder. |
head_radius |
Radius at the base of the conical head. |
head_length |
Length of the arrowhead. |
segments |
Subdivision count (default 32). |
color |
Length-4 RGBA colour. |
A mesh descriptor list.
mesh <- generate_arrow(c(0, 0, 0), c(0, 2, 0)) nrow(mesh$vertices)mesh <- generate_arrow(c(0, 0, 0), c(0, 2, 0)) nrow(mesh$vertices)
Creates three coloured arrow meshes (red X, green Y, blue Z) from a centre point.
generate_axes(center = c(0, 0, 0), size = 1, shaft_radius = 0.02)generate_axes(center = c(0, 0, 0), size = 1, shaft_radius = 0.02)
center |
Length-3 vector: origin of the axes. |
size |
Length of each axis. |
shaft_radius |
Cylinder radius for axis shafts. |
A mesh descriptor list suitable for render_mesh()
or inclusion in a scene list.
axes_mesh <- generate_axes(size = 2) nrow(axes_mesh$vertices)axes_mesh <- generate_axes(size = 2) nrow(axes_mesh$vertices)
Creates 12 edge segments around an axis-aligned bounding box.
generate_bbox(bbox, color = c(0, 0, 0, 1), radius = 0.01)generate_bbox(bbox, color = c(0, 0, 0, 1), radius = 0.01)
bbox |
A bounding box list from |
color |
RGBA colour for the edges (length 4, 0-1 scale). |
radius |
Cylinder radius for the edges. |
A mesh descriptor list suitable for render_mesh()
or inclusion in a scene list.
mesh <- generate_cuboid(c(0, 0, 0), c(1, 2, 3)) bbox_mesh <- generate_bbox(mesh) nrow(bbox_mesh$vertices)mesh <- generate_cuboid(c(0, 0, 0), c(1, 2, 3)) bbox_mesh <- generate_bbox(mesh) nrow(bbox_mesh$vertices)
Creates a cone from base to tip with the given base
radius, subdivided into segments around the axis.
The base cap is included.
generate_cone(base, tip, radius = 0.5, segments = 32, color = c(1, 1, 1, 1))generate_cone(base, tip, radius = 0.5, segments = 32, color = c(1, 1, 1, 1))
base |
Length-3 vector: centre of the circular base. |
tip |
Length-3 vector: tip of the cone. |
radius |
Base radius. |
segments |
Subdivision count (default 32). |
color |
Length-4 RGBA colour. |
A mesh descriptor list.
mesh <- generate_cone(c(0, -1, 0), c(0, 1, 0), radius = 0.8) nrow(mesh$vertices)mesh <- generate_cone(c(0, -1, 0), c(0, 1, 0), radius = 0.8) nrow(mesh$vertices)
Creates an axis-aligned cuboid (box) centred at center
with the given half-extents along each axis.
generate_cuboid(center, half_extents, color = c(0.7, 0.7, 0.7, 1))generate_cuboid(center, half_extents, color = c(0.7, 0.7, 0.7, 1))
center |
Length-3 vector: centre of the cuboid. |
half_extents |
Length-3 vector: half-width, half-height, half-depth. |
color |
Length-4 RGBA colour (0-1 scale). |
A mesh descriptor list.
mesh <- generate_cuboid(c(0, 0, 0), c(1, 2, 0.5)) nrow(mesh$vertices) nrow(mesh$triangles)mesh <- generate_cuboid(c(0, 0, 0), c(1, 2, 0.5)) nrow(mesh$vertices) nrow(mesh$triangles)
Creates a cylinder from start to end with the given
radius, subdivided into segments around the axis.
Both end caps are included unless caps = FALSE is passed.
generate_cylinder( start, end, radius = 0.5, segments = 32, color = c(1, 1, 1, 1), caps = TRUE )generate_cylinder( start, end, radius = 0.5, segments = 32, color = c(1, 1, 1, 1), caps = TRUE )
start |
Length-3 vector: cylinder start point. |
end |
Length-3 vector: cylinder end point. |
radius |
Cylinder radius. |
segments |
Subdivision count (default 32). |
color |
Length-4 RGBA colour. |
caps |
Whether to close both ends with caps (default TRUE). Pass FALSE for an open tube, which roughly halves the number of vertices and triangles. Useful for edges whose ends are hidden by other geometry. |
A mesh descriptor list.
mesh <- generate_cylinder(c(0, -1, 0), c(0, 1, 0), radius = 0.5) nrow(mesh$vertices) open <- generate_cylinder(c(0, -1, 0), c(0, 1, 0), radius = 0.5, caps = FALSE) nrow(open$vertices)mesh <- generate_cylinder(c(0, -1, 0), c(0, 1, 0), radius = 0.5) nrow(mesh$vertices) open <- generate_cylinder(c(0, -1, 0), c(0, 1, 0), radius = 0.5, caps = FALSE) nrow(open$vertices)
Batched variant of generate_cylinder(): all cylinders are generated
into one mesh. This is the function to use for thousands of straight edges,
e.g. the edges of a network graph or a connectome. Pass caps = FALSE
to leave the ends open, which is usually what you want when the ends are
hidden inside spherical nodes (and roughly halves the geometry).
generate_multi_cylinders( from, to, radii = 0.1, colors = c(1, 1, 1, 1), segments = 12L, caps = TRUE )generate_multi_cylinders( from, to, radii = 0.1, colors = c(1, 1, 1, 1), segments = 12L, caps = TRUE )
from |
Nx3 numeric matrix of start points (or a length-3 vector). |
to |
Nx3 numeric matrix of end points (same number of rows as
|
radii |
Numeric vector of radii (length 1, recycled; or one per cylinder). |
colors |
RGBA colour(s): a single vector applied to all cylinders, or an
Nx4 numeric matrix (values in |
segments |
Subdivision count around the circumference (default 12). |
caps |
Whether to close both ends of every cylinder (default TRUE). |
A mesh descriptor list with vertices, triangles and
colors.
generate_cylinder, generate_tubes
from <- matrix(c(0, 0, 0, 1, 0, 0), ncol = 3, byrow = TRUE) to <- matrix(c(0, 3, 0, 1, 3, 0), ncol = 3, byrow = TRUE) mesh <- generate_multi_cylinders(from, to, radii = 0.1, colors = c(0.7, 0.7, 0.7, 1), caps = FALSE) nrow(mesh$vertices) > 0from <- matrix(c(0, 0, 0, 1, 0, 0), ncol = 3, byrow = TRUE) to <- matrix(c(0, 3, 0, 1, 3, 0), ncol = 3, byrow = TRUE) mesh <- generate_multi_cylinders(from, to, radii = 0.1, colors = c(0.7, 0.7, 0.7, 1), caps = FALSE) nrow(mesh$vertices) > 0
Batched variant of generate_sphere(): all spheres are generated into
one mesh with a single vertex/triangle array, which is much faster than
generating and merging them one by one. This is the function to use for
thousands of nodes, e.g. the nodes of a network graph or a point cloud. The
returned mesh can be added to a scene and rendered with render_scene()
or render_mesh().
generate_multi_spheres( centers, radii = 1, colors = c(1, 1, 1, 1), segments = 16L )generate_multi_spheres( centers, radii = 1, colors = c(1, 1, 1, 1), segments = 16L )
centers |
Nx3 numeric matrix of sphere centres (or a length-3 vector for a single sphere). |
radii |
Numeric vector of radii (length 1, recycled; or one per sphere). |
colors |
RGBA colour(s): a single vector applied to all spheres, or an
Nx4 numeric matrix (values in |
segments |
Subdivision count per sphere (default 16). |
A mesh descriptor list with vertices, triangles and
colors.
generate_sphere, generate_multi_cylinders
centers <- matrix(c(0, 0, 0, 2, 0, 0), ncol = 3, byrow = TRUE) mesh <- generate_multi_spheres(centers, radii = c(0.5, 0.3), colors = c(1, 0, 0, 1), segments = 12) nrow(mesh$vertices) > 0centers <- matrix(c(0, 0, 0, 2, 0, 0), ncol = 3, byrow = TRUE) mesh <- generate_multi_spheres(centers, radii = c(0.5, 0.3), colors = c(1, 0, 0, 1), segments = 12) nrow(mesh$vertices) > 0
Creates a flat rectangular plane centred at center and
oriented perpendicular to normal.
generate_plane( center = c(0, 0, 0), normal = c(0, 1, 0), half_size_x = 1, half_size_y = 1, color = c(0.7, 0.7, 0.7, 1) )generate_plane( center = c(0, 0, 0), normal = c(0, 1, 0), half_size_x = 1, half_size_y = 1, color = c(0.7, 0.7, 0.7, 1) )
center |
Length-3 vector: centre of the plane. |
normal |
Length-3 vector: surface normal. |
half_size_x |
Half-extent along the first tangent axis. |
half_size_y |
Half-extent along the second tangent axis. |
color |
Length-4 RGBA colour. |
A mesh descriptor list.
mesh <- generate_plane(c(0, 0, 0), normal = c(0, 1, 0), half_size_x = 2, half_size_y = 1) nrow(mesh$vertices)mesh <- generate_plane(c(0, 0, 0), normal = c(0, 1, 0), half_size_x = 2, half_size_y = 1) nrow(mesh$vertices)
Creates a pyramid with a square base centred at base_center
in the XZ plane, with the apex above it along Y.
generate_pyramid( base_center, apex, half_width = 1, color = c(0.7, 0.7, 0.7, 1) )generate_pyramid( base_center, apex, half_width = 1, color = c(0.7, 0.7, 0.7, 1) )
base_center |
Length-3 vector: centre of the square base. |
apex |
Length-3 vector: position of the tip. |
half_width |
Half-width of the square base. |
color |
Length-4 RGBA colour. |
A mesh descriptor list.
mesh <- generate_pyramid(c(0, 0, 0), c(0, 2, 0), half_width = 1) mesh$verticesmesh <- generate_pyramid(c(0, 0, 0), c(0, 2, 0), half_width = 1) mesh$vertices
Creates a UV sphere centred at center with the given
radius. The sphere is subdivided into segments
rings and segments per ring.
generate_sphere(center, radius = 1, segments = 32, color = c(1, 1, 1, 1))generate_sphere(center, radius = 1, segments = 32, color = c(1, 1, 1, 1))
center |
Length-3 vector: sphere centre. |
radius |
Sphere radius. |
segments |
Subdivision count (default 32). |
color |
Length-4 RGBA colour. |
A mesh descriptor list.
mesh <- generate_sphere(c(0, 0, 0), radius = 1.5, segments = 32) nrow(mesh$vertices)mesh <- generate_sphere(c(0, 0, 0), radius = 1.5, segments = 32) nrow(mesh$vertices)
Creates a tetrahedron (triangular pyramid) from four arbitrary 3D points.
generate_tetrahedron(p0, p1, p2, p3, color = c(0.7, 0.7, 0.7, 1))generate_tetrahedron(p0, p1, p2, p3, color = c(0.7, 0.7, 0.7, 1))
p0 |
Length-3 vector: first vertex. |
p1 |
Length-3 vector: second vertex. |
p2 |
Length-3 vector: third vertex. |
p3 |
Length-3 vector: fourth vertex. |
color |
Length-4 RGBA colour. |
A mesh descriptor list.
mesh <- generate_tetrahedron( c(0, 0, 0), c(1, 0, 0), c(0.5, 1, 0), c(0.5, 0.5, 1)) nrow(mesh$vertices)mesh <- generate_tetrahedron( c(0, 0, 0), c(1, 0, 0), c(0.5, 1, 0), c(0.5, 0.5, 1)) nrow(mesh$vertices)
Creates a torus (donut shape) centred at center, lying in
the XZ plane.
generate_torus( center = c(0, 0, 0), major_radius = 1, minor_radius = 0.3, major_segments = 32, minor_segments = 16, color = c(0.7, 0.7, 0.7, 1) )generate_torus( center = c(0, 0, 0), major_radius = 1, minor_radius = 0.3, major_segments = 32, minor_segments = 16, color = c(0.7, 0.7, 0.7, 1) )
center |
Length-3 vector: centre of the torus. |
major_radius |
Radius of the ring (tube path). |
minor_radius |
Radius of the tube cross-section. |
major_segments |
Number of segments around the ring. |
minor_segments |
Number of segments around the tube. |
color |
Length-4 RGBA colour. |
A mesh descriptor list.
mesh <- generate_torus(major_radius = 2, minor_radius = 0.5) nrow(mesh$vertices)mesh <- generate_torus(major_radius = 2, minor_radius = 0.5) nrow(mesh$vertices)
Sweeps a circular cross-section along the points of path, which allows
for curved shapes such as arcs, Bezier samples of network edges or
streamlines. A path of exactly two points produces the same mesh as
generate_cylinder().
generate_tube( path, radius = 0.1, segments = 12L, color = c(1, 1, 1, 1), cap_start = TRUE, cap_end = TRUE )generate_tube( path, radius = 0.1, segments = 12L, color = c(1, 1, 1, 1), cap_start = TRUE, cap_end = TRUE )
path |
Nx3 numeric matrix of path points (or a length-3 vector). |
radius |
Tube radius (default 0.1). |
segments |
Subdivision count around the circumference (default 12). |
color |
Length-4 RGBA colour. |
cap_start |
Whether to close the beginning of the tube (default TRUE). |
cap_end |
Whether to close the end of the tube (default TRUE). |
The cross-section frames are computed by parallel transport (rotation-minimizing frames), so the tube does not twist around its own axis. Consecutive duplicate points are removed; a path with fewer than two distinct points yields an empty mesh.
A mesh descriptor list.
generate_tubes, generate_cylinder
path <- matrix(c(0, 0, 0, 1, 1, 0, 2, 0, 0), ncol = 3, byrow = TRUE) arc <- generate_tube(path, radius = 0.1, segments = 12, cap_start = FALSE, cap_end = FALSE) nrow(arc$vertices) > 0path <- matrix(c(0, 0, 0, 1, 1, 0, 2, 0, 0), ncol = 3, byrow = TRUE) arc <- generate_tube(path, radius = 0.1, segments = 12, cap_start = FALSE, cap_end = FALSE) nrow(arc$vertices) > 0
Batched variant of generate_tube(): all tubes are generated into one
mesh. Paths may differ in length. This is the function to use for curved
edges, e.g. connectome edges drawn as arcs.
generate_tubes( paths, radii = 0.1, colors = c(1, 1, 1, 1), segments = 12L, caps = FALSE )generate_tubes( paths, radii = 0.1, colors = c(1, 1, 1, 1), segments = 12L, caps = FALSE )
paths |
List of Nx3 numeric matrices (one per tube). Each path needs at least two distinct points to produce geometry. |
radii |
Numeric vector of radii (length 1, recycled; or one per tube). |
colors |
RGBA colour(s): a single vector applied to all tubes, or an Nx4
numeric matrix (values in |
segments |
Subdivision count around the circumference (default 12). |
caps |
Whether to close both ends of every tube (default FALSE, since batched tubes are typically connected at their ends). |
A mesh descriptor list.
generate_tube, generate_multi_cylinders
paths <- list(matrix(c(0, 0, 0, 1, 1, 0), ncol = 3, byrow = TRUE), matrix(c(0, 0, 2, 1, 1, 2, 2, 0, 2), ncol = 3, byrow = TRUE)) mesh <- generate_tubes(paths, radii = 0.05, segments = 8) nrow(mesh$vertices) > 0paths <- list(matrix(c(0, 0, 0, 1, 1, 0), ncol = 3, byrow = TRUE), matrix(c(0, 0, 2, 1, 1, 2, 2, 0, 2), ncol = 3, byrow = TRUE)) mesh <- generate_tubes(paths, radii = 0.05, segments = 8) nrow(mesh$vertices) > 0
Applies a contrast stretch (S-curve) to the RGB channels of a
rendered image. Formula: (value - 0.5) * contrast + 0.5,
clamped to [0, 1]. The default 1.0 means no change.
Values > 1.0 produce darker darks and lighter highlights.
image_apply_contrast(image, contrast = 1)image_apply_contrast(image, contrast = 1)
image |
An image list returned by |
contrast |
Contrast multiplier. Default 1.0 (no change). Typical values: 1.1–1.2 for subtle S-curve, 1.5 for strong. |
A new image list with contrast-adjusted pixel data.
cube <- generate_cuboid(c(0, 0, 0), c(1, 1, 1)) img <- render_mesh(cube$vertices, cube$triangles) img <- image_apply_contrast(img, contrast = 1.1)cube <- generate_cuboid(c(0, 0, 0), c(1, 1, 1)) img <- render_mesh(cube$vertices, cube$triangles) img <- image_apply_contrast(img, contrast = 1.1)
Crop an image to a rectangular region
image_crop(image, x, y, w, h)image_crop(image, x, y, w, h)
image |
An image list returned by |
x |
Left edge of the crop region (0-based pixel coordinate). |
y |
Top edge of the crop region (0-based pixel coordinate). |
w |
Crop width in pixels. |
h |
Crop height in pixels. |
A new image list with the cropped dimensions.
cube <- generate_cuboid(c(0, 0, 0), c(1, 1, 1)) img <- render_mesh(cube$vertices, cube$triangles) img <- image_crop(img, 100, 50, 400, 300)cube <- generate_cuboid(c(0, 0, 0), c(1, 1, 1)) img <- render_mesh(cube$vertices, cube$triangles) img <- image_crop(img, 100, 50, 400, 300)
Removes background-coloured margin from the specified edges of the image. The first non-background pixel found on each edge defines the crop boundary.
image_crop_to_content(image, direction, background)image_crop_to_content(image, direction, background)
image |
An image list. |
direction |
One of |
background |
Numeric vector of length 4 with RGBA values in
|
A new image list with cropped dimensions.
cube <- generate_cuboid(c(0, 0, 0), c(1, 1, 1)) img <- render_mesh(cube$vertices, cube$triangles, options = render_options(background_color = c(0, 0, 0, 0))) img <- image_crop_to_content(img, "all", c(0, 0, 0, 0))cube <- generate_cuboid(c(0, 0, 0), c(1, 1, 1)) img <- render_mesh(cube$vertices, cube$triangles, options = render_options(background_color = c(0, 0, 0, 0))) img <- image_crop_to_content(img, "all", c(0, 0, 0, 0))
Expands the canvas by adding pixel rows/columns filled with a background colour.
image_grow(image, top, bottom, left, right, background)image_grow(image, top, bottom, left, right, background)
image |
An image list. |
top |
Number of pixel rows to add above. |
bottom |
Number of pixel rows to add below. |
left |
Number of pixel columns to add to the left. |
right |
Number of pixel columns to add to the right. |
background |
Numeric vector of length 4 with RGBA values in
|
A new image list with the expanded dimensions.
cube <- generate_cuboid(c(0, 0, 0), c(1, 1, 1)) img <- render_mesh(cube$vertices, cube$triangles) img <- image_grow(img, 10, 10, 20, 20, c(1, 1, 1, 1))cube <- generate_cuboid(c(0, 0, 0), c(1, 1, 1)) img <- render_mesh(cube$vertices, cube$triangles) img <- image_grow(img, 10, 10, 20, 20, c(1, 1, 1, 1))
Merges another image into this one at the specified edge. For left/right merging, the heights must match. For top/bottom, the widths must match.
image_merge(image, other, direction)image_merge(image, other, direction)
image |
An image list. |
other |
Another image list. |
direction |
One of |
A new image list with the merged dimensions.
cube <- generate_cuboid(c(0, 0, 0), c(1, 1, 1)) sphere <- generate_sphere(c(0, 0, 0), radius = 0.5) left <- render_mesh(sphere$vertices, sphere$triangles) right <- render_mesh(cube$vertices, cube$triangles) merged <- image_merge(left, right, "right")cube <- generate_cuboid(c(0, 0, 0), c(1, 1, 1)) sphere <- generate_sphere(c(0, 0, 0), radius = 0.5) left <- render_mesh(sphere$vertices, sphere$triangles) right <- render_mesh(cube$vertices, cube$triangles) merged <- image_merge(left, right, "right")
Rotate an image by 90 degrees
image_rotate_90(image, clockwise = TRUE)image_rotate_90(image, clockwise = TRUE)
image |
An image list. |
clockwise |
Logical, if |
A new image list with width and height swapped.
Resizes the image to the given dimensions using nearest-neighbour interpolation.
image_scale(image, new_width, new_height)image_scale(image, new_width, new_height)
image |
An image list. |
new_width |
Target width in pixels. |
new_height |
Target height in pixels. |
A new image list with the new dimensions.
Converts the output of render_mesh() or render_scene()
into a 3-dimensional R array of dimensions (height x width x 4) with
RGBA channels.
image_to_array(image)image_to_array(image)
image |
An image list returned by |
A 3D array of dimensions (height, width, 4) with values in
[0, 1].
mesh <- generate_cuboid(c(0, 0, 0), c(1, 1, 1)) img <- render_mesh(mesh$vertices, mesh$triangles) arr <- image_to_array(img) dim(arr) # height x width x 4mesh <- generate_cuboid(c(0, 0, 0), c(1, 1, 1)) img <- render_mesh(mesh$vertices, mesh$triangles) arr <- image_to_array(img) dim(arr) # height x width x 4
Bundles a set of independent line segments with a width measured in pixels
into a layer that can be added to a scene (see the lines argument of
scene()), or drawn directly with render_segments.
In contrast to tube meshes (generate_tubes), no geometry is
created: the renderer draws the segments itself, so thousands of lines cost
almost nothing, and a width of 1 stays 1 pixel wide no matter how far away
the geometry is. This is what hardware line rendering (and
rgl::segments3d()) does.
line_layer( from, to, colors = NULL, width = 1, depth_test = TRUE, lit = FALSE, affects_bounds = TRUE )line_layer( from, to, colors = NULL, width = 1, depth_test = TRUE, lit = FALSE, affects_bounds = TRUE )
from |
Nx3 numeric matrix of segment start points (or a length-3 vector for a single segment). |
to |
Nx3 numeric matrix of segment end points (same number of rows as
|
colors |
RGBA colour(s): a single vector applied to all segments, or an
Nx4 numeric matrix (values in |
width |
Line width in pixels (default 1). |
depth_test |
Whether to test the lines against the depth buffer
(default |
lit |
Whether to apply lighting to the lines (default |
affects_bounds |
Whether this layer contributes to the bounding box of
the scene, and thus to the camera fitted to it (default |
Line layers are drawn together with the meshes of the scene, after them and against the same depth buffer, so opaque meshes can hide lines and opaque lines can hide meshes. Segments whose colors have an alpha value below 1 are drawn in the blended pass, back to front, exactly like translucent triangles.
Lines usually *are* the content of a figure (graph or connectome edges,
streamlines, trajectories), so by default a layer contributes to the bounding
box of its scene, exactly like a mesh does: it defines the extent that the
camera has to cover. Set affects_bounds = FALSE for a layer that is
decoration rather than content (a leader line to a label, an axis cross, a
scale bar drawn as segments), so that it can never push the camera away from
the data. A scene that contains no mesh at all is framed by its line layers
even when they all opted out, since there would otherwise be no geometry to
derive a camera from.
A line layer object (a list with class scimesh_lines) for use
in scene() or render_segments.
render_segments, generate_tubes
from <- matrix(c(-1, 0, 0, 0, -1, 0), ncol = 3, byrow = TRUE) to <- matrix(c(1, 0, 0, 0, 1, 0), ncol = 3, byrow = TRUE) layer <- line_layer(from, to, colors = c(0.2, 0.2, 0.2, 0.8), width = 2) sc <- scene(list(generate_sphere(c(0, 0, 0), 0.5)), lines = layer)from <- matrix(c(-1, 0, 0, 0, -1, 0), ncol = 3, byrow = TRUE) to <- matrix(c(1, 0, 0, 0, 1, 0), ncol = 3, byrow = TRUE) layer <- line_layer(from, to, colors = c(0.2, 0.2, 0.2, 0.8), width = 2) sc <- scene(list(generate_sphere(c(0, 0, 0), 0.5)), lines = layer)
Compute the axis-aligned bounding box of a mesh
mesh_bbox(mesh)mesh_bbox(mesh)
mesh |
A mesh descriptor list with |
A list with min and max (each length-3 numeric).
mesh <- generate_cuboid(c(0, 0, 0), c(1, 2, 3)) bb <- mesh_bbox(mesh) bb$min bb$maxmesh <- generate_cuboid(c(0, 0, 0), c(1, 2, 3)) bb <- mesh_bbox(mesh) bb$min bb$max
Extracts vertices and triangle indices from an rgl
tmesh3d object into the format expected by
render_mesh(). Does not require the rgl
package – any list with components vb (4xN
homogeneous coordinates) and it (3xM index matrix)
works.
mesh_from_rgl(tmesh)mesh_from_rgl(tmesh)
tmesh |
A list with components |
A mesh descriptor list with vertices (Nx3)
and triangles (Mx3, 1-based indices).
fake <- list(vb = rbind(0:3, 0:3, 0:3, rep(1, 4)), it = matrix(1:6, nrow = 3)) m <- mesh_from_rgl(fake) m$vertices m$trianglesfake <- list(vb = rbind(0:3, 0:3, 0:3, rep(1, 4)), it = matrix(1:6, nrow = 3)) m <- mesh_from_rgl(fake) m$vertices m$triangles
Builds an rgl-compatible triangular mesh from a scimesh mesh
descriptor so that the result can be used with
rgl::shade3d() or other rgl functions.
mesh_to_rgl(mesh, color = NULL, face_color = NULL)mesh_to_rgl(mesh, color = NULL, face_color = NULL)
mesh |
A scimesh mesh descriptor list with |
color |
Optional per-vertex colour, either a single length-4 RGBA vector (applied to all vertices) or an Nx4 matrix. |
face_color |
Optional per-face colour (Mx4 matrix). |
A list with components vb (4xN homogeneous
coordinates), it (3xM 1-based index matrix), and
optionally normals and mat (material), suitable
for use with rgl's tmesh3d() and shade3d().
mesh <- generate_cuboid(c(0, 0, 0), c(1, 1, 1)) rgl_mesh <- mesh_to_rgl(mesh) str(rgl_mesh)mesh <- generate_cuboid(c(0, 0, 0), c(1, 1, 1)) rgl_mesh <- mesh_to_rgl(mesh) str(rgl_mesh)
Estimates the curvature at every point from the neighbouring points, using
the standard formula , which does not
care how the points are spaced. On a circle of radius the values
come out as , and on a straight line as 0.
path_curvature(path, closed = FALSE)path_curvature(path, closed = FALSE)
path |
Nx3 numeric matrix of path points (or a length-3 vector). |
closed |
Whether the path loops back to its first point (default
|
This is a diagnostic tool rather than a rendering input: sweeping a tube of
radius along a curve whose curvature reaches folds the
tube inside out, so a path can be swept safely up to a radius of
, which is worth checking for tight turns in measured
data (max(path_curvature(path))).
A numeric vector with one curvature value per input point. The endpoints of an open path report the value of their only neighbour; fewer than 3 points yield a zero-length vector, since curvature is undefined there.
# Largest tube radius that does not fold this path onto itself: path <- spline_path(matrix(c(0, 0, 0, 1, 1, 0, 2, 0, 0, 3, 1, 0), ncol = 3, byrow = TRUE)) 1 / max(path_curvature(path))# Largest tube radius that does not fold this path onto itself: path <- spline_path(matrix(c(0, 0, 0, 1, 1, 0, 2, 0, 0, 3, 1, 0), ncol = 3, byrow = TRUE)) 1 / max(path_curvature(path))
Sums the distances between consecutive points, plus the closing distance from
the last point back to the first for a closed path. Useful to pick a
sensible step for resample_path, or to derive a sampling
density from the size of the data.
path_length(path, closed = FALSE)path_length(path, closed = FALSE)
path |
Nx3 numeric matrix of path points (or a length-3 vector). |
closed |
Whether the path loops back to its first point (default
|
A single number: the arc length of the path (0 for fewer than two points).
path_length(matrix(c(0, 0, 0, 1, 0, 0, 1, 1, 0), ncol = 3, byrow = TRUE))path_length(matrix(c(0, 0, 0, 1, 0, 0, 1, 1, 0), ncol = 3, byrow = TRUE))
Reads the geometry (vertices and triangles) of a Wavefront OBJ file and
returns a scimesh mesh descriptor list with vertices and
triangles. Normals and texture coordinates in the file are ignored:
call compute_vertex_normals() if you need normals, and assign
uv yourself (see render_mesh) if you want to render the
mesh with a texture.
read_obj(path)read_obj(path)
path |
Path to the OBJ file. |
A mesh descriptor list with vertices and triangles.
## Not run: mesh <- read_obj("model.obj") nrow(mesh$vertices) ## End(Not run)## Not run: mesh <- read_obj("model.obj") nrow(mesh$vertices) ## End(Not run)
Reads a PLY file (ASCII or binary) with optional per-vertex colors
and returns a scimesh mesh descriptor list with vertices,
triangles, and optionally colors. Texture coordinates in the
file are ignored.
read_ply(path)read_ply(path)
path |
Path to the PLY file. |
A mesh descriptor list.
## Not run: mesh <- read_ply("model.ply") nrow(mesh$vertices) ## End(Not run)## Not run: mesh <- read_ply("model.ply") nrow(mesh$vertices) ## End(Not run)
Reads an ASCII or binary STL file and returns a scimesh mesh
descriptor list with vertices, triangles, and
normals.
read_stl(path)read_stl(path)
path |
Path to the STL file. |
A mesh descriptor list.
## Not run: mesh <- read_stl("model.stl") nrow(mesh$vertices) ## End(Not run)## Not run: mesh <- read_stl("model.stl") nrow(mesh$vertices) ## End(Not run)
Generates a merged cylinder mesh from start/end point pairs, radii, and colors, then renders it.
render_lines( from, to, radii = 0.1, colors, camera, options = render_options(), segments = 12L )render_lines( from, to, radii = 0.1, colors, camera, options = render_options(), segments = 12L )
from |
Nx3 numeric matrix of segment start points. |
to |
Nx3 numeric matrix of segment end points. |
radii |
Numeric vector of cylinder radii (length N, or 1 recycled to N). |
colors |
Nx4 numeric matrix of RGBA colours, or a single colour recycled to N. |
camera |
A camera list. |
options |
Render options. |
segments |
Number of sides around the cylinder (default 12). |
An image list.
from <- matrix(c(0, 0, 0, 1, 1, 1), ncol = 3, byrow = TRUE) to <- matrix(c(3, 0, 0, 0, 3, 0), ncol = 3, byrow = TRUE) img <- render_lines(from, to, radii = 0.05, colors = c(0, 0, 1, 1), camera = camera_auto(rbind(from, to))) tmp_file <- tempfile(fileext = ".png") write_png(img, tmp_file)from <- matrix(c(0, 0, 0, 1, 1, 1), ncol = 3, byrow = TRUE) to <- matrix(c(3, 0, 0, 0, 3, 0), ncol = 3, byrow = TRUE) img <- render_lines(from, to, radii = 0.05, colors = c(0, 0, 1, 1), camera = camera_auto(rbind(from, to))) tmp_file <- tempfile(fileext = ".png") write_png(img, tmp_file)
Renders a single mesh using the scimesh software renderer.
The mesh can be specified either as separate
vertices/triangles matrices, as a scimesh mesh
descriptor list, or as an rgl tmesh3d-style list (with
vb/it components). rgl meshes are transparently
converted via mesh_from_rgl().
render_mesh( vertices, triangles = NULL, colors = NULL, face_colors = NULL, normals = NULL, uv = NULL, texture = NULL, camera = NULL, options = render_options() )render_mesh( vertices, triangles = NULL, colors = NULL, face_colors = NULL, normals = NULL, uv = NULL, texture = NULL, camera = NULL, options = render_options() )
vertices |
Either an Nx3 numeric matrix of vertex positions,
or a scimesh mesh descriptor list (with |
triangles |
Mx3 integer matrix of triangle indices (1-based).
Ignored when |
colors |
Optional Nx4 numeric matrix of RGBA vertex colors (0-1).
The fourth column is the alpha value; alpha < 1 renders the mesh
translucently (see the Transparency section below).
Use |
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 |
normals |
Optional Nx3 numeric matrix of vertex normals. |
uv |
Optional Nx2 numeric matrix of texture coordinates (0-1). scimesh
uses image-space UVs: |
texture |
Optional texture image as a 3D array (H x W x 3 or 4)
with values in |
camera |
A camera list from |
options |
A render options list from |
A list with components width, height, and
pixels (raw vector of RGBA values).
The fourth column of colors (and of face_colors) is the
alpha value: values < 1 make the mesh translucent, and the renderer
blends it with whatever is behind it automatically - there is no flag to
set. Per-vertex alpha is interpolated across each triangle, so a smooth
fade is possible, and alpha = 0 makes geometry invisible (holes).
Translucent triangles are drawn back-to-front after the opaque geometry,
so they are correctly hidden by opaque meshes in front of them. Use
set_mesh_alpha() to set one alpha value for a whole mesh.
# 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)# 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)
Create render options
render_options( width = 800L, height = 600L, shading = c("smooth", "flat"), backface_culling = TRUE, background_color = c(1, 1, 1, 1), 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, fog_enabled = FALSE, fog_start = 0, fog_end = 1, fog_color = c(0, 0, 0, 0), fog_space = c("world", "ndc"), threads = 0L, clip_planes = NULL, ssao_enabled = FALSE, ssao_radius = 16, ssao_intensity = 0.8, aa_samples = NULL, near_plane = 0.1, far_plane = 10000 )render_options( width = 800L, height = 600L, shading = c("smooth", "flat"), backface_culling = TRUE, background_color = c(1, 1, 1, 1), 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, fog_enabled = FALSE, fog_start = 0, fog_end = 1, fog_color = c(0, 0, 0, 0), fog_space = c("world", "ndc"), threads = 0L, clip_planes = NULL, ssao_enabled = FALSE, ssao_radius = 16, ssao_intensity = 0.8, aa_samples = NULL, near_plane = 0.1, far_plane = 10000 )
width |
Output image width in pixels. |
height |
Output image height in pixels. |
shading |
Shading mode: |
backface_culling |
Whether to cull back-facing triangles. |
background_color |
Background RGBA color as numeric vector of length 4 (values 0-1). |
default_color |
Default vertex color when no colors are provided. |
invert_normals |
Whether to invert surface normals. |
wireframe |
Whether to render in wireframe mode. |
wireframe_color |
RGBA color for wireframe edges (0-1
scale). Default |
projection |
Projection type: |
specular_color |
Specular highlight color (0-1 scale). When
|
shininess |
Specular exponent controlling highlight sharpness.
Higher values produce a tighter spot. Typical values: 32 (soft
plastic), 64 (shiny), 128 (glass). Default |
lights |
A list of light descriptors, each a list with
|
ambient |
Ambient light contribution (0-1). Default 0.3. |
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:
|
fog_enabled |
Enable depth cueing (fog). Default |
fog_start |
Distance where fog begins, i.e. where objects start
fading toward |
fog_end |
Distance where fog is fully opaque. Must be larger
than |
fog_color |
RGBA fog colour (0-1 scale). Defaults to
|
fog_space |
Character, either
|
threads |
Number of render threads. 0 = auto-detect (use all cores), 1 = single-threaded (deterministic). Default 0. Requires OpenMP at compile time. |
clip_planes |
A list of clip planes (see |
ssao_enabled |
Enable screen-space ambient occlusion.
Default |
ssao_radius |
Screen-space sample radius in pixels. Default 16. |
ssao_intensity |
Occlusion strength (0-1). Default 0.8. |
aa_samples |
Anti-aliasing supersampling factor. Renders
internally at |
near_plane |
Distance of the near clipping plane (default 0.1).
Geometry closer to the camera is clipped away. Also defines the
depth range together with |
far_plane |
Distance of the far clipping plane (default 10000).
Must be larger than |
A render options list for use with render_mesh() or
render_scene().
# 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)) # World-space clip plane: keep the half of the scene with x <= 0. # The cut stays at x = 0, whatever the camera does. opts <- render_options(clip_planes = list( clip_plane(normal = c(-1, 0, 0), offset = 0))) # Eye-space clip plane: additionally remove everything closer than 2 units # to the camera (camera-attached cutaway). opts <- render_options(clip_planes = list( clip_plane(normal = c(-1, 0, 0), offset = 0), clip_plane(normal = c(0, 0, -1), offset = -2, space = "eye"))) # Fog in world units (default): fade from 20 to 60 units away from the # camera opts <- render_options(fog_enabled = TRUE, fog_start = 20, fog_end = 60, fog_color = c(0.9, 0.95, 1, 1)) # Legacy normalized-device-depth fog, for backwards compatibility opts <- render_options(fog_enabled = TRUE, fog_space = "ndc", fog_start = 0.5, fog_end = 1) # Enable 2x2 anti-aliasing for this session: affects all subsequent # render calls that do not pass \code{aa_samples} explicitly. old <- options(scimesh.aa_samples = 2L) opts <- render_options() opts$aa_samples options(old)# 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)) # World-space clip plane: keep the half of the scene with x <= 0. # The cut stays at x = 0, whatever the camera does. opts <- render_options(clip_planes = list( clip_plane(normal = c(-1, 0, 0), offset = 0))) # Eye-space clip plane: additionally remove everything closer than 2 units # to the camera (camera-attached cutaway). opts <- render_options(clip_planes = list( clip_plane(normal = c(-1, 0, 0), offset = 0), clip_plane(normal = c(0, 0, -1), offset = -2, space = "eye"))) # Fog in world units (default): fade from 20 to 60 units away from the # camera opts <- render_options(fog_enabled = TRUE, fog_start = 20, fog_end = 60, fog_color = c(0.9, 0.95, 1, 1)) # Legacy normalized-device-depth fog, for backwards compatibility opts <- render_options(fog_enabled = TRUE, fog_space = "ndc", fog_start = 0.5, fog_end = 1) # Enable 2x2 anti-aliasing for this session: affects all subsequent # render calls that do not pass \code{aa_samples} explicitly. old <- options(scimesh.aa_samples = 2L) opts <- render_options() opts$aa_samples options(old)
Renders points as fixed-size filled circles in screen space with
depth testing. Unlike render_spheres(), point size is
measured in pixels and does not change with camera distance.
render_points( positions, colors, radius = 3, camera = camera_auto(positions), options = render_options() )render_points( positions, colors, radius = 3, camera = camera_auto(positions), options = render_options() )
positions |
Nx3 numeric matrix of point positions. |
colors |
Nx4 numeric matrix of RGBA colours (0-1 scale). |
radius |
Point radius in pixels. |
camera |
A camera list. |
options |
Render options. |
An image list.
pts <- matrix(c(0, 1, 2, 0, 1, 2, 0, 0, 0), ncol = 3) colors = matrix(c(0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1), ncol = 4) img <- render_points(pts, colors = colors, radius = 5) tmp_file <- tempfile(fileext = ".png") write_png(img, tmp_file)pts <- matrix(c(0, 1, 2, 0, 1, 2, 0, 0, 0), ncol = 3) colors = matrix(c(0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1), ncol = 4) img <- render_points(pts, colors = colors, radius = 5) tmp_file <- tempfile(fileext = ".png") write_png(img, tmp_file)
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 vb/it); rgl meshes are
transparently converted.
render_scene(meshes, camera = NULL, options = NULL)render_scene(meshes, camera = NULL, options = NULL)
meshes |
Either a |
camera |
A camera list from |
options |
A render options list from |
A list with components width, height, and
pixels (raw vector of RGBA values).
# 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)))# 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)))
Convenience wrapper around line_layer for the case where no
meshes are involved: the segments are drawn (with a screen-space width) into
an image and nothing else. To combine lines with meshes, add the layer to a
scene instead and render that scene.
render_segments( from, to, colors = NULL, width = 1, camera = NULL, options = render_options(), lit = FALSE )render_segments( from, to, colors = NULL, width = 1, camera = NULL, options = render_options(), lit = FALSE )
from |
Nx3 numeric matrix of segment start points (or a length-3 vector). |
to |
Nx3 numeric matrix of segment end points (same number of rows as
|
colors |
RGBA colour(s): a single vector applied to all segments, or an
Nx4 numeric matrix. |
width |
Line width in pixels (default 1). |
camera |
A camera list, e.g. from |
options |
Render options, see |
lit |
Whether to apply lighting (default |
An image list, see render_scene.
from <- matrix(c(-1, 0, 0, 0, -1, 0), ncol = 3, byrow = TRUE) to <- matrix(c(1, 0, 0, 0, 1, 0), ncol = 3, byrow = TRUE) img <- render_segments(from, to, colors = c(1, 0, 0, 1), width = 3) tmp_file <- tempfile(fileext = ".png") write_png(img, tmp_file)from <- matrix(c(-1, 0, 0, 0, -1, 0), ncol = 3, byrow = TRUE) to <- matrix(c(1, 0, 0, 0, 1, 0), ncol = 3, byrow = TRUE) img <- render_segments(from, to, colors = c(1, 0, 0, 1), width = 3) tmp_file <- tempfile(fileext = ".png") write_png(img, tmp_file)
Generates a merged sphere mesh from a set of center points, radii, and colors, then renders it with the given camera and options.
render_spheres( centers, radii, colors, camera, options = render_options(), segments = 16L )render_spheres( centers, radii, colors, camera, options = render_options(), segments = 16L )
centers |
Nx3 numeric matrix of sphere centre coordinates. |
radii |
Numeric vector of sphere radii (length N, or 1 recycled to N). |
colors |
Nx4 numeric matrix of RGBA colours (0-1 scale), or a single colour recycled to N. |
camera |
A camera list from |
options |
Render options from |
segments |
Number of latitude/longitude segments per sphere (default 16). |
An image list with width, height,
pixels.
centers <- matrix(c(0, 2, 4, 0, 0, 0, 0, 0, 0), ncol = 3) img <- render_spheres(centers, radii = 0.5, colors = c(1, 0, 0, 1), camera = camera_auto(centers)) tmp_file <- tempfile(fileext = ".png") write_png(img, tmp_file)centers <- matrix(c(0, 2, 4, 0, 0, 0, 0, 0, 0), ncol = 3) img <- render_spheres(centers, radii = 0.5, colors = c(1, 0, 0, 1), camera = camera_auto(centers)) tmp_file <- tempfile(fileext = ".png") write_png(img, tmp_file)
Convenience wrapper around text_layer for the case where no
meshes are involved: the labels are drawn onto the background described by
the render options. Screen-space labels (space = "screen") do not use
a camera at all, so this is the quick way to turn a label into an image or to
decorate an empty canvas; world-space labels need a camera.
render_text(positions, text, camera = NULL, options = NULL, ...)render_text(positions, text, camera = NULL, options = NULL, ...)
positions |
Anchor positions, see |
text |
Character vector of labels, see |
camera |
A camera list from |
options |
Render options from |
... |
Further arguments passed to |
A list with components width, height, and
pixels (raw vector of RGBA values).
img <- render_text(c(20, 20), "figure A", space = "screen", adj = c(0, 1), size = 24, options = render_options(width = 300, height = 80)) tmp <- tempfile(fileext = ".png") write_png(img, tmp)img <- render_text(c(20, 20), "figure A", space = "screen", adj = c(0, 1), size = 24, options = render_options(width = 300, height = 80)) tmp <- tempfile(fileext = ".png") write_png(img, tmp)
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.
render_triangles(positions, colors, camera, options = render_options())render_triangles(positions, colors, camera, options = render_options())
positions |
Nx3 numeric matrix of vertex positions, where N is a multiple of 3 (3 per triangle). |
colors |
Nx4 numeric matrix of RGBA colours (0-1 scale). |
camera |
A camera list from |
options |
Render options from |
An image list with width, height,
pixels.
# 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)# 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)
Walks along a path and emits a point every step units of arc length.
The result has (nearly) uniform point spacing regardless of how the input was
parameterized, which is what makes a swept tube look even:
generate_tube places exactly one cross-section per path point,
so uneven spacing means a tube that is finely subdivided in one place and
faceted in another. Typical use is to even out the output of
spline_path, whose samples are evenly spaced in the curve
parameter rather than along the curve.
resample_path(path, step, closed = FALSE)resample_path(path, step, closed = FALSE)
path |
Nx3 numeric matrix of path points (or a length-3 vector). |
step |
Arc length between samples. Must be a single positive number; other values return the path unchanged. |
closed |
Whether the path loops back to its first point (default
|
The spacing that is uniform is the *arc length* along the path; the straight-line distances between consecutive samples are slightly shorter wherever the path curves. An open path always keeps its final point (with a possibly shorter last step); a closed path ends on a copy of its first point and is covered in a whole number of steps, so its effective step can differ from the requested one by a fraction of a percent.
An Nx3 numeric matrix of resampled path points. A path without length (or with fewer than two points) is returned as it is.
waypoints <- matrix(c(0, 0, 0, 1, 1, 0, 2, 0, 0), ncol = 3, byrow = TRUE) smooth <- spline_path(waypoints, samples_per_segment = 32) even <- resample_path(smooth, step = 0.1) nrow(even)waypoints <- matrix(c(0, 0, 0, 1, 1, 0, 2, 0, 0), ncol = 3, byrow = TRUE) smooth <- spline_path(waypoints, samples_per_segment = 32) even <- resample_path(smooth, step = 0.1) nrow(even)
Vertex normals (if the mesh has any) are rotated with the mesh.
rotate_mesh(mesh, angle_rad, axis = c(0, 0, 1))rotate_mesh(mesh, angle_rad, axis = c(0, 0, 1))
mesh |
A mesh descriptor list. |
angle_rad |
Rotation angle in radians. |
axis |
Length-3 numeric vector defining the rotation axis. |
A new mesh descriptor list with rotated vertices.
mesh <- generate_cuboid(c(0, 0, 0), c(1, 1, 1)) rotated <- rotate_mesh(mesh, pi / 4, axis = c(0, 1, 0)) rotated$vertices[1, ]mesh <- generate_cuboid(c(0, 0, 0), c(1, 1, 1)) rotated <- rotate_mesh(mesh, pi / 4, axis = c(0, 1, 0)) rotated$vertices[1, ]
Per-vertex normals (if the mesh has any) are scaled as well, using the inverse transpose of the scaling matrix: a non-uniform scale would otherwise leave normals pointing in a direction that no longer matches the surface, which shows up as wrong shading.
scale_mesh(mesh, scale)scale_mesh(mesh, scale)
mesh |
A mesh descriptor list. |
scale |
A single numeric scale factor (uniform) or a length-3 numeric vector for per-axis scaling (x, y, z). |
A new mesh descriptor list with scaled vertices.
mesh <- generate_cuboid(c(0, 0, 0), c(1, 1, 1)) big <- scale_mesh(mesh, 3) flat <- scale_mesh(mesh, c(2, 0.5, 1))mesh <- generate_cuboid(c(0, 0, 0), c(1, 1, 1)) big <- scale_mesh(mesh, 3) flat <- scale_mesh(mesh, c(2, 0.5, 1))
Bundles a list of meshes together with their placement transforms, a
camera, and render options into a single scene object that can be passed
to render_scene() or write_gltf().
scene( meshes, camera = NULL, options = NULL, transforms = NULL, names = NULL, lines = NULL, texts = NULL )scene( meshes, camera = NULL, options = NULL, transforms = NULL, names = NULL, lines = NULL, texts = NULL )
meshes |
A list of mesh descriptors (scimesh or rgl format, see
|
camera |
A camera list from |
options |
A render options list from |
transforms |
Optional list of 4x4 numeric matrices, one per mesh,
overriding any embedded |
names |
Optional character vector, one per mesh, overriding any
embedded |
lines |
|
texts |
|
Each mesh is wrapped into a scene node list(mesh = ..., transform = ...,
name = ...). The optional transform is a 4x4 numeric matrix
(column-major, GLM style — the same convention accepted by
transform_mesh()) that places the mesh in world space at
render/export time without modifying the mesh itself. The optional
name is used by exporters such as glTF for node names.
A scene descriptor list with S3 class "scimesh_scene",
with components meshes (list of scene nodes), lines (list
of line layers, possibly empty), texts (list of text layers,
possibly empty), camera, and options.
cube1 <- generate_cuboid(c(0, 0, 0), c(0.5, 0.5, 0.5), c(1, 0, 0, 1)) cube2 <- generate_cuboid(c(0, 0, 0), c(0.5, 0.5, 0.5), c(0, 0, 1, 1)) # place the second cube 2 units along +X tr <- diag(1, 4); tr[1, 4] <- 2 sc <- scene(list(cube1, list(mesh = cube2, transform = tr, name = "blue")), camera = camera_auto(list(cube1, cube2), direction = c(1, 1, 1))) img <- render_scene(sc) # add a line layer drawn on top of the meshes: sc2 <- scene(list(cube1), lines = line_layer(matrix(c(0, 0, 1), ncol = 3), matrix(c(1, 1, 1), ncol = 3), width = 3)) # add a label anchored above the cube: sc3 <- scene(list(cube1), texts = text_layer(matrix(c(0, 1, 0), ncol = 3), "cube", size = 18, adj = c(0.5, 0)))cube1 <- generate_cuboid(c(0, 0, 0), c(0.5, 0.5, 0.5), c(1, 0, 0, 1)) cube2 <- generate_cuboid(c(0, 0, 0), c(0.5, 0.5, 0.5), c(0, 0, 1, 1)) # place the second cube 2 units along +X tr <- diag(1, 4); tr[1, 4] <- 2 sc <- scene(list(cube1, list(mesh = cube2, transform = tr, name = "blue")), camera = camera_auto(list(cube1, cube2), direction = c(1, 1, 1))) img <- render_scene(sc) # add a line layer drawn on top of the meshes: sc2 <- scene(list(cube1), lines = line_layer(matrix(c(0, 0, 1), ncol = 3), matrix(c(1, 1, 1), ncol = 3), width = 3)) # add a label anchored above the cube: sc3 <- scene(list(cube1), texts = text_layer(matrix(c(0, 1, 0), ncol = 3), "cube", size = 18, adj = c(0.5, 0)))
Sets the affects_bounds flag of one or more line layers of a scene, see
line_layer. A layer with the flag set contributes to the
bounding box of the scene, and thus to the extent that a camera fitted to the
scene (camera_auto) has to cover; a layer without it is ignored
when the bounds are computed, which is what you want for decorational lines.
scene_set_line_affects_bounds( scene, index = NULL, name = NULL, affects_bounds = TRUE )scene_set_line_affects_bounds( scene, index = NULL, name = NULL, affects_bounds = TRUE )
scene |
A scene descriptor list, see |
index |
Integer vector, the positions (1-based) of the layers to update,
or |
name |
Character vector, the names of the layers to update, or
|
affects_bounds |
Whether the selected layers contribute to the scene
bounds (default |
The layers can be selected by position (index) or by name
(name, for layers that were added as scene nodes with a
name). Exactly one of the two has to be given. A scene that contains
no mesh at all is framed by its line layers even when they all opted out,
since there would otherwise be no geometry to derive a camera from.
The scene with the updated layers, invisibly. Since a scene is a
plain list, the update has to be assigned to take effect, e.g.
sc <- scene_set_line_affects_bounds(sc, name = "leader",
affects_bounds = FALSE).
from <- matrix(c(-1, 0, 0, 5, 0, 0), ncol = 3, byrow = TRUE) to <- matrix(c(1, 0, 0, 6, 0, 0), ncol = 3, byrow = TRUE) # The second segment is a decorational leader line pointing away from the data. sc <- scene(list(generate_cuboid(c(0, 0, 0), c(0.5, 0.5, 0.5))), lines = list(list(lines = line_layer(from, to), name = "edges"))) sc <- scene_set_line_affects_bounds(sc, index = 1, affects_bounds = TRUE)from <- matrix(c(-1, 0, 0, 5, 0, 0), ncol = 3, byrow = TRUE) to <- matrix(c(1, 0, 0, 6, 0, 0), ncol = 3, byrow = TRUE) # The second segment is a decorational leader line pointing away from the data. sc <- scene(list(generate_cuboid(c(0, 0, 0), c(0.5, 0.5, 0.5))), lines = list(list(lines = line_layer(from, to), name = "edges"))) sc <- scene_set_line_affects_bounds(sc, index = 1, affects_bounds = TRUE)
Returns a copy of the mesh in which every vertex (and every face, if per-face colors are used) has the given alpha value. The renderer blends meshes whose colors are not fully opaque automatically, so this is all that is needed to draw a mesh translucently - for example a brain surface at 10 percent opacity for spatial reference.
set_mesh_alpha(mesh, alpha)set_mesh_alpha(mesh, alpha)
mesh |
A mesh descriptor list (see |
alpha |
Alpha value in |
A mesh without colors gets uniform colors first (its default_color
if it has one, light gray otherwise), so the mesh keeps its appearance and
only becomes see-through. Use alpha = 0 for completely invisible
geometry and alpha = 1 to make a mesh opaque again.
A mesh descriptor list with the alpha applied.
sphere <- generate_sphere(c(0, 0, 0), 1) ghost <- set_mesh_alpha(sphere, 0.2) # Per-vertex alpha (here: every other vertex transparent) can be set # directly on the color matrix: cols <- sphere$colors cols[, 4] <- rep(c(0, 1), length.out = nrow(cols)) sphere$colors <- colssphere <- generate_sphere(c(0, 0, 0), 1) ghost <- set_mesh_alpha(sphere, 0.2) # Per-vertex alpha (here: every other vertex transparent) can be set # directly on the color matrix: cols <- sphere$colors cols[, 4] <- rep(c(0, 1), length.out = nrow(cols)) sphere$colors <- cols
Turns a coarse list of waypoints into a dense, smooth path, which is what the
path-taking geometry functions expect: generate_tube sweeps a
cross-section along a path, and line_layer draws one straight
segment per consecutive pair of points. Handing a handful of control points
to those gives a visibly faceted tube and a polygonal line; this is how you
get the smooth version.
spline_path( points, method = c("catmull-rom", "bspline"), samples_per_segment = 8L, closed = FALSE, alpha = 0.5 )spline_path( points, method = c("catmull-rom", "bspline"), samples_per_segment = 8L, closed = FALSE, alpha = 0.5 )
points |
Nx3 numeric matrix of points the curve has to pass through (or
a length-3 vector for a single point). An open curve needs at least 2
points, a closed one at least 3 ( |
method |
Either |
samples_per_segment |
Number of points to generate per segment between two input points (default 8, clamped to at least 1). This is the knob that controls how smooth the result looks when rendered. |
closed |
Whether the curve loops back to its first point (default
|
alpha |
Parameterization exponent of the Catmull-Rom curve (default
|
Two curves are available, and they differ in a way that matters:
"catmull-rom" (the default) **interpolates**: the curve passes
through every input point, and each point's tangent is derived from its
two neighbours. This is what you want when the input points are
positions that the curve has to hit (measured or digitized data).
"bspline" **approximates**: the points act as a control cage
that the curve stays inside, which irons out noise instead of reproducing
it, and the curve is continuous everywhere. It generally does
not pass through the input points.
For the Catmull-Rom curve, alpha selects the parameterization and is
the knob that matters most on real data:
0.5 (default) — *centripetal*. Knot intervals grow with the
square root of the chord length, which prevents the cusps, loops and
self-intersections that the uniform curve produces when the spacing of
the input points is uneven (long segment followed by a short one, the
norm for measured data).
1 — chord length: still free of cusps, but can overshoot more.
0 — uniform: the textbook curve, exact on evenly spaced points
and misbehaving on everything else.
An Nx3 numeric matrix of path points, ready for
generate_tube, generate_tubes,
line_layer or camera_auto. An empty (0-row)
matrix if there are not enough distinct points for the chosen curve.
bezier_path, resample_path,
path_curvature, generate_tube
waypoints <- matrix(c(0, 0, 0, 1, 1, 0, 2, 0, 0, 3, 1, 0), ncol = 3, byrow = TRUE) path <- spline_path(waypoints, samples_per_segment = 8) nrow(path) # 3 segments * 8 samples + the final point # A smooth tube through the waypoints, instead of a faceted one: mesh <- generate_tube(waypoints, radius = 0.1) smooth <- generate_tube(path, radius = 0.1) # An approximating (noise-reducing) closed loop: loop <- spline_path(waypoints, method = "bspline", closed = TRUE)waypoints <- matrix(c(0, 0, 0, 1, 1, 0, 2, 0, 0, 3, 1, 0), ncol = 3, byrow = TRUE) path <- spline_path(waypoints, samples_per_segment = 8) nrow(path) # 3 segments * 8 samples + the final point # A smooth tube through the waypoints, instead of a faceted one: mesh <- generate_tube(waypoints, radius = 0.1) smooth <- generate_tube(path, radius = 0.1) # An approximating (noise-reducing) closed loop: loop <- spline_path(waypoints, method = "bspline", closed = TRUE)
Stacks a list of rendered images side by side. A convenience
wrapper around compose_layout().
stack_horizontal( ..., colorbar = NULL, colorbar_height = 80L, colorbar_width = 80L, background = c(0, 0, 0, 0), colorbar_side = c("right", "left"), crop = FALSE )stack_horizontal( ..., colorbar = NULL, colorbar_height = 80L, colorbar_width = 80L, background = c(0, 0, 0, 0), colorbar_side = c("right", "left"), crop = FALSE )
... |
Images from |
colorbar |
Optional colorbar. |
colorbar_height |
Height of the colorbar in pixels. |
colorbar_width |
Width of the colorbar in pixels. |
background |
Background RGBA color for padding. |
colorbar_side |
Side for the colorbar: |
crop |
If |
A composed image list.
mesh1 <- generate_cuboid(c(-2, 0, 0), c(0.5, 1, 1), c(1, 0, 0, 1)) mesh2 <- generate_cuboid(c( 2, 0, 0), c(0.5, 1, 1), c(0, 0, 1, 1)) img1 <- render_mesh(mesh1$vertices, mesh1$triangles) img2 <- render_mesh(mesh2$vertices, mesh2$triangles) result <- stack_horizontal(img1, img2) tmp_file <- tempfile(fileext = ".png") write_png(result, tmp_file)mesh1 <- generate_cuboid(c(-2, 0, 0), c(0.5, 1, 1), c(1, 0, 0, 1)) mesh2 <- generate_cuboid(c( 2, 0, 0), c(0.5, 1, 1), c(0, 0, 1, 1)) img1 <- render_mesh(mesh1$vertices, mesh1$triangles) img2 <- render_mesh(mesh2$vertices, mesh2$triangles) result <- stack_horizontal(img1, img2) tmp_file <- tempfile(fileext = ".png") write_png(result, tmp_file)
Stacks a list of rendered images vertically (one below another).
A convenience wrapper around compose_layout().
stack_vertical( ..., colorbar = NULL, colorbar_height = 80L, colorbar_width = 80L, background = c(0, 0, 0, 0), colorbar_side = c("right", "left"), crop = FALSE )stack_vertical( ..., colorbar = NULL, colorbar_height = 80L, colorbar_width = 80L, background = c(0, 0, 0, 0), colorbar_side = c("right", "left"), crop = FALSE )
... |
Images from |
colorbar |
Optional colorbar from |
colorbar_height |
Height of the colorbar in pixels. |
colorbar_width |
Width of the colorbar in pixels. |
background |
Background RGBA color for padding. |
colorbar_side |
Side for the colorbar: |
crop |
If |
A composed image list.
mesh1 <- generate_cuboid(c(0, 2, 0), c(1, 0.5, 1), c(1, 0, 0, 1)) mesh2 <- generate_cuboid(c(0, -2, 0), c(1, 0.5, 1), c(0, 1, 0, 1)) img1 <- render_mesh(mesh1$vertices, mesh1$triangles) img2 <- render_mesh(mesh2$vertices, mesh2$triangles) result <- stack_vertical(img1, img2) tmp_file <- tempfile(fileext = ".png") write_png(result, tmp_file)mesh1 <- generate_cuboid(c(0, 2, 0), c(1, 0.5, 1), c(1, 0, 0, 1)) mesh2 <- generate_cuboid(c(0, -2, 0), c(1, 0.5, 1), c(0, 1, 0, 1)) img1 <- render_mesh(mesh1$vertices, mesh1$triangles) img2 <- render_mesh(mesh2$vertices, mesh2$triangles) result <- stack_vertical(img1, img2) tmp_file <- tempfile(fileext = ".png") write_png(result, tmp_file)
Computes the size of the text box that text_layer uses, which
is needed to place labels relative to each other (for example to right-align
a caption, or to keep two labels from overlapping). Multi-line labels are
measured as a whole, using the same line spacing as the renderer.
text_extent(text, size = 18, font_file = NULL, line_spacing = 1.2)text_extent(text, size = 18, font_file = NULL, line_spacing = 1.2)
text |
Character vector of labels (UTF-8; |
size |
Text height in output pixels (default 18). |
font_file |
Path to a |
line_spacing |
Distance between lines, as a multiple of the glyph box height (default 1.2). |
A data frame with one row per input string and the columns
text, width (widest line), height (whole block),
ascent, descent and lines.
text_extent("anterior", size = 20) text_extent(c("left hemisphere", "right hemisphere"), size = 16)text_extent("anterior", size = 20) text_extent(c("left hemisphere", "right hemisphere"), size = 16)
Bundles strings with anchor positions into a layer that can be added to a
scene (see the texts argument of scene()), or drawn
directly with render_text.
text_layer( positions, text, colors = NULL, size = 18, font_file = NULL, space = c("world", "screen"), adj = c(0.5, 0.5), offset = c(0, 0), line_spacing = 1.2, depth_test = TRUE, halo_color = NULL, halo_width = 1.5, rotation = 0 )text_layer( positions, text, colors = NULL, size = 18, font_file = NULL, space = c("world", "screen"), adj = c(0.5, 0.5), offset = c(0, 0), line_spacing = 1.2, depth_test = TRUE, halo_color = NULL, halo_width = 1.5, rotation = 0 )
positions |
Anchor positions: an Nx3 numeric matrix for world space, or
an Nx2/Nx3 numeric matrix for |
text |
Character vector of labels (UTF-8), recycled to
|
colors |
RGBA colour(s): a single vector applied to all labels, or an
Nx4 numeric matrix (values in |
size |
Text height in output pixels (default 18). Independent of the
anti-aliasing setting: a label keeps its physical size when
|
font_file |
Path to a |
space |
|
adj |
Numeric vector of length 2 giving where the position sits on the
text box, in |
offset |
Numeric vector of length 2: extra offset in output pixels (positive x = right, positive y = down), applied after anchoring. Handy to push an atom label next to the atom instead of onto it. |
line_spacing |
Distance between two lines of a multi-line label, as a multiple of the font's glyph box height (default 1.2). |
depth_test |
Whether a label is hidden by geometry in front of its
anchor (default |
halo_color |
RGBA colour of the halo (outline) drawn behind the glyphs,
which keeps labels readable on dark or busy geometry. |
halo_width |
Halo thickness in pixels (default 1.5). |
rotation |
Rotation of the label in degrees, counter-clockwise, about the anchor position (default 0). Use 90 to write along a vertical axis (the usual orientation of a y-axis label), 180 for an upside-down label, or any other angle to follow an annotation line. The anchor stays fixed while the text turns around it. |
Labels are billboards: they always face the camera and keep the size
given in size (in output pixels), so they stay readable from any
viewpoint — unlike text that is turned into 3D geometry, which skews as the
camera moves. This is the screen-friendly counterpart of
rgl::text3d(), and the intended way to label brain regions, atoms,
panels or figure axes.
Positions are given either in world space (the default) or in
screen space: with space = "screen" the coordinates are
pixels of the output image, measured from the top left corner, which is what
you want for titles, panel tags and captions. World-space labels are
projected with the camera of the scene, so they stick to the annotated
location, and they are hidden by geometry in front of them unless
depth_test = FALSE.
A text layer object (a list with class scimesh_text) for use
in scene() or render_text.
render_text, text_extent,
scene, line_layer
sph <- generate_sphere(c(0, 0, 0), radius = 1) # world-space label above the sphere labels <- text_layer(matrix(c(0, 1.4, 0), ncol = 3), "top", size = 20) labels # screen-space panel tag, positioned by its top left corner tag <- text_layer(c(10, 12), "A", space = "screen", adj = c(0, 1), size = 28, halo_color = c(1, 1, 1, 0.9)) tag # multiple labels with per-label colors and positions multi <- text_layer(matrix(c(-1, 0, 0, 1, 0, 0), ncol = 3, byrow = TRUE), c("left", "right"), colors = matrix(c(1, 0, 0, 1, 0, 0, 1, 1), ncol = 4, byrow = TRUE)) multisph <- generate_sphere(c(0, 0, 0), radius = 1) # world-space label above the sphere labels <- text_layer(matrix(c(0, 1.4, 0), ncol = 3), "top", size = 20) labels # screen-space panel tag, positioned by its top left corner tag <- text_layer(c(10, 12), "A", space = "screen", adj = c(0, 1), size = 28, halo_color = c(1, 1, 1, 0.9)) tag # multiple labels with per-label colors and positions multi <- text_layer(matrix(c(-1, 0, 0, 1, 0, 0), ncol = 3, byrow = TRUE), c("left", "right"), colors = matrix(c(1, 0, 0, 1, 0, 0, 1, 1), ncol = 4, byrow = TRUE)) multi
Transforms all vertex positions in a mesh by a 4x4 homogeneous
matrix (applied as M * (x, y, z, 1)^T). Vertex colors are
kept as they are; vertex normals (if the mesh has any) are
transformed by the inverse transpose of M, so that shading
stays correct for shearing and non-uniform scaling. Use
compute_vertex_normals() if the mesh has no normals yet.
transform_mesh(mesh, matrix)transform_mesh(mesh, matrix)
mesh |
A mesh descriptor list with |
matrix |
A 4x4 numeric matrix. |
A new mesh descriptor list with transformed vertices.
mesh <- generate_cuboid(c(0, 0, 0), c(1, 1, 1)) mat <- diag(4) mat[1:3, 4] <- c(2, 3, 4) translated <- transform_mesh(mesh, mat) translated$vertices[1, ]mesh <- generate_cuboid(c(0, 0, 0), c(1, 1, 1)) mat <- diag(4) mat[1:3, 4] <- c(2, 3, 4) translated <- transform_mesh(mesh, mat) translated$vertices[1, ]
Vertex colors and normals are untouched: a translation does not change the orientation of a surface.
translate_mesh(mesh, translation)translate_mesh(mesh, translation)
mesh |
A mesh descriptor list. |
translation |
Length-3 numeric vector (x, y, z). |
A new mesh descriptor list with translated vertices.
mesh <- generate_cuboid(c(0, 0, 0), c(1, 1, 1)) moved <- translate_mesh(mesh, c(5, 0, 0)) colMeans(moved$vertices)mesh <- generate_cuboid(c(0, 0, 0), c(1, 1, 1)) moved <- translate_mesh(mesh, c(5, 0, 0)) colMeans(moved$vertices)
Returns the viridis color palette. A convenience wrapper around
grDevices::hcl.colors that mimics the viridis color scheme
without requiring extra packages.
viridis_colormap(n, alpha = 1, direction = 1)viridis_colormap(n, alpha = 1, direction = 1)
n |
Number of colors. |
alpha |
Alpha channel value (0-1). |
direction |
Forward (1) or reversed (-1) direction. |
A character vector of hex color strings.
cols <- viridis_colormap(10) plot(1:10, pch = 19, col = cols, cex = 3)cols <- viridis_colormap(10) plot(1:10, pch = 19, col = cols, cex = 3)
Runs the same view and projection as the renderer, so the result lands
exactly on the rendered image. Useful to place screen-space annotations
(text_layer(space = "screen")) next to a 3D location, or to
draw callout lines with line_layer in image coordinates.
world_to_screen(points, camera, width, height, options = NULL)world_to_screen(points, camera, width, height, options = NULL)
points |
An Nx3 numeric matrix of world coordinates (or a length-3 vector for a single point). |
camera |
A camera list from |
width, height
|
Size of the rendered image in pixels. |
options |
Render options, used for the projection type and the clipping
planes. |
A data frame with one row per input point and the columns x,
y (pixels, origin top left), depth (NDC depth, smaller is
closer) and in_front (whether the point is in front of the camera;
for FALSE the pixel coordinates are not meaningful).
sph <- generate_sphere(c(0, 0, 0), radius = 1) cam <- camera_auto(list(sph), direction = c(0, 0, 1)) opts <- render_options(width = 400, height = 300) world_to_screen(matrix(c(0, 1, 0), ncol = 3), cam, 400, 300, opts)sph <- generate_sphere(c(0, 0, 0), radius = 1) cam <- camera_auto(list(sph), direction = c(0, 0, 1)) opts <- render_options(width = 400, height = 300) world_to_screen(matrix(c(0, 1, 0), ncol = 3), cam, 400, 300, opts)
Exports a scene (or a list of meshes) to the glTF 2.0 format, either as a
JSON document with an external binary buffer (.gltf + .bin)
or as a single self-contained binary file (.glb). The resulting
file can be viewed in any glTF-capable viewer (e.g. a browser using
three.js) and includes per-mesh placement transforms, vertex colors, and
optionally a camera.
write_gltf(meshes, path, camera = NULL, format = c("gltf", "glb"))write_gltf(meshes, path, camera = NULL, format = c("gltf", "glb"))
meshes |
Either a |
path |
Output file path. For |
camera |
Optional camera list from |
format |
Output format: |
Renderer-specific settings (shading mode, fog, SSAO, ...) are not part of the glTF standard and are not exported. Per-face colors are exported by splitting vertices (each triangle gets its own vertices), which increases geometry roughly 3x.
Invisibly NULL.
cube <- generate_cuboid(c(0, 0, 0), c(0.5, 0.5, 0.5), c(1, 0, 0, 1)) tr <- diag(1, 4); tr[1, 4] <- 2 sc <- scene(list(cube, list(mesh = cube, transform = tr, name = "second"))) out <- tempfile(fileext = ".glb") write_gltf(sc, out, format = "glb")cube <- generate_cuboid(c(0, 0, 0), c(0.5, 0.5, 0.5), c(1, 0, 0, 1)) tr <- diag(1, 4); tr[1, 4] <- 2 sc <- scene(list(cube, list(mesh = cube, transform = tr, name = "second"))) out <- tempfile(fileext = ".glb") write_gltf(sc, out, format = "glb")
Writes the output of render_mesh() or render_scene()
to a PNG file using the built-in C++ PNG writer (stb_image_write).
No additional R packages are required.
write_png(image, filename)write_png(image, filename)
image |
An image list returned by |
filename |
Output PNG file path. |
No return value; called for side effects.
mesh <- generate_cuboid(c(0, 0, 0), c(1, 1, 1)) img <- render_mesh(mesh$vertices, mesh$triangles) tmp_file <- tempfile(fileext = ".png") write_png(img, tmp_file)mesh <- generate_cuboid(c(0, 0, 0), c(1, 1, 1)) img <- render_mesh(mesh$vertices, mesh$triangles) tmp_file <- tempfile(fileext = ".png") write_png(img, tmp_file)
Writes a scimesh mesh descriptor to an ASCII or binary STL file.
write_stl(mesh, path, format = c("binary", "ascii"))write_stl(mesh, path, format = c("binary", "ascii"))
mesh |
A mesh descriptor list. |
path |
Path to the output STL file. |
format |
|
invisible NULL, called for side effects of writing the file.
mesh <- generate_cuboid(c(0, 0, 0), c(1, 1, 1)) tmp_file <- tempfile(fileext = ".stl") write_stl(mesh, tmp_file, format = "binary")mesh <- generate_cuboid(c(0, 0, 0), c(1, 1, 1)) tmp_file <- tempfile(fileext = ".stl") write_stl(mesh, tmp_file, format = "binary")
Writes the output of render_mesh() or render_scene()
to a TGA file using scimesh's own C++ TGA writer (no external
dependencies). TGA output is uncompressed true-color.
write_tga(image, filename, use24bit = FALSE)write_tga(image, filename, use24bit = FALSE)
image |
An image list returned by |
filename |
Output TGA file path. |
use24bit |
If |
No return value; called for side effects.
mesh <- generate_cuboid(c(0, 0, 0), c(1, 1, 1)) img <- render_mesh(mesh$vertices, mesh$triangles) tmp_file <- tempfile(fileext = ".tga") write_tga(img, tmp_file)mesh <- generate_cuboid(c(0, 0, 0), c(1, 1, 1)) img <- render_mesh(mesh$vertices, mesh$triangles) tmp_file <- tempfile(fileext = ".tga") write_tga(img, tmp_file)