Query mesh edges
Find the nearest edge to a point or a ray, get the k nearest edges, inspect edge topology, and mask edges in RayD.
This guide runs the edge queries on a small scene and shows how to restrict them with an edge mask. Read Edges and edge sampling first for what an edge is and why the queries exist.
The examples use a floor at z = 0 (mesh 0) and a lid at z = 3 (mesh 1). Each two-triangle quad has five edges, so the scene has ten.
Nearest edge to a point#
import drjit as dr
import rayd.drjit as rd
from drjit.cuda import Array3f, Array3i, Bool
def quad(z):
return rd.Mesh(Array3f([-1, 1, 1, -1], [-1, -1, 1, 1], [z, z, z, z]),
Array3i([0, 0], [1, 2], [2, 3]))
scene = rd.Scene()
scene.add_mesh(quad(0.0))
scene.add_mesh(quad(3.0))
scene.build()
pts = Array3f([0.0, 0.9], [0.0, 0.0], [0.1, 0.0])
ne = scene.nearest_edge(pts)
print(ne.distance) # [0.1, 0.1]
print(ne.global_edge_id) # [1, 3]
print(ne.is_boundary) # [False, True]: the diagonal, then the x = 1 borderThe record also gives point (the query point), edge_point (the closest point on the edge), edge_t (its parameter in [0, 1] from the edge start), shape_id, and the mesh-local edge_id. In Torch, rayd.torch.NearestPointEdge has distance, edge_point, edge_t, shape_id, edge_id, and global_edge_id.
Nearest edge to a ray#
Pass a ray instead of points to find the edge closest to the ray. The result adds ray_t, the ray parameter at the closest approach. A ray with finite tmax is a segment.
r = rd.Ray(Array3f([0.0], [-2.0], [1.0]), Array3f([0.0], [1.0], [0.0]))
rq = scene.nearest_edge(r)
print(rq.distance, rq.ray_t) # [1] [2]: the diagonal, global edge 1The k nearest edges#
nearest_edges(points, k) returns up to k edges per point, nearest first. k must be at most 16.
topk = scene.nearest_edges(pts, 3)
print(topk.query_count, topk.k) # 2 3
print(topk.distances) # [0.1, 1.00499, 1.00499, 0.1, 0.636396, 1]The Dr.Jit result is flat: entry q * k + j is the j-th edge of query q. Slots without an edge have is_valid false. In Torch every field has shape (N, k).
Mask edges#
set_edge_mask(mask) takes one bool per scene-global edge. Edges with False are skipped by edge queries; ids and offsets do not change. The mask is a queued update, so call sync() before the next query.
info = scene.edge_info()
mask = dr.full(Bool, True, info.size())
mask = dr.select(info.shape_id == 0, Bool(False), mask) # hide the floor's edges
scene.set_edge_mask(mask)
scene.sync()
ne = scene.nearest_edge(pts)
print(ne.shape_id, ne.distance) # [1, 1] [2.9, 3.00167]Torch has the same set_edge_mask(mask) with a (E,) bool tensor, and edge_mask() to read it back. To leave a mesh out of the edge set from the start, create it with edges_enabled=False.
Inspect topology#
The Dr.Jit backend gives direct access to the edge data:
| Call | Returns |
|---|---|
scene.edge_info() | SceneEdgeInfo: start, edge (vector), end, length, normal0, normal1, is_boundary, shape_id, local_edge_id, and global_edge_id for every global edge |
scene.edge_topology() | SceneEdgeTopology: local and global endpoint, adjacent-face, and opposite-vertex ids for every global edge |
scene.mesh_edge_offsets() | prefix sums: mesh m owns global edges offset[m] to offset[m + 1] - 1 |
scene.edge_adjacent_faces(edge_id) | the two adjacent global face ids (-1 for a missing face) |
scene.triangle_edge_indices(prim_id) | the three edge ids of a triangle |
mesh.secondary_edges() | SecondaryEdgeInfo for one mesh, before it joins a scene |
A typical use is to build a silhouette mask: for each edge, compare the signs of dot(n0, view) and dot(n1, view) and keep the edges where they differ, plus the boundary edges.
Gradients#
Edge distances and closest points are differentiable with respect to the query and the mesh vertices, with the chosen edge held fixed. Use AD point arrays (drjit.cuda.ad.Array3f) or Torch tensors with requires_grad=True.