use super::{
compose::{self, HOVER_MARK, Layers, ROUGH_EDGE, RoughLayer, SAW_EDGE},
picking::{BoxTarget, surviving_points},
scene::{BASE_GREY, FitScene, ModelScene, Scene, SceneKind},
};
use glam::Vec3;
use indicatrix::{geometry::stone_metrics::SolidMesh, optics::raytracer::Camera};
use indicatrix_solid::{
preview::{
CameraPose,
camera::{fit_distance_for_radius, orbit_distance_bounds},
},
raster::{FillMode, SolidRasterizer, SolidStyle, project_point},
};
use slint::{Rgba8Pixel, SharedPixelBuffer};
use std::sync::{Arc, Mutex};
pub(super) type Pixels = SharedPixelBuffer<Rgba8Pixel>;
pub(super) const FIELD_OF_VIEW: f32 = 42.0;
const BACKGROUND: [u8; 4] = [0x12, 0x14, 0x1c, 0xff];
const RESET_YAW: f32 = 0.60;
const RESET_PITCH: f32 = 0.45;
#[must_use]
pub(super) fn camera_for(pose: CameraPose) -> Camera {
Camera::new(pose.yaw, pose.pitch, pose.distance, FIELD_OF_VIEW)
}
fn widen(aspect: f32) -> f32 {
if aspect.is_finite() && aspect > 0.0 {
1.0 / aspect.min(1.0)
} else {
1.0
}
}
#[must_use]
pub(super) fn reset_pose(aspect: f32) -> CameraPose {
let (near, far) = orbit_distance_bounds(super::scene::SCENE_RADIUS);
CameraPose {
yaw: RESET_YAW,
pitch: RESET_PITCH,
distance: (fit_distance_for_radius(super::scene::SCENE_RADIUS) * widen(aspect))
.clamp(near, far),
}
}
#[must_use]
pub(super) fn refit_distance(distance: f32, old_aspect: f32, new_aspect: f32) -> f32 {
let (near, far) = orbit_distance_bounds(super::scene::SCENE_RADIUS);
(distance * widen(new_aspect) / widen(old_aspect)).clamp(near, far)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub(super) enum Hover {
#[default]
None,
Box(BoxTarget),
Facet(usize),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) struct RenderOptions {
pub(super) show_cut_faces: bool,
pub(super) show_rough: bool,
pub(super) show_saw: bool,
pub(super) show_stones: bool,
pub(super) selected_cut: Option<usize>,
pub(super) highlight_group: Option<usize>,
pub(super) hover: Hover,
}
impl Default for RenderOptions {
fn default() -> Self {
Self {
show_cut_faces: true,
show_rough: true,
show_saw: true,
show_stones: true,
selected_cut: None,
highlight_group: None,
hover: Hover::None,
}
}
}
pub(super) struct RenderRequest {
pub(super) generation: u64,
pub(super) scene: Arc<Scene>,
pub(super) pose: CameraPose,
pub(super) size: (u32, u32),
pub(super) options: RenderOptions,
}
#[derive(Debug, Default, Clone)]
pub(super) struct PickSnapshot {
pub(super) serial: u64,
pub(super) width: u32,
pub(super) height: u32,
pub(super) pick: Arc<Vec<u32>>,
}
impl PickSnapshot {
#[must_use]
pub(super) fn facet_at(&self, fx: f32, fy: f32) -> Option<usize> {
if self.width == 0
|| self.height == 0
|| !(0.0..1.0).contains(&fx)
|| !(0.0..1.0).contains(&fy)
{
return None;
}
let x = (fx * self.width as f32) as usize;
let y = (fy * self.height as f32) as usize;
let value = *self.pick.get(y * self.width as usize + x)?;
value.checked_sub(1).map(|facet| facet as usize)
}
}
pub(super) type SharedPick = Arc<Mutex<PickSnapshot>>;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct FrameKey {
serial: u64,
pose: [u32; 3],
size: (u32, u32),
options: RenderOptions,
}
impl FrameKey {
fn of(request: &RenderRequest) -> Self {
let options = request.options;
let options = match request.scene.kind {
SceneKind::Model(_) => RenderOptions {
show_rough: false,
show_saw: false,
show_stones: false,
highlight_group: None,
hover: match options.hover {
Hover::Facet(id) => Hover::Facet(id),
Hover::None | Hover::Box(_) => Hover::None,
},
..options
},
SceneKind::Fit(_) => RenderOptions {
show_cut_faces: false,
selected_cut: None,
hover: Hover::None,
..options
},
};
Self {
serial: request.scene.serial,
pose: [
request.pose.yaw.to_bits(),
request.pose.pitch.to_bits(),
request.pose.distance.to_bits(),
],
size: request.size,
options,
}
}
}
fn edge_pass_style(edge: [u8; 3]) -> SolidStyle {
SolidStyle {
background: [0, 0, 0, 0],
fill_mode: FillMode::Transparent,
edge_color: edge,
show_orientation_marker: false,
..SolidStyle::default()
}
}
fn model_style(scene: &ModelScene, options: &RenderOptions) -> SolidStyle {
let mut selected = vec![false; scene.base_facets + scene.cut_count];
if let Some(cut) = options.selected_cut
&& let Some(flag) = selected.get_mut(scene.base_facets + cut)
{
*flag = true;
}
SolidStyle {
background: BACKGROUND,
base_color: BASE_GREY,
facet_base_colors: scene.facet_colors(options.show_cut_faces),
selected,
hovered: match options.hover {
Hover::Facet(id) => u32::try_from(id).ok(),
Hover::None | Hover::Box(_) => None,
},
show_orientation_marker: false,
..SolidStyle::default()
}
}
fn draw_box_hover(
out: &mut [u8],
size: (u32, u32),
camera: &Camera,
target: BoxTarget,
world: &[Vec3],
) {
let points: Vec<(f32, f32)> = world
.iter()
.filter_map(|&p| project_point(camera, p, size.0, size.1).map(|s| (s.0, s.1)))
.collect();
match (target, points.as_slice()) {
(BoxTarget::Edge(_), &[a, b]) => compose::draw_line(out, size, a, b, 3, HOVER_MARK),
(BoxTarget::Corner(_), &[c]) => compose::draw_dot(out, size, c, 6, HOVER_MARK),
_ => {}
}
}
pub(super) struct ViewRenderer {
main: SolidRasterizer,
rough: SolidRasterizer,
saw: SolidRasterizer,
base: Vec<u8>,
key: Option<FrameKey>,
fit_style: Option<(u64, SolidStyle)>,
frames: [Option<Pixels>; 2],
turn: usize,
spare_pick: Option<Arc<Vec<u32>>>,
}
impl Default for ViewRenderer {
fn default() -> Self {
Self {
main: SolidRasterizer::new(1, 1),
rough: SolidRasterizer::new(1, 1),
saw: SolidRasterizer::new(1, 1),
base: Vec::new(),
key: None,
fit_style: None,
frames: [None, None],
turn: 0,
spare_pick: None,
}
}
}
impl ViewRenderer {
pub(super) fn render(&mut self, request: &RenderRequest, picks: Option<&SharedPick>) -> Pixels {
let size = (request.size.0.max(1), request.size.1.max(1));
let key = FrameKey::of(request);
if self.key.as_ref() != Some(&key) {
self.draw_base(request, size);
self.key = Some(key);
if let Some(shared) = picks {
self.publish(shared, request.scene.serial);
}
}
let turn = self.turn;
self.turn = 1 - turn;
let slot = &mut self.frames[turn];
if slot
.as_ref()
.is_none_or(|frame| (frame.width(), frame.height()) != size)
{
*slot = Some(Pixels::new(size.0, size.1));
}
let frame = slot.get_or_insert_with(|| Pixels::new(size.0, size.1));
frame.make_mut_bytes().copy_from_slice(&self.base);
if let (Hover::Box(target), SceneKind::Model(model)) =
(request.options.hover, &request.scene.kind)
&& let Some(world) = surviving_points(target, model.half_extents, &model.cut_planes)
{
let camera = camera_for(request.pose);
draw_box_hover(frame.make_mut_bytes(), size, &camera, target, &world);
}
frame.clone()
}
fn publish(&mut self, shared: &SharedPick, serial: u64) {
let mut buffer = self
.spare_pick
.take()
.and_then(|spare| Arc::try_unwrap(spare).ok())
.unwrap_or_default();
buffer.clear();
buffer.extend_from_slice(&self.main.pick);
let fresh = Arc::new(buffer);
let (width, height) = (self.main.width, self.main.height);
let old = {
let mut snapshot = shared
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
snapshot.serial = serial;
snapshot.width = width;
snapshot.height = height;
std::mem::replace(&mut snapshot.pick, fresh)
};
self.spare_pick = Some(old);
}
fn draw_base(&mut self, request: &RenderRequest, size: (u32, u32)) {
let camera = camera_for(request.pose);
self.main.resize(size.0, size.1);
match &request.scene.kind {
SceneKind::Model(model) => {
let style = model_style(model, &request.options);
self.main.render(&model.mesh, &camera, &style);
self.base.clear();
self.base.extend_from_slice(&self.main.color);
}
SceneKind::Fit(fit) => {
self.draw_fit(request.scene.serial, fit, &camera, size, &request.options);
}
}
}
fn draw_fit(
&mut self,
serial: u64,
fit: &FitScene,
camera: &Camera,
size: (u32, u32),
options: &RenderOptions,
) {
if self
.fit_style
.as_ref()
.is_none_or(|(known, _)| *known != serial)
{
let style = SolidStyle {
background: BACKGROUND,
facet_base_colors: fit.facet_colors.clone(),
show_orientation_marker: false,
..SolidStyle::default()
};
self.fit_style = Some((serial, style));
}
let Some((_, style)) = &self.fit_style else {
return;
};
let nothing = SolidMesh::default();
let stones = if options.show_stones {
&fit.stones
} else {
¬hing
};
self.main.render(stones, camera, style);
self.base.clear();
self.base.extend_from_slice(&self.main.color);
let show_rough = options.show_rough && !fit.rough.rings.is_empty();
let show_saw = options.show_saw && !fit.saw.rings.is_empty();
if show_rough {
self.rough.resize(size.0, size.1);
self.rough
.render(&fit.rough, camera, &edge_pass_style(ROUGH_EDGE));
}
if show_saw {
self.saw.resize(size.0, size.1);
self.saw
.render(&fit.saw, camera, &edge_pass_style(SAW_EDGE));
}
compose::compose(
&mut self.base,
&Layers {
rough: show_rough.then_some(RoughLayer {
edges: &self.rough.color,
cover: &self.rough.pick,
}),
saw: show_saw.then_some(self.saw.color.as_slice()),
},
);
if let Some(group) = options.highlight_group
&& options.show_stones
{
let flags = fit.facets_of_group(group);
let selected = |pick: u32| {
(pick as usize)
.checked_sub(1)
.and_then(|facet| flags.get(facet))
.copied()
.unwrap_or(false)
};
let thickness = if size.0 >= 600 { 2 } else { 1 };
compose::outline_region(&mut self.base, &self.main.pick, size, &selected, thickness);
}
}
}
#[cfg(test)]
mod tests;