use crate::data::{AnyDataArray, DataArray, PolyData};
pub fn gradient(input: &PolyData, scalar_name: &str) -> PolyData {
let n = input.points.len();
let scalars = match input.point_data().get_array(scalar_name) {
Some(arr) => {
let mut vals = vec![0.0f64; n];
let mut buf = [0.0f64];
for (i, val) in vals.iter_mut().enumerate() {
arr.tuple_as_f64(i, &mut buf);
*val = buf[0];
}
vals
}
None => return input.clone(),
};
let offsets = input.polys.offsets();
let conn = input.polys.connectivity();
let nc = input.polys.num_cells();
let mut adj_count = vec![0u32; n];
for ci in 0..nc {
let s = offsets[ci] as usize;
let e = offsets[ci + 1] as usize;
let cn = e - s;
for i in 0..cn {
adj_count[conn[s + i] as usize] += 1;
adj_count[conn[s + if i + 1 < cn { i + 1 } else { 0 }] as usize] += 1;
}
}
let mut adj_off = vec![0u32; n + 1];
for i in 0..n { adj_off[i + 1] = adj_off[i] + adj_count[i]; }
let mut adj_data = vec![0u32; adj_off[n] as usize];
let mut adj_pos = adj_off[..n].to_vec();
for ci in 0..nc {
let s = offsets[ci] as usize;
let e = offsets[ci + 1] as usize;
let cn = e - s;
for i in 0..cn {
let a = conn[s + i] as usize;
let b = conn[s + if i + 1 < cn { i + 1 } else { 0 }] as usize;
adj_data[adj_pos[a] as usize] = b as u32; adj_pos[a] += 1;
adj_data[adj_pos[b] as usize] = a as u32; adj_pos[b] += 1;
}
}
let mut nbr_off = vec![0u32; n + 1];
let mut nbr_data: Vec<u32> = Vec::with_capacity(adj_off[n] as usize / 2);
for v in 0..n {
let s = adj_off[v] as usize;
let e = adj_off[v + 1] as usize;
adj_data[s..e].sort_unstable();
nbr_off[v] = nbr_data.len() as u32;
let mut prev = u32::MAX;
for &nb in &adj_data[s..e] {
if nb != prev { nbr_data.push(nb); prev = nb; }
}
}
nbr_off[n] = nbr_data.len() as u32;
let pts = input.points.as_flat_slice();
let mut grad_data = vec![0.0f64; n * 3];
for i in 0..n {
let bi = i * 3;
let pix = pts[bi]; let piy = pts[bi + 1]; let piz = pts[bi + 2];
let si = scalars[i];
let ns = nbr_off[i] as usize;
let ne = nbr_off[i + 1] as usize;
if ns == ne { continue; }
let mut ata = [[0.0f64; 3]; 3];
let mut atb = [0.0f64; 3];
for k in ns..ne {
let j = unsafe { *nbr_data.get_unchecked(k) } as usize;
let bj = j * 3;
let dx = [pts[bj] - pix, pts[bj + 1] - piy, pts[bj + 2] - piz];
let ds = scalars[j] - si;
ata[0][0] += dx[0] * dx[0]; ata[0][1] += dx[0] * dx[1]; ata[0][2] += dx[0] * dx[2];
ata[1][1] += dx[1] * dx[1]; ata[1][2] += dx[1] * dx[2];
ata[2][2] += dx[2] * dx[2];
atb[0] += dx[0] * ds; atb[1] += dx[1] * ds; atb[2] += dx[2] * ds;
}
ata[1][0] = ata[0][1]; ata[2][0] = ata[0][2]; ata[2][1] = ata[1][2];
if let Some(g) = solve_3x3(&ata, &atb) {
grad_data[i * 3] = g[0];
grad_data[i * 3 + 1] = g[1];
grad_data[i * 3 + 2] = g[2];
}
}
let mut pd = input.clone();
pd.point_data_mut().add_array(AnyDataArray::F64(
DataArray::from_vec("Gradient", grad_data, 3),
));
pd
}
fn solve_3x3(a: &[[f64; 3]; 3], b: &[f64; 3]) -> Option<[f64; 3]> {
let eps = 1e-10;
let ar = [
[a[0][0] + eps, a[0][1], a[0][2]],
[a[1][0], a[1][1] + eps, a[1][2]],
[a[2][0], a[2][1], a[2][2] + eps],
];
let det = ar[0][0] * (ar[1][1] * ar[2][2] - ar[1][2] * ar[2][1])
- ar[0][1] * (ar[1][0] * ar[2][2] - ar[1][2] * ar[2][0])
+ ar[0][2] * (ar[1][0] * ar[2][1] - ar[1][1] * ar[2][0]);
if det.abs() < 1e-40 {
return None;
}
let inv_det = 1.0 / det;
let x = (b[0] * (ar[1][1] * ar[2][2] - ar[1][2] * ar[2][1])
- ar[0][1] * (b[1] * ar[2][2] - ar[1][2] * b[2])
+ ar[0][2] * (b[1] * ar[2][1] - ar[1][1] * b[2]))
* inv_det;
let y = (ar[0][0] * (b[1] * ar[2][2] - ar[1][2] * b[2])
- b[0] * (ar[1][0] * ar[2][2] - ar[1][2] * ar[2][0])
+ ar[0][2] * (ar[1][0] * b[2] - b[1] * ar[2][0]))
* inv_det;
let z = (ar[0][0] * (ar[1][1] * b[2] - b[1] * ar[2][1])
- ar[0][1] * (ar[1][0] * b[2] - b[1] * ar[2][0])
+ b[0] * (ar[1][0] * ar[2][1] - ar[1][1] * ar[2][0]))
* inv_det;
Some([x, y, z])
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn gradient_of_linear_field() {
let mut pd = PolyData::from_triangles(
vec![
[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [2.0, 0.0, 0.0], [0.5, 1.0, 0.0], [1.5, 1.0, 0.0], [1.0, 2.0, 0.0], ],
vec![[0, 1, 3], [1, 2, 4], [1, 4, 3], [3, 4, 5]],
);
let scalars = DataArray::from_vec("f", vec![0.0, 1.0, 2.0, 0.5, 1.5, 1.0], 1);
pd.point_data_mut().add_array(scalars.into());
pd.point_data_mut().set_active_scalars("f");
let result = gradient(&pd, "f");
let g = result.point_data().get_array("Gradient").unwrap();
assert_eq!(g.num_tuples(), 6);
assert_eq!(g.num_components(), 3);
let mut val = [0.0f64; 3];
let mut found = false;
for vi in 0..6 {
g.tuple_as_f64(vi, &mut val);
if (val[0] - 1.0).abs() < 0.5 && val[1].abs() < 0.5 {
found = true;
break;
}
}
assert!(found, "no vertex recovered gradient ~(1,0,0)");
}
#[test]
fn gradient_missing_array_returns_clone() {
let pd = PolyData::from_triangles(
vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.5, 1.0, 0.0]],
vec![[0, 1, 2]],
);
let result = gradient(&pd, "nonexistent");
assert_eq!(result.points.len(), 3);
}
}