use super::*;
pub(in crate::blend) struct CornerContact {
pub(in crate::blend) face_id: u64,
pub(in crate::blend) uv: [f64; 2],
pub(in crate::blend) point: Vec3,
pub(in crate::blend) normal: Vec3,
}
pub(in crate::blend) struct CornerBall {
pub(in crate::blend) center: Vec3,
pub(in crate::blend) contacts: Vec<CornerContact>,
pub(in crate::blend) residual: f64,
}
pub(in crate::blend) struct CornerFace<'a> {
pub(in crate::blend) face: &'a FaceRecord,
pub(in crate::blend) rho: f64,
pub(in crate::blend) seed: [f64; 2],
}
const CORNER_ITERATIONS: usize = 40;
fn centers(faces: &[CornerFace<'_>], x: &[f64]) -> Result<Vec<Vec3>, String> {
faces
.iter()
.enumerate()
.map(|(index, face)| {
blend_offset(&face.face.surface)
.at(x[2 * index], x[2 * index + 1], face.rho)
.map(|sample| sample.point)
})
.collect()
}
fn residual(faces: &[CornerFace<'_>], x: &[f64]) -> Result<Vec<f64>, String> {
let centers = centers(faces, x)?;
let mut rows = Vec::with_capacity(3 * (faces.len() - 1));
for center in ¢ers[1..] {
let delta = center.sub(centers[0]);
rows.extend_from_slice(&[delta.x, delta.y, delta.z]);
}
Ok(rows)
}
fn norm(values: &[f64]) -> f64 {
values.iter().fold(0.0f64, |worst, v| worst.max(v.abs()))
}
pub(in crate::blend) fn solve_corner_ball(
faces: &[CornerFace<'_>],
scale: f64,
) -> Result<CornerBall, String> {
if faces.len() < 3 {
return Err(format!(
"corner ball: needs at least three faces at the vertex, got {}",
faces.len()
));
}
let unknowns = 2 * faces.len();
let equations = 3 * (faces.len() - 1);
let mut x: Vec<f64> = faces
.iter()
.flat_map(|face| [face.seed[0], face.seed[1]])
.collect();
let tolerance = 1e-11 * (1.0 + scale);
let step = 1e-7;
let mut current = residual(faces, &x)?;
for _ in 0..CORNER_ITERATIONS {
if norm(¤t) <= tolerance {
break;
}
let mut jacobian = vec![vec![0.0f64; unknowns]; equations];
for column in 0..unknowns {
let mut probe = x.clone();
probe[column] += step;
let probed = residual(faces, &probe)?;
for row in 0..equations {
jacobian[row][column] = (probed[row] - current[row]) / step;
}
}
let mut normal_matrix = vec![vec![0.0f64; unknowns]; unknowns];
let mut normal_rhs = vec![0.0f64; unknowns];
for row in 0..equations {
for a in 0..unknowns {
normal_rhs[a] += jacobian[row][a] * current[row];
for b in 0..unknowns {
normal_matrix[a][b] += jacobian[row][a] * jacobian[row][b];
}
}
}
let delta = fit::solve_dense(normal_matrix, normal_rhs)
.map_err(|error| format!("corner ball: Gauss-Newton is singular ({error})"))?;
let mut accepted = false;
let mut damping = 1.0f64;
for _ in 0..8 {
let trial: Vec<f64> = x
.iter()
.zip(&delta)
.map(|(value, correction)| value - damping * correction)
.collect();
if let Ok(trial_residual) = residual(faces, &trial) {
if norm(&trial_residual) < norm(¤t) {
x = trial;
current = trial_residual;
accepted = true;
break;
}
}
damping *= 0.5;
}
if !accepted {
break;
}
}
let final_residual = norm(¤t);
let centers = centers(faces, &x)?;
let center = centers
.iter()
.fold(Vec3::default(), |sum, point| sum.add(*point))
.scale(1.0 / centers.len() as f64);
let mut contacts = Vec::with_capacity(faces.len());
for (index, face) in faces.iter().enumerate() {
let sample = blend_offset(&face.face.surface).at(x[2 * index], x[2 * index + 1], face.rho)?;
contacts.push(CornerContact {
face_id: face.face.id,
uv: [x[2 * index], x[2 * index + 1]],
point: sample.source,
normal: sample.normal,
});
}
Ok(CornerBall {
center,
contacts,
residual: final_residual,
})
}
pub(in crate::blend) fn corner_faces<'a>(
faces: &[&'a FaceRecord],
rho_of: &dyn Fn(u64) -> Option<f64>,
corner: Vec3,
) -> Result<Vec<CornerFace<'a>>, String> {
let mut prepared = Vec::with_capacity(faces.len());
for face in faces {
let rho = rho_of(face.id).ok_or_else(|| {
format!(
"corner ball: no stripe supplies a signed radius for face {}",
face.id
)
})?;
let projection = crate::project_point_to_surface(&face.surface, corner)?;
prepared.push(CornerFace {
face,
rho,
seed: [projection.u, projection.v],
});
}
Ok(prepared)
}