mesh — target satellite surface geometry

Purpose

Owns the target-satellite mesh: the Open3D raycasting scene, triangle area/centroid tables,
kd-tree nearest-surface queries, area-uniform surface sampling, and the orbit→surface aim path
(SurfacePath). The stateless geometry oracle the guidance stack queries.

Role in the system

  • Consumed by ee_guidance (EEGuidance): constructs a Mesh, builds surface_path(orbit), and
    calls SurfacePath.eval2(s) each step for the analytic-FF aim point (ee_guidance.py:93,99,277).
  • Consumed by target_finder (EETargetFinder): holds its own Mesh copy and raycasts through
    geometry.scene for candidate scoring + per-triangle FOV coverage marking (target_finder.py:54,59).
  • Consumed by plotter3d: sample, surface_path, and scene for the standalone report figures.
  • Geometry helpers safe_normalize / saturate come from geometry; orbit input is an
    orbit OrbitGenerator (uniform-arclength path + arclength_samples).

Inputs / Outputs

  • In: cfg (mesh path, scoring.sampling.n_targets); query points / ray origins+directions; an
    orbit for the surface path.
  • Out: Packages of nearest surface points + normals + distances (query*), ray hits
    (raycast: t_hit, points, finite-hit mask), area-uniform point clouds (sample), and a
    SurfacePath exposing x_surf, dx_ds, d2x_ds2 with eval(s) / eval2(s).

Key methods

  • query — closest surface point per query; exact=True uses the o3d scene (true closest-on-triangle, slow), else the sampled-surface kd-tree (fast) — utils/mesh.py:54
  • query_surface / query_triangle — nearest sampled-surface point / nearest triangle centroid (kd-tree) — utils/mesh.py:80 / :89
  • raycast — cast rays, return t_hit, hit points, finite-hit mask — utils/mesh.py:97
  • sample — area-uniform surface sample; seeds open3d’s global RNG (its only hook) — utils/mesh.py:112
  • surface_path — build the SurfacePath projecting the COM orbit onto the mesh — utils/mesh.py:124
  • SurfacePath.eval2x, dx/ds, d²x/ds² at arclength s for the analytic feedforward — utils/mesh.py:175
  • _ensure_surface_samples — lazily build the sampled-surface kd-tree on first use (the only RNG-touching path) — utils/mesh.py:47

Footguns

open3d sampling mutates a GLOBAL RNG — surface samples are built LAZILY

sample_points_uniformly honors only the global seed o3d.utility.random.seed. Sampling at
construction would perturb RNG state other components rely on, so the sampled-surface kd-tree is
built lazily on first use (_ensure_surface_samples, seed=0) — the only RNG-touching path.
(utils/INSIGHTS.md [footgun])

EETargetFinder keeps its own redundant Mesh/scene copy (until Chain B)

Promoted from the sandbox/mesh_oo prototype (Jun 8) and reconciled against the live
OrbitSurfacePath algorithm. Until Chain B unifies the finder onto this class, the finder loads
a second mesh/scene — one redundant load/run, accepted deliberately. (utils/INSIGHTS.md [history])

Raw orbit→surface projection is full of inf/nan misses and facet jumps

A bare inward ray produces inf/nan at concavities and discontinuous jumps at facet crossings.
SurfacePath cleans this in three stages so dx/ds (the analytic-FF velocity) stays bounded —
see Pseudocode. If no ray hits the mesh it asserts. (utils/INSIGHTS.md [mesh])

Pseudocode (SurfacePath construction)

dirs = -path / |path|                       # inward ray from each COM-orbit point
t_hit = scene.cast_rays([path, dirs])        # project onto the mesh
x = path + t_hit * dirs                       # surface hit points
(1) fill misses (inf/nan) with nearest valid neighbour
(2) Savitzky-Golay smooth to C1 (window=151, poly=3)
(3) rate-limit residual jumps: x[i] = x[i-1] + saturate(x[i]-x[i-1], max_step)
dx_ds  = gradient(x_surf, s)                  # arclength derivative (FF velocity)
d2x_ds2 = gradient(dx_ds, s)                  # 2nd derivative (FF acceleration)

Equations & references

ee_guidance · target_finder · orbit · geometry · analytic_feedforward · plotter3d · robot · terminology