terrain op• Data kinds: mesh → mesh
• Call: import fullseye as fs; fs.ledger.mesh_scatter_boulders(V, F, *, density: 'float', d_min: 'float', d_max=None, exponent: 'float' = 3.1, seed: 'int' = 0, region_weights=None, aspect=(1.0, 0.7, 0.5), embed: 'float' = 0.35, subdiv: 'int' = 1, shape: 'str' = 'ellipsoid', orientation: 'str' = 'normal', burial=(0.3, 0.6), max_count=None, hull_points=None, return_info: 'bool' = False) (to call the implementation directly, import render3d; render3d.mesh_scatter_boulders(V, F, *, density: 'float', d_min: 'float', d_max=None, exponent: 'float' = 3.1, seed: 'int' = 0, region_weights=None, aspect=(1.0, 0.7, 0.5), embed: 'float' = 0.35, subdiv: 'int' = 1, shape: 'str' = 'ellipsoid', orientation: 'str' = 'normal', burial=(0.3, 0.6), max_count=None, hull_points=None, return_info: 'bool' = False); from the registry, ops3d.get("mesh_scatter_boulders"))
Scatter partly-buried boulders on a mesh (power-law sizes, seeded) → (V, F)
(or (V, F, info) with return_info=True).
shape='ellipsoid' (default, unchanged behaviour): icosphere ellipsoids with semi-axes D/2 × aspect (1 : 0.7 : 0.5, shortest axis along the local surface normal), spun about the normal and sunk by embed (0 = resting, 1 = centre on the surface). shape='hull': angular blocks — the convex hull of seeded random points on an ellipsoid of the same aspect with 20 % radial jitter (flat facets, sharp edges, the look of Itokawa's boulders), hull_points per block (default size-dependent:
10 + 8·log2(D/d_min), 10..40). orientation='random' gives each block a uniform random rotation instead of aligning its short axis with the normal. For hulls the sink is the size-dependent burial fraction from sample_boulders (30–60 % of the block's height below the surface by default; embed is ignored). max_count caps the expected number by raising d_min along the same law (see sample_boulders; info['d_min_effective'] reports it).
Placement and sizes come from sample_boulders (Poisson process, N(>D) ∝ D^-exponent); boulders sit on the mesh *as passed* — pass the displaced / subdivided terrain so they rest on the final surface. region_weights from terrain_region_mask keeps the seas smooth. The boulders are appended as real geometry, so they self-shadow, cast shadows and occlude through the same rasteriser / ray-cast path as the terrain. info = the sample dict plus faces_per_boulder and n_boulders. Deterministic under seed. Fail-closed.
Procedure: sample_boulders draws the positions (uniform points on the faces), face normals and diameters D; for each boulder a small mesh is built and appended after the input mesh (vertices are added at the end, face indices are already offset). The input vertices and faces are kept as they are, so from info['faces_per_boulder'] and the original face count you can cut out just the boulder faces.
• density: expected number of boulders with D ≥ d_min per unit area (mesh units²). The actual count follows a Poisson distribution and can be 0 (in that case a copy of the input is returned).
• d_min < d_max (None = 10·d_min), exponent > 0, aspect is 3 positive elements, embed ∈ [0, 1], subdiv ∈ [0, 3] (icosphere subdivision of the ellipsoid), hull_points in [6, 200]. Any of them out of range raises ValueError.
• shape='ellipsoid': places an ellipsoid with semi-axes D/2·aspect with its shortest axis along the normal and lifts its centre by semi_z·(1 − embed) along the normal.
• shape='hull': convex hull of random points on the ellipsoid. Of the height range along the normal, the burial fraction (0.3 to 0.6 by default, deeper for smaller boulders) sinks below the surface. embed is ignored.
• orientation='random' is a uniform random rotation (4-component quaternion). 'normal' is a random rotation about the normal. The rotation draws use a generator seeded with seed + 1.
Boulders are real geometry, so the rays of ambient_occlusion / cast_shadow / shadow_raycast hit them like anything else. The surface they sit on is exactly the mesh you pass, so with mesh_displace_spectrum or mesh_subdivide, call this after them (calling it before makes the boulders float above / sink into the ground).
• Sample-data catalog (download URLs / licences) — 2-D uses skimage.data (BSD/public domain) plus synthetic images; 3-D lists download URLs for real data sources (Stanford, PDS, …).
• Operator provenance and references — the sources of the research/methods this op family came from.
• itokawa_regolith_hero — py -3.11 examples_3d/itokawa_regolith_hero.py
mesh as input)mesh_to_voxel · mesh_to_points · to_points · fuse_to_voxel · ambient_occlusion · cast_shadow · supersample_mesh · render_beauty
terrain)mesh_displace_fbm · terrain_region_mask · mesh_edge_lengths · mesh_subdivide · displacement_band_weights · mesh_displace_spectrum · bump_normals_fbm
*Provenance: render3d.py — 3D operator registry. This per-op note is generated by tools/opdocs.py md (do not hand-edit).*
© 2026 Kazufumi Furuse — Fullseye operator documentation. Licensed under Apache-2.0.