use super::*;
use crate::SurfaceIntersectionCurve;
pub(crate) fn carried_uv_enabled() -> bool {
switch!("BREP_MARCH_UV_SECTIONS", true) || emission_refine_enabled()
}
fn emission_refine_enabled() -> bool {
switch!("BREP_MARCH_UV_REFINE", true)
}
pub(crate) fn carried_uv_min_stations() -> usize {
if emission_refine_enabled() { 32 } else { 2 }
}
pub(super) fn refinement_carriers_eligible(first: &NurbsSurface, second: &NurbsSurface) -> bool {
!emission_refine_enabled() || [first, second].iter().all(|surface| {
matches!(surface.closed_directions(), Ok((u, v)) if !(u && v))
})
}
pub(super) fn carried_uv_emission_budget(requested: Option<f64>, fit_budget: f64) -> Option<f64> {
if carried_uv_enabled() {
Some(requested.unwrap_or(fit_budget).min(crate::pcurve::PCURVE_REFINEMENT_TOLERANCE * 0.1))
} else {
requested
}
}
#[derive(Clone, Copy, Debug)]
struct Station {
point: Vec3,
tangent: Vec3,
uv_a: [f64; 2],
uv_b: [f64; 2],
marched: bool,
}
pub(super) struct HermiteSection {
pub(super) curve: NurbsCurve,
pub(super) pcurve_a: NurbsCurve,
pub(super) pcurve_b: NurbsCurve,
pub(super) deviation: f64,
pub(super) stations: usize,
pub(super) inserted: usize,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(super) enum HermiteDecline {
NoTangents,
TooShort,
DegenerateChord,
Unprojectable,
OverBudget,
NotAmortized,
}
fn key(point: Vec3) -> [u64; 3] {
[point.x.to_bits(), point.y.to_bits(), point.z.to_bits()]
}
pub(crate) fn hermite_3d_enabled() -> bool {
switch!("BREP_SECTION_HERMITE_3D", true)
}
fn end_weld_enabled() -> bool {
switch!("BREP_HERMITE_END_WELD", true)
}
fn decline_3d(site: usize) {
if std::env::var("BREP_DEBUG_HERMITE_3D").is_ok() {
eprintln!("hermite 3-D decline at site {site}");
}
}
type Station3 = (Vec3, Vec3, [f64; 2], [f64; 2]);
fn crease_lines(surface: &NurbsSurface) -> [Vec<f64>; 2] {
let lines = |knots: &[f64], degree: usize| -> Vec<f64> {
let (low, high) = (knots[degree], knots[knots.len() - degree - 1]);
let mut out = Vec::new();
let mut index = 0;
while index < knots.len() {
let value = knots[index];
let run = knots[index..].iter().take_while(|knot| **knot == value).count();
if value > low && value < high && run >= degree.max(1) {
out.push(value);
}
index += run;
}
out
};
[lines(&surface.knots_u, surface.degree_u), lines(&surface.knots_v, surface.degree_v)]
}
pub(super) fn hermite_section_3d(
run: &[Vec3],
branch: &SurfaceIntersectionCurve,
first: &NurbsSurface,
second: &NurbsSurface,
budget: f64,
) -> Result<Option<(NurbsCurve, [NurbsCurve; 2])>, String> {
if run.len() < 2 || !(budget > 0.0 && budget.is_finite()) {
{ decline_3d(1); return Ok(None); }
}
let tangents = branch.tangents.as_ref();
let by_point: HashMap<[u64; 3], usize> = branch.points.iter().enumerate().map(|(i, p)| (key(*p), i)).collect();
let (closed_a, closed_b) = (first.closed_directions()?, second.closed_directions()?);
let (ua, va, ub, vb) = (first.domain_u()?, first.domain_v()?, second.domain_u()?, second.domain_v()?);
let in_a = |uv: [f64; 2]| [into_domain(uv[0], closed_a.0, ua), into_domain(uv[1], closed_a.1, va)];
let in_b = |uv: [f64; 2]| [into_domain(uv[0], closed_b.0, ub), into_domain(uv[1], closed_b.1, vb)];
let near_a = |uv: [f64; 2], near: [f64; 2]| [unwrap(uv[0], near[0], closed_a.0, ua), unwrap(uv[1], near[1], closed_a.1, va)];
let near_b = |uv: [f64; 2], near: [f64; 2]| [unwrap(uv[0], near[0], closed_b.0, ub), unwrap(uv[1], near[1], closed_b.1, vb)];
let frame = |point: Vec3, along: Vec3| -> Result<Option<Station3>, String> {
let (Ok(on_a), Ok(on_b)) = (project_point_to_surface(first, point), project_point_to_surface(second, point)) else {
return Ok(None);
};
let cross = first.normal(on_a.u, on_a.v)?.cross(second.normal(on_b.u, on_b.v)?);
if cross.length() <= 1e-9 {
return Ok(None);
}
let mut tangent = cross.normalized()?;
if tangent.dot(along) < 0.0 {
tangent = tangent.scale(-1.0);
}
Ok(Some((point, tangent, [on_a.u, on_a.v], [on_b.u, on_b.v])))
};
let reach = 1e-7 * (1.0 + run[0].length());
let mut stations: Vec<Station3> = Vec::with_capacity(run.len());
let (head, tail) = (run[0], run[run.len() - 1]);
let end_weld = end_weld_enabled();
for (k, point) in run.iter().enumerate() {
if end_weld
&& k > 0
&& k + 1 < run.len()
&& (point.sub(head).length() <= crate::tolerance::WELD_FLOOR || point.sub(tail).length() <= crate::tolerance::WELD_FLOOR)
{
continue;
}
if stations.last().is_some_and(|last: &Station3| last.0.sub(*point).length() <= reach) {
if k + 1 < run.len() || stations.len() < 2 {
continue;
}
stations.pop();
}
let along = run[(k + 1).min(run.len() - 1)].sub(run[k.saturating_sub(1)]);
let marched = by_point.get(&key(*point)).and_then(|&i| Some((tangents?.get(i).copied()?, branch.params_a[i], branch.params_b[i])));
match marched {
Some((tangent, uv_a, uv_b)) if tangent.length() > 0.5 => stations.push((*point, tangent.normalized()?, uv_a, uv_b)),
_ => match frame(*point, along)? {
Some(station) => stations.push(station),
None => { decline_3d(2); return Ok(None); }
},
}
}
if stations.len() < 2 {
{ decline_3d(4); return Ok(None); }
}
for i in 0..stations.len() {
let point = stations[i].0;
let ahead = stations[i + 1..].iter().find(|s| s.0.sub(point).length() > reach).map(|s| s.0.sub(point));
let behind = stations[..i].iter().rev().find(|s| point.sub(s.0).length() > reach).map(|s| point.sub(s.0));
let Some(chord) = ahead.or(behind) else {
{ decline_3d(10); return Ok(None); }
};
if stations[i].1.dot(chord) < 0.0 {
stations[i].1 = stations[i].1.scale(-1.0);
}
}
for i in 1..stations.len() {
let (pa, pb) = (stations[i - 1].2, stations[i - 1].3);
stations[i].2 = near_a(stations[i].2, pa);
stations[i].3 = near_b(stations[i].3, pb);
}
let creases = [crease_lines(first), crease_lines(second)];
let cubic = |a: &Station3, b: &Station3, s: f64| -> Vec3 {
let h = b.0.sub(a.0).length();
let (d0, d1) = (a.1.scale(h), b.1.scale(h));
let c = hermite_at([a.0.x, a.0.y, a.0.z], [d0.x, d0.y, d0.z], [b.0.x, b.0.y, b.0.z], [d1.x, d1.y, d1.z], s);
Vec3::new(c[0], c[1], c[2])
};
let cubic_tangent = |a: &Station3, b: &Station3, s: f64| -> Vec3 {
let (p, q) = (cubic(a, b, (s - 1e-6).max(0.0)), cubic(a, b, (s + 1e-6).min(1.0)));
q.sub(p)
};
let velocities = |a: &Station3, b: &Station3| -> Result<Option<[[f64; 2]; 4]>, String> {
let inside = |p: [f64; 2], q: [f64; 2]| [p[0] + 1e-9 * (q[0] - p[0]), p[1] + 1e-9 * (q[1] - p[1])];
let (Ok(a_on_a), Ok(b_on_a), Ok(a_on_b), Ok(b_on_b)) = (
parameter_velocity(first, in_a(inside(a.2, b.2)), a.1),
parameter_velocity(first, in_a(inside(b.2, a.2)), b.1),
parameter_velocity(second, in_b(inside(a.3, b.3)), a.1),
parameter_velocity(second, in_b(inside(b.3, a.3)), b.1),
) else {
return Ok(None);
};
Ok(Some([a_on_a, b_on_a, a_on_b, b_on_b]))
};
let span_miss = |a: &Station3, b: &Station3| -> Result<f64, String> {
let h = b.0.sub(a.0).length();
let Some([va0, va1, vb0, vb1]) = velocities(a, b)? else {
return Ok(f64::INFINITY);
};
let scale = |v: [f64; 2]| [v[0] * h, v[1] * h];
let mut worst = 0.0f64;
for s in [0.17, 0.33, 0.5, 0.67, 0.83] {
let c = cubic(a, b, s);
let pa = hermite_at(a.2, scale(va0), b.2, scale(va1), s);
let pb = hermite_at(a.3, scale(vb0), b.3, scale(vb1), s);
let (pa, pb) = (in_a(pa), in_b(pb));
let on_a = first.evaluate(pa[0], pa[1])?;
let on_b = second.evaluate(pb[0], pb[1])?;
let foot_a = crate::project_point_to_surface_seeded(first, c, pa[0], pa[1]).map_or(f64::INFINITY, |p| p.distance);
let foot_b = crate::project_point_to_surface_seeded(second, c, pb[0], pb[1]).map_or(f64::INFINITY, |p| p.distance);
worst = worst.max(on_a.sub(c).length()).max(on_b.sub(c).length()).max(foot_a).max(foot_b);
}
Ok(worst)
};
let crossed = |a: &Station3, b: &Station3| -> Option<(usize, usize, f64)> {
let mut best: Option<(f64, (usize, usize, f64))> = None;
for (carrier, (p, q)) in [(a.2, b.2), (a.3, b.3)].into_iter().enumerate() {
for direction in 0..2 {
let (x0, x1) = (p[direction], q[direction]);
let band = 1e-10 * (1.0 + x0.abs().max(x1.abs()));
for &line in &creases[carrier][direction] {
if (line - x0) * (line - x1) < 0.0 && (line - x0).abs() > band && (line - x1).abs() > band {
let fraction = (line - x0) / (x1 - x0);
if best.is_none_or(|(f, _)| fraction < f) {
best = Some((fraction, (carrier, direction, line)));
}
}
}
}
}
best.map(|(_, crease)| crease)
};
let on_crease = |a: &Station3, b: &Station3, (carrier, direction, line): (usize, usize, f64)| -> Result<Option<Station3>, String> {
let read = |station: &Station3| if carrier == 0 { station.2[direction] } else { station.3[direction] };
let (x0, x1) = (read(a), read(b));
let lerp = |p: [f64; 2], q: [f64; 2], s: f64| [p[0] + (q[0] - p[0]) * s, p[1] + (q[1] - p[1]) * s];
let at = |s: f64| -> Result<Option<Station3>, String> {
let predicted = cubic(a, b, s);
let direction3 = cubic_tangent(a, b, s).normalized()?;
let Some((point, uv_a, uv_b)) = crate::surface_surface_intersection::correct_in_plane(first, second, lerp(a.2, b.2, s), lerp(a.3, b.3, s), predicted, direction3, budget * 0.1)? else {
return Ok(None);
};
let cross = first.normal(uv_a[0], uv_a[1])?.cross(second.normal(uv_b[0], uv_b[1])?);
if cross.length() <= 1e-9 {
return Ok(None);
}
let tangent = if cross.dot(direction3) < 0.0 { cross.normalized()?.scale(-1.0) } else { cross.normalized()? };
Ok(Some((point, tangent, near_a(uv_a, a.2), near_b(uv_b, a.3))))
};
let (mut s0, mut f0, mut s1, mut f1) = (0.0, x0 - line, 1.0, x1 - line);
let mut s = f0 / (f0 - f1);
for _ in 0..40 {
let Some(station) = at(s)? else { return Ok(None) };
let f = read(&station) - line;
if f.abs() <= 1e-13 * (1.0 + line.abs()) {
let mut station = station;
if carrier == 0 { station.2[direction] = line } else { station.3[direction] = line }
return Ok(Some(station));
}
if f.signum() == f0.signum() { s0 = s; f0 = f } else { s1 = s; f1 = f }
let secant = s0 + (s1 - s0) * f0 / (f0 - f1);
s = if secant > s0 && secant < s1 { secant } else { 0.5 * (s0 + s1) };
}
Ok(None)
};
let mut pending: Vec<(Station3, Station3, usize)> = stations.windows(2).rev().map(|pair| (pair[0], pair[1], 0usize)).collect();
let mut out: Vec<Station3> = vec![stations[0]];
let mut inserted = 0usize;
while let Some((a, b, depth)) = pending.pop() {
if inserted >= 4096 {
{ decline_3d(5); return Ok(None); }
}
if let Some(crease) = crossed(&a, &b) {
let Some(station) = on_crease(&a, &b, crease)? else {
{ decline_3d(11); return Ok(None); }
};
inserted += 1;
pending.push((station, b, depth));
pending.push((a, station, depth));
continue;
}
let worst = span_miss(&a, &b)?;
if worst <= budget {
out.push(b);
continue;
}
let chord_vector = b.0.sub(a.0);
let chord = chord_vector.length();
if depth >= 24 || chord <= 4.0 * crate::KNOT_IDENTITY_TOL {
{ decline_3d(5); return Ok(None); }
}
let predicted = cubic(&a, &b, 0.5);
let direction = a.1.add(b.1).normalized()?;
let lerp = |p: [f64; 2], q: [f64; 2]| [0.5 * (p[0] + q[0]), 0.5 * (p[1] + q[1])];
let Some((mid, uv_a, uv_b)) = crate::surface_surface_intersection::correct_in_plane(first, second, lerp(a.2, b.2), lerp(a.3, b.3), predicted, direction, budget * 0.1)? else {
{ decline_3d(6); return Ok(None); }
};
let along = mid.sub(a.0).dot(chord_vector) / (chord * chord);
if !(0.1..=0.9).contains(&along) || mid.sub(predicted).length() > 0.25 * chord {
{ decline_3d(7); return Ok(None); }
}
let cross = first.normal(uv_a[0], uv_a[1])?.cross(second.normal(uv_b[0], uv_b[1])?);
if cross.length() <= 1e-9 {
{ decline_3d(8); return Ok(None); }
}
let mut tangent = cross.normalized()?;
if tangent.dot(direction) < 0.0 {
tangent = tangent.scale(-1.0);
}
let station = (mid, tangent, near_a(uv_a, a.2), near_b(uv_b, a.3));
inserted += 1;
pending.push((station, b, depth + 1));
pending.push((a, station, depth + 1));
}
let mut chords = Vec::with_capacity(out.len() - 1);
let mut span_velocities = Vec::with_capacity(out.len() - 1);
for pair in out.windows(2) {
let chord = pair[1].0.sub(pair[0].0).length();
if !(chord.is_finite() && chord > crate::KNOT_IDENTITY_TOL) {
{ decline_3d(9); return Ok(None); }
}
let Some(velocity) = velocities(&pair[0], &pair[1])? else {
{ decline_3d(12); return Ok(None); }
};
chords.push(chord);
span_velocities.push(velocity);
}
let points: Vec<[f64; 3]> = out.iter().map(|s| [s.0.x, s.0.y, s.0.z]).collect();
let velocity: Vec<[f64; 3]> = out.iter().map(|s| [s.1.x, s.1.y, s.1.z]).collect();
let curve = hermite_spline(&points, &velocity, &chords)?;
let uv_a: Vec<[f64; 2]> = out.iter().map(|s| s.2).collect();
let uv_b: Vec<[f64; 2]> = out.iter().map(|s| s.3).collect();
let pcurve_a = hermite_spline_split(&uv_a, &span_velocities.iter().map(|v| [v[0], v[1]]).collect::<Vec<_>>(), &chords)?;
let pcurve_b = hermite_spline_split(&uv_b, &span_velocities.iter().map(|v| [v[2], v[3]]).collect::<Vec<_>>(), &chords)?;
Ok(Some((curve, [pcurve_a, pcurve_b])))
}
fn hermite_spline_split<const N: usize>(points: &[[f64; N]], velocity: &[[[f64; N]; 2]], chords: &[f64]) -> Result<NurbsCurve, String> {
let n = points.len();
let mut knots = Vec::with_capacity(2 * n + 4);
let mut s = 0.0;
knots.extend([0.0; 4]);
for (i, chord) in chords.iter().enumerate() {
s += chord;
if i + 1 < chords.len() {
knots.extend([s, s]);
}
}
knots.extend([s; 4]);
let lift = |value: [f64; N]| Vec4 {
x: value[0],
y: if N > 1 { value[1] } else { 0.0 },
z: if N > 2 { value[2] } else { 0.0 },
w: 1.0,
};
let mut controls = Vec::with_capacity(2 * n);
for i in 0..n - 1 {
let h = chords[i];
let mut a = points[i];
let mut b = points[i + 1];
for k in 0..N {
a[k] += h * velocity[i][0][k] / 3.0;
b[k] -= h * velocity[i][1][k] / 3.0;
}
if i == 0 {
controls.push(lift(points[0]));
}
controls.push(lift(a));
controls.push(lift(b));
}
controls.push(lift(points[n - 1]));
NurbsCurve::new(3, knots, controls)
}
fn parameter_velocity(surface: &NurbsSurface, uv: [f64; 2], tangent: Vec3) -> Result<[f64; 2], String> {
let d = surface.derivatives(uv[0], uv[1], 1)?;
let (su, sv) = (d[1][0], d[0][1]);
let (e, f, g) = (su.dot(su), su.dot(sv), sv.dot(sv));
let (bu, bv) = (su.dot(tangent), sv.dot(tangent));
let det = e * g - f * f;
if det.abs() <= 1e-300 * (1.0 + e * g) || !det.is_finite() {
return Err("degenerate first fundamental form".into());
}
Ok([(g * bu - f * bv) / det, (e * bv - f * bu) / det])
}
fn unwrap(value: f64, near: f64, closed: bool, domain: [f64; 2]) -> f64 {
let period = domain[1] - domain[0];
if closed && period > 0.0 && (value - near).abs() > 0.5 * period {
value - period * ((value - near) / period).round()
} else {
value
}
}
fn into_domain(value: f64, closed: bool, domain: [f64; 2]) -> f64 {
let period = domain[1] - domain[0];
if closed && period > 0.0 {
domain[0] + (value - domain[0]).rem_euclid(period)
} else {
value.clamp(domain[0], domain[1])
}
}
fn hermite_at<const N: usize>(p0: [f64; N], d0: [f64; N], p1: [f64; N], d1: [f64; N], s: f64) -> [f64; N] {
let (s2, s3) = (s * s, s * s * s);
let (h00, h10, h01, h11) = (2.0 * s3 - 3.0 * s2 + 1.0, s3 - 2.0 * s2 + s, -2.0 * s3 + 3.0 * s2, s3 - s2);
let mut out = [0.0; N];
for k in 0..N {
out[k] = h00 * p0[k] + h10 * d0[k] + h01 * p1[k] + h11 * d1[k];
}
out
}
fn hermite_spline<const N: usize>(points: &[[f64; N]], velocity: &[[f64; N]], chords: &[f64]) -> Result<NurbsCurve, String> {
let n = points.len();
let mut knots = Vec::with_capacity(2 * n + 4);
let mut s = 0.0;
knots.extend([0.0; 4]);
for (i, chord) in chords.iter().enumerate() {
s += chord;
if i + 1 < chords.len() {
knots.extend([s, s]);
}
}
knots.extend([s; 4]);
let mut controls = Vec::with_capacity(2 * n);
let lift = |value: [f64; N]| Vec4 {
x: value[0],
y: if N > 1 { value[1] } else { 0.0 },
z: if N > 2 { value[2] } else { 0.0 },
w: 1.0,
};
for i in 0..n - 1 {
let h = chords[i];
let mut a = points[i];
let mut b = points[i + 1];
for k in 0..N {
a[k] += h * velocity[i][k] / 3.0;
b[k] -= h * velocity[i + 1][k] / 3.0;
}
if i == 0 {
controls.push(lift(points[0]));
}
controls.push(lift(a));
controls.push(lift(b));
}
controls.push(lift(points[n - 1]));
NurbsCurve::new(3, knots, controls)
}
pub(super) fn hermite_section(
run: &[Vec3],
branch: &SurfaceIntersectionCurve,
first: &NurbsSurface,
second: &NurbsSurface,
budget: f64,
) -> Result<Result<HermiteSection, HermiteDecline>, String> {
let Some(tangents) = branch.tangents.as_ref() else {
return Ok(Err(HermiteDecline::NoTangents));
};
if run.len() < 2 || tangents.len() != branch.points.len() {
return Ok(Err(if run.len() < 2 { HermiteDecline::TooShort } else { HermiteDecline::NoTangents }));
}
if run.len() < carried_uv_min_stations() {
return Ok(Err(HermiteDecline::NotAmortized));
}
let by_point: HashMap<[u64; 3], usize> = branch.points.iter().enumerate().map(|(i, p)| (key(*p), i)).collect();
let (closed_a, closed_b) = (first.closed_directions()?, second.closed_directions()?);
let (ua, va, ub, vb) = (first.domain_u()?, first.domain_v()?, second.domain_u()?, second.domain_v()?);
let mut stations: Vec<Station> = Vec::with_capacity(run.len());
let mut inserted = 0usize;
for (k, point) in run.iter().enumerate() {
if let Some(&i) = by_point.get(&key(*point)) {
stations.push(Station {
point: *point,
tangent: tangents[i],
uv_a: branch.params_a[i],
uv_b: branch.params_b[i],
marched: true,
});
continue;
}
inserted += 1;
let (Ok(on_a), Ok(on_b)) = (project_point_to_surface(first, *point), project_point_to_surface(second, *point)) else {
return Ok(Err(HermiteDecline::Unprojectable));
};
let (Ok(na), Ok(nb)) = (first.normal(on_a.u, on_a.v), second.normal(on_b.u, on_b.v)) else {
return Ok(Err(HermiteDecline::Unprojectable));
};
let cross = na.cross(nb);
if cross.length() <= 1e-9 {
return Ok(Err(HermiteDecline::Unprojectable));
}
let mut tangent = cross.normalized()?;
let along = run[(k + 1).min(run.len() - 1)].sub(run[k.saturating_sub(1)]);
if tangent.dot(along) < 0.0 {
tangent = tangent.scale(-1.0);
}
stations.push(Station { point: *point, tangent, uv_a: [on_a.u, on_a.v], uv_b: [on_b.u, on_b.v], marched: false });
}
let mut deduped: Vec<Station> = Vec::with_capacity(stations.len());
for station in stations {
match deduped.last_mut() {
Some(last) if last.point.sub(station.point).length() <= 1e-12 => {
if !last.marched && station.marched {
*last = station;
} else if !station.marched {
inserted -= 1;
}
}
_ => deduped.push(station),
}
}
let stations = if emission_refine_enabled() {
use crate::surface_surface_intersection::{EmissionSample, refine_emission_samples};
let samples: Vec<_> = deduped.iter().map(|s| EmissionSample {
point: s.point, uv_a: s.uv_a, uv_b: s.uv_b, tangent: s.tangent,
}).collect();
let Some(refined) = refine_emission_samples(first, second, &samples, budget).ok().flatten() else {
return Ok(Err(HermiteDecline::OverBudget));
};
if profile_enabled() {
profile_line!("imprint.emission_refine input={} output={}", samples.len(), refined.len());
}
if refined.len() < samples.len().saturating_mul(2).saturating_sub(1) {
return Ok(Err(HermiteDecline::NotAmortized));
}
refined.into_iter().map(|s| Station {
point: s.point, uv_a: s.uv_a, uv_b: s.uv_b, tangent: s.tangent, marched: false,
}).collect()
} else {
deduped
};
if stations.len() < 2 {
return Ok(Err(HermiteDecline::TooShort));
}
let mut chords = Vec::with_capacity(stations.len() - 1);
let mut knot = 0.0;
for pair in stations.windows(2) {
let chord = pair[1].point.sub(pair[0].point).length();
if !(chord.is_finite() && chord > 1e-12) {
return Ok(Err(HermiteDecline::DegenerateChord));
}
let next = knot + chord;
if carried_uv_enabled() && next - knot <= crate::KNOT_IDENTITY_TOL {
return Ok(Err(HermiteDecline::DegenerateChord));
}
knot = next;
chords.push(chord);
}
let mut uv_a: Vec<[f64; 2]> = Vec::with_capacity(stations.len());
let mut uv_b: Vec<[f64; 2]> = Vec::with_capacity(stations.len());
for station in &stations {
let (a, b) = match (uv_a.last(), uv_b.last()) {
(Some(pa), Some(pb)) => (
[unwrap(station.uv_a[0], pa[0], closed_a.0, ua), unwrap(station.uv_a[1], pa[1], closed_a.1, va)],
[unwrap(station.uv_b[0], pb[0], closed_b.0, ub), unwrap(station.uv_b[1], pb[1], closed_b.1, vb)],
),
_ => (station.uv_a, station.uv_b),
};
uv_a.push(a);
uv_b.push(b);
}
let mut velocity_a = Vec::with_capacity(stations.len());
let mut velocity_b = Vec::with_capacity(stations.len());
for (i, station) in stations.iter().enumerate() {
let at_a = [into_domain(uv_a[i][0], closed_a.0, ua), into_domain(uv_a[i][1], closed_a.1, va)];
let at_b = [into_domain(uv_b[i][0], closed_b.0, ub), into_domain(uv_b[i][1], closed_b.1, vb)];
let (Ok(da), Ok(db)) = (parameter_velocity(first, at_a, station.tangent), parameter_velocity(second, at_b, station.tangent)) else {
return Ok(Err(HermiteDecline::Unprojectable));
};
velocity_a.push(da);
velocity_b.push(db);
}
let mut deviation = if emission_refine_enabled() { 0.0 } else { branch.span_deviation.unwrap_or(0.0) };
for i in 0..stations.len() - 1 {
if !carried_uv_enabled() && stations[i].marched && stations[i + 1].marched {
continue;
}
let h = chords[i];
let (p0, p1) = (stations[i].point, stations[i + 1].point);
let (d0, d1) = (stations[i].tangent.scale(h), stations[i + 1].tangent.scale(h));
let scale2 = |v: [f64; 2]| [v[0] * h, v[1] * h];
for s in [0.17, 0.33, 0.5, 0.67, 0.83] {
let c = hermite_at([p0.x, p0.y, p0.z], [d0.x, d0.y, d0.z], [p1.x, p1.y, p1.z], [d1.x, d1.y, d1.z], s);
let c = Vec3::new(c[0], c[1], c[2]);
let a = hermite_at(uv_a[i], scale2(velocity_a[i]), uv_a[i + 1], scale2(velocity_a[i + 1]), s);
let b = hermite_at(uv_b[i], scale2(velocity_b[i]), uv_b[i + 1], scale2(velocity_b[i + 1]), s);
let a = first.evaluate(into_domain(a[0], closed_a.0, ua), into_domain(a[1], closed_a.1, va))?;
let b = second.evaluate(into_domain(b[0], closed_b.0, ub), into_domain(b[1], closed_b.1, vb))?;
deviation = deviation.max(a.sub(b).length()).max(a.sub(c).length()).max(b.sub(c).length());
}
}
if !(deviation <= budget) {
return Ok(Err(HermiteDecline::OverBudget));
}
let points3: Vec<[f64; 3]> = stations.iter().map(|s| [s.point.x, s.point.y, s.point.z]).collect();
let velocity3: Vec<[f64; 3]> = stations.iter().map(|s| [s.tangent.x, s.tangent.y, s.tangent.z]).collect();
let curve = hermite_spline(&points3, &velocity3, &chords)?;
let pcurve_a = hermite_spline(&uv_a, &velocity_a, &chords)?;
let pcurve_b = hermite_spline(&uv_b, &velocity_b, &chords)?;
Ok(Ok(HermiteSection { curve, pcurve_a, pcurve_b, deviation, stations: stations.len(), inserted }))
}
pub(super) fn piece_pcurve_on_chart(pcurve: &NurbsCurve, chart: &NurbsSurface, start: Vec3, end: Vec3) -> Result<NurbsCurve, String> {
let closed = chart.closed_directions()?;
let (du, dv) = (chart.domain_u()?, chart.domain_v()?);
let [t0, t1] = pcurve.domain()?;
let middle = pcurve.evaluate(0.5 * (t0 + t1))?;
let shift = |value: f64, closed: bool, domain: [f64; 2]| -> f64 {
let period = domain[1] - domain[0];
if !closed || period <= 0.0 {
return 0.0;
}
let wrapped = domain[0] + (value - domain[0]).rem_euclid(period);
wrapped - value
};
let (sx, sy) = (shift(middle.x, closed.0, du), shift(middle.y, closed.1, dv));
let mut controls = pcurve.control_points.clone();
if sx != 0.0 || sy != 0.0 {
for control in &mut controls {
control.x += sx * control.w;
control.y += sy * control.w;
}
}
let last = controls.len() - 1;
for (index, point) in [(0usize, start), (last, end)] {
let Ok(on) = project_point_to_surface(chart, point) else { continue };
let w = controls[index].w;
let (cx, cy) = (controls[index].x / w, controls[index].y / w);
let u = unwrap(on.u, cx, closed.0, du);
let v = unwrap(on.v, cy, closed.1, dv);
let reach = 1e-2 * ((du[1] - du[0]).abs() + (dv[1] - dv[0]).abs());
if ((u - cx).powi(2) + (v - cy).powi(2)).sqrt() <= reach {
controls[index].x = u * w;
controls[index].y = v * w;
}
}
NurbsCurve::new(pcurve.degree, pcurve.knots.clone(), controls)
}