use nalgebra as na;
use oxigaf_flame::{Camera, FlameError, FlameModel, FlameParams, Mesh, NormalMapRenderer};
use std::path::PathBuf;
fn test_data_dir() -> PathBuf {
PathBuf::from("test_data")
}
#[test]
fn test_flame_model_creation() {
let model_dir = test_data_dir();
if !model_dir.exists() {
eprintln!("Skipping test_flame_model_creation: test data not available");
return;
}
match FlameModel::load(&model_dir) {
Ok(model) => {
assert!(model.num_vertices() > 0);
assert_eq!(model.n_joints, 5); }
Err(FlameError::Io(_)) => {
eprintln!("Skipping test: model files not found");
}
Err(e) => panic!("Unexpected error: {e:?}"),
}
}
#[test]
fn test_flame_params_default() {
let params = FlameParams::default();
assert_eq!(params.pose.len(), 15);
let [rx, ry, rz] = params.joint_pose(0);
assert!((rx.abs() < 1e-8) && (ry.abs() < 1e-8) && (rz.abs() < 1e-8));
let [tx, ty, tz] = params.translation;
assert!((tx.abs() < 1e-8) && (ty.abs() < 1e-8) && (tz.abs() < 1e-8));
}
#[test]
fn test_flame_params_builder() {
let params = FlameParams {
shape: vec![0.5, -0.3, 0.1],
expression: vec![0.2, 0.4],
pose: vec![0.0; 15], translation: [1.0, 2.0, 3.0],
};
assert_eq!(params.shape.len(), 3);
assert_eq!(params.expression.len(), 2);
assert_eq!(params.pose.len(), 15);
assert_eq!(params.translation, [1.0, 2.0, 3.0]);
}
#[test]
fn test_mesh_normal_computation() {
let vertices = vec![
na::Point3::new(0.0, 0.0, 0.0),
na::Point3::new(1.0, 0.0, 0.0),
na::Point3::new(0.0, 1.0, 0.0),
];
let faces = vec![[0, 1, 2]];
let mesh = Mesh::new(vertices, faces);
assert_eq!(mesh.vertices.len(), 3);
assert_eq!(mesh.faces.len(), 1);
assert_eq!(mesh.normals.len(), 3);
for normal in &mesh.normals {
assert!(normal.x.abs() < 1e-5, "normal.x = {}", normal.x);
assert!(normal.y.abs() < 1e-5, "normal.y = {}", normal.y);
assert!((normal.z - 1.0).abs() < 1e-3, "normal.z = {}", normal.z); }
}
#[test]
fn test_mesh_empty() {
let mesh = Mesh::new(vec![], vec![]);
assert_eq!(mesh.vertices.len(), 0);
assert_eq!(mesh.faces.len(), 0);
assert_eq!(mesh.normals.len(), 0);
}
#[test]
fn test_camera_construction() {
let camera = Camera::default_front(256, 256);
assert_eq!(camera.width, 256);
assert_eq!(camera.height, 256);
assert!(camera.focal_x > 0.0);
assert!(camera.focal_y > 0.0);
assert_eq!(camera.rotation, na::Matrix3::identity());
}
#[test]
fn test_normal_map_renderer_static() {
let _renderer = NormalMapRenderer;
}
#[test]
fn test_normal_map_rendering() {
let vertices = vec![
na::Point3::new(-1.0, -1.0, 0.0),
na::Point3::new(1.0, -1.0, 0.0),
na::Point3::new(0.0, 1.0, 0.0),
];
let faces = vec![[0, 1, 2]];
let mesh = Mesh::new(vertices, faces);
let camera = Camera::default_front(64, 64);
let image = NormalMapRenderer::render(&mesh, &camera);
assert_eq!(image.width(), 64);
assert_eq!(image.height(), 64);
assert_eq!(image.pixels().count(), 64 * 64);
}
#[test]
fn test_rodrigues_edge_cases() {
use approx::assert_relative_eq;
let r = oxigaf_flame::model::rodrigues(0.0, 0.0, 0.0);
assert_relative_eq!(r, na::Matrix3::identity(), epsilon = 1e-6);
let r = oxigaf_flame::model::rodrigues(1e-10, 0.0, 0.0);
assert_relative_eq!(r, na::Matrix3::identity(), epsilon = 1e-6);
let r = oxigaf_flame::model::rodrigues(std::f32::consts::PI, 0.0, 0.0);
let v = na::Vector3::new(0.0, 1.0, 0.0);
let rv = r * v;
assert_relative_eq!(rv.y, -1.0, epsilon = 1e-5);
let r1 = oxigaf_flame::model::rodrigues(0.1, 0.2, 0.3);
let r2 = oxigaf_flame::model::rodrigues(0.2, -0.1, 0.4);
let combined = r1 * r2;
let should_be_identity = combined * combined.transpose();
assert_relative_eq!(should_be_identity, na::Matrix3::identity(), epsilon = 1e-4);
}
#[test]
fn test_mesh_sampling() {
use rand::SeedableRng;
let vertices = vec![
na::Point3::new(0.0, 0.0, 0.0),
na::Point3::new(1.0, 0.0, 0.0),
na::Point3::new(1.0, 1.0, 0.0),
na::Point3::new(0.0, 1.0, 0.0),
];
let faces = vec![[0, 1, 2], [0, 2, 3]];
let mesh = Mesh::new(vertices, faces);
let mut rng = rand::rngs::StdRng::seed_from_u64(42);
let samples = oxigaf_flame::sample_mesh_surface(&mesh, 1000, &mut rng);
assert_eq!(samples.len(), 1000);
for sample in &samples {
assert!(sample.position.x >= -1e-5 && sample.position.x <= 1.0 + 1e-5);
assert!(sample.position.y >= -1e-5 && sample.position.y <= 1.0 + 1e-5);
assert!(sample.position.z.abs() < 1e-5);
for &bc in &sample.barycentric {
assert!((-1e-5..=1.0 + 1e-5).contains(&bc));
}
let sum: f32 = sample.barycentric.iter().sum();
assert!((sum - 1.0).abs() < 1e-4);
}
}
#[test]
fn test_mesh_sampling_empty() {
use rand::SeedableRng;
let mesh = Mesh::new(vec![], vec![]);
let mut rng = rand::rngs::StdRng::seed_from_u64(42);
let samples = oxigaf_flame::sample_mesh_surface(&mesh, 100, &mut rng);
assert_eq!(samples.len(), 0);
}
#[test]
fn test_mesh_sampling_deterministic() {
use rand::SeedableRng;
let vertices = vec![
na::Point3::new(0.0, 0.0, 0.0),
na::Point3::new(1.0, 0.0, 0.0),
na::Point3::new(0.0, 1.0, 0.0),
];
let faces = vec![[0, 1, 2]];
let mesh = Mesh::new(vertices, faces);
let mut rng1 = rand::rngs::StdRng::seed_from_u64(123);
let samples1 = oxigaf_flame::sample_mesh_surface(&mesh, 50, &mut rng1);
let mut rng2 = rand::rngs::StdRng::seed_from_u64(123);
let samples2 = oxigaf_flame::sample_mesh_surface(&mesh, 50, &mut rng2);
assert_eq!(samples1.len(), samples2.len());
for (s1, s2) in samples1.iter().zip(samples2.iter()) {
assert!((s1.position - s2.position).norm() < 1e-6);
}
}