Skip to content

shore.surface.cap & shore.surface.equator

The two k=0 builders consumed by CubedSphereTopology:

  • shore.surface.cap.build_cap_k0 — gnomonic flat-square cap k=0 seed (one cap face per pole).
  • shore.surface.equator.build_equator — great-circle meridian equator k=0 from the two cap seam rings, with per-j C1 step matching at the cap-equator seam.

Both consume a Projector and emit a coordinate array on the body surface. They are decoupled from the HexBlock wiring — CubedSphereTopology.build calls them, then stitches the output into the 6-block ring + cap allocations.

For the algorithm, see k=0 mesh construction.

build_cap_k0

python
def build_cap_k0(
    theta_cap: float,
    nc: int,
    pole: str,
    projector: object,
    j_spacing: str = "uniform",
    j_beta: float = 3.0,
) -> np.ndarray

Build the (nc, nc, 3) cap k=0 seed face by gnomonic flat-square projection: a flat diamond is placed in the tangent plane at the cap pole with corners on the cardinal axes at distance tan(theta_cap) (4 corners at azimuths {0°, 90°, 180°, 270°}, aligned with sub-block junctions). Interior nodes by bilinear interpolation, then radially projected through the cached BVH onto the STL.

ParameterDescription
theta_capPolar exclusion angle in radians. Cap corners sit at colatitude theta_cap. Cap-edge midpoints sit at colatitude atan(tan(theta_cap)/sqrt(2)) — slightly poleward of the corners (the natural shape of a great-circle arc between two parallel-circle corners).
ncCap resolution. Must equal nj // 4 + 1 so the cap edges have the same node count as the sub-block pole rows.
pole"north" (cap_north, z<0 of body) or "south" (cap_south, z>0). The two caps differ in their (z, y) sign convention so the index winding matches _overwrite_cap_boundaries.
projectorA Projector with cached BVH and the body's anchor.
j_spacingTangent-plane node distribution along cap edges: "uniform" (default) or "tanh2". Inherited by the equator seam.
j_betaClustering strength for "tanh2".

Returns numpy.ndarray (nc, nc, 3) — cap k=0 layer with nodes on the body surface.

The 4-fold symmetry of the gnomonic square gives square cap cells by construction (no concentric pole pattern, AR ≈ 1.0); see Cubed-sphere topology and k=0 mesh construction — gnomonic flat-square cap.

build_equator

python
def build_equator(
    cap_south: np.ndarray,
    cap_north: np.ndarray,
    ni: int,
    theta_cap: float,
    projector: object,
    i_spacing: str = "uniform",
    i_beta: float = 3.0,
) -> np.ndarray

Build the (ni, nj, 3) equator k=0 layer from the two cap seam rings.

  • Row i=0 is the south seam (cap_south boundary, traversed in sub-block order); row i=ni-1 is the north seam. Both are bit-exact copies of the cap edges (no projector round-trip).
  • Interior rows: per-j slerp meridian from south seam to north seam, ray-cast through projector onto the STL.
  • Each meridian's first/last interval is per-j pinned to the cap-edge first cell at the corresponding seam vertex (C1 spacing match at every seam vertex — including the 3-edge cap-corner junctions). The pinning is enabled when the cap and equator radial step magnitudes are comparable; if the equator's natural first step disagrees with the cap by more than ~2×, the equator falls back to its unpinned i_spacing law and emits a UserWarning.
ParameterDescription
cap_south, cap_northCap k=0 layers, (nc, nc, 3) each, as returned by build_cap_k0.
niNumber of equator latitude rows including both seam rows.
theta_capPolar exclusion angle in radians (matches the value used to build the caps).
projectorThe same Projector used for the cap build.
i_spacingLatitude spacing law: "uniform" (default) / "tanh" / "tanh2".
i_betaClustering strength for tanh / tanh2.

Returns numpy.ndarray (ni, nj, 3) with nj = 4 * (nc - 1).

Example

python
from shore.surface.io import load_stl
from shore.surface.anchor import resolve_anchor
from shore.surface.projector import Projector
from shore.surface.cap import build_cap_k0
from shore.surface.equator import build_equator

mesh = load_stl("body.stl")
proj = Projector(mesh, resolve_anchor(mesh))

theta_cap, nc, ni = 0.5236, 16, 40   # ~30 deg, 16 nodes per cap edge

cap_s = build_cap_k0(theta_cap, nc, pole="south", projector=proj)
cap_n = build_cap_k0(theta_cap, nc, pole="north", projector=proj)
equator = build_equator(cap_s, cap_n, ni=ni, theta_cap=theta_cap, projector=proj)

print(cap_s.shape, cap_n.shape, equator.shape)
# (16, 16, 3) (16, 16, 3) (40, 60, 3)

In practice this orchestration is hidden inside CubedSphereTopology.build — call it via Mesh.from_stl(... topology=CubedSphereTopology()). The two functions are documented separately for users who want to read the kernel or reuse the cap k=0 / equator k=0 algorithm in a new topology.

See also

Released under the MIT License.