use super::{
fill::meet_marker_points,
layout::{crown_visible, pavilion_visible, profile_visible, wheel_direction},
*,
};
use glam::{DVec3, Vec3};
use indicatrix::geometry::stone_metrics::{SolidMesh, SolidStatus, build_solid_mesh};
#[test]
fn cube_visibility_predicates_select_one_crown_facet_and_four_profile_facets() {
let cube_normals = [
Vec3::X,
Vec3::NEG_X,
Vec3::Y,
Vec3::NEG_Y,
Vec3::Z,
Vec3::NEG_Z,
];
let crown_count = cube_normals.iter().filter(|&&n| crown_visible(n)).count();
let pavilion_count = cube_normals
.iter()
.filter(|&&n| pavilion_visible(n))
.count();
let profile_count = cube_normals.iter().filter(|&&n| profile_visible(n)).count();
assert_eq!(crown_count, 1, "only +Y should read as crown-facing");
assert_eq!(pavilion_count, 1, "only -Y should read as pavilion-facing");
assert_eq!(
profile_count, 4,
"the four vertical faces (+-X, +-Z) should all read as profile-facing"
);
}
#[test]
fn crown_index_wheel_places_index_0_at_top_and_index_24_at_three_oclock() {
let gear_teeth = 96.0f32;
let phi0 = 2.0 * std::f32::consts::PI * 0.0 / gear_teeth;
let phi24 = 2.0 * std::f32::consts::PI * 24.0 / gear_teeth;
let (su0, sv0) = wheel_direction(phi0, false);
assert!(su0.abs() < 1e-5, "index 0 must have no horizontal offset");
assert!(sv0 > 0.99, "index 0 must point straight up (screen_up > 0)");
let (su24, sv24) = wheel_direction(phi24, false);
assert!(
su24 > 0.99,
"index 24 must point straight right (3 o'clock)"
);
assert!(sv24.abs() < 1e-5, "index 24 must have no vertical offset");
}
#[test]
fn pavilion_index_wheel_mirrors_the_horizontal_axis() {
let gear_teeth = 96.0f32;
let phi24 = 2.0 * std::f32::consts::PI * 24.0 / gear_teeth;
let (su24, sv24) = wheel_direction(phi24, true);
assert!(
su24 < -0.99,
"index 24 must point left (9 o'clock) when mirrored"
);
assert!(sv24.abs() < 1e-5);
}
#[test]
fn pick_buffer_round_trips_each_panels_center_to_the_right_facet() {
let mesh = match build_solid_mesh(&[
(DVec3::X, 1.0),
(DVec3::NEG_X, 1.0),
(DVec3::Y, 0.6),
(DVec3::NEG_Y, 0.6),
(DVec3::Z, 1.0),
(DVec3::NEG_Z, 1.0),
]) {
SolidStatus::Closed(mesh) => mesh,
other => panic!("box fixture must close: {other:?}"),
};
let config = DiagramConfig {
width: 300,
height: 120,
gear_teeth: 96,
gear_reference_angle: 0.0,
symmetry_order: 8,
mirror: true,
};
let style = DiagramStyle::default();
let frame = render_diagram(&mesh, &config, &style);
assert_eq!(
frame.pick_at(50, 60),
Some(2),
"crown panel center must show the +Y facet (id 2)"
);
assert_eq!(
frame.pick_at(150, 60),
Some(3),
"pavilion panel center must show the -Y facet (id 3)"
);
assert_eq!(
frame.pick_at(250, 60),
Some(4),
"profile panel center must show the nearer of +-Z (id 4, +Z)"
);
assert_eq!(frame.panel_at(50, 60), Some(PanelKind::Crown));
assert_eq!(frame.panel_at(150, 60), Some(PanelKind::Pavilion));
assert_eq!(frame.panel_at(250, 60), Some(PanelKind::Profile));
}
#[test]
fn an_empty_or_zero_sized_config_never_panics() {
let mesh = SolidMesh::default();
let config = DiagramConfig {
width: 0,
height: 0,
gear_teeth: 96,
gear_reference_angle: 0.0,
symmetry_order: 8,
mirror: false,
};
let frame = render_diagram(&mesh, &config, &DiagramStyle::default());
assert_eq!(frame.width, 0);
assert_eq!(frame.pick_at(0, 0), None);
}
#[test]
fn render_diagram_single_panel_fills_the_whole_frame() {
let mesh = match build_solid_mesh(&[
(DVec3::X, 1.0),
(DVec3::NEG_X, 1.0),
(DVec3::Y, 0.6),
(DVec3::NEG_Y, 0.6),
(DVec3::Z, 1.0),
(DVec3::NEG_Z, 1.0),
]) {
SolidStatus::Closed(mesh) => mesh,
other => panic!("box fixture must close: {other:?}"),
};
let config = DiagramConfig {
width: 300,
height: 120,
gear_teeth: 96,
gear_reference_angle: 0.0,
symmetry_order: 8,
mirror: false,
};
let style = DiagramStyle::default();
let frame = render_diagram_single_panel(&mesh, &config, &style, PanelKind::Crown);
assert_eq!(
frame.pick_at(150, 60),
Some(2),
"the enlarged panel's own center must show the +Y facet"
);
assert_eq!(frame.panel_at(150, 60), Some(PanelKind::Crown));
for y in 0..config.height {
for x in 0..config.width {
let tag = frame.panel_at(x, y);
assert!(
tag.is_none() || tag == Some(PanelKind::Crown),
"pixel ({x}, {y}) is tagged {tag:?}, but only the enlarged crown panel may paint in a single-panel frame"
);
}
}
}
#[test]
fn meet_marker_points_finds_the_shared_edge_between_two_touching_facets() {
let mesh = match build_solid_mesh(&[
(DVec3::X, 1.0),
(DVec3::NEG_X, 1.0),
(DVec3::Y, 0.6),
(DVec3::NEG_Y, 0.6),
(DVec3::Z, 1.0),
(DVec3::NEG_Z, 1.0),
]) {
SolidStatus::Closed(mesh) => mesh,
other => panic!("box fixture must close: {other:?}"),
};
let points = meet_marker_points(&mesh, &[(0, 2)], 1e-4);
assert_eq!(points.len(), 2, "got: {points:?}");
for expected_z in [-1.0, 1.0] {
assert!(
points.iter().any(|p| (p.x - 1.0).abs() < 1e-6
&& (p.y - 0.6).abs() < 1e-6
&& (p.z - expected_z).abs() < 1e-6),
"expected a marker at z={expected_z}, got: {points:?}"
);
}
assert_eq!(meet_marker_points(&mesh, &[(0, 1)], 1e-4).len(), 0);
}