Nothing
#' Create a camera specification
#'
#' Defines a camera for rendering by specifying the eye position,
#' look-at center, up vector, projection type, and field of view.
#'
#' @param eye Numeric vector of length 3: camera position.
#' @param center Numeric vector of length 3: point the camera looks at.
#' @param up Numeric vector of length 3: camera up direction.
#' @param projection Projection type: \code{"perspective"} (default)
#' or \code{"orthographic"}.
#' @param fov Field of view in degrees (perspective only).
#' @return A camera list suitable for \code{render_mesh()} or
#' \code{render_scene()}.
#'
#' @examples
#' cam <- camera(eye = c(0, 0, 5), center = c(0, 0, 0))
#' cam$eye
#'
#' @export
camera <- function(eye, center, up = c(0, 1, 0),
projection = c("perspective", "orthographic"),
fov = 45) {
projection <- match.arg(projection)
structure(list(
eye = as.numeric(eye),
center = as.numeric(center),
up = as.numeric(up),
projection = projection,
fov = as.numeric(fov)
), class = "scimesh_camera")
}
#' Auto-frame a camera to fit a mesh or vertex set
#'
#' Computes a camera position that frames the entire mesh in view.
#' The camera looks along the given direction, positioned at a distance
#' that ensures the mesh fits within the field of view.
#'
#' When \code{rgl_compat = TRUE}, the camera mimics rgl's default
#' auto-framing behaviour: a 30° FOV, 15° elevation, and the distance
#' is computed from the \emph{bounding sphere} of the mesh (the
#' half-diagonal of the axis-aligned bounding box), reproducing the
#' formula \code{distance = sphere_radius / sin(FOV/2)} used by rgl.
#'
#' @param mesh Either an Nx3 numeric matrix of vertex positions, or a
#' mesh descriptor list with a \code{vertices} component.
#' @param direction The view direction as a length-3 vector. For
#' example, \code{c(0, 0, -1)} looks along the negative Z axis.
#' Ignored when \code{rgl_compat = TRUE}.
#' @param up The up vector as a length-3 vector. Default \code{c(0, 1, 0)}.
#' Ignored when \code{rgl_compat = TRUE}.
#' @param fov Field of view in degrees. Default 45° (30° when
#' \code{rgl_compat = TRUE}).
#' @param margin Extra margin factor (1.0 = tight fit, 1.1 = 10\% margin).
#' @param rgl_compat Logical. If \code{TRUE}, use rgl's camera defaults
#' and bounding-sphere distance formula. Default \code{FALSE}.
#' @param projection Projection type: \code{"perspective"} (default) or
#' \code{"orthographic"}. When orthographic, the camera distance is
#' computed to tightly frame the mesh regardless of FOV.
#' @return A camera list, with S3 class \code{"scimesh_camera"}.
#'
#' @examples
#' 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)
#'
#' @export
camera_auto <- function(mesh, direction = c(0, 0, -1), up = c(0, 1, 0),
fov = 45, margin = 1.1,
rgl_compat = FALSE,
projection = c("perspective", "orthographic")) {
projection <- match.arg(projection)
# Transparently accept rgl-style meshes (vb/it format)
if (is.list(mesh) && !is.null(mesh$vb) && !is.null(mesh$it)) {
mesh <- mesh_from_rgl(mesh)
}
if (is.list(mesh) && !is.null(mesh$vertices)) {
mesh_data <- mesh
} else if (is.matrix(mesh) && ncol(mesh) == 3L) {
mesh_data <- list(
vertices = mesh,
triangles = matrix(integer(0), nrow = 0, ncol = 3)
)
} else if (is.list(mesh) && !is.null(mesh[[1]]$vertices)) {
all_verts <- do.call(rbind, lapply(mesh, function(m) m$vertices))
all_tris <- do.call(rbind, lapply(seq_along(mesh), function(i) {
m <- mesh[[i]]
if (!is.null(m$triangles) && nrow(m$triangles) > 0) {
offset <- if (i == 1) 0L else sum(sapply(mesh[seq_len(i - 1)],
function(mm) nrow(mm$vertices)))
m$triangles + offset
} else {
matrix(integer(0), nrow = 0, ncol = 3)
}
}))
mesh_data <- list(vertices = all_verts, triangles = all_tris)
} else {
stop(
"mesh must be an Nx3 matrix, a mesh descriptor list, or a list of mesh descriptors"
)
}
if (isTRUE(rgl_compat)) {
# --- rgl-compatible auto-framing ----------------------------------------
# rgl defaults: FOV = 30°, phi = 15° elevation, theta = 0°
# Distance = bounding_sphere_radius / sin(FOV / 2)
#
# The bounding sphere is the sphere that encloses the AABB, with
# radius = half the length of the AABB diagonal.
rgl_fov <- 30
verts <- mesh_data$vertices
vmin <- apply(verts, 2, min)
vmax <- apply(verts, 2, max)
half_diag <- (vmax - vmin) / 2
sphere_radius <- sqrt(sum(half_diag^2))
fov_half_rad <- (rgl_fov / 2) * pi / 180
distance <- sphere_radius / sin(fov_half_rad)
# rgl default orientation: theta = 0 (no azimuthal rotation),
# phi = 15° elevation above the horizontal plane.
# This is equivalent to looking along -Z tilted upward by phi.
phi_rad <- 15 * pi / 180
# Compute center: centroid of the bounding box
center <- (vmin + vmax) / 2
# Direction tilted up by phi from -Z axis
direction <- c(0, sin(phi_rad), -cos(phi_rad))
# Up vector: rotated with the direction
up <- c(0, cos(phi_rad), sin(phi_rad))
eye <- center + direction * distance
camera(eye = eye, center = center, up = up,
projection = projection, fov = rgl_fov)
} else {
scimesh_camera_fit_mesh(mesh_data, direction, up, fov, margin, projection)
}
}
#' Orbit a camera around an axis
#'
#' 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.
#'
#' @param camera A camera list from \code{camera()} or \code{camera_auto()}.
#' @param axis Rotation axis as a length-3 vector. Default \code{c(0, 0, 1)} (Z axis).
#' @param angle_degrees Rotation angle in degrees.
#' @return A camera list with S3 class \code{"scimesh_camera"}.
#'
#' @examples
#' 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)
#'
#' @export
camera_orbit <- function(camera, axis = c(0, 0, 1), angle_degrees) {
axis <- as.numeric(axis) / sqrt(sum(axis^2))
angle <- angle_degrees * pi / 180
rotate <- function(v) {
cos_a <- cos(angle)
sin_a <- sin(angle)
dot <- sum(v * axis)
cross <- c(
v[2] * axis[3] - v[3] * axis[2],
v[3] * axis[1] - v[1] * axis[3],
v[1] * axis[2] - v[2] * axis[1]
)
v * cos_a + cross * sin_a + axis * dot * (1 - cos_a)
}
cam <- camera
cam$eye <- cam$center + rotate(cam$eye - cam$center)
cam$up <- rotate(cam$up)
cam
}
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.