use super::*;
use crate::blend::miter::MarchedCurve;
pub(in crate::blend) struct HoodFrame {
pub(in crate::blend) n_top: Vec3,
pub(in crate::blend) n_side: Vec3,
pub(in crate::blend) origin: Vec3,
pub(in crate::blend) a: f64,
pub(in crate::blend) r: f64,
}
impl HoodFrame {
pub(in crate::blend) fn s(&self, point: Vec3) -> f64 {
point.sub(self.origin).dot(self.n_side)
}
pub(in crate::blend) fn t(&self, point: Vec3) -> f64 {
point.sub(self.origin).dot(self.n_top)
}
pub(in crate::blend) fn level(&self, point: Vec3) -> f64 {
let s = (self.s(point) + self.a) / self.a;
let t = (self.t(point) + self.r) / self.r;
s * s + t * t - 1.0
}
}
pub(in crate::blend) fn hood_rows(rows: &FittedRows, top_is_first: bool, frame: &HoodFrame) -> Result<FittedRows, KernelRefusal> {
let control = &rows.surface.control_points;
if !rows.exact_extrusion || control.len() != 2 || control.iter().any(|row| row.len() != 3) {
return Err(KernelRefusal::unsupported(KernelStage::Refine, "crossed_rails", format!(
"blend network: the crossed stripe's rows are not the exact extrusion of one section \
({} rows of {} controls); the hood is built on that stripe only",
control.len(),
control.first().map_or(0, Vec::len)
)));
}
let top_column = if top_is_first { 0 } else { 2 };
let shift = frame.n_side.scale(-(frame.a - frame.r));
let mut moved = control.clone();
let mut top_points = Vec::with_capacity(2);
for row in &mut moved {
let point = &mut row[top_column];
if (point.w - 1.0).abs() > 1e-12 {
return Err(KernelRefusal::internal(KernelStage::Refine, "crossed_rails_weight", "blend network: a section's contact control point is weighted"));
}
let shifted = Vec3::new(point.x, point.y, point.z).add(shift);
*point = Vec4::from_point(shifted, 1.0);
top_points.push(shifted);
}
let surface = NurbsSurface::new(
rows.surface.degree_u,
rows.surface.degree_v,
rows.surface.knots_u.clone(),
rows.surface.knots_v.clone(),
moved,
)
.or_refuse(KernelStage::Refine, "new")?;
let top_line = crate::make_line(top_points[0], top_points[1]).or_refuse(KernelStage::Refine, "make_line")?;
let (cr, cs) = if top_is_first {
(top_line, rows.cs.clone())
} else {
(rows.cr.clone(), top_line)
};
Ok(FittedRows {
surface,
cr,
cs,
cr_pcurve: rows.cr_pcurve.clone(),
cs_pcurve: rows.cs_pcurve.clone(),
u_domain: rows.u_domain,
center: None,
exact_extrusion: false,
vertex_stations: rows.vertex_stations,
revolution_axis: None,
})
}
pub(in crate::blend) const RIB_SAMPLES: usize = 48;
#[allow(clippy::too_many_arguments)]
pub(in crate::blend) fn march_rib(
blend: &NurbsSurface,
hood: &NurbsSurface,
frame: &HoodFrame,
m: Vec3,
u_top: f64,
z_top: f64,
q: Vec3,
u_side: f64,
z_side: f64,
edge_id: u64,
) -> Result<MarchedCurve, KernelRefusal> {
let [u_low, u_high] = {
let knots = &blend.knots_u;
[knots[0], knots[knots.len() - 1]]
};
let pad = 0.05 * (u_top - u_side).abs().max(1e-6);
let u_far = if u_top < u_side { (u_top - pad).max(u_low) } else { (u_top + pad).min(u_high) };
let level_at = |u: f64, z: f64| -> Result<f64, KernelRefusal> {
Ok(frame.level(blend.evaluate(u, z).or_refuse(KernelStage::Refine, "evaluate")?))
};
const SCAN: usize = 96;
let on_hood = |point: Vec3| -> Result<[f64; 2], KernelRefusal> {
let foot = crate::project_point_to_surface(hood, point).or_refuse(KernelStage::Refine, "project_point_to_surface")?;
Ok([foot.u, foot.v])
};
let mut marched = MarchedCurve { points: Vec::with_capacity(RIB_SAMPLES + 1), uv_first: Vec::new(), uv_second: Vec::new() };
for sample in 0..=RIB_SAMPLES {
let fraction = sample as f64 / RIB_SAMPLES as f64;
let z = z_top + (z_side - z_top) * fraction;
let (point, u) = if sample == 0 {
(m, u_top)
} else if sample == RIB_SAMPLES {
(q, u_side)
} else {
let mut bracket: Option<(f64, f64, f64)> = None;
let mut previous = (u_side, level_at(u_side, z)?);
for step in 1..=SCAN {
let u = u_side + (u_far - u_side) * step as f64 / SCAN as f64;
let value = level_at(u, z)?;
if previous.1 * value <= 0.0 {
bracket = Some((previous.0, u, previous.1));
break;
}
previous = (u, value);
}
let Some((mut a, mut b, mut fa)) = bracket else {
return Err(KernelRefusal::unsupported(KernelStage::Refine, "crossed_rails", format!(
"blend network: the merged corner's rib on edge {edge_id} is not bracketed at \
z = {z:.4} (the level does not change sign between stations {u_side:.6} and \
{u_far:.6}); the hood does not cut that stripe's section"
)));
};
for _ in 0..80 {
let mid = 0.5 * (a + b);
let fm = level_at(mid, z)?;
if fa * fm <= 0.0 {
b = mid;
} else {
a = mid;
fa = fm;
}
if (b - a).abs() <= 1e-13 * (1.0 + u_high.abs()) {
break;
}
}
let u = 0.5 * (a + b);
(blend.evaluate(u, z).or_refuse(KernelStage::Refine, "evaluate")?, u)
};
marched.points.push(point);
marched.uv_first.push([u, z]);
marched.uv_second.push(on_hood(point)?);
}
Ok(marched)
}