use halfedge::ids::HalfEdgeId;
use halfedge::{
build_cube, build_icosphere,
geometry::{mesh_volume, mesh_volume_par, ray_mesh_intersects, surface_area, surface_area_par},
io::{build_mesh_from_vertices_and_faces, parse_obj},
linalg::{SparseSystem, conjugate_gradient},
storage::MeshStorage,
topology_ops::{collapse_edge, flip_edge, split_edge},
traversal::is_closed,
validate::validate_topology,
};
#[test]
fn regression_mesh_volume_par_on_empty_mesh_no_panic() {
let empty = MeshStorage::new();
let v = mesh_volume_par(&empty);
assert_eq!(v, 0.0);
}
#[test]
fn regression_mesh_volume_par_consistent_with_serial() {
let mesh = build_icosphere(1);
let serial = mesh_volume(&mesh);
let par = mesh_volume_par(&mesh);
assert!(
(serial - par).abs() < 1e-9,
"串行与并行体积应一致: {} vs {}",
serial,
par
);
}
#[test]
fn regression_surface_area_par_on_empty_mesh_no_panic() {
let empty = MeshStorage::new();
let a = surface_area_par(&empty);
assert_eq!(a, 0.0);
}
#[test]
fn regression_ray_mesh_intersects_empty_mesh_no_panic() {
let empty = MeshStorage::new();
let origin = [0.0, 0.0, 0.0];
let dir = [0.0, 0.0, 1.0];
let _ = ray_mesh_intersects(origin, dir, &empty);
}
#[test]
fn regression_ray_mesh_intersects_cube_outside_returns_false() {
let cube = build_cube(1.0);
let origin = [-5.0, 0.5, 0.5];
let dir = [1.0, 0.0, 0.0];
let inside = ray_mesh_intersects(origin, dir, &cube);
assert!(!inside, "起点在网格外应返回 false");
}
#[test]
fn regression_cg_dimension_mismatch_returns_none() {
let mut sys = SparseSystem::new(3);
sys.add(0, 0, 1.0);
sys.add(1, 1, 1.0);
sys.add(2, 2, 1.0);
let a = sys.finish();
let b = vec![1.0, 2.0]; assert!(conjugate_gradient(&a, &b, 10, 1e-6).is_none());
}
#[test]
fn regression_cg_empty_system_returns_empty_vec() {
let mut sys = SparseSystem::new(0);
sys.add(0, 0, 1.0); let a = sys.finish();
let b: Vec<f64> = vec![];
let x = conjugate_gradient(&a, &b, 10, 1e-6);
assert!(x.is_some());
assert!(x.unwrap().is_empty());
}
#[test]
fn regression_cg_zero_rhs_returns_zero_solution() {
let mut sys = SparseSystem::new(3);
sys.add(0, 0, 1.0);
sys.add(1, 1, 1.0);
sys.add(2, 2, 1.0);
let a = sys.finish();
let b = vec![0.0, 0.0, 0.0];
let x = conjugate_gradient(&a, &b, 10, 1e-6).unwrap();
assert!(x.iter().all(|&v| v.abs() < 1e-30));
}
#[test]
fn regression_cg_max_iter_zero_no_panic() {
let mut sys = SparseSystem::new(3);
sys.add(0, 0, 1.0);
sys.add(1, 1, 1.0);
sys.add(2, 2, 1.0);
let a = sys.finish();
let b = vec![1.0, 2.0, 3.0];
assert!(conjugate_gradient(&a, &b, 0, 1e-6).is_none());
}
#[test]
fn regression_cg_semidefinite_no_panic() {
let mut sys = SparseSystem::new(3);
sys.add(0, 1, -1.0);
sys.add(1, 2, -1.0);
sys.add(0, 0, 1.0);
sys.add(1, 1, 2.0);
sys.add(2, 2, 1.0);
let a = sys.finish();
let b = vec![1.0, 0.0, -1.0];
let _ = conjugate_gradient(&a, &b, 100, 1e-6);
}
#[test]
fn regression_remesh_on_empty_mesh_no_panic() {
use halfedge::remesh::{quick_remesh, remesh_to_length};
let mut empty = MeshStorage::new();
let _ = quick_remesh(&mut empty);
let _ = remesh_to_length(&mut empty, 0.5);
}
#[test]
fn regression_remesh_on_cube_preserves_topology() {
use halfedge::isotropic_remesh;
let mut mesh = build_cube(1.0);
let _ = isotropic_remesh(&mut mesh, Some(0.5), 3, false);
assert!(
validate_topology(&mesh).is_empty(),
"remesh 后拓扑应保持有效"
);
assert!(is_closed(&mesh), "remesh 后应仍闭合");
}
#[test]
fn regression_split_edge_on_invalid_he_returns_err_no_panic() {
let mut mesh = build_icosphere(0);
let fake = HalfEdgeId::default();
let res = split_edge(&mut mesh, fake);
assert!(res.is_err());
}
#[test]
fn regression_flip_edge_on_invalid_he_returns_err_no_panic() {
let mut mesh = build_icosphere(0);
let fake = HalfEdgeId::default();
let res = flip_edge(&mut mesh, fake);
assert!(res.is_err());
}
#[test]
fn regression_collapse_edge_on_invalid_he_returns_err_no_panic() {
let mut mesh = build_icosphere(0);
let fake = HalfEdgeId::default();
let res = collapse_edge(&mut mesh, fake);
assert!(res.is_err());
}
#[test]
fn regression_build_mesh_index_out_of_range_returns_error() {
let vertices = [[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
let faces = [[0u32, 1, 99]];
let res = build_mesh_from_vertices_and_faces(&vertices, &faces);
assert!(res.is_err());
}
#[test]
fn regression_parse_obj_empty_input_no_panic() {
let mesh = parse_obj("").expect("空 OBJ 应可解析为空网格");
assert_eq!(mesh.vertex_count(), 0);
assert_eq!(mesh.face_count(), 0);
}
#[test]
fn regression_parse_obj_only_vertices_no_faces_no_panic() {
let input = "v 0 0 0\nv 1 0 0\nv 0 1 0\n";
let mesh = parse_obj(input).expect("仅有顶点的 OBJ 应可解析");
assert_eq!(mesh.vertex_count(), 3);
assert_eq!(mesh.face_count(), 0);
}
#[test]
fn regression_parse_obj_malformed_face_skipped_no_panic() {
let input = "v 0 0 0\nv 1 0 0\nv 0 1 0\nf 1 2\nf 1 2 3\n";
let _ = parse_obj(input);
}
#[test]
fn regression_cube_volume_and_area_correct() {
let cube = build_cube(2.0); let v = mesh_volume(&cube);
let a = surface_area(&cube);
assert!(
(v.abs() - 8.0).abs() < 0.01,
"边长 2 立方体体积应 ≈ 8.0,实际 {}",
v
);
assert!(
(a - 24.0).abs() < 0.01,
"边长 2 立方体表面积应 ≈ 24.0,实际 {}",
a
);
}