use super::super::{
frame_updates_mast_cache,
handles::{CAMERA_FOV_DEG, ids_aligned},
};
use crate::gui::solid_preview::{facet_map::FacetMap, preview_state::FrameGeometry};
use glam::Vec3;
use indicatrix::{
geometry::meet_solver::{MeetConstraint, SolvedTier},
optics::raytracer::Camera,
};
use indicatrix_cut_core::{ConstraintTier, Design, Edit, expected_orbit, rotate_indices};
use indicatrix_editor::{
manipulate::{
ScreenPoint, ScreenSize, SliceSide, SnappedFacet, slice_normal, slice_tier, snap_to_gear,
tangency_mast,
},
session::clamp_nudge_to_side,
};
use std::{collections::BTreeSet, rc::Rc, sync::Arc};
const SLICE_ANGLE_STEP_DEG: f64 = 0.1;
pub(in super::super) const fn session_outlives(
base_generation: u64,
current_generation: u64,
) -> bool {
base_generation == current_generation
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(in super::super) enum LandedAction {
Ignore,
Resubmit,
Discard,
}
pub(in super::super) const fn landed_action(
base_generation: Option<u64>,
awaiting: bool,
current_generation: u64,
frame_generation: u64,
) -> LandedAction {
match base_generation {
None => LandedAction::Ignore,
Some(base) if !session_outlives(base, current_generation) => LandedAction::Discard,
Some(_) if awaiting || !frame_updates_mast_cache(frame_generation) => LandedAction::Ignore,
Some(_) => LandedAction::Resubmit,
}
}
pub(in super::super) fn replan_chain(
masts: Option<&[SolvedTier]>,
tier_count: usize,
tier_index: usize,
) -> (Option<Vec<SolvedTier>>, BTreeSet<usize>) {
match masts {
Some(masts) if masts.len() == tier_count => {
(Some(masts.to_vec()), BTreeSet::from([tier_index]))
}
_ => (None, BTreeSet::new()),
}
}
pub(in super::super) fn surviving_facets(
tier_facets: &[u32],
centroids: &[Option<Vec3>],
map_facets: usize,
) -> Option<usize> {
ids_aligned(map_facets, centroids.len()).then(|| {
tier_facets
.iter()
.filter(|&&id| centroids.get(id as usize).is_some_and(Option::is_some))
.count()
})
}
pub(in super::super) fn cut_hides_tier(cutoff: i32, tier_index: usize) -> bool {
usize::try_from(cutoff).is_ok_and(|through| through < tier_index)
}
pub(in super::super) fn keep_allowed(surviving: Option<usize>) -> bool {
surviving.is_some_and(|count| count > 0)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(in super::super) enum DiscardReason {
User,
DesignChanged,
SelectionChanged,
}
impl DiscardReason {
pub(super) const fn replans(self) -> bool {
!matches!(self, Self::SelectionChanged)
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub(in super::super) struct SliceLine {
pub(in super::super) a: ScreenPoint,
pub(in super::super) b: ScreenPoint,
pub(in super::super) size: ScreenSize,
pub(in super::super) pose: (f32, f32, f32),
}
impl SliceLine {
pub(super) const fn new(a: ScreenPoint, b: ScreenPoint, geometry: &FrameGeometry) -> Self {
Self {
a,
b,
size: ScreenSize::new(geometry.size.0 as f32, geometry.size.1 as f32),
pose: (
geometry.camera.yaw,
geometry.camera.pitch,
geometry.camera.distance,
),
}
}
pub(in super::super) fn normal(&self, side: SliceSide) -> Option<Vec3> {
let (yaw, pitch, distance) = self.pose;
let camera = Camera::new(yaw, pitch, distance, CAMERA_FOV_DEG);
slice_normal(&camera, self.a, self.b, self.size, side)
}
}
pub(in super::super) struct SlicePlan {
pub(in super::super) snapped: SnappedFacet,
pub(in super::super) tier: ConstraintTier,
}
pub(in super::super) fn plan_slice(
line: &SliceLine,
side: SliceSide,
corners: &[Vec3],
design: &Design,
symmetric: bool,
) -> Option<SlicePlan> {
let normal = line.normal(side)?;
let snapped = snap_to_gear(normal, design.meta.gear_teeth_abs(), SLICE_ANGLE_STEP_DEG);
let mast = tangency_mast(snapped.normal, corners);
let names: Vec<String> = design
.tiers
.iter()
.flat_map(|tier| tier.names().into_iter().map(str::to_string))
.collect();
let tier = slice_tier(&snapped, mast, &design.meta, symmetric, &names);
Some(SlicePlan { snapped, tier })
}
pub(in super::super) fn with_provisional_tier(
design: &Design,
tier: ConstraintTier,
) -> Option<(Design, usize)> {
let mut provisional = design.clone();
let index = provisional.tiers.len();
provisional.apply_edit(Edit::AddTier { index, tier }).ok()?;
Some((provisional, index))
}
#[derive(Debug, Clone)]
pub(in super::super) struct Snapshot {
pub(super) tier: ConstraintTier,
pub(super) snapped: SnappedFacet,
}
pub(super) struct ProvisionalSlice {
pub(super) base_generation: u64,
pub(super) design: Arc<Design>,
pub(super) tier_index: usize,
pub(super) side: SliceSide,
pub(super) snapped: SnappedFacet,
pub(super) line: SliceLine,
pub(super) base_corners: Arc<Vec<Vec3>>,
pub(super) masts: Option<Vec<SolvedTier>>,
pub(super) facet_map: Option<Rc<FacetMap>>,
pub(super) outline: Vec<u32>,
pub(super) masts_new: bool,
pub(super) awaiting: bool,
pub(super) surviving: Option<usize>,
}
impl ProvisionalSlice {
pub(super) fn new(
base_generation: u64,
design: Design,
tier_index: usize,
side: SliceSide,
snapped: SnappedFacet,
line: SliceLine,
base_corners: Arc<Vec<Vec3>>,
) -> Self {
Self {
base_generation,
design: Arc::new(design),
tier_index,
side,
snapped,
line,
base_corners,
masts: None,
facet_map: None,
outline: Vec::new(),
masts_new: false,
awaiting: false,
surviving: None,
}
}
pub(super) fn tier(&self) -> Option<&ConstraintTier> {
self.design.tiers.get(self.tier_index)
}
pub(super) fn facet_map(&mut self) -> Option<Rc<FacetMap>> {
if self.facet_map.is_none() {
let masts = self
.masts
.as_ref()
.filter(|masts| masts.len() == self.design.tiers.len())?;
self.facet_map = Some(Rc::new(FacetMap::from_design(&self.design, masts)));
}
self.facet_map.clone()
}
pub(super) fn facet_count(&self) -> usize {
self.surviving
.unwrap_or_else(|| self.tier().map_or(0, |tier| tier.indices.len()))
}
}
pub(in super::super) struct SliceState {
pub(super) gesture: Option<(f32, f32)>,
pub(super) provisional: Option<ProvisionalSlice>,
}
impl SliceState {
pub(in super::super) const fn new() -> Self {
Self {
gesture: None,
provisional: None,
}
}
}
pub(super) fn set_angle(p: &mut ProvisionalSlice, deg: f64) -> bool {
let index = p.tier_index;
let Some(tier) = Arc::make_mut(&mut p.design).tiers.get_mut(index) else {
return false;
};
let new = clamp_nudge_to_side(tier.angle_deg, deg);
if !new.is_finite() || new.to_bits() == tier.angle_deg.to_bits() {
return false;
}
tier.angle_deg = new;
p.snapped.angle_deg = new;
true
}
pub(super) fn set_mast(p: &mut ProvisionalSlice, mast: f64) -> bool {
let index = p.tier_index;
let Some(tier) = Arc::make_mut(&mut p.design).tiers.get_mut(index) else {
return false;
};
let unchanged = matches!(&tier.constraint, MeetConstraint::ScaleReference(m) if m.to_bits() == mast.to_bits());
if !mast.is_finite() || unchanged {
return false;
}
tier.constraint = MeetConstraint::ScaleReference(mast);
true
}
pub(in super::super) fn wheel_turn(more: i64, gear: u32) -> Option<u32> {
if gear == 0 {
return None;
}
u32::try_from(more.rem_euclid(i64::from(gear)))
.ok()
.filter(|&teeth| teeth != 0)
}
pub(super) fn turn(p: &mut ProvisionalSlice, more: i64) -> bool {
let gear = p.design.meta.gear_teeth_abs();
let Some(teeth) = wheel_turn(more, gear).map(f64::from) else {
return false;
};
let index = p.tier_index;
let Some(tier) = Arc::make_mut(&mut p.design).tiers.get_mut(index) else {
return false;
};
let mut indices = rotate_indices(&tier.indices, teeth, gear);
indices.sort_by(f64::total_cmp);
tier.indices = indices;
let anchor = rotate_indices(&[p.snapped.index], teeth, gear);
p.snapped.index = anchor.first().copied().unwrap_or(p.snapped.index);
true
}
pub(super) fn rebuild_indices(p: &mut ProvisionalSlice, symmetric: bool) -> bool {
let indices = {
let meta = &p.design.meta;
if symmetric {
expected_orbit(
p.snapped.index,
meta.symmetry_order,
meta.mirror,
meta.gear_teeth_abs(),
)
} else {
vec![p.snapped.index]
}
};
let index = p.tier_index;
let Some(tier) = Arc::make_mut(&mut p.design).tiers.get_mut(index) else {
return false;
};
tier.indices = indices;
tier.detached.clear();
p.facet_map = None;
true
}