wip: surface.surface_point_solve_p2 port, shim, ffi, runner

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BY8UpXKuMci15rAWGXsbsm
This commit is contained in:
bherbruck
2026-10-14 15:58:48 -04:00
parent c685538729
commit c39c1fd388
8 changed files with 3973 additions and 32 deletions
@@ -240,7 +240,7 @@ fn cpp(i: &Inputs) -> Result<Output, HarnessError> {
// ---- Rust side ----------------------------------------------------------------------
fn wrapped_of(wrap: bool, s: &SurfIn) -> Result<Surface, HarnessError> {
pub(crate) fn wrapped_of(wrap: bool, s: &SurfIn) -> Result<Surface, HarnessError> {
let s = surface_in(s)?;
Ok(if wrap {
Surface::from(StepSurface::new(s, true))
@@ -249,15 +249,15 @@ fn wrapped_of(wrap: bool, s: &SurfIn) -> Result<Surface, HarnessError> {
})
}
fn p2(uv: [f64; 2]) -> Point2 {
pub(crate) fn p2(uv: [f64; 2]) -> Point2 {
Point2::new(uv[0], uv[1])
}
fn push2(o: &mut Vec<f64>, v: Point2) {
pub(crate) fn push2(o: &mut Vec<f64>, v: Point2) {
o.extend([v.x, v.y]);
}
fn solver_op(code: u8) -> Result<SolverOperation, HarnessError> {
pub(crate) fn solver_op(code: u8) -> Result<SolverOperation, HarnessError> {
SolverOperation::ALL
.get(usize::from(code))
.copied()
@@ -485,10 +485,10 @@ fn rust(i: &Inputs) -> Result<Output, HarnessError> {
// ---- generated cases ----------------------------------------------------------------
/// The supported solver operations.
const SOLVERS: [u8; 5] = [0, 3, 4, 5, 10];
pub(crate) const SOLVERS: [u8; 5] = [0, 3, 4, 5, 10];
/// A point of `d`: a corner, a point on a side, or inside.
fn gen_uv(rng: &mut Rng, d: [f64; 4]) -> [f64; 2] {
pub(crate) fn gen_uv(rng: &mut Rng, d: [f64; 4]) -> [f64; 2] {
let pick = |rng: &mut Rng, a: f64, b: f64| -> f64 {
match rng.below(6) {
0 => a,
@@ -500,7 +500,7 @@ fn gen_uv(rng: &mut Rng, d: [f64; 4]) -> [f64; 2] {
}
/// A point in `d` or, half the time, somewhat outside it.
fn gen_uv_any(rng: &mut Rng, d: [f64; 4]) -> [f64; 2] {
pub(crate) fn gen_uv_any(rng: &mut Rng, d: [f64; 4]) -> [f64; 2] {
let [u, v] = gen_uv(rng, d);
if rng.chance(0.5) {
return [u, v];
@@ -514,7 +514,7 @@ fn gen_uv_any(rng: &mut Rng, d: [f64; 4]) -> [f64; 2] {
/// A test point for the surface: on it, slightly or well off it along its normal, or
/// anywhere around it.
fn gen_test(rng: &mut Rng, s: &Surface, d: [f64; 4], size: f64) -> [f64; 3] {
pub(crate) fn gen_test(rng: &mut Rng, s: &Surface, d: [f64; 4], size: f64) -> [f64; 3] {
let uv = gen_uv(rng, d);
let Ok((p, su, sv)) = s.evaluate_1st_derivatives(p2(uv), false, false) else {
return gvec(rng, size * 2.0);
@@ -539,7 +539,7 @@ fn gen_test(rng: &mut Rng, s: &Surface, d: [f64; 4], size: f64) -> [f64; 3] {
/// A test point on the surface normal at `uv`: on the surface, or off it on either
/// side by a small to large distance.
fn test_on_normal(rng: &mut Rng, s: &Surface, uv: [f64; 2], size: f64) -> [f64; 3] {
pub(crate) fn test_on_normal(rng: &mut Rng, s: &Surface, uv: [f64; 2], size: f64) -> [f64; 3] {
let Ok((p, su, sv)) = s.evaluate_1st_derivatives(p2(uv), false, false) else {
return gvec(rng, size);
};
@@ -555,7 +555,7 @@ fn test_on_normal(rng: &mut Rng, s: &Surface, uv: [f64; 2], size: f64) -> [f64;
}
/// A closed B-spline: a full rational circle (or a closed polygon) in u swept in v.
fn gen_closed(rng: &mut Rng) -> InlineSrf {
pub(crate) fn gen_closed(rng: &mut Rng) -> InlineSrf {
let r = rng.uniform(0.5, 5.0);
let h = rng.uniform(0.5, 5.0);
let v_rows = 2 + rng.below(2) as usize;
@@ -623,7 +623,7 @@ fn gen_closed(rng: &mut Rng) -> InlineSrf {
}
/// A bilinear (2 x 2) surface, sometimes twisted.
fn gen_bilinear(rng: &mut Rng) -> InlineSrf {
pub(crate) fn gen_bilinear(rng: &mut Rng) -> InlineSrf {
let tw = if rng.chance(0.5) { 0.0 } else { rng.uniform(-1.0, 1.0) };
let net = vec![
vec![[0.0, 0.0, 0.0, NOW], [0.0, 1.0, 0.0, NOW]],
@@ -675,7 +675,7 @@ fn gen_zigzag(rng: &mut Rng) -> InlineSrf {
}
}
fn gen_surface(rng: &mut Rng) -> SurfIn {
pub(crate) fn gen_surface(rng: &mut Rng) -> SurfIn {
let bss = |s: InlineSrf| BssIn {
oob: false,
form: default_form(),
@@ -690,7 +690,7 @@ fn gen_surface(rng: &mut Rng) -> SurfIn {
}
/// The control-net counts of a surface input (a plane's net is 2 x 2).
fn net_counts(s: &SurfIn) -> Result<(usize, usize), HarnessError> {
pub(crate) fn net_counts(s: &SurfIn) -> Result<(usize, usize), HarnessError> {
match s {
SurfIn::Plane(_) => Ok((2, 2)),
SurfIn::Bspline(b) | SurfIn::CurveBounded(b) => {
@@ -845,7 +845,7 @@ fn gen_case(rng: &mut Rng) -> Result<Inputs, HarnessError> {
/// The test points of `my_test_surface_closest_point` on `Surface.sms`
/// (`tests/prog_test/src/smSurfaceTest.cpp:4117`).
const SURFACE_SMS_POINTS: [[f64; 3]; 15] = [
pub(crate) const SURFACE_SMS_POINTS: [[f64; 3]; 15] = [
[-1.0616429349692744, -20.167126109553802, 20.020823739437560],
[-1.1583434957685239, -19.953438722680168, 20.005921768970701],
[-4.5941574701649142, -9.6528635542995485, 19.290949275641431],
@@ -0,0 +1,948 @@
//! Runner for unit `surface.surface_point_solve_p2`: the global solver's guesses,
//! refinements, seam solutions and `Solve`, and the local stepper's step adjustment,
//! norms, convergence and boundary tests, steps and line searches
//! (`SmSurfacePointSolve.cpp`).
//!
//! C++: `solidcore_ffi::units::surface_surface_point_solve_p2` (the shim compiles the
//! upstream file, whose classes are file-local). Rust:
//! `solidcore::surface::surface_point_solve::p2`.
//!
//! A case is a problem as in the `surface.surface_point_solve_p1` runner (surface `surf`,
//! wrapped in a STEP surface when `wrap`; solver domain `dom`, operation `solver` as an
//! `SmSolverOperationType` code, test point `test`, tolerance `tol`) and an `op` with its
//! parameters. Both sides produce the flat record documented in the ffi module; the
//! runner reports its first entry as the `status` (`0` = `SM_SUCCESS`, else the
//! `SmStatus`; `-1` for a Rust error upstream cannot produce) and the rest as the `out`
//! values. Solutions are `n` then `u v value objects variables object0_is_surface` each.
//!
//! Cases: inline cases on the upstream `Surface.sms` with the test points of
//! `my_test_surface_closest_point` (`tests/prog_test/src/smSurfaceTest.cpp:4117`), and
//! generated cases (planes, B-splines incl. closed, rational, bilinear, zigzag and
//! huge / tiny ones, curve-bounded and STEP-wrapped surfaces; sub-domains; test points on,
//! near and far from the surface; stepper points inside, on and near the boundary).
//!
//! Deferred cases: `SmSurface::LocalPointSolve` (`surface.surface_p5`; every `Solve` that
//! gets a rough guess), `SmSurface::IsOnSeam` (`surface.surface_p4`; `RefineGuess` on any
//! surface but a plane), `SmSurfaceEval::Normal` (`surface.surface_p4`; convergence at a
//! singularity), `SmSurface::InvertPointNearSingularity` (`surface.surface_p8`; a step
//! from a singularity), and the bounding box of a curve-bounded or STEP surface for the
//! directed operation (`surface.surface_p6`, as in p1).
use serde::{Deserialize, Serialize};
use serde_json::Value;
use solidcore::geom::extent2d::Extent2d;
use solidcore::geom::global_solver::SolutionRequested;
use solidcore::geom::solution_array::{ObjectKind, Solution, SolutionArray, SolutionObject};
use solidcore::kernel::Kernel;
use solidcore::surface::surface::Surface;
use solidcore::surface::surface_point_solve::p1::ErrorNormType;
use solidcore::surface::surface_point_solve::{SurfPtGlobalSolver, SurfPtSolveStepper};
use solidcore_ffi::units::surface_surface_point_solve_p2::{self as ffi, Op, MAX_GRID, MAX_HITS};
use super::surface_surface_cache_p1::{dom_of, flag, gen_sub_dom, harness, srf, status, v3, BssIn, SrfIn};
use super::surface_surface_cache_p3::{push_surf, surf_domain, surf_size, surface_in, SurfIn};
use super::surface_surface_point_solve_p1::{
gen_surface, gen_test, gen_uv, gen_uv_any, net_counts, p2, push2, solver_op, test_on_normal,
wrapped_of, SOLVERS, SURFACE_SMS_POINTS,
};
use crate::case::{CaseContext, CaseSource};
use crate::runner::default_cases;
use crate::{Case, HarnessError, Output, Rng, UnitDiff};
/// Generated cases per run, whatever `--seeds` says (it can only raise this).
pub const MIN_GENERATED: usize = 900;
/// `surface.surface_point_solve_p2` runner.
pub struct Runner;
// ---- inputs -----------------------------------------------------------------------
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "op", rename_all = "snake_case")]
enum OpIn {
Rough,
Poly {
iu: usize,
iv: usize,
},
Refine {
uv: [f64; 2],
},
Patch {
sub: [f64; 4],
nu: i32,
nv: i32,
uv: [f64; 2],
best: f64,
},
Grid {
uv: [f64; 2],
iu: usize,
iv: usize,
},
Seams {
uv: [f64; 2],
value: f64,
},
Solve,
Adjust {
uv0: [f64; 2],
tight: f64,
uv1: [f64; 2],
hu: i32,
hv: i32,
},
Norms {
uv0: [f64; 2],
tight: f64,
},
Conv {
uv0: [f64; 2],
tight: f64,
ctol: f64,
},
Push {
uv0: [f64; 2],
tight: f64,
ptol: f64,
},
Snap {
uv0: [f64; 2],
tight: f64,
stol: f64,
},
Step {
uv0: [f64; 2],
tight: f64,
#[serde(default)]
plane: bool,
},
Line {
uv0: [f64; 2],
tight: f64,
uv1: [f64; 2],
},
LineB {
uv0: [f64; 2],
tight: f64,
uv1: [f64; 2],
norm: u8,
},
}
impl OpIn {
fn ffi(&self) -> Op {
match self {
OpIn::Rough => Op::Rough,
OpIn::Poly { .. } => Op::Poly,
OpIn::Refine { .. } => Op::Refine,
OpIn::Patch { .. } => Op::Patch,
OpIn::Grid { .. } => Op::Grid,
OpIn::Seams { .. } => Op::Seams,
OpIn::Solve => Op::Solve,
OpIn::Adjust { .. } => Op::Adjust,
OpIn::Norms { .. } => Op::Norms,
OpIn::Conv { .. } => Op::Conv,
OpIn::Push { .. } => Op::Push,
OpIn::Snap { .. } => Op::Snap,
OpIn::Step { .. } => Op::Step,
OpIn::Line { .. } => Op::Line,
OpIn::LineB { .. } => Op::LineB,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct Inputs {
/// Wrap the surface in an `SmSTEPSurface`.
#[serde(default)]
wrap: bool,
surf: SurfIn,
/// The solver domain.
dom: [f64; 4],
/// The `SmSolverOperationType` code.
solver: u8,
test: [f64; 3],
tol: f64,
#[serde(flatten)]
op: OpIn,
}
// ---- C++ side -----------------------------------------------------------------------
fn cpp_input(i: &Inputs) -> Result<(Op, Vec<f64>), HarnessError> {
let mut o = vec![flag(i.wrap)];
push_surf(&mut o, &i.surf)?;
o.extend(i.dom);
o.push(f64::from(i.solver));
o.extend(i.test);
o.push(i.tol);
match &i.op {
OpIn::Rough | OpIn::Solve => {}
OpIn::Poly { iu, iv } => o.extend([*iu as f64, *iv as f64]),
OpIn::Refine { uv } => o.extend(uv),
OpIn::Patch {
sub,
nu,
nv,
uv,
best,
} => {
o.extend(sub);
o.extend([f64::from(*nu), f64::from(*nv)]);
o.extend(uv);
o.push(*best);
}
OpIn::Grid { uv, iu, iv } => {
o.extend(uv);
o.extend([*iu as f64, *iv as f64]);
}
OpIn::Seams { uv, value } => {
o.extend(uv);
o.push(*value);
}
OpIn::Adjust {
uv0,
tight,
uv1,
hu,
hv,
} => {
o.extend(uv0);
o.push(*tight);
o.extend(uv1);
o.extend([f64::from(*hu), f64::from(*hv)]);
}
OpIn::Norms { uv0, tight } => {
o.extend(uv0);
o.push(*tight);
}
OpIn::Conv {
uv0,
tight,
ctol: t,
}
| OpIn::Push {
uv0,
tight,
ptol: t,
}
| OpIn::Snap {
uv0,
tight,
stol: t,
} => {
o.extend(uv0);
o.extend([*tight, *t]);
}
OpIn::Step { uv0, tight, plane } => {
o.extend(uv0);
o.extend([*tight, flag(*plane)]);
}
OpIn::Line { uv0, tight, uv1 } => {
o.extend(uv0);
o.push(*tight);
o.extend(uv1);
}
OpIn::LineB {
uv0,
tight,
uv1,
norm,
} => {
o.extend(uv0);
o.push(*tight);
o.extend(uv1);
o.push(f64::from(*norm));
}
}
Ok((i.op.ffi(), o))
}
fn to_output(flat: &[f64]) -> Output {
let (status, rest) = flat
.split_first()
.map_or((f64::NAN, &[][..]), |(s, r)| (*s, r));
Output::new()
.values("status", vec![status])
.values("out", rest.to_vec())
}
fn cpp(i: &Inputs) -> Result<Output, HarnessError> {
let (op, input) = cpp_input(i)?;
let flat = ffi::eval(op, &input).map_err(|e| harness(e.to_string()))?;
Ok(to_output(&flat))
}
// ---- Rust side ----------------------------------------------------------------------
/// The solutions' handle for the case's surface: it reports the surface's natural
/// domain (`GetNaturalUVDomain` of the solution object, for `IsEqual`).
#[derive(Debug, Clone, Copy, PartialEq)]
struct SrfObj(Extent2d);
impl SolutionObject for SrfObj {
fn kind(&self) -> ObjectKind {
ObjectKind::Surface
}
fn natural_uv_domain(&self) -> Option<Extent2d> {
Some(self.0)
}
}
fn push_solutions(o: &mut Vec<f64>, sols: &SolutionArray<SrfObj>, obj: SrfObj) {
o.push(sols.len() as f64);
for s in sols.as_slice() {
o.extend([
s.start.parameters[0],
s.start.parameters[1],
s.start.solution_value,
s.num_objects as f64,
s.num_variables as f64,
flag(s.objects[0] == Some(obj)),
]);
}
}
fn norm_of(code: u8) -> ErrorNormType {
match code {
0 => ErrorNormType::SumAbs,
1 => ErrorNormType::Sum,
2 => ErrorNormType::F,
3 => ErrorNormType::G,
4 => ErrorNormType::FAbs,
5 => ErrorNormType::GAbs,
6 => ErrorNormType::Max,
7 => ErrorNormType::MaxAbs,
_ => ErrorNormType::Gap,
}
}
const ALL_NORMS: [ErrorNormType; 9] = [
ErrorNormType::SumAbs,
ErrorNormType::Sum,
ErrorNormType::F,
ErrorNormType::G,
ErrorNormType::FAbs,
ErrorNormType::GAbs,
ErrorNormType::Max,
ErrorNormType::MaxAbs,
ErrorNormType::Gap,
];
/// `UV BoundHitsU BoundHitsV NRStepSuspect IterCount` of a stepper.
fn push_state(o: &mut Vec<f64>, s: &SurfPtSolveStepper<'_>) {
push2(o, s.uv());
o.extend([
f64::from(s.bound_hits_u()),
f64::from(s.bound_hits_v()),
flag(s.nr_step_suspect()),
f64::from(s.iter_count()),
]);
}
/// `HitBound(which, sign)` `|n|` times.
fn hits(s: &mut SurfPtSolveStepper<'_>, which: solidcore::nurbs::b_spline_surface::SurfParamType, n: i32) {
let sign = if n < 0 { -1 } else { 1 };
for _ in 0..n.unsigned_abs() {
s.hit_bound(which, sign);
}
}
/// An error status record (`-1` for an error upstream cannot produce), or the deferral.
fn err_record(e: &solidcore::SolidError) -> Result<Vec<f64>, solidcore::SolidError> {
if e.is_unported() {
return Err(e.clone());
}
Ok(vec![status(e)])
}
#[allow(clippy::too_many_lines)]
fn rust_op(i: &Inputs) -> Result<solidcore::Result<Vec<f64>>, HarnessError> {
use solidcore::nurbs::b_spline_surface::SurfParamType;
let k = Kernel::default();
let surface: Surface = wrapped_of(i.wrap, &i.surf)?;
let dom = dom_of(i.dom);
let op = solver_op(i.solver)?;
let test = v3(i.test);
let tol = i.tol;
let obj = SrfObj(surface.natural_uv_domain());
let global = || {
SurfPtGlobalSolver::new(
&k,
&surface,
&dom,
op,
test,
tol,
None,
SolutionRequested::Single,
)
};
let stepper = |uv: [f64; 2], tight: f64| {
SurfPtSolveStepper::new(&k, &surface, &dom, test, p2(uv), tight, tol, op)
};
let run = || -> solidcore::Result<Vec<f64>> {
let mut o = Vec::new();
match &i.op {
OpIn::Rough => {
let mut g = global()?;
match g.find_rough_guess() {
Ok(r) => {
o.push(0.0);
push2(&mut o, r.uv);
o.extend([r.best_idx_u as f64, r.best_idx_v as f64]);
}
Err(e) => return err_record(&e),
}
}
OpIn::Poly { iu, iv } => {
let g = global()?;
match g.find_polygon_guess((*iu, *iv)) {
Ok(r) => {
o.push(0.0);
push2(&mut o, r.uv);
o.extend([r.best_idx_u as f64, r.best_idx_v as f64, flag(r.suspicious)]);
}
Err(e) => return err_record(&e),
}
}
OpIn::Refine { uv } => {
let mut g = global()?;
let mut uv = p2(*uv);
match g.refine_guess(&mut uv) {
Ok(()) => o.push(0.0),
Err(e) => o = err_record(&e)?,
}
push2(&mut o, uv);
}
OpIn::Patch {
sub,
nu,
nv,
uv,
best,
} => {
let g = global()?;
let mut uv = p2(*uv);
let mut best = *best;
g.refine_patch_by_grid(&dom_of(*sub), *nu, *nv, &mut uv, &mut best)?;
o.push(0.0);
push2(&mut o, uv);
o.push(best);
}
OpIn::Grid { uv, iu, iv } => {
let g = global()?;
let r = g.refine_guess_by_grid(p2(*uv), *iu, *iv)?;
o.push(0.0);
push2(&mut o, r);
}
OpIn::Seams { uv, value } => {
let mut g = global()?;
let mut sol: Solution<SrfObj> = Solution::new();
sol.num_variables = 2;
sol.num_objects = 1;
sol.objects[0] = Some(obj);
sol.start.parameters[0] = uv[0];
sol.start.parameters[1] = uv[1];
sol.start.solution_value = *value;
let tight = g.base.tight_tol();
let sols = g.check_seams(&k, tight, &sol, Some(obj))?;
o.push(0.0);
push_solutions(&mut o, &sols, obj);
}
OpIn::Solve => {
let mut g = global()?;
match g.solve(&k, Some(obj)) {
Ok(sols) => {
o.push(0.0);
push_solutions(&mut o, &sols, obj);
}
Err(e) => return err_record(&e),
}
}
OpIn::Adjust {
uv0,
tight,
uv1,
hu,
hv,
} => {
let mut prev = stepper(*uv0, *tight)?;
hits(&mut prev, SurfParamType::U, *hu);
hits(&mut prev, SurfParamType::V, *hv);
let mut next = SurfPtSolveStepper::copy_of(&prev);
next.set_uv(p2(*uv1));
match next.adjust_step(&prev) {
Ok((adjusted, scale)) => o.extend([0.0, flag(adjusted), scale]),
Err(e) => {
o = err_record(&e)?;
// upstream's outputs on SM_ERR
o.extend([flag(true), 0.0]);
}
}
push_state(&mut o, &next);
}
OpIn::Norms { uv0, tight } => {
let mut s = stepper(*uv0, *tight)?;
o.push(0.0);
for n in ALL_NORMS {
o.push(s.error_norm(n)?);
}
let (b, which) = s.is_singular()?;
o.extend([flag(b), f64::from(which as u8)]);
}
OpIn::Conv { uv0, tight, ctol } => {
let mut s = stepper(*uv0, *tight)?;
o.push(0.0);
o.push(flag(s.converged(*ctol)?));
o.push(flag(s.converged_with(true)?));
o.push(flag(s.converged_with(false)?));
}
OpIn::Push { uv0, tight, ptol } => {
let mut s = stepper(*uv0, *tight)?;
let mut c = SurfPtSolveStepper::copy_of(&s);
o.push(0.0);
let (b, which) = s.pushing_boundary(*ptol)?;
o.extend([flag(b), f64::from(which as u8)]);
let (b, which) = c.converged_on_boundary(*ptol)?;
o.extend([flag(b), f64::from(which as u8)]);
}
OpIn::Snap { uv0, tight, stol } => {
let mut s = stepper(*uv0, *tight)?;
let b = s.snap_to_boundaries(*stol)?;
o.extend([0.0, flag(b)]);
push_state(&mut o, &s);
}
OpIn::Step { uv0, tight, plane } => {
let mut prev = stepper(*uv0, *tight)?;
let mut next = SurfPtSolveStepper::copy_of(&prev);
let r = next.step(&mut prev, *plane)?;
o.extend([0.0, f64::from(r.code())]);
push_state(&mut o, &next);
}
OpIn::Line { uv0, tight, uv1 } => {
let mut prev = stepper(*uv0, *tight)?;
let mut next = SurfPtSolveStepper::copy_of(&prev);
next.set_uv(p2(*uv1));
match next.line_search(&mut prev) {
Ok(()) => o.push(0.0),
Err(e) => o = err_record(&e)?,
}
push_state(&mut o, &next);
}
OpIn::LineB {
uv0,
tight,
uv1,
norm,
} => {
let mut prev = stepper(*uv0, *tight)?;
let mut next = SurfPtSolveStepper::copy_of(&prev);
next.set_uv(p2(*uv1));
match next.line_search_bracketted(&mut prev, norm_of(*norm)) {
Ok(()) => o.push(0.0),
Err(e) => o = err_record(&e)?,
}
push_state(&mut o, &next);
}
}
Ok(o)
};
Ok(run())
}
fn rust(i: &Inputs) -> Result<Output, HarnessError> {
Ok(match rust_op(i)? {
Ok(o) => to_output(&o),
Err(e) => Output::unported(&e).unwrap_or_else(|| to_output(&[status(&e)])),
})
}
// ---- generated cases ----------------------------------------------------------------
/// A point on the boundary of `d` (a side or a corner), or just inside or outside it.
fn gen_uv_edge(rng: &mut Rng, d: [f64; 4]) -> [f64; 2] {
let [mut u, mut v] = gen_uv(rng, d);
let (w, h) = (d[2] - d[0], d[3] - d[1]);
match rng.below(4) {
0 => u = if rng.chance(0.5) { d[0] } else { d[2] },
1 => v = if rng.chance(0.5) { d[1] } else { d[3] },
2 => {
// near a side, by a tiny to small amount
let e = 10f64.powf(rng.uniform(-12.0, -2.0));
u = if rng.chance(0.5) { d[0] + e * w } else { d[2] - e * w };
}
_ => {
let e = 10f64.powf(rng.uniform(-12.0, -2.0));
v = if rng.chance(0.5) { d[1] + e * h } else { d[3] - e * h };
}
}
[u, v]
}
/// A point of `d` clipped into the surface domain `sd`.
fn clip(uv: [f64; 2], sd: [f64; 4]) -> [f64; 2] {
[uv[0].clamp(sd[0], sd[2]), uv[1].clamp(sd[1], sd[3])]
}
#[allow(clippy::too_many_lines)]
fn gen_op(rng: &mut Rng, s: &SurfIn, d: [f64; 4], sd: [f64; 4], size: f64) -> Result<OpIn, HarnessError> {
let (nu, nv) = net_counts(s)?;
let idx = |rng: &mut Rng, n: usize| -> usize {
if rng.chance(0.3) {
if rng.chance(0.5) {
0
} else {
n.saturating_sub(1)
}
} else {
rng.below(n.max(1) as u64) as usize
}
};
let small_tol = |rng: &mut Rng| -> f64 {
match rng.below(5) {
0 => 1e-10,
1 => size * 1e-6,
2 => -size * 1e-6,
3 => 0.0,
_ => size * rng.uniform(1e-4, 0.1),
}
};
// stepper points: inside, on or near the boundary of the solver domain
let st_uv = |rng: &mut Rng| -> [f64; 2] {
if rng.chance(0.5) {
gen_uv(rng, sd)
} else {
clip(gen_uv_edge(rng, sd), d)
}
};
let tight = |rng: &mut Rng| -> f64 {
match rng.below(3) {
0 => 1e-12,
1 => size * 1e-10,
_ => size * 1e-7,
}
};
Ok(match rng.below(40) {
0..=2 => OpIn::Rough,
3..=5 => OpIn::Poly {
iu: idx(rng, nu),
iv: idx(rng, nv),
},
6..=7 => OpIn::Refine {
uv: clip(gen_uv_any(rng, sd), d),
},
8..=10 => {
let sub = if rng.chance(0.3) {
sd
} else {
gen_sub_dom(rng, d)
};
let g = |rng: &mut Rng| -> i32 {
if rng.chance(0.2) {
rng.range_i64(-2, 2) as i32
} else {
rng.range_i64(2, MAX_GRID.min(9)) as i32
}
};
OpIn::Patch {
sub,
nu: g(rng),
nv: g(rng),
uv: gen_uv(rng, sd),
best: match rng.below(3) {
0 => 1e300,
1 => size * rng.uniform(0.0, 2.0),
_ => -size,
},
}
}
11..=13 => OpIn::Grid {
uv: gen_uv(rng, sd),
iu: idx(rng, nu),
iv: idx(rng, nv),
},
14..=17 => OpIn::Seams {
uv: if rng.chance(0.7) {
clip(gen_uv_edge(rng, sd), d)
} else {
gen_uv(rng, sd)
},
value: size * rng.uniform(0.0, 1.0),
},
18 => OpIn::Solve,
19..=21 => {
let uv0 = st_uv(rng);
let uv1 = match rng.below(4) {
0 => uv0,
1 => [uv0[0] + 1e-13, uv0[1]],
_ => gen_uv_any(rng, sd),
};
OpIn::Adjust {
uv0,
tight: tight(rng),
uv1,
hu: rng.range_i64(-MAX_HITS, MAX_HITS) as i32,
hv: rng.range_i64(-MAX_HITS, MAX_HITS) as i32,
}
}
22..=24 => OpIn::Norms {
uv0: st_uv(rng),
tight: tight(rng),
},
25..=26 => OpIn::Conv {
uv0: st_uv(rng),
tight: tight(rng),
ctol: small_tol(rng).abs().max(1e-12),
},
27..=29 => OpIn::Push {
uv0: st_uv(rng),
tight: tight(rng),
ptol: small_tol(rng),
},
30..=31 => OpIn::Snap {
uv0: st_uv(rng),
tight: tight(rng),
stol: {
let w = (sd[2] - sd[0]).min(sd[3] - sd[1]);
w * 10f64.powf(rng.uniform(-10.0, -1.0))
},
},
32..=35 => OpIn::Step {
uv0: st_uv(rng),
tight: tight(rng),
plane: rng.chance(0.5),
},
36..=37 => OpIn::Line {
uv0: st_uv(rng),
tight: tight(rng),
uv1: gen_uv(rng, d),
},
_ => OpIn::LineB {
uv0: st_uv(rng),
tight: tight(rng),
uv1: gen_uv(rng, d),
norm: rng.below(9) as u8,
},
})
}
fn gen_case(rng: &mut Rng) -> Result<Inputs, HarnessError> {
let surf = gen_surface(rng);
let wrap = rng.chance(0.15);
let d = surf_domain(&surf)?;
let size = surf_size(&surf)?;
let sd = if rng.chance(0.5) { d } else { gen_sub_dom(rng, d) };
let solver = *rng.choose(&SOLVERS).unwrap_or(&3);
let s = surface_in(&surf)?;
let test = if solver == 10 {
match rng.below(4) {
0 => [1.0, 0.0, 0.0],
1 => [0.0, -1.0, 0.0],
_ => super::surface_surface_cache_p1::gvec(rng, 1.0),
}
} else {
gen_test(rng, &s, sd, size)
};
let tol = match rng.below(4) {
0 => 1e-6,
1 => size * 1e-8,
2 => size * rng.uniform(1e-5, 1e-2),
_ => 1e-10,
};
let op = gen_op(rng, &surf, d, sd, size)?;
let mut test = test;
// converged / boundary tests: test points on the normal at the stepper point
if solver != 10 && rng.chance(0.5) {
if let OpIn::Conv { uv0, .. } | OpIn::Push { uv0, .. } | OpIn::Seams { uv: uv0, .. } = &op {
test = test_on_normal(rng, &s, *uv0, size);
}
}
Ok(Inputs {
wrap,
surf,
dom: sd,
solver,
test,
tol,
op,
})
}
// ---- inline cases from the upstream tests ----------------------------------------------------
fn upstream_cases(ctx: &CaseContext, id: &str) -> Result<Vec<Case>, HarnessError> {
let path = ctx.upstream_test_file("Surface.sms")?;
let sms = SrfIn::File {
sms: path.to_string_lossy().into_owned(),
};
let inline = srf(&sms)?;
let surf = SurfIn::Bspline(BssIn {
oob: false,
form: super::surface_surface_cache_p1::default_form(),
srf: sms,
});
let d = [
inline.u.first().copied().unwrap_or(0.0),
inline.v.first().copied().unwrap_or(0.0),
inline.u.last().copied().unwrap_or(1.0),
inline.v.last().copied().unwrap_or(1.0),
];
let (nu, nv) = (inline.net.len(), inline.net.first().map_or(0, Vec::len));
let mid = [0.5 * (d[0] + d[2]), 0.5 * (d[1] + d[3])];
let at = |fu: f64, fv: f64| [d[0] + (d[2] - d[0]) * fu, d[1] + (d[3] - d[1]) * fv];
let mut out = Vec::new();
for (pi, test) in SURFACE_SMS_POINTS.iter().enumerate() {
let f = pi as f64 / 14.0;
let ops = [
OpIn::Rough,
OpIn::Poly { iu: 0, iv: 0 },
OpIn::Grid {
uv: at(f, 1.0 - f),
iu: pi % nu,
iv: (pi * 3) % nv,
},
OpIn::Patch {
sub: d,
nu: 7,
nv: 7,
uv: mid,
best: 1e300,
},
OpIn::Seams {
uv: at(0.0, f),
value: 0.0,
},
OpIn::Adjust {
uv0: mid,
tight: 1e-12,
uv1: at(1.2, f),
hu: 0,
hv: 0,
},
OpIn::Norms {
uv0: at(f, 0.5),
tight: 1e-12,
},
OpIn::Conv {
uv0: at(f, f),
tight: 1e-12,
ctol: 1e-6,
},
OpIn::Push {
uv0: at(1.0, f),
tight: 1e-12,
ptol: 1e-9,
},
OpIn::Step {
uv0: mid,
tight: 1e-12,
plane: false,
},
OpIn::Step {
uv0: at(f, 0.25),
tight: 1e-12,
plane: true,
},
OpIn::Line {
uv0: at(0.1, 0.1),
tight: 1e-12,
uv1: at(0.9, f),
},
];
for (oi, op) in ops.into_iter().enumerate() {
let inputs = Inputs {
wrap: false,
surf: surf.clone(),
dom: d,
solver: 3,
test: *test,
tol: 1e-6,
op,
};
out.push(Case {
unit: id.to_string(),
name: format!("surface_sms_pt{:02}_op{oi}", pi + 1),
source: CaseSource::Inline,
inputs: serde_json::to_value(inputs).map_err(|e| harness(e.to_string()))?,
});
}
}
Ok(out)
}
impl UnitDiff for Runner {
fn id(&self) -> &'static str {
"surface.surface_point_solve_p2"
}
fn generate(&self, rng: &mut Rng) -> Option<Value> {
for _ in 0..64 {
let Ok(i) = gen_case(rng) else {
continue;
};
let Ok((op, input)) = cpp_input(&i) else {
continue;
};
if ffi::validate(op, &input).is_ok() {
return serde_json::to_value(i).ok();
}
}
None
}
fn cases(&self, ctx: &CaseContext) -> Result<Vec<Case>, HarnessError> {
let mut v = upstream_cases(ctx, self.id())?;
let ctx = CaseContext {
seeds: ctx.seeds.max(MIN_GENERATED),
..ctx.clone()
};
v.extend(default_cases(self, &ctx)?);
Ok(v)
}
fn run_cpp(&self, case: &Case) -> Result<Output, HarnessError> {
cpp(&case.parse()?)
}
fn run_rust(&self, case: &Case) -> Result<Output, HarnessError> {
rust(&case.parse()?)
}
}
#[cfg(test)]
mod tests {
use super::*;
use super::super::surface_surface_cache_p1::{default_form, InlineSrf};
use super::super::surface_surface_point_solve_p1::gen_bilinear;
#[test]
fn op_codes_round_trip() {
let bil: InlineSrf = gen_bilinear(&mut Rng::new(1));
let i = Inputs {
wrap: false,
surf: SurfIn::Bspline(BssIn {
oob: false,
form: default_form(),
srf: SrfIn::Inline(bil),
}),
dom: [0.0, 0.0, 1.0, 1.0],
solver: 3,
test: [0.5, 0.5, 1.0],
tol: 1e-6,
op: OpIn::Patch {
sub: [0.0, 0.0, 1.0, 1.0],
nu: MAX_GRID as i32,
nv: 1,
uv: [0.5, 0.5],
best: 1e300,
},
};
let (op, input) = cpp_input(&i).unwrap();
assert_eq!(op, Op::Patch);
assert!(ffi::validate(op, &input).is_ok());
}
}
@@ -108,7 +108,7 @@ impl Op {
pub const MAX_HIT_STEPS: usize = 64;
/// The solver operations the file's classes accept.
const OPS: [usize; 5] = [0, 3, 4, 5, 10];
pub(crate) const OPS: [usize; 5] = [0, 3, 4, 5, 10];
mod sys {
extern "C" {
@@ -124,13 +124,13 @@ mod sys {
}
/// The problem `P`: the surface domain and the control-net counts (`2 x 2` for a plane).
struct Problem {
srf: Srf,
num_u: usize,
num_v: usize,
pub(crate) struct Problem {
pub(crate) srf: Srf,
pub(crate) num_u: usize,
pub(crate) num_v: usize,
}
fn problem(rd: &mut Reader<'_>) -> Result<Problem, FfiError> {
pub(crate) fn problem(rd: &mut Reader<'_>) -> Result<Problem, FfiError> {
rd.flag()?;
let at = rd.at;
let (kind, srf, _) = sized_surface(rd)?;
@@ -151,7 +151,7 @@ fn problem(rd: &mut Reader<'_>) -> Result<Problem, FfiError> {
}
/// A point `uv` inside the surface domain.
fn uv_in(rd: &mut Reader<'_>, srf: &Srf) -> Result<(), FfiError> {
pub(crate) fn uv_in(rd: &mut Reader<'_>, srf: &Srf) -> Result<(), FfiError> {
let u = rd.finite()?;
let v = rd.finite()?;
let d = srf.dom;
@@ -162,7 +162,7 @@ fn uv_in(rd: &mut Reader<'_>, srf: &Srf) -> Result<(), FfiError> {
}
/// A point `uv` within a domain size of the surface domain.
fn uv_near(rd: &mut Reader<'_>, srf: &Srf) -> Result<(), FfiError> {
pub(crate) fn uv_near(rd: &mut Reader<'_>, srf: &Srf) -> Result<(), FfiError> {
let u = rd.finite()?;
let v = rd.finite()?;
let d = srf.dom;
@@ -174,7 +174,7 @@ fn uv_near(rd: &mut Reader<'_>, srf: &Srf) -> Result<(), FfiError> {
}
/// Control-net indices `i j` with `i + du < num_u`, `j + dv < num_v`.
fn net_index(rd: &mut Reader<'_>, p: &Problem, d: usize) -> Result<(), FfiError> {
pub(crate) fn net_index(rd: &mut Reader<'_>, p: &Problem, d: usize) -> Result<(), FfiError> {
let i = rd.index(1 << 20)?;
let j = rd.index(1 << 20)?;
if i + d >= p.num_u || j + d >= p.num_v {
@@ -0,0 +1,219 @@
//! FFI wrappers for unit `surface.surface_point_solve_p2`.
//!
//! Shim: `harness/cpp/shim/surface_surface_point_solve_p2.cpp`
//! (`sc_surface_surface_point_solve_p2_eval`, which compiles the upstream file with its
//! file-local classes renamed). Upstream: `source/SMLib/src/SmSurfacePointSolve.cpp`.
//!
//! Notation as in [`super::surface_surface_point_solve_p1`]: every op starts with the
//! problem `P = W S D op test(3) tol`; `uv` is a point `u v`, `D` a domain
//! `umin vmin umax vmax`. The stepper ops continue with `uv0 tight`: the stepper
//! `SmSurfPtSolveStepper(S, D, test, uv0, tight, tol, op)`.
//!
//! | [`Op`] | upstream | parameters |
//! |------------|----------------------------------------------------------|------------------------|
//! | `Rough` | `SmSurfPtGlobalSolver::FindRoughGuess` | `P` |
//! | `Poly` | `FindPolygonGuess` (from best indices `iu iv`) | `P iu iv` |
//! | `Refine` | `RefineGuess` | `P uv` |
//! | `Patch` | `RefinePatchByGrid` | `P sub nu nv uv best` |
//! | `Grid` | `RefineGuessByGrid` | `P uv iu iv` |
//! | `Seams` | `CheckSeams` of the solution `(u, v)` with `value` | `P uv value` |
//! | `Solve` | `Solve` | `P` |
//! | `Adjust` | `AdjustStep` of a copy moved to `uv1` (after `hu`/`hv` boundary hits) | `P uv0 tight uv1 hu hv` |
//! | `Norms` | the nine `ErrorNorm`s, `IsSingular` | `P uv0 tight` |
//! | `Conv` | `Converged(ctol)`, `Converged(TRUE)`, `Converged(FALSE)` | `P uv0 tight ctol` |
//! | `Push` | `PushingBoundary(ptol)`, `ConvergedOnBoundary(ptol)` | `P uv0 tight ptol` |
//! | `Snap` | `SnapToBoundaries(stol)` | `P uv0 tight stol` |
//! | `Step` | `Step` of a copy, Newton or plane | `P uv0 tight plane` |
//! | `Line` | `LineSearch` of a copy moved to `uv1` | `P uv0 tight uv1` |
//! | `LineB` | `LineSearchBracketted` of a copy moved to `uv1` | `P uv0 tight uv1 norm` |
//!
//! The output is the op status (`0` = `SM_SUCCESS`, else the `SmStatus`) and the op's
//! outputs (see the shim). [`eval`] accepts only what upstream handles without undefined
//! behaviour: the problems of [`super::surface_surface_point_solve_p1`], points inside the
//! surface domain wherever a surface evaluator reads them (the step target of `Adjust`,
//! which is only clamped, within a domain size of it), a grid sub-domain inside the
//! surface domain with grid sizes up to [`MAX_GRID`], control-net indices inside the net,
//! boundary-hit counts up to 3, an `SmSrfPtErrorNormType`, finite values.
use super::surface_surface_cache_p1::{bad, Reader};
use super::surface_surface_point_solve_p1::{net_index, problem, uv_in, uv_near};
use crate::{call_with_buffer, FfiError};
/// The upstream routine run by the shim.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Op {
/// `FindRoughGuess`.
Rough,
/// `FindPolygonGuess`.
Poly,
/// `RefineGuess`.
Refine,
/// `RefinePatchByGrid`.
Patch,
/// `RefineGuessByGrid`.
Grid,
/// `CheckSeams`.
Seams,
/// `Solve`.
Solve,
/// `AdjustStep`.
Adjust,
/// `ErrorNorm`, `IsSingular`.
Norms,
/// `Converged`.
Conv,
/// `PushingBoundary`, `ConvergedOnBoundary`.
Push,
/// `SnapToBoundaries`.
Snap,
/// `Step`.
Step,
/// `LineSearch`.
Line,
/// `LineSearchBracketted`.
LineB,
}
impl Op {
/// The shim's op code.
#[must_use]
pub fn code(self) -> i32 {
match self {
Op::Rough => 0,
Op::Poly => 1,
Op::Refine => 2,
Op::Patch => 3,
Op::Grid => 4,
Op::Seams => 5,
Op::Solve => 6,
Op::Adjust => 7,
Op::Norms => 8,
Op::Conv => 9,
Op::Push => 10,
Op::Snap => 11,
Op::Step => 12,
Op::Line => 13,
Op::LineB => 14,
}
}
}
/// Largest grid size of `Patch` (sizes below 2 count as 2 upstream).
pub const MAX_GRID: i64 = 24;
/// Largest boundary-hit count of `Adjust` (either sign).
pub const MAX_HITS: i64 = 3;
/// Largest `SmSrfPtErrorNormType` (`SM_SPN_GAP`).
pub const MAX_NORM: usize = 8;
mod sys {
extern "C" {
pub fn sc_surface_surface_point_solve_p2_eval(
op: i32,
input: *const f64,
n_in: i64,
out: *mut f64,
cap: i64,
out_len: *mut i64,
) -> i32;
}
}
/// An integer in `-max..=max`.
fn small_int(rd: &mut Reader<'_>, max: i64, what: &str) -> Result<(), FfiError> {
let v = rd.num()?;
if v.fract() != 0.0 || v.abs() > max as f64 {
return Err(bad(format!("{what}: bad integer {v}")));
}
Ok(())
}
fn validate_op(op: Op, rd: &mut Reader<'_>) -> Result<(), FfiError> {
let p = problem(rd)?;
match op {
Op::Rough | Op::Solve => {}
Op::Poly => net_index(rd, &p, 0)?,
Op::Refine => uv_in(rd, &p.srf)?,
Op::Patch => {
rd.domain_in(&p.srf, false)?;
small_int(rd, MAX_GRID, "grid size")?;
small_int(rd, MAX_GRID, "grid size")?;
rd.finite()?;
rd.finite()?;
if rd.num()?.is_nan() {
return Err(bad("best value is NaN"));
}
}
Op::Grid => {
uv_in(rd, &p.srf)?;
net_index(rd, &p, 0)?;
}
Op::Seams => {
uv_in(rd, &p.srf)?;
rd.finite()?;
}
Op::Adjust | Op::Norms | Op::Conv | Op::Push | Op::Snap | Op::Step | Op::Line | Op::LineB => {
uv_in(rd, &p.srf)?;
rd.finite()?;
match op {
Op::Adjust => {
uv_near(rd, &p.srf)?;
small_int(rd, MAX_HITS, "boundary hits")?;
small_int(rd, MAX_HITS, "boundary hits")?;
}
Op::Conv | Op::Push | Op::Snap => {
rd.finite()?;
}
Op::Step => {
rd.flag()?;
}
Op::Line => uv_in(rd, &p.srf)?,
Op::LineB => {
uv_in(rd, &p.srf)?;
rd.index(MAX_NORM)?;
}
_ => {}
}
}
}
Ok(())
}
/// Checks `input` for `op` (layouts in the module docs).
///
/// # Errors
///
/// [`FfiError::InvalidArgument`] for input the shim must not see.
pub fn validate(op: Op, input: &[f64]) -> Result<(), FfiError> {
let mut rd = Reader { input, at: 0 };
validate_op(op, &mut rd)?;
rd.done()
}
/// Runs `op` on `input` (layouts in the module docs); returns the shim's output record.
///
/// # Errors
///
/// [`FfiError::InvalidArgument`] for input the shim must not see; [`FfiError::Status`]
/// for a shim failure.
pub fn eval(op: Op, input: &[f64]) -> Result<Vec<f64>, FfiError> {
validate(op, input)?;
call_with_buffer(4096, |buf, len| {
// SAFETY: input is valid for input.len() reads and holds exactly what the shim
// reads for `op` (validated above: well-formed clamped surfaces, planes with
// independent axes, a solver domain inside the surface domain, supported
// operations, evaluated points and grid sub-domains inside the surface domain,
// control-net indices inside the net, bounded grid sizes and hit counts, a valid
// norm type); buf is valid for buf.len() writes and the shim honours the
// capacity; len is valid.
unsafe {
sys::sc_surface_surface_point_solve_p2_eval(
op.code(),
input.as_ptr(),
input.len() as i64,
buf.as_mut_ptr(),
buf.len() as i64,
&raw mut *len,
)
}
})
}
@@ -8,5 +8,6 @@
//! `_p3`; each lives in its own submodule and adds methods to the classes of `p1`.
pub mod p1;
pub mod p2;
pub use p1::{SurfPtGlobalSolver, SurfPtSolveStepper, SurfacePointSolver};
@@ -150,14 +150,7 @@ impl<'s> SurfacePointSolver<'s> {
_solution_requested: SolutionRequested,
) -> Result<Self> {
// We need an SmBSplineSurface for some operations.
// (An SmOffsetSurface would work from its base surface: not ported.)
let b_spline = match surface.kind() {
SurfaceKind::BSpline(b) => Some(b),
SurfaceKind::Plane(p) => Some(p.base()),
SurfaceKind::SurfOfRevolution(s) => Some(s.base()),
SurfaceKind::CurveBounded(_) | SurfaceKind::Step(_) => None,
}
.filter(|b| b.gw_nurb().is_some());
let b_spline = b_spline_part(surface);
let mut solver = Self {
surface,
@@ -255,6 +248,21 @@ impl<'s> SurfacePointSolver<'s> {
self.b_spline
}
/// The same problem over `surface` (the surface data re-read from it; every other
/// field copied), for a solve on a copy of the surface.
pub(super) fn with_surface<'t>(&self, surface: &'t Surface) -> SurfacePointSolver<'t> {
SurfacePointSolver {
surface,
domain: self.domain,
test_pt: self.test_pt,
dir_vec: self.dir_vec,
solver_op: self.solver_op,
b_spline: b_spline_part(surface),
tight_tol: self.tight_tol,
loose_tol: self.loose_tol,
}
}
/// `crGap` reduced to its component along the direction for the directed
/// operation (`dDot * m_sDirVec`), else unchanged.
fn directed(&self, gap: Vec3) -> Vec3 {
@@ -267,11 +275,23 @@ impl<'s> SurfacePointSolver<'s> {
}
}
/// `m_pBSplSrf`: the surface as an `SmBSplineSurface` with a NURB, if it is one (an
/// `SmOffsetSurface` would work from its base surface: not ported).
fn b_spline_part(surface: &Surface) -> Option<&BSplineSurface> {
match surface.kind() {
SurfaceKind::BSpline(b) => Some(b),
SurfaceKind::Plane(p) => Some(p.base()),
SurfaceKind::SurfOfRevolution(s) => Some(s.base()),
SurfaceKind::CurveBounded(_) | SurfaceKind::Step(_) => None,
}
.filter(|b| b.gw_nurb().is_some())
}
// ---- control-net helpers ------------------------------------------------------------------
/// The control point `(i, j)` of a net stored row by row with `num_v` points per row,
/// divided by its weight when the net is rational.
fn control_point(
pub(super) fn control_point(
pts: &[CPoint],
num_v: usize,
i: usize,
@@ -498,6 +518,22 @@ impl<'s> SurfPtGlobalSolver<'s> {
})
}
/// The same solver over `surface` (see [`SurfacePointSolver::with_surface`]),
/// keeping the closure computed so far.
pub(super) fn with_surface<'t>(&self, surface: &'t Surface) -> SurfPtGlobalSolver<'t> {
SurfPtGlobalSolver {
base: self.base.with_surface(surface),
closed_in_u: self.closed_in_u,
closed_in_v: self.closed_in_v,
}
}
/// Takes over the closure `other` has computed (the same surface geometry).
pub(super) fn copy_closure_from(&mut self, other: &SurfPtGlobalSolver<'_>) {
self.closed_in_u = other.closed_in_u;
self.closed_in_v = other.closed_in_v;
}
/// Whether the surface is closed over the domain in `which_dir` (`SM_SP_U` or
/// `SM_SP_V`; `false` for any other value), computed once (`ClosedSurface`).
///
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,516 @@
// SPDX-License-Identifier: Apache-2.0
// difftest shim for unit `surface.surface_point_solve_p2`: the global solver's guesses,
// refinements, seam solutions and Solve, and the local stepper's step adjustment, norms,
// convergence tests, steps and line searches, of the file-local classes of upstream
// source/SMLib/src/SmSurfacePointSolve.cpp.
//
// As the p1 shim (surface_surface_point_solve_p1.cpp), this translation unit compiles the
// upstream source itself with the classes renamed (sc_spsp2_*), SmSurface::GlobalPointSolve_1
// / LocalPointSolve_1 renamed before any header is read, and `private` / `protected` made
// public for the class definitions only.
//
// One entry point, sc_surface_surface_point_solve_p2_eval(op, in, n_in, out, cap, out_len).
// Layouts are documented in crates/solidcore-ffi/src/units/surface_surface_point_solve_p2.rs,
// which validates every input.
#define GlobalPointSolve_1 sc_spsp2_GlobalPointSolve_1
#define LocalPointSolve_1 sc_spsp2_LocalPointSolve_1
#include "sc_shim.h"
#include "StdAfx.h"
#include <SmBSplineCurve.h>
#include <SmBSplineSurface.h>
#include <SmBrep.h>
#include <SmContext.h>
#include <SmCurveBoundedSurface.h>
#include <SmExtent1d.h>
#include <SmExtent2d.h>
#include <SmExtent3d.h>
#include <SmFace.h>
#include <SmGeomUtility.h>
#include <SmGraphicsExtern.h>
#include <SmGraphicsOutput.h>
#include <SmIsoCurve.h>
#include <SmLocalSolve1d.h>
#include <SmNurbsSrf.h>
#include <SmOffsetSurface.h>
#include <SmPlane.h>
#include <SmSTEPSurface.h>
#include <SmSolutionArray.h>
#include <SmSurface.h>
#include <SmSurfaceCache.h>
#include <SmTArray.h>
#include <SmVector2d.h>
#include <SmVector3d.h>
#include <nurbs.h>
#include <vector>
#define SmSurfacePointSolver sc_spsp2_SmSurfacePointSolver
#define SmSurfPtGlobalSolver sc_spsp2_SmSurfPtGlobalSolver
#define SmSurfPtSolveStepper sc_spsp2_SmSurfPtSolveStepper
#define SmSrfPtErrorNormType sc_spsp2_SmSrfPtErrorNormType
#define GetTrianglesDist sc_spsp2_GetTrianglesDist
#define WellBehavedControlNet sc_spsp2_WellBehavedControlNet
#define private public
#define protected public
#include "../../../upstream/source/SMLib/src/SmSurfacePointSolve.cpp"
#undef private
#undef protected
namespace sc_spsp2
{
enum
{
OP_ROUGH = 0,
OP_POLY = 1,
OP_REFINE = 2,
OP_PATCH = 3,
OP_GRID = 4,
OP_SEAMS = 5,
OP_SOLVE = 6,
OP_ADJUST = 7,
OP_NORMS = 8,
OP_CONV = 9,
OP_PUSH = 10,
OP_SNAP = 11,
OP_STEP = 12,
OP_LINE = 13,
OP_LINE_B = 14,
};
struct Rd
{
const double* in;
int64_t at;
double num() { return in[at++]; }
int64_t idx() { return (int64_t)in[at++]; }
bool flag() { return in[at++] != 0.0; }
SmVector3d vec()
{
double x = num();
double y = num();
double z = num();
return SmVector3d(x, y, z);
}
SmPoint2d uv()
{
double x = num();
double y = num();
return SmPoint2d(x, y);
}
SmExtent2d domain()
{
double a = num();
double b = num();
double c = num();
double d = num();
return SmExtent2d(a, b, c, d);
}
};
double st(SmStatus s)
{
return s == SM_SUCCESS ? 0.0 : (double)s;
}
double flg(bool b)
{
return b ? 1.0 : 0.0;
}
void put_uv(std::vector<double>& o, const SmVector2d& v)
{
o.push_back(v.x);
o.push_back(v.y);
}
gw_SURFACE* read_nurb(Rd& rd)
{
gw_INDEX n = (gw_INDEX)rd.idx();
gw_INDEX m = (gw_INDEX)rd.idx();
gw_DEGREE p = (gw_DEGREE)rd.idx();
gw_DEGREE q = (gw_DEGREE)rd.idx();
gw_INDEX r = (gw_INDEX)rd.idx();
gw_INDEX s = (gw_INDEX)rd.idx();
gw_SURFACE* sur = sm_AllocateNurbSurface(n, m, p, q, r, s);
for (gw_INDEX i = 0; i <= n; i++)
for (gw_INDEX j = 0; j <= m; j++)
{
sur->net->Pw[i][j].x = rd.num();
sur->net->Pw[i][j].y = rd.num();
sur->net->Pw[i][j].z = rd.num();
sur->net->Pw[i][j].w = rd.num();
}
for (gw_INDEX i = 0; i <= r; i++)
sur->knu->U[i] = rd.num();
for (gw_INDEX i = 0; i <= s; i++)
sur->knv->U[i] = rd.num();
return sur;
}
// `oob form nurb`.
SmBSplineSurface* read_bspline(Rd& rd, const SmContext& ctx)
{
bool oob = rd.flag();
SmBSplineSurfaceForm form = (SmBSplineSurfaceForm)rd.idx();
SmBSplineSurface* s = new (ctx) SmBSplineSurface(read_nurb(rd), FALSE, NULL);
s->GetOutOfBoundsEnabled() = oob ? TRUE : FALSE;
s->SetBSplineSurfaceForm(form);
return s;
}
// A surface `kind ...`: 0 B-spline `oob form nurb`; 1 bounded plane `o x y sx sy D`;
// 2 curve-bounded surface (implicit outer, no boundary curves) over a B-spline block.
SmSurface* read_surface(Rd& rd, const SmContext& ctx)
{
int64_t kind = rd.idx();
if (kind == 1)
{
SmPoint3d o = rd.vec();
SmVector3d x = rd.vec();
SmVector3d y = rd.vec();
double sx = rd.num();
double sy = rd.num();
SmExtent2d d = rd.domain();
return new (ctx) SmPlane(o, x, y, SmVector2d(sx, sy), d, &ctx);
}
SmBSplineSurface* b = read_bspline(rd, ctx);
if (kind == 0)
return b;
// the curve-bounded surface owns (deletes) its basis
SmTArray<SmCurve*> c3d(ctx), cuv(ctx);
return new (ctx) SmCurveBoundedSurface(*b, &c3d, &cuv, TRUE);
}
// `W S`: the surface, wrapped in an SmSTEPSurface owning it when W = 1.
SmSurface* read_wrapped(Rd& rd, const SmContext& ctx)
{
bool wrap = rd.flag();
SmSurface* s = read_surface(rd, ctx);
if (!wrap)
return s;
return new (ctx) SmSTEPSurface(*s, TRUE);
}
// The problem: `W S D op test tol`.
struct Problem
{
SmSurface* srf;
SmExtent2d dom;
SmSolverOperationType op;
SmPoint3d test;
double tol;
};
Problem read_problem(Rd& rd, const SmContext& ctx)
{
Problem p;
p.srf = read_wrapped(rd, ctx);
p.dom = rd.domain();
p.op = (SmSolverOperationType)rd.idx();
p.test = rd.vec();
p.tol = rd.num();
return p;
}
// `(u v value nobj nvar obj0_is_srf)` of each solution, after their count.
void put_solutions(std::vector<double>& o, SmSolutionArray& sols, const SmSurface* srf)
{
o.push_back((double)sols.GetSize());
for (ULONG i = 0; i < sols.GetSize(); i++)
{
const SmSolution& s = sols[i];
o.push_back(s.m_vStart.m_adParameters[0]);
o.push_back(s.m_vStart.m_adParameters[1]);
o.push_back(s.m_vStart.m_dSolutionValue);
o.push_back((double)s.m_lNumObjects);
o.push_back((double)s.m_lNumVariables);
o.push_back(flg(s.m_apObjects[0] == (const SmObject*)srf));
}
}
// `P`: FindRoughGuess.
void op_rough(Rd& rd, const SmContext& ctx, std::vector<double>& o)
{
Problem p = read_problem(rd, ctx);
SmSurfPtGlobalSolver g(p.srf, p.dom, p.op, p.test, p.tol, NULL, SM_SR_SINGLE);
SmPoint2d uv(0.0, 0.0);
ULONG iu = 7, iv = 7;
SmStatus s = g.FindRoughGuess(uv, iu, iv);
o.push_back(st(s));
if (s != SM_SUCCESS)
return;
put_uv(o, uv);
o.push_back((double)iu);
o.push_back((double)iv);
}
// `P iu iv`: FindPolygonGuess from the given best indices.
void op_poly(Rd& rd, const SmContext& ctx, std::vector<double>& o)
{
Problem p = read_problem(rd, ctx);
ULONG iu = (ULONG)rd.idx();
ULONG iv = (ULONG)rd.idx();
SmSurfPtGlobalSolver g(p.srf, p.dom, p.op, p.test, p.tol, NULL, SM_SR_SINGLE);
SmPoint2d uv(0.0, 0.0);
SmBoolean susp = FALSE;
SmStatus s = g.FindPolygonGuess(uv, iu, iv, susp);
o.push_back(st(s));
if (s != SM_SUCCESS)
return;
put_uv(o, uv);
o.push_back((double)iu);
o.push_back((double)iv);
o.push_back(flg(susp != FALSE));
}
// `P uv`: RefineGuess.
void op_refine(Rd& rd, const SmContext& ctx, std::vector<double>& o)
{
Problem p = read_problem(rd, ctx);
SmPoint2d uv = rd.uv();
SmSurfPtGlobalSolver g(p.srf, p.dom, p.op, p.test, p.tol, NULL, SM_SR_SINGLE);
ULONG iu = 0, iv = 0;
SmStatus s = g.RefineGuess(uv, iu, iv);
o.push_back(st(s));
put_uv(o, uv);
}
// `P sub nu nv uv best`: RefinePatchByGrid.
void op_patch(Rd& rd, const SmContext& ctx, std::vector<double>& o)
{
Problem p = read_problem(rd, ctx);
SmExtent2d sub = rd.domain();
int nu = (int)rd.idx();
int nv = (int)rd.idx();
SmPoint2d uv = rd.uv();
double best = rd.num();
SmSurfPtGlobalSolver g(p.srf, p.dom, p.op, p.test, p.tol, NULL, SM_SR_SINGLE);
SmStatus s = g.RefinePatchByGrid(sub, nu, nv, uv, best);
o.push_back(st(s));
put_uv(o, uv);
o.push_back(best);
}
// `P uv iu iv`: RefineGuessByGrid.
void op_grid(Rd& rd, const SmContext& ctx, std::vector<double>& o)
{
Problem p = read_problem(rd, ctx);
SmPoint2d uv = rd.uv();
ULONG iu = (ULONG)rd.idx();
ULONG iv = (ULONG)rd.idx();
SmSurfPtGlobalSolver g(p.srf, p.dom, p.op, p.test, p.tol, NULL, SM_SR_SINGLE);
SmStatus s = g.RefineGuessByGrid(uv, iu, iv);
o.push_back(st(s));
put_uv(o, uv);
}
// `P uv value`: CheckSeams of the solution (u, v) on the surface with that value.
void op_seams(Rd& rd, const SmContext& ctx, std::vector<double>& o)
{
Problem p = read_problem(rd, ctx);
SmPoint2d uv = rd.uv();
double value = rd.num();
SmSurfPtGlobalSolver g(p.srf, p.dom, p.op, p.test, p.tol, NULL, SM_SR_SINGLE);
SmSolution sol;
sol.m_lNumVariables = 2;
sol.m_lNumObjects = 1;
sol.m_apObjects[0] = p.srf;
sol.m_vStart[0] = uv.x;
sol.m_vStart[1] = uv.y;
sol.m_vStart.m_dSolutionValue = value;
SmSolutionArray sols;
SmStatus s = g.CheckSeams(g.m_dTightTol, sol, sols);
o.push_back(st(s));
put_solutions(o, sols, p.srf);
}
// `P`: Solve.
void op_solve(Rd& rd, const SmContext& ctx, std::vector<double>& o)
{
Problem p = read_problem(rd, ctx);
SmSurfPtGlobalSolver g(p.srf, p.dom, p.op, p.test, p.tol, NULL, SM_SR_SINGLE);
SmSolutionArray sols;
SmStatus s = g.Solve(sols);
o.push_back(st(s));
if (s != SM_SUCCESS)
return;
put_solutions(o, sols, p.srf);
}
// `HitBound(which, sign)` |n| times.
void hits(SmSurfPtSolveStepper& s, SmSurfParamType which, int64_t n)
{
int sign = n < 0 ? -1 : 1;
for (int64_t i = 0; i < (n < 0 ? -n : n); i++)
s.HitBound(which, sign);
}
// The stepper of `P uv0 tight`.
SmSurfPtSolveStepper read_stepper(Rd& rd, const Problem& p)
{
SmPoint2d uv0 = rd.uv();
double tight = rd.num();
return SmSurfPtSolveStepper(p.srf, p.dom, p.test, uv0, tight, p.tol, p.op);
}
// `UV BoundHitsU BoundHitsV NRStepSuspect IterCount` of a stepper.
void put_state(std::vector<double>& o, SmSurfPtSolveStepper& s)
{
put_uv(o, s.UV());
o.push_back((double)s.BoundHitsU());
o.push_back((double)s.BoundHitsV());
o.push_back(flg(s.NRStepSuspect() != FALSE));
o.push_back((double)s.IterCount());
}
// `P uv0 tight uv1 hu hv`: AdjustStep of a copy of the stepper at uv0 (after hu / hv
// boundary hits) moved to uv1.
void op_adjust(Rd& rd, const SmContext& ctx, std::vector<double>& o)
{
Problem p = read_problem(rd, ctx);
SmSurfPtSolveStepper prev = read_stepper(rd, p);
SmPoint2d uv1 = rd.uv();
int64_t hu = rd.idx();
int64_t hv = rd.idx();
hits(prev, SM_SP_U, hu);
hits(prev, SM_SP_V, hv);
SmSurfPtSolveStepper next(prev);
next.SetUV(uv1);
SmBoolean adjusted = FALSE;
double scale = -7.0;
SmStatus s = next.AdjustStep(prev, adjusted, scale);
o.push_back(st(s));
o.push_back(flg(adjusted != FALSE));
o.push_back(scale);
put_state(o, next);
}
// `P uv0 tight`: the nine ErrorNorms, IsSingular.
void op_norms(Rd& rd, const SmContext& ctx, std::vector<double>& o)
{
Problem p = read_problem(rd, ctx);
SmSurfPtSolveStepper s = read_stepper(rd, p);
o.push_back(0.0);
for (int n = 0; n <= (int)SM_SPN_GAP; n++)
o.push_back(s.ErrorNorm((SmSrfPtErrorNormType)n));
SmSurfParamType which = SM_SP_UNKNOWN;
SmBoolean b = s.IsSingular(&which);
o.push_back(flg(b != FALSE));
o.push_back((double)which);
}
// `P uv0 tight ctol`: Converged(ctol), Converged(TRUE), Converged(FALSE).
void op_conv(Rd& rd, const SmContext& ctx, std::vector<double>& o)
{
Problem p = read_problem(rd, ctx);
SmSurfPtSolveStepper s = read_stepper(rd, p);
double tol = rd.num();
o.push_back(0.0);
o.push_back(flg(s.Converged(tol) != FALSE));
o.push_back(flg(s.Converged((SmBoolean)TRUE) != FALSE));
o.push_back(flg(s.Converged((SmBoolean)FALSE) != FALSE));
}
// `P uv0 tight ptol`: PushingBoundary(ptol), ConvergedOnBoundary(ptol) (fresh steppers).
void op_push(Rd& rd, const SmContext& ctx, std::vector<double>& o)
{
Problem p = read_problem(rd, ctx);
SmSurfPtSolveStepper s = read_stepper(rd, p);
double tol = rd.num();
SmSurfPtSolveStepper c(s);
o.push_back(0.0);
SmSurfParamType which = SM_SP_NEITHER;
SmBoolean b = s.PushingBoundary(tol, &which);
o.push_back(flg(b != FALSE));
o.push_back((double)which);
which = SM_SP_NEITHER;
b = c.ConvergedOnBoundary(tol, &which);
o.push_back(flg(b != FALSE));
o.push_back((double)which);
}
// `P uv0 tight stol`: SnapToBoundaries(stol).
void op_snap(Rd& rd, const SmContext& ctx, std::vector<double>& o)
{
Problem p = read_problem(rd, ctx);
SmSurfPtSolveStepper s = read_stepper(rd, p);
double tol = rd.num();
SmBoolean b = s.SnapToBoundaries(tol);
o.push_back(0.0);
o.push_back(flg(b != FALSE));
put_state(o, s);
}
// `P uv0 tight plane`: Step of a copy of the stepper at uv0, from it.
void op_step(Rd& rd, const SmContext& ctx, std::vector<double>& o)
{
Problem p = read_problem(rd, ctx);
SmSurfPtSolveStepper prev = read_stepper(rd, p);
bool plane = rd.flag();
SmSurfPtSolveStepper next(prev);
SmTerminationReasonType r = next.Step(prev, plane ? TRUE : FALSE);
o.push_back(0.0);
o.push_back((double)r);
put_state(o, next);
}
// `P uv0 tight uv1`: LineSearch of a copy of the stepper at uv0 moved to uv1, from it.
void op_line(Rd& rd, const SmContext& ctx, std::vector<double>& o)
{
Problem p = read_problem(rd, ctx);
SmSurfPtSolveStepper prev = read_stepper(rd, p);
SmPoint2d uv1 = rd.uv();
SmSurfPtSolveStepper next(prev);
next.SetUV(uv1);
SmStatus s = next.LineSearch(prev);
o.push_back(st(s));
put_state(o, next);
}
// `P uv0 tight uv1 norm`: LineSearchBracketted as op_line.
void op_line_b(Rd& rd, const SmContext& ctx, std::vector<double>& o)
{
Problem p = read_problem(rd, ctx);
SmSurfPtSolveStepper prev = read_stepper(rd, p);
SmPoint2d uv1 = rd.uv();
SmSrfPtErrorNormType norm = (SmSrfPtErrorNormType)rd.idx();
SmSurfPtSolveStepper next(prev);
next.SetUV(uv1);
SmStatus s = next.LineSearchBracketted(prev, norm);
o.push_back(st(s));
put_state(o, next);
}
} // namespace sc_spsp2
SC_EXPORT int32_t sc_surface_surface_point_solve_p2_eval(int32_t op, const double* in, int64_t n_in, double* out,
int64_t cap, int64_t* out_len)
{
(void)n_in;
SC_TRY
SmContext ctx;
std::vector<double> o;
sc_spsp2::Rd rd{in, 0};
switch (op)
{
case sc_spsp2::OP_ROUGH: sc_spsp2::op_rough(rd, ctx, o); break;
case sc_spsp2::OP_POLY: sc_spsp2::op_poly(rd, ctx, o); break;
case sc_spsp2::OP_REFINE: sc_spsp2::op_refine(rd, ctx, o); break;
case sc_spsp2::OP_PATCH: sc_spsp2::op_patch(rd, ctx, o); break;
case sc_spsp2::OP_GRID: sc_spsp2::op_grid(rd, ctx, o); break;
case sc_spsp2::OP_SEAMS: sc_spsp2::op_seams(rd, ctx, o); break;
case sc_spsp2::OP_SOLVE: sc_spsp2::op_solve(rd, ctx, o); break;
case sc_spsp2::OP_ADJUST: sc_spsp2::op_adjust(rd, ctx, o); break;
case sc_spsp2::OP_NORMS: sc_spsp2::op_norms(rd, ctx, o); break;
case sc_spsp2::OP_CONV: sc_spsp2::op_conv(rd, ctx, o); break;
case sc_spsp2::OP_PUSH: sc_spsp2::op_push(rd, ctx, o); break;
case sc_spsp2::OP_SNAP: sc_spsp2::op_snap(rd, ctx, o); break;
case sc_spsp2::OP_STEP: sc_spsp2::op_step(rd, ctx, o); break;
case sc_spsp2::OP_LINE: sc_spsp2::op_line(rd, ctx, o); break;
case sc_spsp2::OP_LINE_B: sc_spsp2::op_line_b(rd, ctx, o); break;
default: return SC_ERR_ARG;
}
return sc_copy_out(o.data(), (int64_t)o.size(), out, cap, out_len);
SC_CATCH
}