use crate::{KernelRefusal, KernelStage, OrRefuse};
use crate::topology::{coedge_error_floored, EdgeRecord};
use crate::{MeasuredTolerance, NurbsCurve, NurbsSurface, OffsetEvaluator, OffsetNormal, Vec3};
const FIT_SAMPLES: usize = 15;
const FIT_OFFSET_U: f64 = 0.31;
const FIT_OFFSET_V: f64 = 0.43;
pub fn measure_edge_against_pcurve_image(
surface: &NurbsSurface,
pcurve: &NurbsCurve,
edge: &EdgeRecord,
forward: bool,
band: f64,
) -> Result<MeasuredTolerance, KernelRefusal> {
let adaptive = coedge_error_floored(surface, pcurve, &edge.curve, edge, forward, band).or_refuse(KernelStage::Validate, "coedge_error_floored")?;
let spans = span_midpoint_error(surface, pcurve, edge, forward)?;
Ok(MeasuredTolerance::new(adaptive.max(spans), band))
}
pub fn span_midpoint_error(
surface: &NurbsSurface,
pcurve: &NurbsCurve,
edge: &EdgeRecord,
forward: bool,
) -> Result<f64, KernelRefusal> {
let span = edge.t1 - edge.t0;
if !span.is_finite() || span.abs() <= 0.0 {
return Ok(0.0);
}
let [q0, q1] = pcurve.domain().or_refuse(KernelStage::Validate, "domain")?;
let low = edge.t0.min(edge.t1);
let high = edge.t0.max(edge.t1);
let mut breaks: Vec<f64> = vec![low];
for &knot in &edge.curve.knots {
if knot > low && knot < high && knot > *breaks.last().unwrap_or(&low) {
breaks.push(knot);
}
}
breaks.push(high);
let mut worst = 0.0f64;
for pair in breaks.windows(2) {
let midpoint = (pair[0] + pair[1]) * 0.5;
if !(midpoint > pair[0] && midpoint < pair[1]) {
continue;
}
let fraction = if forward {
(midpoint - edge.t0) / span
} else {
(edge.t1 - midpoint) / span
};
let uv = pcurve.evaluate(q0 + (q1 - q0) * fraction).or_refuse(KernelStage::Validate, "evaluate")?;
let on_surface = surface.evaluate_extended(uv.x, uv.y).or_refuse(KernelStage::Validate, "evaluate_extended")?;
let on_curve = edge.curve.evaluate(midpoint).or_refuse(KernelStage::Validate, "evaluate")?;
worst = worst.max(on_surface.sub(on_curve).length());
}
Ok(worst)
}
pub fn measure_surface_fit_against_pointwise_offset(
source: &NurbsSurface,
same_sense: bool,
fitted: &NurbsSurface,
distance: f64,
band: f64,
) -> Result<MeasuredTolerance, KernelRefusal> {
let evaluator = OffsetEvaluator::new(
"measure_offset_fit",
source,
OffsetNormal::FaceStable { same_sense },
);
let [u0, u1] = source.domain_u().or_refuse(KernelStage::Validate, "domain_u")?;
let [v0, v1] = source.domain_v().or_refuse(KernelStage::Validate, "domain_v")?;
let mut worst = 0.0f64;
for iu in 0..FIT_SAMPLES {
let u = u0 + (u1 - u0) * (iu as f64 + FIT_OFFSET_U) / FIT_SAMPLES as f64;
for iv in 0..FIT_SAMPLES {
let v = v0 + (v1 - v0) * (iv as f64 + FIT_OFFSET_V) / FIT_SAMPLES as f64;
let want = evaluator.at(u, v, -distance).or_refuse(KernelStage::Validate, "at")?.point;
let got = fitted.evaluate(u, v).or_refuse(KernelStage::Validate, "evaluate")?;
worst = worst.max(got.sub(want).length());
}
}
Ok(MeasuredTolerance::new(worst, band))
}
pub fn vertex_endpoint_gap(vertex_point: Vec3, curve_end: Vec3) -> f64 {
curve_end.sub(vertex_point).length()
}
pub(crate) fn sphere_fit_reading(
source: &NurbsSurface, same_sense: bool, fitted: &NurbsSurface, distance: f64, band: f64,
) -> Result<MeasuredTolerance, KernelRefusal> {
sphere_fit_reading_mode(source, same_sense, fitted, distance, band,
std::env::var("BREP_OFFSET_SPHERE_READING").map_or(true, |v| v != "0" && v != "off"))
}
thread_local! {
static SPHERE_FIT_READINGS: std::cell::RefCell<rustc_hash::FxHashMap<Vec<u64>, f64>> =
std::cell::RefCell::new(rustc_hash::FxHashMap::default());
}
fn sphere_fit_key(source: &NurbsSurface, fitted: &NurbsSurface, same_sense: bool, distance: f64) -> Option<Vec<u64>> {
let size = |s: &NurbsSurface| 5 + s.knots_u.len() + s.knots_v.len()
+ s.control_points.iter().map(|row| 1 + 4 * row.len()).sum::<usize>();
if size(source) + size(fitted) + 3 > 4096 { return None; }
let mut key = vec![same_sense as u64, distance.to_bits(),
(std::env::var("BREP_CUSP_TANGENT_RESCUE").as_deref() == Ok("0")) as u64];
for surface in [source, fitted] {
key.extend([surface.degree_u as u64, surface.degree_v as u64]);
for knots in [&surface.knots_u, &surface.knots_v] {
key.push(knots.len() as u64);
key.extend(knots.iter().map(|v| v.to_bits()));
}
key.push(surface.control_points.len() as u64);
for row in &surface.control_points {
key.push(row.len() as u64);
for p in row { key.extend([p.x.to_bits(), p.y.to_bits(), p.z.to_bits(), p.w.to_bits()]); }
}
}
Some(key)
}
fn sphere_fit_reading_mode(
source: &NurbsSurface, same_sense: bool, fitted: &NurbsSurface, distance: f64, band: f64, enabled: bool,
) -> Result<MeasuredTolerance, KernelRefusal> {
let key = (enabled && std::env::var_os("BREP_OFFSET_DIAG").is_none())
.then(|| sphere_fit_key(source, fitted, same_sense, distance)).flatten();
if let Some(deviation) = key.as_ref().and_then(|key|
SPHERE_FIT_READINGS.with(|memo| memo.borrow().get(key).copied())) {
if crate::profile::profile_enabled() {
profile_line!("offset.sphere_fit_memo hit=1 stations=0");
}
return Ok(MeasuredTolerance::new(deviation, band));
}
let measured = measure_surface_fit_against_pointwise_offset(source, same_sense, fitted, distance, band)?;
if let Some(key) = key {
SPHERE_FIT_READINGS.with(|memo| {
let mut memo = memo.borrow_mut();
if memo.len() >= 64 { memo.clear(); }
memo.insert(key, measured.deviation());
});
}
if crate::profile::profile_enabled() {
profile_line!("offset.sphere_fit_memo hit=0 stations={}", FIT_SAMPLES * FIT_SAMPLES);
}
Ok(measured)
}