use super::super::curve_math::bspline as curve;
use crate::{Point3, Vector3};
#[allow(clippy::too_many_arguments)] pub(crate) fn surface_d0(
u: f64,
v: f64,
u_degree: usize,
v_degree: usize,
u_periodic: bool,
v_periodic: bool,
u_flat: &[f64],
v_flat: &[f64],
poles_homog: &[f64],
n_u: usize,
n_v: usize,
h: usize,
) -> Point3 {
debug_assert_eq!(poles_homog.len(), n_u * n_v * h);
let mega = n_v * h;
let mut v_curve = vec![0.0f64; mega];
curve::eval_dn(
u,
u_degree,
u_periodic,
u_flat,
poles_homog,
mega,
0,
&mut v_curve,
);
let mut homog = vec![0.0f64; h];
curve::eval_dn(v, v_degree, v_periodic, v_flat, &v_curve, h, 0, &mut homog);
if h == 4 {
let w = homog[3];
Point3::new(homog[0] / w, homog[1] / w, homog[2] / w)
} else {
Point3::new(homog[0], homog[1], homog[2])
}
}
#[allow(clippy::too_many_arguments)] pub(crate) fn surface_d1(
u: f64,
v: f64,
u_degree: usize,
v_degree: usize,
u_periodic: bool,
v_periodic: bool,
u_flat: &[f64],
v_flat: &[f64],
poles_homog: &[f64],
n_u: usize,
n_v: usize,
h: usize,
) -> (Point3, Vector3, Vector3) {
debug_assert_eq!(poles_homog.len(), n_u * n_v * h);
let mega = n_v * h;
let mut u_pass = vec![0.0f64; 2 * mega];
curve::eval_dn(
u,
u_degree,
u_periodic,
u_flat,
poles_homog,
mega,
1,
&mut u_pass,
);
let (value_curve, du_curve) = u_pass.split_at(mega);
let mut val = vec![0.0f64; 2 * h];
curve::eval_dn(v, v_degree, v_periodic, v_flat, value_curve, h, 1, &mut val);
let h_s = &val[..h]; let h_sv = &val[h..];
let mut du = vec![0.0f64; h];
curve::eval_dn(v, v_degree, v_periodic, v_flat, du_curve, h, 0, &mut du);
let h_su = &du[..];
if h == 4 {
let w = h_s[3];
let s = Point3::new(h_s[0] / w, h_s[1] / w, h_s[2] / w);
let su = Vector3::new(
(h_su[0] - s.x * h_su[3]) / w,
(h_su[1] - s.y * h_su[3]) / w,
(h_su[2] - s.z * h_su[3]) / w,
);
let sv = Vector3::new(
(h_sv[0] - s.x * h_sv[3]) / w,
(h_sv[1] - s.y * h_sv[3]) / w,
(h_sv[2] - s.z * h_sv[3]) / w,
);
(s, su, sv)
} else {
let s = Point3::new(h_s[0], h_s[1], h_s[2]);
let su = Vector3::new(h_su[0], h_su[1], h_su[2]);
let sv = Vector3::new(h_sv[0], h_sv[1], h_sv[2]);
(s, su, sv)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn bilinear_poles() -> Vec<f64> {
vec![
0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0, ]
}
#[test]
fn test_bilinear_patch_point_by_hand() {
let poles = bilinear_poles();
let flat = curve::flat_knots(&[0.0, 1.0], &[2, 2]);
let s = surface_d0(0.5, 0.5, 1, 1, false, false, &flat, &flat, &poles, 2, 2, 3);
assert_close3(&[s.x, s.y, s.z], &[0.5, 0.5, 0.25]);
}
#[test]
fn test_bilinear_patch_derivatives_by_hand() {
let poles = bilinear_poles();
let flat = curve::flat_knots(&[0.0, 1.0], &[2, 2]);
let (s, su, sv) = surface_d1(0.3, 0.7, 1, 1, false, false, &flat, &flat, &poles, 2, 2, 3);
assert_close3(&[s.x, s.y, s.z], &[0.3, 0.7, 0.21]);
assert_close3(&[su.x, su.y, su.z], &[1.0, 0.0, 0.7]);
assert_close3(&[sv.x, sv.y, sv.z], &[0.0, 1.0, 0.3]);
}
#[test]
fn test_fixture_replay_all_cases() {
replay_fixture();
}
fn assert_close3(actual: &[f64], expected: &[f64]) {
assert_eq!(actual.len(), expected.len());
for (i, (&a, &e)) in actual.iter().zip(expected.iter()).enumerate() {
let tol = 1e-9 * f64::max(1.0, e.abs());
assert!((a - e).abs() <= tol, "index {i}: {a} vs {e}");
}
}
use serde_json::Value;
fn replay_fixture() {
let path = concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/fixtures/surfaces_bspline.json"
);
let text = std::fs::read_to_string(path).expect("read fixture");
let json: Value = serde_json::from_str(&text).expect("parse fixture");
for case in json["cases"].as_array().expect("cases array") {
replay_case(case);
}
}
fn arr_f64(v: &Value) -> Vec<f64> {
v.as_array()
.unwrap()
.iter()
.map(|x| x.as_f64().unwrap())
.collect()
}
fn arr_u32(v: &Value) -> Vec<u32> {
v.as_array()
.unwrap()
.iter()
.map(|x| x.as_u64().unwrap() as u32)
.collect()
}
fn replay_case(case: &Value) {
let name = case["name"].as_str().unwrap();
let u_degree = case["u_degree"].as_u64().unwrap() as usize;
let v_degree = case["v_degree"].as_u64().unwrap() as usize;
let u_periodic = case["u_periodic"].as_bool().unwrap();
let v_periodic = case["v_periodic"].as_bool().unwrap();
let u_knots = arr_f64(&case["u_knots"]);
let v_knots = arr_f64(&case["v_knots"]);
let u_mults = arr_u32(&case["u_mults"]);
let v_mults = arr_u32(&case["v_mults"]);
let pole_grid: Vec<Vec<Vec<f64>>> = case["poles"]
.as_array()
.unwrap()
.iter()
.map(|row| row.as_array().unwrap().iter().map(arr_f64).collect())
.collect();
let n_u = pole_grid.len();
let n_v = pole_grid[0].len();
let weights: Option<Vec<Vec<f64>>> = case.get("weights").and_then(|w| {
if w.is_null() {
None
} else {
Some(w.as_array().unwrap().iter().map(arr_f64).collect())
}
});
let h = if weights.is_some() { 4 } else { 3 };
let mut poles = Vec::with_capacity(n_u * n_v * h);
for iu in 0..n_u {
for iv in 0..n_v {
let p = &pole_grid[iu][iv];
match &weights {
Some(ws) => {
let w = ws[iu][iv];
poles.extend_from_slice(&[p[0] * w, p[1] * w, p[2] * w, w]);
}
None => poles.extend_from_slice(&p[0..3]),
}
}
}
let u_flat = curve::flat_knots(&u_knots, &u_mults);
let v_flat = curve::flat_knots(&v_knots, &v_mults);
for sample in case["samples"].as_array().unwrap() {
let u = sample["u"].as_f64().unwrap();
let v = sample["v"].as_f64().unwrap();
let s0 = surface_d0(
u, v, u_degree, v_degree, u_periodic, v_periodic, &u_flat, &v_flat, &poles, n_u,
n_v, h,
);
let (s, su, sv) = surface_d1(
u, v, u_degree, v_degree, u_periodic, v_periodic, &u_flat, &v_flat, &poles, n_u,
n_v, h,
);
check(name, u, v, "d0", &[s0.x, s0.y, s0.z], &[s.x, s.y, s.z]);
check(
name,
u,
v,
"point",
&[s.x, s.y, s.z],
&arr_f64(&sample["point"]),
);
check(
name,
u,
v,
"d1u",
&[su.x, su.y, su.z],
&arr_f64(&sample["d1u"]),
);
check(
name,
u,
v,
"d1v",
&[sv.x, sv.y, sv.z],
&arr_f64(&sample["d1v"]),
);
}
}
fn check(name: &str, u: f64, v: f64, key: &str, got: &[f64], expected: &[f64]) {
for (c, (&g, &e)) in got.iter().zip(expected.iter()).enumerate() {
let tol = 1e-7 * f64::max(1.0, e.abs());
assert!(
(g - e).abs() <= tol,
"{name} (u={u}, v={v}) {key}[{c}]: got {g}, expected {e} (tol {tol})"
);
}
}
}