use crate::gui::{
render::camera_lighting::fit_distance_for_radius,
solid_preview::{
mesh_cache::MeshCache,
raster::{SolidRasterizer, SolidStyle},
to_pixel_buffer,
},
};
use glam::Vec3;
use indicatrix::optics::raytracer::{Camera, DEFAULT_FOV_DEG, DEFAULT_POSE};
use indicatrix_editor::{HistorySnapshot, solve_policy::design_to_gpu_planes_from_solved};
use slint::{Rgba8Pixel, SharedPixelBuffer};
pub(super) type Pixels = SharedPixelBuffer<Rgba8Pixel>;
fn thumb_style(preform_planes: usize) -> SolidStyle {
SolidStyle {
background: [0, 0, 0, 0],
preform_plane_count: preform_planes,
show_orientation_marker: false,
..SolidStyle::default()
}
}
pub(super) struct ThumbRenderer {
cache: MeshCache,
raster: SolidRasterizer,
}
impl Default for ThumbRenderer {
fn default() -> Self {
Self {
cache: MeshCache::default(),
raster: SolidRasterizer::new(1, 1),
}
}
}
impl ThumbRenderer {
pub(super) fn render_planes(
&mut self,
planes: &[(Vec3, f32)],
preform_planes: usize,
edge: u32,
) -> Option<Pixels> {
let prepared = self.cache.get_or_build(planes)?;
let camera = Camera::new(
DEFAULT_POSE.yaw,
DEFAULT_POSE.pitch,
fit_distance_for_radius(prepared.bounding_radius()),
DEFAULT_FOV_DEG,
);
self.raster.resize(edge.max(1), edge.max(1));
self.raster
.render_prepared(prepared, &camera, &thumb_style(preform_planes));
Some(to_pixel_buffer(&self.raster))
}
}
pub(super) fn render_step(
renderer: &mut ThumbRenderer,
session: &HistorySnapshot,
position: usize,
edge: u32,
) -> Option<Pixels> {
let design = session.design_at(position).ok()?;
let preform_planes = design.preform.planes().len();
let solved = design.solve().ok()?;
let planes: Vec<(Vec3, f32)> = design_to_gpu_planes_from_solved(&design, &solved)
.iter()
.map(|plane| (Vec3::from(plane.normal), -plane.d))
.collect();
renderer.render_planes(&planes, preform_planes, edge)
}
#[cfg(test)]
mod tests {
use super::*;
use indicatrix_cut_core::Edit;
use indicatrix_editor::EditorSession;
fn cube() -> Vec<(Vec3, f32)> {
[
Vec3::X,
Vec3::NEG_X,
Vec3::Y,
Vec3::NEG_Y,
Vec3::Z,
Vec3::NEG_Z,
]
.into_iter()
.map(|normal| (normal, 1.0))
.collect()
}
fn opaque_pixels(pixels: &Pixels) -> usize {
pixels
.as_slice()
.iter()
.filter(|pixel| pixel.a == 255)
.count()
}
#[test]
fn a_closed_solid_is_drawn_at_the_asked_size_on_a_transparent_background() {
let mut renderer = ThumbRenderer::default();
let pixels = renderer
.render_planes(&cube(), 0, 48)
.expect("a cube closes");
assert_eq!((pixels.width(), pixels.height()), (48, 48));
let opaque = opaque_pixels(&pixels);
assert!(
opaque > 100,
"the cube covers part of the picture: {opaque}"
);
assert!(
opaque < 48 * 48,
"and the corners stay transparent: {opaque}"
);
let corner = pixels.as_slice()[0];
assert_eq!(corner.a, 0, "the background is transparent");
}
#[test]
fn planes_that_do_not_close_draw_nothing() {
let mut renderer = ThumbRenderer::default();
assert!(renderer.render_planes(&[(Vec3::Y, 1.0)], 0, 32).is_none());
assert!(renderer.render_planes(&[], 0, 32).is_none());
}
#[test]
fn the_renderer_can_be_reused_at_another_size() {
let mut renderer = ThumbRenderer::default();
let small = renderer.render_planes(&cube(), 0, 24).unwrap();
let large = renderer.render_planes(&cube(), 0, 96).unwrap();
assert_eq!((small.width(), large.width()), (24, 96));
}
#[test]
fn a_step_of_a_real_session_is_drawn_and_an_unknown_one_is_not() {
let mut session = EditorSession::fresh();
session
.apply(Edit::SetPreformYOffset { y_offset: 0.1 })
.expect("edit must apply");
let snapshot = session.history_snapshot();
let mut renderer = ThumbRenderer::default();
for position in 0..=1 {
let pixels = render_step(&mut renderer, &snapshot, position, 40)
.unwrap_or_else(|| panic!("step {position} draws"));
assert_eq!(pixels.width(), 40);
assert!(opaque_pixels(&pixels) > 0, "step {position} has a solid");
}
assert!(render_step(&mut renderer, &snapshot, 2, 40).is_none());
}
}