use egui_kittest::Harness;
use egui_kittest::wgpu::{WgpuTestRenderer, create_render_state, default_wgpu_setup};
use siplot::egui::{self, Color32};
use siplot::egui_wgpu::RenderState;
use siplot::{Camera, Scene3dGeometry, Vec3, install_scene3d, paint_scene3d, set_scene3d};
use std::cell::RefCell;
use std::rc::Rc;
const SCENE_ID: u64 = 0;
const WIN: f32 = 300.0;
struct App {
camera: Camera,
last_rect: Option<egui::Rect>,
}
impl App {
fn new(rs: &RenderState) -> Self {
install_scene3d(rs);
let mut g = Scene3dGeometry::new();
g.add_triangle(
[-0.3, -0.3, 1.0],
[0.3, -0.3, 1.0],
[0.0, 0.3, 1.0],
Color32::from_rgb(255, 0, 0),
);
g.add_triangle(
[-0.6, -0.6, -2.0],
[0.6, -0.6, -2.0],
[0.0, 0.6, -2.0],
Color32::from_rgb(0, 200, 0),
);
let magenta = Color32::from_rgb(220, 0, 220);
for i in 0..5 {
let y = 0.78 + 0.01 * i as f32;
g.add_line([-0.5, y, 0.0], [0.5, y, 0.0], magenta);
}
set_scene3d(rs, SCENE_ID, &g);
let camera = Camera::new(
30.0,
0.1,
100.0,
(1.0, 1.0),
Vec3::new(0.0, 0.0, 5.0),
Vec3::new(0.0, 0.0, -1.0),
Vec3::new(0.0, 1.0, 0.0),
);
Self {
camera,
last_rect: None,
}
}
fn ui(&mut self, ui: &mut egui::Ui) {
let (rect, _resp) = ui.allocate_exact_size(ui.available_size(), egui::Sense::hover());
paint_scene3d(ui, rect, SCENE_ID, &self.camera, Color32::BLACK);
self.last_rect = Some(rect);
}
}
#[test]
fn scene3d_renders_depth_tested_geometry_and_lines() {
let rs = create_render_state(default_wgpu_setup());
let app = Rc::new(RefCell::new(App::new(&rs)));
let renderer = WgpuTestRenderer::from_render_state(rs);
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 rect = app.borrow().last_rect.expect("scene rect captured");
let image = harness.render().expect("headless wgpu render");
let (iw, ih) = (image.width() as usize, image.height() as usize);
let raw = image.as_raw();
let at = |fx: f32, fy: f32| -> (u8, u8, u8) {
let x = ((rect.min.x + fx * rect.width()).round() as usize).min(iw - 1);
let y = ((rect.min.y + fy * rect.height()).round() as usize).min(ih - 1);
let i = (y * iw + x) * 4;
(raw[i], raw[i + 1], raw[i + 2])
};
let is_red = |(r, g, b): (u8, u8, u8)| r > 150 && g < 90 && b < 90;
let is_green = |(r, g, b): (u8, u8, u8)| g > 120 && r < 90 && b < 90;
let is_magenta = |(r, g, b): (u8, u8, u8)| r > 120 && b > 120 && g < 90;
let is_black = |(r, g, b): (u8, u8, u8)| r < 50 && g < 50 && b < 50;
let centre = at(0.5, 0.5);
assert!(
is_red(centre),
"scene centre should be the near RED triangle (depth-tested over the \
later-drawn far green); got rgb{centre:?}"
);
for (fx, fy) in [(0.03, 0.03), (0.97, 0.03), (0.03, 0.97), (0.97, 0.97)] {
let c = at(fx, fy);
assert!(
is_black(c),
"corner ({fx},{fy}) should be the black background clear; got rgb{c:?}"
);
}
let mut green = 0usize;
let mut magenta = 0usize;
let x0 = rect.min.x.max(0.0) as usize;
let y0 = rect.min.y.max(0.0) as usize;
let x1 = (rect.max.x as usize).min(iw);
let y1 = (rect.max.y as usize).min(ih);
for y in y0..y1 {
for x in x0..x1 {
let i = (y * iw + x) * 4;
let px = (raw[i], raw[i + 1], raw[i + 2]);
if is_green(px) {
green += 1;
}
if is_magenta(px) {
magenta += 1;
}
}
}
assert!(
green > 0,
"far GREEN triangle should be visible around the red one (got 0 green px)"
);
assert!(
magenta >= 10,
"MAGENTA line band should render (LineList pipeline); got {magenta} magenta px"
);
}