terrain op• Data kinds: mesh → mesh
• Call: import fullseye as fs; fs.ledger.mesh_displace_spectrum(V, F, wavelengths=(0.06, 0.03, 0.015, 0.0075, 0.00375), amplitudes=(0.003, 0.00176, 0.00103, 0.0006, 0.00035), *, seed: 'int' = 0, nyquist: 'float' = 2.0, fade: 'float' = 1.0, weights=None, local_edge=None) (to call the implementation directly, import render3d; render3d.mesh_displace_spectrum(V, F, wavelengths=(0.06, 0.03, 0.015, 0.0075, 0.00375), amplitudes=(0.003, 0.00176, 0.00103, 0.0006, 0.00035), *, seed: 'int' = 0, nyquist: 'float' = 2.0, fade: 'float' = 1.0, weights=None, local_edge=None); from the registry, ops3d.get("mesh_displace_spectrum"))
Displace vertices along their normals with a stated amplitude spectrum, band-limited.
The band limit is per vertex; returns (V, F).
displacement_i = Σ_k A_k · n_k(x_i) · gate_k(i) · w_k(i) with one seeded value-noise octave n_k ∈ [−1, 1] per (wavelength_k, amplitude_k) pair (mesh units — the Itokawa STL is in km, so the default is 3 m at 60 m falling as A ∝ λ^0.77 to 0.35 m at 3.75 m), gate = displacement_band_weights on *this* mesh (an octave shorter than nyquist × local edge is not applied — it would alias into facet noise; route it to bump_normals_fbm instead) and weights = optional (N,) per-vertex or (K, N) per-octave-per-vertex factor in [0,1] (e.g. the synthetic-relief weight 1 − gate of the source model). The peak displacement at a vertex is at most Σ_k A_k (tests pin it). Unlike mesh_displace_fbm (one amplitude, octave ratio 2, no band limit) every octave's amplitude is explicit. Deterministic under seed. Fail-closed on shapes / non-finite / negative values.
Procedure: (1) build gate (K,N) with displacement_band_weights, (2) if weights is given, multiply it in, (3) for each octave k evaluate a seed-fixed value noise n_k(x) ∈ [-1,1] (wavelength λ_k, lattice offset varied by the octave number) and take Σ_k A_k · gate_k · n_k as the displacement along the normal, (4) move the vertices along the area-weighted vertex normals. Returns (V' (N,3) float64, copy of F).
• wavelengths / amplitudes: positive sequences of the same length (1 to 32 entries). Mesh units. A count mismatch, empty, 33 or more entries, a negative amplitude or amplitudes=None raise ValueError. Octaves with amplitude 0 are skipped.
• nyquist / fade / local_edge: band-gate settings (same as displacement_band_weights).
• weights: (N,) is a factor shared by all octaves, (K,N) a per-octave factor. Values are limited to [0, 1]; out of range or a shape mismatch raises ValueError.
• seed: the same seed shares the same lattice as bump_normals_fbm (octaves that the geometry cannot carry can be handed on seamlessly to the shading side).
The difference from mesh_displace_fbm is that the amplitude is explicit per octave and that the band is cut per vertex by edge length. Giving short wavelengths to a coarse mesh does not alias; they are simply ignored.
• 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_scatter_boulders · mesh_edge_lengths · mesh_subdivide · displacement_band_weights · 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.