use super::{
CachedFacetMap, SESSION, Shared, Target, kind_to_int, set_hint, slice, snap_mode, tier_label,
};
use crate::{
EditorModel, MainWindow, ManipulateModel, SolidPreviewModel,
gui::{
editor::callbacks::{
map_to_pick_coordinates, pick_margin, pick_pixels_to_logical, selected_facet_id,
},
solid_preview::{facet_map::FacetMap, preview_state::FrameGeometry},
},
};
use glam::Vec3;
use indicatrix::{geometry::meet_solver::SolvedTier, optics::raytracer::Camera};
use indicatrix_editor::manipulate::{
FacetFrame, HANDLE_HIT_RADIUS_PX, HandleKind, HandleLayout, ScreenPoint, ScreenSize,
handle_layout, hit_test, text, tiers_meeting,
};
use slint::ComponentHandle as _;
use std::{rc::Rc, sync::PoisonError};
pub(super) const CAMERA_FOV_DEG: f32 = indicatrix::optics::raytracer::DEFAULT_FOV_DEG;
const HANDLE_LENGTH_FRACTION: f64 = 0.35;
#[derive(Debug, Clone, Copy, PartialEq)]
pub(super) struct LogicalHandles {
pub(super) anchor: (f32, f32),
pub(super) angle_tip: (f32, f32),
pub(super) depth_tip: (f32, f32),
pub(super) index_tip: (f32, f32),
}
pub(super) fn layout_to_logical(
layout: &HandleLayout,
scale: f32,
margin: (f32, f32),
) -> LogicalHandles {
let convert = |p: ScreenPoint| pick_pixels_to_logical((p.x, p.y), scale, margin);
LogicalHandles {
anchor: convert(layout.anchor),
angle_tip: convert(layout.angle_tip),
depth_tip: convert(layout.depth_tip),
index_tip: convert(layout.index_tip),
}
}
pub(super) fn pick_facet(
remembered: Option<u32>,
tier: usize,
facets_of_tier: &[u32],
tier_of: impl Fn(u32) -> Option<usize>,
has_centroid: impl Fn(u32) -> bool,
) -> Option<u32> {
remembered
.filter(|&id| tier_of(id) == Some(tier) && has_centroid(id))
.or_else(|| facets_of_tier.iter().copied().find(|&id| has_centroid(id)))
}
pub(super) const fn masts_aligned(tier_count: usize, masts_len: usize) -> bool {
tier_count == masts_len
}
pub(super) const fn ids_aligned(map_facets: usize, centroid_count: usize) -> bool {
map_facets == centroid_count
}
pub(super) fn hit_kind(
layout: &HandleLayout,
indexless: bool,
p: ScreenPoint,
radius: f32,
) -> Option<HandleKind> {
let mut layout = *layout;
if indexless {
layout.index_tip = ScreenPoint::new(f32::NAN, f32::NAN);
}
hit_test(&layout, p, radius)
}
pub(super) fn pointer_to_pick(ui: &MainWindow, x: f32, y: f32) -> ScreenPoint {
let (px, py) = map_to_pick_coordinates(ui, x, y);
ScreenPoint::new(px, py)
}
fn solved_summary(ctx: &Shared) -> Option<(u64, usize)> {
ctx.solid_last_solved
.lock()
.unwrap_or_else(PoisonError::into_inner)
.as_ref()
.map(|(generation, masts)| (*generation, masts.len()))
}
pub(super) fn aligned_masts(ctx: &Shared, tier_count: usize) -> Option<Vec<SolvedTier>> {
ctx.solid_last_solved
.lock()
.unwrap_or_else(PoisonError::into_inner)
.as_ref()
.filter(|(_, masts)| masts_aligned(tier_count, masts.len()))
.map(|(_, masts)| masts.clone())
}
pub(super) fn facet_map_for(
ctx: &Shared,
design_generation: u64,
design: &indicatrix_cut_core::Design,
) -> Option<Rc<FacetMap>> {
let (solved_generation, masts_len) = solved_summary(ctx)?;
if !masts_aligned(design.tiers.len(), masts_len) {
return None;
}
let cached = SESSION.with(|cell| {
cell.borrow()
.facet_map
.as_ref()
.filter(|c| {
c.design_generation == design_generation && c.solved_generation == solved_generation
})
.map(|c| Rc::clone(&c.map))
});
if cached.is_some() {
return cached;
}
let masts = aligned_masts(ctx, design.tiers.len())?;
let map = Rc::new(FacetMap::from_design(design, &masts));
SESSION.with(|cell| {
cell.borrow_mut().facet_map = Some(CachedFacetMap {
design_generation,
solved_generation,
map: Rc::clone(&map),
});
});
Some(map)
}
fn centroid_of(centroids: &[Option<Vec3>], facet_id: u32) -> Option<Vec3> {
centroids.get(facet_id as usize).copied().flatten()
}
pub(super) fn target_for(
geometry: &FrameGeometry,
map: &FacetMap,
design: &indicatrix_cut_core::Design,
tier: usize,
remembered: Option<u32>,
provisional: bool,
) -> Option<Target> {
let tier_data = design.tiers.get(tier)?;
let centroids = geometry.facet_centroids.as_slice();
if !ids_aligned(map.facet_count(), centroids.len()) {
return None;
}
let facet_id = pick_facet(
remembered,
tier,
map.facets_of_tier(tier),
|id| map.tier_of(id as usize),
|id| centroid_of(centroids, id).is_some(),
)?;
let centroid = centroid_of(centroids, facet_id)?;
let frame = FacetFrame::from_tier(
tier_data,
f64::from(map.index_on_gear(facet_id as usize)),
design.meta.gear_teeth_abs(),
centroid,
);
let pose = geometry.camera;
let camera = Camera::new(pose.yaw, pose.pitch, pose.distance, CAMERA_FOV_DEG);
let size = ScreenSize::new(geometry.size.0 as f32, geometry.size.1 as f32);
let length = (HANDLE_LENGTH_FRACTION * geometry.bounding_radius) as f32;
let layout = handle_layout(&frame, &camera, size, length)?;
Some(Target {
tier,
frame,
layout,
label: tier_label(tier_data, tier),
provisional,
})
}
fn place(ui: &MainWindow, ctx: &Shared) -> Option<Target> {
if ui.global::<SolidPreviewModel>().get_view_mode() == 3 {
return None;
}
let geometry = ctx
.geometry
.lock()
.unwrap_or_else(PoisonError::into_inner)
.clone()?;
if ui.global::<ManipulateModel>().get_slice_mode() {
return slice::place_provisional(&geometry);
}
let tier = usize::try_from(ui.global::<EditorModel>().get_selected_tier_index()).ok()?;
let st = ctx.state.try_borrow().ok()?;
let map = facet_map_for(ctx, st.current_generation(), &st.design)?;
target_for(
&geometry,
&map,
&st.design,
tier,
selected_facet_id(),
false,
)
}
pub(super) fn refresh_handles(ui: &MainWindow, ctx: &Shared) {
match place(ui, ctx) {
Some(target) => show(ui, target),
None => hide(ui),
}
}
fn show(ui: &MainWindow, target: Target) {
let scale = ui.window().scale_factor();
let logical = layout_to_logical(&target.layout, scale, pick_margin(ui));
let indexless = target.frame.is_indexless();
let model = ui.global::<ManipulateModel>();
model.set_anchor_x(logical.anchor.0);
model.set_anchor_y(logical.anchor.1);
model.set_angle_tip_x(logical.angle_tip.0);
model.set_angle_tip_y(logical.angle_tip.1);
model.set_depth_tip_x(logical.depth_tip.0);
model.set_depth_tip_y(logical.depth_tip.1);
model.set_index_tip_x(logical.index_tip.0);
model.set_index_tip_y(logical.index_tip.1);
model.set_index_visible(!indexless);
model.set_handles_visible(true);
SESSION.with(|cell| cell.borrow_mut().target = Some(target));
}
fn hide(ui: &MainWindow) {
let had_hint = SESSION.with(|cell| {
let mut session = cell.borrow_mut();
session.target = None;
session.hovered = None;
std::mem::take(&mut session.hover_hint_active)
});
let model = ui.global::<ManipulateModel>();
model.set_handles_visible(false);
model.set_hovered_handle(-1);
if had_hint {
slice::resting_hint(ui);
}
}
fn hit_kind_at(ui: &MainWindow, x: f32, y: f32) -> Option<HandleKind> {
let (layout, indexless) = SESSION.with(|cell| {
cell.borrow()
.target
.as_ref()
.map(|t| (t.layout, t.frame.is_indexless()))
})?;
let radius = HANDLE_HIT_RADIUS_PX * ui.window().scale_factor();
hit_kind(&layout, indexless, pointer_to_pick(ui, x, y), radius)
}
pub(super) fn hit_test_int(ui: &MainWindow, x: f32, y: f32) -> i32 {
hit_kind_at(ui, x, y).map_or(-1, kind_to_int)
}
fn hover_hint(ui: &MainWindow, ctx: &Shared, kind: HandleKind) -> Option<String> {
let (tier, label, provisional) = SESSION.with(|cell| {
cell.borrow()
.target
.as_ref()
.map(|t| (t.tier, t.label.clone(), t.provisional))
})?;
let meeting = if provisional {
0
} else {
ctx.state
.try_borrow()
.map_or(0, |st| tiers_meeting(&st.design, tier).len())
};
let snap = snap_mode(false, ui.global::<ManipulateModel>().get_snap_off());
Some(text::handle_hover_hint(kind, &label, meeting, snap))
}
fn apply_hover(ui: &MainWindow, ctx: &Shared, kind: Option<HandleKind>) {
let model = ui.global::<ManipulateModel>();
model.set_hovered_handle(kind.map_or(-1, kind_to_int));
let Some(hint) = kind.and_then(|kind| hover_hint(ui, ctx, kind)) else {
let had_hint =
SESSION.with(|cell| std::mem::take(&mut cell.borrow_mut().hover_hint_active));
if had_hint {
slice::resting_hint(ui);
}
return;
};
SESSION.with(|cell| cell.borrow_mut().hover_hint_active = true);
set_hint(ui, &hint);
}
pub(super) fn hover(ui: &MainWindow, ctx: &Shared, x: f32, y: f32) {
if ui.global::<ManipulateModel>().get_dragging() {
return;
}
let kind = hit_kind_at(ui, x, y);
let changed = SESSION.with(|cell| {
let mut session = cell.borrow_mut();
let changed = session.hovered != kind;
session.hovered = kind;
changed
});
if changed {
apply_hover(ui, ctx, kind);
}
}
pub(super) fn refresh_hover_hint(ui: &MainWindow, ctx: &Shared) {
if ui.global::<ManipulateModel>().get_dragging() {
return;
}
let hovered = SESSION.with(|cell| cell.borrow().hovered);
if hovered.is_some() {
apply_hover(ui, ctx, hovered);
}
}