vcg_detect_collision: Detect collisions between two geometries

View source: R/vcg-collision.R

vcg_detect_collisionR Documentation

Detect collisions between two geometries

Description

Reports, for every element of y, whether it comes within radius of x, together with the exact minimum distance. Each side may be a point cloud, a set of connected line segments (for example diffusion streamlines or an electrode shaft), or a triangular mesh, so the function covers all nine pairings with one call.

The test is exact: it produces neither false negatives nor false positives, and distance is the true minimum distance rather than a vertex-sampled approximation.

Usage

vcg_detect_collision(
  x,
  y,
  mode_x = c("auto", "points", "segments", "mesh"),
  mode_y = c("auto", "points", "segments", "mesh"),
  radius = 0,
  early_stop = FALSE,
  include_interior = FALSE
)

Arguments

x

the geometry that gets indexed; a matrix with n rows and 2 or 3 columns, or a surface (see ensure_mesh3d)

y

the geometry that gets queried; results are aligned to this side. Same accepted types as x

mode_x, mode_y

how to interpret x and y: 'points' treats each row as an isolated point, 'segments' chains consecutive rows together (p1-p2, p2-p3, ...), and 'mesh' uses the triangular faces. The default 'auto' resolves to 'mesh' for a surface that has faces and 'points' otherwise, so a matrix of connected line segments must ask for 'segments' explicitly

radius

distance tolerance; a collision is reported when the minimum distance is at most radius. Defaults to 0, meaning literal contact; a positive tolerance is usually what is wanted

early_stop

whether to stop as soon as a collision is found, scanning independently within each group of y delimited by NA rows. A matrix carrying no NA rows is a single group, so a point cloud stops after one hit overall rather than per row; likewise mode_y = 'mesh' stops at the first colliding face. Skipped elements report hit = NA, meaning not evaluated rather than no collision, and the collision reported is the first in row order, not the closest. Use FALSE (the default) whenever the per-element profile or a minimum distance is wanted

include_interior

whether geometry lying strictly inside a closed x surface counts as a collision even when it never comes within radius of the surface itself; requires mode_x = 'mesh'. Default is FALSE

Details

x is indexed once into a uniform spatial grid and y is streamed against it, so put the larger or repeatedly-reused geometry in x and the geometry you want per-element answers about in y.

Several chains share one matrix and are delimited by rows of NA; any row that is not fully finite is treated as a separator. This lets a whole bundle of streamlines be passed as a single matrix:

  rbind(
    c(0, 0, 0), c(1, 0, 0), c(2, 0, 0),   # first streamline
    c(NA, NA, NA),                        # separator
    c(0, 5, 0), c(1, 5, 0)                # second streamline
  )

Those separators also define the unit that early_stop works on: it stops once per group, so a bundle of streamlines yields at most one hit per streamline.

When mode_y = 'segments', row i of the result describes the segment running from row i to row i + 1.

Both geometries must already share one coordinate space. Surface coordinates, volume ⁠IJK⁠ indices, and scanner coordinates are all different spaces, and no transform is applied here.

include_interior relies on ray casting and therefore needs x to be watertight with coherently oriented faces; see vcg_fix_defects to repair a surface that is not.

Multi-threading follows ravetools_threads. The interior test always runs single-threaded.

Value

A list of three vectors, each aligned one-to-one with the rows of y (or with the faces of y when mode_y = 'mesh'):

hit

logical. NA marks an element with nothing to test: a separator row, or the final vertex of each group when mode_y = 'segments'. When early_stop = TRUE, NA also marks elements the scan never reached

distance

the exact minimum distance where hit is TRUE, and NA everywhere else. Distances beyond radius are never computed, so a FALSE element carries no distance

index

1-based index of the closest element of x within radius: a row when mode_x is 'points', the row where the segment starts when 'segments', or a face column when 'mesh'. NA where there is no collision

Coercing Surface Inputs

The surface objects are converted to 'mesh3d' object before applying further calculations.

When surface is a surface ieegio object, the returned mesh3d$vb contains vertices that have been left-multiplied by surface$geometry$transforms[[1]] (the first transform stored in the geometry, typically the ScannerAnat or voxel-to-world transform).

Breaking change: Earlier versions (before 0.2.6) of ravetools returned the raw surface$geometry$vertices without applying any transform, so downstream code often multiplied by surface$geometry$transforms[[1]] (or an equivalent) manually before working in world space. Such code will now double apply the transform and produce incorrect coordinates. If you previously applied a transform from surface$geometry$transforms by hand after calling a ravetools mesh function on an 'ieegio_surface', remove that manual step.

Surfaces with an empty or missing geometry$transforms list (for example, surfaces produced by ieegio's volume_to_surface, which stores an identity transform) are unaffected.

If geometry$transforms contains multiple transforms targeting different coordinate spaces, only the first one is used. Callers that need a specific target space should select and apply that transform themselves before calling ravetools mesh functions.

Examples


library(ravetools)

# A spherical region of interest
roi <- vcg_sphere()

# Two streamlines in one matrix, separated by an NA row: the first passes
# through the sphere, the second stays well clear of it
streamlines <- rbind(
  cbind(seq(-3, 3, by = 0.5), 0, 0),
  c(NA, NA, NA),
  cbind(seq(-3, 3, by = 0.5), 5, 0)
)

result <- vcg_detect_collision(
  x = roi,
  y = streamlines,
  mode_y = "segments",
  radius = 0.1
)

# Which segments touch the sphere, and how close do they get?
data.frame(
  hit = result$hit,
  distance = round(result$distance, 4)
)

# A point cloud against the same region, asking only for proximity
points <- rbind(c(0, 0, 0), c(1.05, 0, 0), c(10, 10, 10))
vcg_detect_collision(roi, points, radius = 0.1)$hit

# The centre of the sphere is far from its surface, so it only counts as a
# collision when the interior is included
vcg_detect_collision(roi, rbind(c(0, 0, 0)), radius = 0.1)$hit
vcg_detect_collision(roi, rbind(c(0, 0, 0)), radius = 0.1,
                     include_interior = TRUE)$hit


ravetools documentation built on Aug. 21, 2026, 5:06 p.m.