Co-Authored-By: selune <[email protected]> Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
105 lines
4.5 KiB
Rust
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));
|
|
}
|