indicatrix 0.8.1

A physically-based spectral gemstone renderer that turns GemCAD-style cutting instructions into rendered output.
Documentation
//! The two public entry points: [`solve_meet_points`] (plain) and
//! [`solve_meet_points_with`] (cancellable, progress-reporting).

use std::collections::BTreeMap;

use super::{
    super::{
        MAX_PLANES, MeetTierInput, SolveControl, SolveError, SolvedTier, first_non_finite_tier,
    },
    context::SolveContext,
};

/// Solves every tier's mast distance.
///
/// See the module docs for the model (vertex incidence), what the caller must
/// anchor (one scale reference per crown/pavilion/girdle block), and the
/// three-phase algorithm.
///
/// `gear_teeth_abs` is the index wheel's tooth count (see
/// [`indicatrix_formats::asc::AscSchedule::gear_teeth_abs`]). `tiers` must be in the
/// schedule's own file order: phase 1's dependency-order pass
/// (see the module docs' "three-phase algorithm") walks tiers in this same
/// file order and its result depends on it -- permuting the tier list changes
/// which mast a `DependencyOrder`-strategy tier solves to, not just the
/// order of the returned [`SolvedTier`]s. This is why
/// `Design::apply_edit(Edit::MoveTier { .. })` (in `indicatrix-cut-core`,
/// outside this crate) re-solves after moving a tier rather than reusing the
/// previous solve's masts.
///
/// Deterministic: two calls with identical inputs produce identical outputs. No
/// hashed iteration and no convex-hull library anywhere in this path.
///
/// Latency: `O(P^3)` per refinement sweep in the plane count `P`; see the
/// module docs' "Cost envelope" section for the measured figures. Never
/// cancels and reports no progress -- see [`solve_meet_points_with`] for
/// that; this is a thin wrapper passing a no-op [`SolveControl`], and above
/// [`MAX_PLANES`] it keeps the legacy behavior of returning an all-
/// [`SolveStrategy::Failed`](super::super::SolveStrategy::Failed) result instead of an error.
#[must_use]
pub fn solve_meet_points(gear_teeth_abs: u32, tiers: &[MeetTierInput]) -> Vec<SolvedTier> {
    match solve_meet_points_with(gear_teeth_abs, tiers, &SolveControl::default()) {
        Ok(solved) => solved,
        Err(SolveError::TooManyPlanes { .. } | SolveError::NonFiniteInput { .. }) => {
            SolveContext::new(gear_teeth_abs, tiers).failed_solved()
        }
        Err(SolveError::Cancelled) => {
            unreachable!("SolveControl::default() never sets cancel")
        }
    }
}

/// Cancellable, progress-reporting sibling of [`solve_meet_points`].
///
/// Same algorithm and same determinism guarantee: an unused `control`, i.e.
/// [`SolveControl::default`], reproduces [`solve_meet_points`] exactly, bit
/// for bit. Checks `control` at every cancel point (see the module docs,
/// "Cancellation and progress") and reports a [`SolveProgress`](super::super::SolveProgress)
/// at every point that has one.
///
/// # Errors
///
/// [`SolveError::NonFiniteInput`] when any tier carries a non-finite (NaN or
/// infinite) `angle_deg`, index, or `ScaleReference` mast (checked before any
/// solving starts, so this returns immediately -- see that variant's doc
/// comment for why); [`SolveError::TooManyPlanes`] when the design has more
/// than [`MAX_PLANES`] facet-plane instances; [`SolveError::Cancelled`] the
/// first time `control`'s cancel flag is observed set.
pub fn solve_meet_points_with(
    gear_teeth_abs: u32,
    tiers: &[MeetTierInput],
    control: &SolveControl<'_>,
) -> Result<Vec<SolvedTier>, SolveError> {
    if let Some(err) = first_non_finite_tier(tiers) {
        return Err(err);
    }
    let ctx = SolveContext::new(gear_teeth_abs, tiers);
    if ctx.total_planes > MAX_PLANES {
        return Err(SolveError::TooManyPlanes {
            planes: ctx.total_planes,
            max: MAX_PLANES,
        });
    }
    if control.is_cancelled() {
        return Err(SolveError::Cancelled);
    }
    let result = ctx.run_pipeline(&BTreeMap::new(), &BTreeMap::new(), control)?;
    Ok(ctx.to_solved(&result))
}