use egui_kittest::Harness;
use egui_kittest::kittest::Queryable;
use egui_kittest::wgpu::{WgpuTestRenderer, create_render_state, default_wgpu_setup};
use siplot::egui;
use siplot::egui_wgpu::RenderState;
use siplot::{CameraFace, SceneWidget, Vec3, viewpoint_menu};
use std::cell::RefCell;
use std::rc::Rc;
const WIN: f32 = 320.0;
const UNIT: (Vec3, Vec3) = (Vec3::ZERO, Vec3::new(1.0, 1.0, 1.0));
fn expected_offset(face: CameraFace) -> Vec3 {
match face {
CameraFace::Front => Vec3::new(0.0, 0.0, 1.0),
CameraFace::Back => Vec3::new(0.0, 0.0, -1.0),
CameraFace::Top => Vec3::new(0.0, 1.0, 0.0),
CameraFace::Bottom => Vec3::new(0.0, -1.0, 0.0),
CameraFace::Right => Vec3::new(1.0, 0.0, 0.0),
CameraFace::Left => Vec3::new(-1.0, 0.0, 0.0),
CameraFace::Side => Vec3::new(1.0, 1.0, 1.0).normalized(),
}
}
#[test]
fn viewpoint_presets_orient_camera_along_expected_axis() {
let rs = create_render_state(default_wgpu_setup());
let mut scene = SceneWidget::new(&rs, 0);
scene.set_bounds(&rs, UNIT);
let center = scene.center();
for face in [
CameraFace::Front,
CameraFace::Back,
CameraFace::Top,
CameraFace::Bottom,
CameraFace::Right,
CameraFace::Left,
CameraFace::Side,
] {
scene.set_viewpoint(face);
let offset = (scene.camera().extrinsic.position() - center).normalized();
let want = expected_offset(face);
let alignment = offset.dot(want);
assert!(
alignment > 0.999,
"viewpoint {face:?}: eye offset {offset:?} should align with {want:?} (dot={alignment})"
);
}
}
#[test]
fn rotate_scene_orbits_around_the_center_preserving_radius() {
let rs = create_render_state(default_wgpu_setup());
let mut scene = SceneWidget::new(&rs, 1);
scene.set_bounds(&rs, UNIT);
let center = scene.center();
scene.set_viewpoint(CameraFace::Side);
let before = scene.camera().extrinsic.position();
let r_before = (before - center).length();
scene.rotate_scene(45.0);
let after = scene.camera().extrinsic.position();
let r_after = (after - center).length();
assert_ne!(before, after, "rotate_scene must move the camera");
assert!(
(r_before - r_after).abs() < 1e-3,
"an orbit preserves the distance to the centre: {r_before} vs {r_after}"
);
}
struct MenuApp {
scene: SceneWidget,
}
impl MenuApp {
fn new(rs: &RenderState) -> Self {
let mut scene = SceneWidget::new(rs, 2);
scene.set_bounds(rs, UNIT);
Self { scene }
}
fn ui(&mut self, ui: &mut egui::Ui) {
ui.horizontal(|ui| {
let _ = viewpoint_menu(ui, &mut self.scene);
});
self.scene.show(ui);
}
}
#[test]
fn viewpoint_menu_click_reorients_the_camera() {
let rs = create_render_state(default_wgpu_setup());
let app = Rc::new(RefCell::new(MenuApp::new(&rs)));
let renderer = WgpuTestRenderer::from_render_state(rs.clone());
let app_ui = app.clone();
let mut harness = Harness::builder()
.with_size(egui::vec2(WIN, WIN))
.with_pixels_per_point(1.0)
.renderer(renderer)
.build_ui(move |ui| app_ui.borrow_mut().ui(ui));
harness.step();
let center = app.borrow().scene.center();
let before = app.borrow().scene.camera().extrinsic.position();
harness.get_by_label("View").click();
harness.run();
harness.get_by_label("Top").click();
harness.run();
let after = app.borrow().scene.camera().extrinsic.position();
let offset = (after - center).normalized();
assert_ne!(
before, after,
"selecting a viewpoint from the menu must move the camera"
);
assert!(
offset.dot(Vec3::new(0.0, 1.0, 0.0)) > 0.999,
"menu 'Top' must orient the camera along +Y; got offset {offset:?}"
);
}