Files
OneCAD/crates/render/src/texture_interpolation_tests.rs
T
2026-10-15 18:04:09 -04:00

105 lines
4.5 KiB
Rust

use super::*;
const UV: [[f64; 2]; 3] = [[0., 0.], [1., 0.], [0., 1.]];
const WEIGHTS: [f64; 3] = [0.5, 0.25, 0.25];
const DX: [f64; 3] = [-1., 1., 0.];
const DY: [f64; 3] = [-1., 0., 1.];
fn close(actual: [f64; 2], expected: [f64; 2]) {
for (a, b) in actual.into_iter().zip(expected) {
assert!((a - b).abs() < 1e-10, "{actual:?} != {expected:?}");
}
}
#[test]
fn orthographic_uv_and_derivatives_match_the_affine_plane() {
let result = PerspectiveUv::new(UV, [1.; 3]).unwrap().evaluate(WEIGHTS, DX, DY).unwrap();
close(result.uv, [0.25, 0.25]);
close(result.duv_dx, [1., 0.]);
close(result.duv_dy, [0., 1.]);
}
#[test]
fn perspective_quotient_and_derivatives_match_closed_form() {
let result = PerspectiveUv::new(UV, [1., 2., 1.]).unwrap().evaluate(WEIGHTS, DX, DY).unwrap();
close(result.uv, [0.4, 0.2]);
close(result.duv_dx, [1.28, -0.16]);
close(result.duv_dy, [0., 0.8]);
}
#[test]
fn common_clip_scale_and_vertex_order_preserve_the_result() {
for scale in [1e-6, 1., 1e6] {
for order in [[0, 1, 2], [2, 1, 0], [1, 2, 0]] {
let q = [scale, 2. * scale, scale];
let interpolation = PerspectiveUv::new(order.map(|i| UV[i]), order.map(|i| q[i])).unwrap();
let result = interpolation.evaluate(order.map(|i| WEIGHTS[i]), order.map(|i| DX[i]), order.map(|i| DY[i])).unwrap();
close(result.uv, [0.4, 0.2]);
close(result.duv_dx, [1.28, -0.16]);
close(result.duv_dy, [0., 0.8]);
}
}
}
#[test]
fn constant_large_offset_has_exactly_zero_gradients() {
let interpolation = PerspectiveUv::new([[999_999., -999_999.]; 3], [1e-12, 1., 1e12]).unwrap();
let result = interpolation.evaluate(WEIGHTS, DX, DY).unwrap();
assert_eq!(result.uv, [999_999., -999_999.]);
assert_eq!(result.duv_dx, [0.; 2]);
assert_eq!(result.duv_dy, [0.; 2]);
}
#[test]
fn vertices_edges_and_shading_grid_units_remain_explicit() {
let interpolation = PerspectiveUv::new(UV, [1., 2., 1.]).unwrap();
close(interpolation.evaluate([0., 1., 0.], DX, DY).unwrap().uv, [1., 0.]);
close(interpolation.evaluate([0.5, 0.5, 0.], DX, DY).unwrap().uv, [2. / 3., 0.]);
let fine = interpolation.evaluate(WEIGHTS, DX.map(|v| v / 2.), DY.map(|v| v / 4.)).unwrap();
close(fine.uv, [0.4, 0.2]);
close(fine.duv_dx, [0.64, -0.08]);
close(fine.duv_dy, [0., 0.2]);
}
#[test]
fn invalid_vertices_and_reciprocal_w_are_refused() {
for value in [f64::NAN, f64::INFINITY, MAX_UV + 1., -MAX_UV - 1.] {
assert!(matches!(PerspectiveUv::new([[value, 0.]; 3], [1.; 3]), Err(InterpolationError::VertexUv)));
}
for value in [f64::NAN, f64::INFINITY, 0., -1., 1e-13, 1e13] {
assert!(matches!(PerspectiveUv::new(UV, [1., value, 1.]), Err(InterpolationError::ReciprocalW)));
}
}
#[test]
fn malformed_partitions_and_gradients_are_refused() {
let interpolation = PerspectiveUv::new(UV, [1.; 3]).unwrap();
for weights in [[0.; 3], [1.; 3], [-0.1, 0.6, 0.5], [f64::NAN, 0., 1.], [f64::INFINITY, 0., 0.]] {
assert_eq!(interpolation.evaluate(weights, DX, DY), Err(InterpolationError::Barycentrics));
}
for derivatives in [[1.; 3], [f64::NAN, 0., 0.], [f64::INFINITY, 0., 0.], [-MAX_DERIVATIVE - 1., MAX_DERIVATIVE + 1., 0.]] {
assert_eq!(interpolation.evaluate(WEIGHTS, derivatives, DY), Err(InterpolationError::Derivatives));
assert_eq!(interpolation.evaluate(WEIGHTS, DX, derivatives), Err(InterpolationError::Derivatives));
}
}
#[test]
fn unsupported_output_gradient_refuses_without_affine_fallback() {
let interpolation = PerspectiveUv::new(UV, [1e-12, 1e12, 1.]).unwrap();
assert_eq!(interpolation.evaluate([1., 0., 0.], DX, DY), Err(InterpolationError::Output));
assert!(PerspectiveUv::new(UV, [1.; 3]).unwrap().evaluate(WEIGHTS, DX, DY).is_ok());
}
#[test]
fn near_edge_admission_keeps_exact_weights_and_refuses_beyond_bound_or_nonpositive_denominator() {
let e = BARYCENTRIC_EDGE_TOLERANCE;
let interpolation = PerspectiveUv::new(UV, [1.; 3]).unwrap();
let result = interpolation.evaluate([-e, 1. + 2. * e, -e], DX, DY).unwrap();
close(result.uv, [1. + 2. * e, -e]);
close(result.duv_dx, [1., 0.]);
close(result.duv_dy, [0., 1.]);
let outside = -e * 1.00001;
assert_eq!(interpolation.evaluate([outside, 0.5, 0.5 - outside], DX, DY), Err(InterpolationError::Barycentrics));
let perspective = PerspectiveUv::new(UV, [1e12, 1e-12, 1.]).unwrap();
assert_eq!(perspective.evaluate([-e, 1. + e, 0.], DX, DY), Err(InterpolationError::Output));
}