Mesh
Ray queries, curvature, quality metrics and orientation repair on triangle meshes. 6 figures.

Ray queries
Which triangles and which voxels a ray hits, found through the BVH.
from conquer3d._C import MeshBVH
# The BVH indexes triangle boxes, so build it from those.
tv = verts[faces.long()]
bvh = MeshBVH(tv.min(1).values.contiguous(),
tv.max(1).values.contiguous())
_, hit_tris, hit_points, hit_dist = bvh.get_ray_intersection(
ray_origins, ray_dirs, verts, faces, True)
Curvature
Mean curvature in all three modes, plus Gaussian and both principal curvatures, computed on the GPU from the mesh itself.
# Mean curvature in each of its three modes.
for mode in (0, 1, 2):
H = mesh.get_mean_curvature(mode)
K = mesh.get_gaussian_curvature()
k1, k2 = mesh.get_principal_curvatures()
Laplacian smoothing
The same mesh smoothed for 10, 50 and 100 iterations, with the mean curvature of each result on one shared scale beneath it. The last panel repeats 100 iterations with a patch held fixed, which keeps the detail the others lose.
from conquer3d.data_structure import TriangleMesh
base_v = mesh.vertices.clone()
faces = mesh.triangles.int().clone()
for iterations in (10, 50, 100):
# smooth() is in place, so each level starts fresh.
level = TriangleMesh(base_v.clone().contiguous(),
faces.clone().contiguous())
level.smooth(iterations=iterations, damping=0.5,
mode=1) # 1 = cotangent weights
H = level.get_mean_curvature(0)
# locked holds vertices fixed, so detail inside the patch
# survives while everything around it diffuses. It is
# unioned with the boundary pin, never replaces it.
patch = (base_v - centre).norm(dim=-1) < 0.45
level.smooth(iterations=100, damping=0.5, mode=1,
locked=patch.contiguous())
Higher-order fairing
The bend of a tube faired at orders 1, 2 and 3 with the straight ends held fixed, after Botsch et al., Polygon Mesh Processing, Figure 4.8. The order of the flow sets how smoothly the faired region meets what is held.
from conquer3d.data_structure import TriangleMesh
# locked is True where a vertex is held fixed: here the two
# straight ends, leaving the bend between them free to move.
for k, iterations in ((1, 50_000), (2, 400_000),
(3, 4_000_000)):
bend = TriangleMesh(verts.clone(), faces.clone())
# Order k runs the flow whose steady state is the
# membrane (1), thin plate (2) or minimum variation (3)
# surface. Stability is guaranteed up to 2 ** (1 - k).
bend.fair(k=k, iterations=iterations,
damping=0.5 * 2 ** (1 - k),
mode=0, locked=locked)
Mesh quality
Every quality metric a mesh reports, drawn on the mesh with its mean value.
# The maps the metrics are computed from.
mesh.compute_triangle_areas()
mesh.compute_vertices_to_triangle_map()
mesh.compute_edges_to_triangle_map()
aspect = mesh.get_aspect_ratio(0)
radii = mesh.get_radii_ratio()
radius_edge = mesh.get_radius_edge_ratio()
regularity = mesh.get_triangle_regularity()
deviation = mesh.get_angle_deviation()
q_min, q_mean = mesh.get_quality()
Orientation repair
Half the triangle windings reversed at random, then recovered exactly by fix_normals.
from conquer3d.data_structure import TriangleMesh
# Half the windings reversed at random.
broken = faces.clone()
flip = torch.rand(broken.shape[0], device=DEV) < 0.5
broken[flip] = broken[flip][:, [0, 2, 1]]
damaged = TriangleMesh(verts, broken.int().contiguous())
damaged.fix_normals() # recovers the orientation