use egui_kittest::wgpu::{create_render_state, default_wgpu_setup};
use rsplot::egui::Color32;
use rsplot::{
CameraFace, ImageInterpolation, PointMarker, Scatter2D, Scatter2DVisualization, Scatter3D,
Scene3dGeometry, Scene3dImageLayer, Scene3dTexturedMesh, ScenePickKind, SceneWidget, Vec3,
};
fn cut_plane_quad(z: f32) -> Scene3dTexturedMesh {
Scene3dTexturedMesh {
pixels: vec![255; 4], width: 1,
height: 1,
vertices: vec![
[0.0, 0.0, z],
[1.0, 0.0, z],
[1.0, 1.0, z],
[0.0, 0.0, z],
[1.0, 1.0, z],
[0.0, 1.0, z],
],
uvs: vec![
[0.0, 0.0],
[1.0, 0.0],
[1.0, 1.0],
[0.0, 0.0],
[1.0, 1.0],
[0.0, 1.0],
],
interpolation: ImageInterpolation::Nearest,
}
}
fn front_view_widget(id: u64, geometry: Scene3dGeometry) -> SceneWidget {
let rs = create_render_state(default_wgpu_setup());
let mut w = SceneWidget::new(&rs, id);
w.set_geometry(&rs, geometry);
w.set_viewpoint(CameraFace::Front); w.camera_mut().set_size((200.0, 200.0));
w
}
#[test]
fn pick_hits_surface_under_screen_centre() {
let mut geo = Scene3dGeometry::new();
geo.add_triangle(
[0.0, 0.0, 0.5],
[1.0, 0.0, 0.5],
[0.5, 1.0, 0.5],
Color32::WHITE,
);
let w = front_view_widget(21, geo);
let pick = w.pick((0.0, 0.0)).expect("centre ray hits the triangle");
assert_eq!(pick.kind, ScenePickKind::Surface);
assert!(
(pick.position.z - 0.5).abs() < 1e-3,
"hit z = {} (want 0.5)",
pick.position.z
);
assert!(
(pick.position.x - 0.5).abs() < 0.1,
"hit x = {}",
pick.position.x
);
assert!(
(pick.position.y - 0.5).abs() < 0.25,
"hit y = {}",
pick.position.y
);
}
#[test]
fn pick_misses_when_ray_clears_the_geometry() {
let mut geo = Scene3dGeometry::new();
geo.add_triangle(
[0.0, 0.0, 0.5],
[0.1, 0.0, 0.5],
[0.0, 0.1, 0.5],
Color32::WHITE,
);
let w = front_view_widget(22, geo);
assert!(
w.pick((0.0, 0.0)).is_none(),
"centre ray must miss the corner triangle"
);
}
#[test]
fn pick_selects_scatter_point_under_the_cursor() {
let mut geo = Scene3dGeometry::new();
geo.add_point([0.5, 0.5, 0.5], Color32::WHITE, 12.0, PointMarker::Circle);
let w = front_view_widget(23, geo);
let pick = w.pick((0.0, 0.0)).expect("centre ray hits the point");
assert_eq!(pick.kind, ScenePickKind::Point { index: 0 });
assert!((pick.position.x - 0.5).abs() < 1e-6);
assert!((pick.position.y - 0.5).abs() < 1e-6);
assert!((pick.position.z - 0.5).abs() < 1e-6);
}
#[test]
fn pick_prefers_the_nearer_surface() {
let mut geo = Scene3dGeometry::new();
geo.add_triangle(
[0.0, 0.0, 0.2],
[1.0, 0.0, 0.2],
[0.5, 1.0, 0.2],
Color32::WHITE,
);
geo.add_triangle(
[0.0, 0.0, 0.8],
[1.0, 0.0, 0.8],
[0.5, 1.0, 0.8],
Color32::WHITE,
);
let w = front_view_widget(24, geo);
let pick = w.pick((0.0, 0.0)).expect("centre ray hits a triangle");
assert_eq!(pick.kind, ScenePickKind::Surface);
assert!(
(pick.position.z - 0.8).abs() < 1e-2,
"nearer plane (z=0.8) should win, got z = {}",
pick.position.z
);
}
#[test]
fn pick_hits_the_cut_plane_textured_mesh() {
let mut geo = Scene3dGeometry::new();
geo.add_textured_mesh(cut_plane_quad(0.5));
let w = front_view_widget(28, geo);
let pick = w
.pick((0.0, 0.0))
.expect("centre ray hits the cut-plane mesh");
assert_eq!(pick.kind, ScenePickKind::TexturedSurface { mesh: 0 });
assert!(
(pick.position.z - 0.5).abs() < 1e-3,
"hit z = {} (want 0.5)",
pick.position.z
);
assert!(
(pick.position.x - 0.5).abs() < 0.1,
"hit x = {}",
pick.position.x
);
assert!(
(pick.position.y - 0.5).abs() < 0.1,
"hit y = {}",
pick.position.y
);
}
#[test]
fn pick_orders_cut_plane_against_data_surface_by_depth() {
let mut geo = Scene3dGeometry::new();
geo.add_triangle(
[0.0, 0.0, 0.2],
[1.0, 0.0, 0.2],
[0.5, 1.0, 0.2],
Color32::WHITE,
);
geo.add_textured_mesh(cut_plane_quad(0.8));
let w = front_view_widget(29, geo);
let pick = w.pick((0.0, 0.0)).expect("centre ray hits geometry");
assert_eq!(pick.kind, ScenePickKind::TexturedSurface { mesh: 0 });
assert!(
(pick.position.z - 0.8).abs() < 1e-2,
"nearer cut plane wins, z={}",
pick.position.z
);
let mut geo = Scene3dGeometry::new();
geo.add_triangle(
[0.0, 0.0, 0.8],
[1.0, 0.0, 0.8],
[0.5, 1.0, 0.8],
Color32::WHITE,
);
geo.add_textured_mesh(cut_plane_quad(0.2));
let w = front_view_widget(30, geo);
let pick = w.pick((0.0, 0.0)).expect("centre ray hits geometry");
assert_eq!(pick.kind, ScenePickKind::Surface);
assert!(
(pick.position.z - 0.8).abs() < 1e-2,
"nearer surface wins, z={}",
pick.position.z
);
}
fn ndc_of(w: &SceneWidget, world: Vec3) -> (f32, f32) {
let p = w.camera().matrix().transform_point(world, true);
(p.x, p.y)
}
#[test]
fn pick_scatter2d_lines_mode_hits_data_points_not_segments() {
let scatter = Scatter2D::new()
.with_data(&[0.25, 0.75, 0.5], &[0.25, 0.25, 0.75], &[0.0, 1.0, 2.0])
.with_visualization(Scatter2DVisualization::Lines);
let mut geo = Scene3dGeometry::new();
scatter.append_to(&mut geo);
assert_eq!(
geo.line_pick_anchors().len(),
3,
"one anchor per data point"
);
let w = front_view_widget(25, geo);
let target = Vec3::new(0.75, 0.25, 0.0);
let pick = w.pick(ndc_of(&w, target)).expect("hits the data point");
assert_eq!(pick.kind, ScenePickKind::LinePoint { index: 1 });
assert!((pick.position.x - 0.75).abs() < 1e-6);
assert!((pick.position.y - 0.25).abs() < 1e-6);
let mid = Vec3::new(0.5, 0.25, 0.0);
assert!(
w.pick(ndc_of(&w, mid)).is_none(),
"silx picks the points, not the line segments"
);
}
#[test]
fn pick_image_quad_returns_row_and_column() {
let mut geo = Scene3dGeometry::new();
geo.add_image_layer(Scene3dImageLayer {
pixels: vec![255; 3 * 2 * 4],
width: 3,
height: 2,
origin: [0.0, 0.0, 0.5],
scale: [0.25, 0.25],
interpolation: ImageInterpolation::Nearest,
});
let w = front_view_widget(26, geo);
let target = Vec3::new(0.6, 0.3, 0.5);
let pick = w.pick(ndc_of(&w, target)).expect("ray crosses the image");
assert_eq!(
pick.kind,
ScenePickKind::Image {
image: 0,
row: 1,
col: 2
}
);
assert!((pick.position.x - 0.6).abs() < 1e-3);
assert!((pick.position.y - 0.3).abs() < 1e-3);
assert!((pick.position.z - 0.5).abs() < 1e-3);
assert!(w.pick(ndc_of(&w, Vec3::new(0.9, 0.3, 0.5))).is_none());
assert!(w.pick(ndc_of(&w, Vec3::new(-0.2, 0.3, 0.5))).is_none());
}
#[test]
fn pick_follows_the_item_transform() {
let mut scatter = Scatter3D::new().with_data(&[0.2], &[0.2], &[0.5], &[1.0]);
scatter.transform_mut().set_translation(0.4, 0.2, 0.0);
let mut geo = Scene3dGeometry::new();
scatter.append_to(&mut geo);
let w = front_view_widget(27, geo);
let target = Vec3::new(0.6, 0.4, 0.5);
let pick = w.pick(ndc_of(&w, target)).expect("transformed point picks");
assert_eq!(pick.kind, ScenePickKind::Point { index: 0 });
assert!((pick.position.x - 0.6).abs() < 1e-6);
assert!((pick.position.y - 0.4).abs() < 1e-6);
assert!((pick.position.z - 0.5).abs() < 1e-6);
assert!(w.pick(ndc_of(&w, Vec3::new(0.2, 0.2, 0.5))).is_none());
let (lo, hi) = scatter.bounds().expect("has data");
assert!((lo.x - 0.6).abs() < 1e-6 && (lo.y - 0.4).abs() < 1e-6);
assert!((hi.x - 0.6).abs() < 1e-6 && (hi.z - 0.5).abs() < 1e-6);
}