use crate::{AnalyticSurface, NurbsSurface, Vec3};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum OffsetNormal {
Raw,
Face { same_sense: bool },
FaceStable { same_sense: bool },
ExactAnalytic { same_sense: bool },
}
impl OffsetNormal {
fn same_sense(self) -> bool {
match self {
OffsetNormal::Raw => true,
OffsetNormal::Face { same_sense }
| OffsetNormal::FaceStable { same_sense }
| OffsetNormal::ExactAnalytic { same_sense } => same_sense,
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct OffsetSample {
pub source: Vec3,
pub normal: Vec3,
pub point: Vec3,
}
#[derive(Clone, Copy)]
pub struct OffsetEvaluator<'a> {
site: &'static str,
surface: &'a NurbsSurface,
convention: OffsetNormal,
}
impl<'a> OffsetEvaluator<'a> {
pub fn new(site: &'static str, surface: &'a NurbsSurface, convention: OffsetNormal) -> Self {
Self {
site,
surface,
convention,
}
}
pub fn normal(&self, u: f64, v: f64) -> Result<Vec3, String> {
let result = self.normal_with(self.convention, u, v);
offset_normal_diagnostic(self.site, self.surface, self.convention, u, v, &result);
result
}
pub fn on_real_carrier(&self, u: f64, v: f64, tolerance: f64) -> Result<bool, String> {
let [u0, u1] = self.surface.domain_u()?;
let [v0, v1] = self.surface.domain_v()?;
let (closed_u, closed_v) = self.surface.closed_directions()?;
let out = |value: f64, lo: f64, hi: f64, closed: bool| -> f64 {
if closed {
0.0
} else if value < lo {
value - lo
} else if value > hi {
value - hi
} else {
0.0
}
};
let du_out = out(u, u0, u1, closed_u);
let dv_out = out(v, v0, v1, closed_v);
if du_out == 0.0 && dv_out == 0.0 {
return Ok(true);
}
let derivatives = self.surface.derivatives(u - du_out, v - dv_out, 2)?;
let normal = match derivatives[1][0].cross(derivatives[0][1]).normalized() {
Ok(normal) => normal,
Err(_) => {
return self.degenerate_anchor_on_real_carrier(u, v, du_out, dv_out, tolerance)
}
};
let departure = |step: f64, second: Vec3| 0.5 * step * step * second.dot(normal).abs();
if du_out != 0.0 && departure(du_out, derivatives[2][0]) > tolerance {
return Ok(false);
}
if dv_out != 0.0 && departure(dv_out, derivatives[0][2]) > tolerance {
return Ok(false);
}
Ok(true)
}
fn degenerate_anchor_on_real_carrier(
&self,
u: f64,
v: f64,
du_out: f64,
dv_out: f64,
tolerance: f64,
) -> Result<bool, String> {
let (anchor_u, anchor_v) = (u - du_out, v - dv_out);
let charts = [
(du_out != 0.0)
.then(|| self.stationary_chart(true, anchor_u, anchor_v))
.transpose()?
.flatten(),
(dv_out != 0.0)
.then(|| self.stationary_chart(false, anchor_u, anchor_v))
.transpose()?
.flatten(),
];
if charts.iter().all(Option::is_none) {
return Ok(false);
}
let (anchor_point, su, sv) = self.surface.deriv1(anchor_u, anchor_v)?;
let Ok(normal) = raw_unit_normal(self.surface, anchor_u, anchor_v, su, sv) else {
return Ok(false);
};
let derivatives = self.surface.derivatives(anchor_u, anchor_v, 2)?;
let ([u0, u1], [v0, v1]) = domains(self.surface)?;
for (along_u, out, chart) in [(true, du_out, charts[0]), (false, dv_out, charts[1])] {
if out == 0.0 {
continue;
}
let departure = match chart {
Some(chart) => {
let excursion = chart.regular(if along_u { u } else { v });
let (lo, hi) = if along_u { (u0, u1) } else { (v0, v1) };
let far = if chart.outward > 0.0 { lo } else { hi };
let reach = -chart.regular(far);
let (inside, growth) = if excursion <= reach {
(chart.parameter(-excursion), 1.0)
} else {
(far, (excursion / reach).powi(2))
};
let mirrored = if along_u {
self.surface.evaluate(inside, anchor_v)?
} else {
self.surface.evaluate(anchor_u, inside)?
};
growth * mirrored.sub(anchor_point).dot(normal).abs()
}
None => {
let second = if along_u { derivatives[2][0] } else { derivatives[0][2] };
0.5 * out * out * second.dot(normal).abs()
}
};
if departure > tolerance {
return Ok(false);
}
}
Ok(true)
}
pub fn at(&self, u: f64, v: f64, distance: f64) -> Result<OffsetSample, String> {
let (source, normal) = self.evaluate(u, v)?;
Ok(OffsetSample {
source,
normal,
point: source.add(normal.scale(distance)),
})
}
fn evaluate(&self, u: f64, v: f64) -> Result<(Vec3, Vec3), String> {
let result = match self.convention {
OffsetNormal::Raw => self
.surface
.deriv1_extended(u, v)
.and_then(|(point, su, sv)| {
let normal = raw_unit_normal(self.surface, u, v, su, sv)?;
Ok((raw_extended_point(self.surface, u, v, point, su, sv)?, normal))
}),
convention => self
.surface
.evaluate(u, v)
.and_then(|point| Ok((point, self.normal_with(convention, u, v)?))),
};
let normal = result
.as_ref()
.map(|(_, normal)| *normal)
.map_err(Clone::clone);
offset_normal_diagnostic(self.site, self.surface, self.convention, u, v, &normal);
result
}
fn normal_with(&self, convention: OffsetNormal, u: f64, v: f64) -> Result<Vec3, String> {
match convention {
OffsetNormal::Raw => {
let (_, su, sv) = self.surface.deriv1_extended(u, v)?;
raw_unit_normal(self.surface, u, v, su, sv)
}
OffsetNormal::Face { same_sense } => {
Ok(orient(self.surface.normal(u, v)?, same_sense))
}
OffsetNormal::FaceStable { same_sense } => {
stable_normal(self.surface, same_sense, u, v)
}
OffsetNormal::ExactAnalytic { same_sense } => {
let point = self.surface.evaluate(u, v)?;
match exact_analytic_normal(self.surface, u, v, point)? {
Some(normal) => Ok(orient(normal, same_sense)),
None => Ok(orient(self.surface.normal(u, v)?, same_sense)),
}
}
}
}
}
fn orient(normal: Vec3, same_sense: bool) -> Vec3 {
if same_sense {
normal
} else {
normal.scale(-1.0)
}
}
fn domains(surface: &NurbsSurface) -> Result<([f64; 2], [f64; 2]), String> {
Ok((surface.domain_u()?, surface.domain_v()?))
}
fn raw_unit_normal(
surface: &NurbsSurface,
u: f64,
v: f64,
su: Vec3,
sv: Vec3,
) -> Result<Vec3, String> {
let (su, sv) = well_rounded_partials(surface, u, v, su, sv)?;
let error = match su.cross(sv).normalized() {
Ok(normal) => return Ok(normal),
Err(error) => error,
};
if std::env::var("BREP_CUSP_TANGENT_RESCUE").as_deref() == Ok("0") {
return Err(error);
}
let ([u0, u1], [v0, v1]) = domains(surface)?;
let (closed_u, closed_v) = surface.closed_directions()?;
let anchor = |value: f64, lo: f64, hi: f64, closed: bool| -> f64 {
if closed && (value < lo || value > hi) {
lo + (value - lo).rem_euclid(hi - lo)
} else {
value.clamp(lo, hi)
}
};
let (anchor_u, anchor_v) = (anchor(u, u0, u1, closed_u), anchor(v, v0, v1, closed_v));
let along_u_first = su.length() <= sv.length();
let directions = if along_u_first {
[(1.0, 0.0), (0.0, 1.0)]
} else {
[(0.0, 1.0), (1.0, 0.0)]
};
for scale in [1e-6, 1e-4, 1e-2, 1e-1] {
for (along_u, along_v) in directions {
let step_u = (u1 - u0) * scale * along_u;
let step_v = (v1 - v0) * scale * along_v;
for sign in [1.0, -1.0] {
let probe_u = anchor_u + sign * step_u;
let probe_v = anchor_v + sign * step_v;
if probe_u < u0 || probe_u > u1 || probe_v < v0 || probe_v > v1 {
continue;
}
if let Some(normal) = surface
.deriv1_extended(probe_u, probe_v)
.and_then(|(_, su, sv)| {
well_rounded_partials(surface, probe_u, probe_v, su, sv)
})
.ok()
.and_then(|(su, sv)| su.cross(sv).normalized().ok())
{
return Ok(normal);
}
}
}
}
Err(format!(
"offset normal: S_u x S_v vanishes at (u, v) = ({u}, {v}) and at every probe the \
one-sided limit can reach in [{u0}, {u1}] x [{v0}, {v1}], so this carrier has no \
normal to report there"
))
}
fn raw_extended_point(
surface: &NurbsSurface,
u: f64,
v: f64,
point: Vec3,
su: Vec3,
sv: Vec3,
) -> Result<Vec3, String> {
let ([u0, u1], [v0, v1]) = domains(surface)?;
if u >= u0 && u <= u1 && v >= v0 && v <= v1 {
return Ok(point);
}
if su.normalized().is_ok() && sv.normalized().is_ok() {
return Ok(point);
}
if std::env::var("BREP_CUSP_TANGENT_RESCUE").as_deref() == Ok("0") {
return Ok(point);
}
let (closed_u, closed_v) = surface.closed_directions()?;
let excursion = |value: f64, lo: f64, hi: f64, closed: bool| -> f64 {
if closed {
0.0
} else if value < lo {
value - lo
} else if value > hi {
value - hi
} else {
0.0
}
};
let du_out = excursion(u, u0, u1, closed_u);
let dv_out = excursion(v, v0, v1, closed_v);
let wrap = |value: f64, lo: f64, hi: f64, closed: bool| -> f64 {
if closed && (value < lo || value > hi) {
lo + (value - lo).rem_euclid(hi - lo)
} else {
value.clamp(lo, hi)
}
};
let (anchor_u, anchor_v) = (wrap(u, u0, u1, closed_u), wrap(v, v0, v1, closed_v));
let mut point = point;
for (along_u, out, partial) in [(true, du_out, su), (false, dv_out, sv)] {
if out == 0.0 || partial.normalized().is_ok() {
continue;
}
let (lo, hi) = if along_u { (u0, u1) } else { (v0, v1) };
let inward = -out.signum();
let direction = [1e-6, 1e-4, 1e-2, 1e-1].iter().find_map(|scale| {
let step = inward * (hi - lo) * scale;
let (probe_u, probe_v) = if along_u {
(anchor_u + step, anchor_v)
} else {
(anchor_u, anchor_v + step)
};
if probe_u < u0 || probe_u > u1 || probe_v < v0 || probe_v > v1 {
return None;
}
let (_, probe_su, probe_sv) = surface.deriv1(probe_u, probe_v).ok()?;
let (probe_su, probe_sv) =
well_rounded_partials(surface, probe_u, probe_v, probe_su, probe_sv).ok()?;
(if along_u { probe_su } else { probe_sv }).normalized().ok()
});
let Some(direction) = direction else {
return Err(format!(
"offset point: S_{} vanishes on the boundary of [{u0}, {u1}] x [{v0}, {v1}] at \
(u, v) = ({anchor_u}, {anchor_v}) and at every probe the one-sided limit can \
reach, so the surface has no direction to extend along toward ({u}, {v})",
if along_u { "u" } else { "v" }
));
};
let speed = isoline_mean_speed(surface, along_u, anchor_u, anchor_v)?;
point = point.add(direction.scale(out * speed));
}
Ok(point)
}
fn isoline_mean_speed(
surface: &NurbsSurface,
along_u: bool,
anchor_u: f64,
anchor_v: f64,
) -> Result<f64, String> {
let ([u0, u1], [v0, v1]) = domains(surface)?;
let (lo, hi) = if along_u { (u0, u1) } else { (v0, v1) };
let (start, end) = if along_u {
(surface.evaluate(lo, anchor_v)?, surface.evaluate(hi, anchor_v)?)
} else {
(surface.evaluate(anchor_u, lo)?, surface.evaluate(anchor_u, hi)?)
};
Ok(end.sub(start).length() / (hi - lo))
}
fn extension_anchor(surface: &NurbsSurface, u: f64, v: f64) -> Result<(f64, f64), String> {
let ([u0, u1], [v0, v1]) = domains(surface)?;
let (closed_u, closed_v) = surface.closed_directions()?;
let anchor = |value: f64, lo: f64, hi: f64, closed: bool| -> f64 {
if closed && (value < lo || value > hi) {
lo + (value - lo).rem_euclid(hi - lo)
} else {
value.clamp(lo, hi)
}
};
Ok((anchor(u, u0, u1, closed_u), anchor(v, v0, v1, closed_v)))
}
fn stationary_boundary(
surface: &NurbsSurface,
along_u: bool,
u: f64,
v: f64,
) -> Result<Option<f64>, String> {
let ([u0, u1], [v0, v1]) = domains(surface)?;
let (closed_u, closed_v) = surface.closed_directions()?;
if (along_u && closed_u) || (!along_u && closed_v) {
return Ok(None);
}
let (anchor_u, anchor_v) = extension_anchor(surface, u, v)?;
let (value, lo, hi) = if along_u { (u, u0, u1) } else { (v, v0, v1) };
let boundary = if (value - lo).abs() <= (hi - value).abs() { lo } else { hi };
let (at_u, at_v) = if along_u { (boundary, anchor_v) } else { (anchor_u, boundary) };
let (_, su, sv) = surface.deriv1(at_u, at_v)?;
let (across, along) = if along_u { (su, sv) } else { (sv, su) };
Ok((across.normalized().is_err() && along.normalized().is_ok()).then_some(boundary))
}
const STATIONARY_PARTIAL_REACH: f64 = 1e-3;
fn well_rounded_partials(
surface: &NurbsSurface,
u: f64,
v: f64,
su: Vec3,
sv: Vec3,
) -> Result<(Vec3, Vec3), String> {
let ([u0, u1], [v0, v1]) = domains(surface)?;
let reach_u = su.length_squared() * (u1 - u0) * (u1 - u0);
let reach_v = sv.length_squared() * (v1 - v0) * (v1 - v0);
let gate = STATIONARY_PARTIAL_REACH * STATIONARY_PARTIAL_REACH;
let small_u = reach_u <= gate * reach_v;
let small_v = reach_v <= gate * reach_u;
if !small_u && !small_v {
return Ok((su, sv));
}
if std::env::var("BREP_CUSP_TANGENT_RESCUE").as_deref() == Ok("0") {
return Ok((su, sv));
}
let (anchor_u, anchor_v) = extension_anchor(surface, u, v)?;
let mut partials = (su, sv);
if small_u && stationary_boundary(surface, true, u, v)?.is_some() {
if let Ok(partial) = surface.partial_by_differences(true, anchor_u, anchor_v) {
partials.0 = partial;
}
}
if small_v && stationary_boundary(surface, false, u, v)?.is_some() {
if let Ok(partial) = surface.partial_by_differences(false, anchor_u, anchor_v) {
partials.1 = partial;
}
}
Ok(partials)
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct StationaryChart {
boundary: f64,
outward: f64,
order: i32,
coefficient: f64,
speed: f64,
}
impl StationaryChart {
pub(crate) fn regular(&self, parameter: f64) -> f64 {
let outward = self.outward * (parameter - self.boundary);
if outward >= 0.0 {
self.speed * outward
} else {
-self.coefficient * (-outward).powi(self.order)
}
}
pub(crate) fn parameter(&self, regular: f64) -> f64 {
let outward = if regular >= 0.0 {
regular / self.speed
} else if self.order == 2 {
-(-regular / self.coefficient).sqrt()
} else {
-(-regular / self.coefficient).powf(1.0 / f64::from(self.order))
};
self.boundary + self.outward * outward
}
}
impl OffsetEvaluator<'_> {
pub(crate) fn stationary_chart(
&self,
along_u: bool,
u: f64,
v: f64,
) -> Result<Option<StationaryChart>, String> {
if self.convention != OffsetNormal::Raw
|| std::env::var("BREP_CUSP_TANGENT_RESCUE").as_deref() == Ok("0")
{
return Ok(None);
}
let surface = self.surface;
let Some(boundary) = stationary_boundary(surface, along_u, u, v)? else {
return Ok(None);
};
let ([u0, u1], [v0, v1]) = domains(surface)?;
let hi = if along_u { u1 } else { v1 };
let (anchor_u, anchor_v) = extension_anchor(surface, u, v)?;
let (at_u, at_v) = if along_u { (boundary, anchor_v) } else { (anchor_u, boundary) };
let degree = if along_u { surface.degree_u } else { surface.degree_v };
let derivatives = surface.derivatives(at_u, at_v, degree)?;
let mut factorial = 1.0;
let mut found = None;
for order in 2..=degree {
factorial *= order as f64;
let partial = if along_u {
derivatives[order][0]
} else {
derivatives[0][order]
};
if partial.normalized().is_ok() {
found = Some((order, partial.length() / factorial));
break;
}
}
let Some((order, coefficient)) = found else {
return Err(format!(
"offset chart: S_{} vanishes to order {degree} across the boundary of \
[{u0}, {u1}] x [{v0}, {v1}] at (u, v) = ({at_u}, {at_v}), so the surface does \
not leave that boundary and has no regular parameter there",
if along_u { "u" } else { "v" }
));
};
let speed = isoline_mean_speed(surface, along_u, anchor_u, anchor_v)?;
if !(speed > 0.0) {
return Ok(None);
}
Ok(Some(StationaryChart {
boundary,
outward: if boundary == hi { 1.0 } else { -1.0 },
order: order as i32,
coefficient,
speed,
}))
}
}
fn stable_normal(
surface: &NurbsSurface,
same_sense: bool,
u: f64,
v: f64,
) -> Result<Vec3, String> {
let normal_at = |u, v| surface.normal(u, v).ok();
let mut normal = normal_at(u, v);
let ([u0, u1], [v0, v1]) = domains(surface)?;
if normal.is_none() {
let du = (u1 - u0) * 1e-5;
let dv = (v1 - v0) * 1e-5;
for (candidate_u, candidate_v) in [
((u + du).clamp(u0, u1), v),
((u - du).clamp(u0, u1), v),
(u, (v + dv).clamp(v0, v1)),
(u, (v - dv).clamp(v0, v1)),
] {
normal = normal_at(candidate_u, candidate_v);
if normal.is_some() {
break;
}
}
}
let singular_here = {
let du = (u1 - u0) * 1e-4;
let dv = (v1 - v0) * 1e-4;
let here = surface.evaluate(u, v)?;
let along_u = surface
.evaluate((u + du).clamp(u0, u1), v)?
.sub(here)
.length()
.max(
surface
.evaluate((u - du).clamp(u0, u1), v)?
.sub(here)
.length(),
);
let along_v = surface
.evaluate(u, (v + dv).clamp(v0, v1))?
.sub(here)
.length()
.max(
surface
.evaluate(u, (v - dv).clamp(v0, v1))?
.sub(here)
.length(),
);
let scale = along_u.max(along_v);
scale > 0.0 && along_u.min(along_v) < scale * 1e-6
};
if singular_here {
let v_mid = (v0 + v1) * 0.5;
let u_mid = (u0 + u1) * 0.5;
let mut interior = None;
for fraction in [1e-3, 1e-2, 5e-2, 0.25] {
let candidate_v = v + (v_mid - v) * fraction;
let candidate_u = u + (u_mid - u) * fraction;
for (cu, cv) in [(u, candidate_v), (candidate_u, v), (candidate_u, candidate_v)] {
if let Some(candidate) = normal_at(cu, cv) {
interior = Some(candidate);
break;
}
}
if interior.is_some() {
break;
}
}
normal = match (normal, interior) {
(Some(at_point), Some(interior)) if at_point.dot(interior) > 1.0 - 1e-6 => {
Some(at_point)
}
(_, Some(interior)) => Some(interior),
(at_point, None) => at_point,
};
}
let normal =
normal.ok_or_else(|| "offset_surface: cannot determine surface normal".to_string())?;
Ok(orient(normal, same_sense))
}
fn exact_analytic_normal(
surface: &NurbsSurface,
u: f64,
v: f64,
point: Vec3,
) -> Result<Option<Vec3>, String> {
let Some(analytic) = surface.analytic() else {
return Ok(None);
};
let Some(direction) = exact_direction(surface, analytic, u, v, point)? else {
return Ok(None);
};
let ([u0, u1], [v0, v1]) = domains(surface)?;
let probes = [
(u, v),
((u0 + u1) * 0.5, (v0 + v1) * 0.5),
(u0 + (u1 - u0) * 0.37, v0 + (v1 - v0) * 0.41),
(u0 + (u1 - u0) * 0.63, v0 + (v1 - v0) * 0.59),
];
for (pu, pv) in probes {
let Ok(parametric) = surface.normal(pu, pv) else {
continue;
};
let probe_point = if (pu, pv) == (u, v) {
point
} else {
surface.evaluate(pu, pv)?
};
let Some(probe_direction) = exact_direction(surface, analytic, pu, pv, probe_point)? else {
continue;
};
let alignment = probe_direction.dot(parametric);
if alignment.abs() < 0.5 {
continue;
}
return Ok(Some(if alignment > 0.0 {
direction
} else {
direction.scale(-1.0)
}));
}
Ok(None)
}
fn exact_direction(
surface: &NurbsSurface,
analytic: &AnalyticSurface,
u: f64,
v: f64,
point: Vec3,
) -> Result<Option<Vec3>, String> {
Ok(match analytic {
AnalyticSurface::Plane { u_dir, v_dir, .. } => u_dir.cross(*v_dir).normalized().ok(),
AnalyticSurface::Sphere { frame, .. } => {
point.sub(frame.origin).normalized().ok()
}
AnalyticSurface::RuledRevolution {
frame,
rho0,
rho1,
height,
} => {
let radial = match radial_direction(surface, frame.origin, frame.axis, u, v, point)? {
Some(radial) => radial,
None => return Ok(None),
};
let dr = rho1 - rho0;
let length = (dr * dr + height * height).sqrt();
if length <= 0.0 {
return Ok(None);
}
radial
.scale(*height / length)
.sub(frame.axis.scale(dr / length))
.normalized()
.ok()
}
AnalyticSurface::Torus {
frame,
major_radius,
minor_radius,
} => {
let radial = match radial_direction(surface, frame.origin, frame.axis, u, v, point)? {
Some(radial) => radial,
None => return Ok(None),
};
if *minor_radius <= 0.0 {
return Ok(None);
}
let tube_centre = frame.origin.add(radial.scale(*major_radius));
point.sub(tube_centre).normalized().ok()
}
AnalyticSurface::Revolution { .. } => None,
})
}
fn radial_direction(
surface: &NurbsSurface,
origin: Vec3,
axis: Vec3,
u: f64,
v: f64,
point: Vec3,
) -> Result<Option<Vec3>, String> {
let radial_of = |point: Vec3| {
let relative = point.sub(origin);
relative.sub(axis.scale(relative.dot(axis)))
};
let here = radial_of(point);
let [v0, v1] = surface.domain_v()?;
let scale = point.sub(origin).length().max(1.0);
if here.length() > 1e-12 * scale {
return Ok(here.normalized().ok());
}
let far = if (v - v0).abs() >= (v1 - v).abs() {
v0
} else {
v1
};
let candidate = radial_of(surface.evaluate(u, far)?);
if candidate.length() <= 1e-12 * scale {
return Ok(None);
}
Ok(candidate.normalized().ok())
}
#[derive(Default, Clone, Copy)]
struct LaneStats {
compared: u64,
lane_errors: u64,
lane_rescues: u64,
worst_chord: f64,
flipped: u64,
worst_u: f64,
worst_v: f64,
}
#[derive(Default, Clone, Copy)]
struct SiteStats {
calls: u64,
errors: u64,
convention: Option<OffsetNormal>,
lanes: [LaneStats; 4],
}
const LANE_NAMES: [&str; 4] = ["raw", "face", "stable", "exact"];
fn lane_of(index: usize, same_sense: bool) -> OffsetNormal {
match index {
0 => OffsetNormal::Raw,
1 => OffsetNormal::Face { same_sense },
2 => OffsetNormal::FaceStable { same_sense },
_ => OffsetNormal::ExactAnalytic { same_sense },
}
}
struct DiagnosticSink {
sites: std::collections::BTreeMap<&'static str, SiteStats>,
}
impl Drop for DiagnosticSink {
fn drop(&mut self) {
use std::fmt::Write as _;
for (site, stats) in &self.sites {
let mut line = format!(
"offset-diag site={site} convention={} calls={} errors={}",
stats
.convention
.map(|convention| format!("{convention:?}"))
.unwrap_or_else(|| "?".to_string()),
stats.calls,
stats.errors
);
for (index, lane) in stats.lanes.iter().enumerate() {
if lane.compared == 0 && lane.lane_errors == 0 && lane.lane_rescues == 0 {
continue;
}
let _ = write!(
line,
" | {}: n={} chord={:.3e} at=({:.6},{:.6}) flip={} lane_err={} rescue={}",
LANE_NAMES[index],
lane.compared,
lane.worst_chord,
lane.worst_u,
lane.worst_v,
lane.flipped,
lane.lane_errors,
lane.lane_rescues
);
}
eprintln!("{line}");
}
}
}
fn diagnostic_stride() -> u64 {
static STRIDE: std::sync::OnceLock<u64> = std::sync::OnceLock::new();
*STRIDE.get_or_init(|| match std::env::var("BREP_OFFSET_DIAG") {
Ok(value) => value.trim().parse::<u64>().unwrap_or(1).max(1),
Err(_) => 0,
})
}
thread_local! {
static DIAGNOSTIC: std::cell::RefCell<DiagnosticSink> = std::cell::RefCell::new(DiagnosticSink {
sites: std::collections::BTreeMap::new(),
});
}
fn offset_normal_diagnostic(
site: &'static str,
surface: &NurbsSurface,
convention: OffsetNormal,
u: f64,
v: f64,
selected: &Result<Vec3, String>,
) {
let stride = diagnostic_stride();
if stride == 0 {
return;
}
let sampled = DIAGNOSTIC.with(|sink| {
let mut sink = sink.borrow_mut();
let stats = sink.sites.entry(site).or_default();
stats.convention = Some(convention);
stats.calls += 1;
if selected.is_err() {
stats.errors += 1;
}
stats.calls % stride == 0
});
if !sampled {
return;
}
let evaluator = OffsetEvaluator::new(site, surface, convention);
let same_sense = convention.same_sense();
let mut readings = [(0usize, f64::NAN, false, false); 4];
for (index, reading) in readings.iter_mut().enumerate() {
let lane = lane_of(index, same_sense);
let value = evaluator.normal_with(lane, u, v);
*reading = match (selected, &value) {
(Ok(chosen), Ok(other)) => {
let aligned = chosen.sub(*other).length();
let opposed = chosen.add(*other).length();
(index, aligned.min(opposed), true, opposed < aligned)
}
(Ok(_), Err(_)) => (index, f64::NAN, false, false),
(Err(_), Ok(_)) => (index, f64::NAN, true, false),
(Err(_), Err(_)) => (index, f64::NAN, false, false),
};
}
DIAGNOSTIC.with(|sink| {
let mut sink = sink.borrow_mut();
let stats = sink.sites.entry(site).or_default();
for (index, chord, lane_ok, flipped) in readings {
let lane = &mut stats.lanes[index];
if chord.is_finite() {
lane.compared += 1;
if flipped {
lane.flipped += 1;
}
if chord > lane.worst_chord {
lane.worst_chord = chord;
lane.worst_u = u;
lane.worst_v = v;
}
} else if selected.is_err() && lane_ok {
lane.lane_rescues += 1;
} else if selected.is_ok() && !lane_ok {
lane.lane_errors += 1;
}
}
});
}