use super::event::{Hit, Sample};
use super::gestures::Gesture;
use super::marks::Shown;
use super::{Device, View};
use crate::gpu::readback::Readback;
use crate::gpu::shaders as wgsl;
use crate::{Camera, Row};
#[derive(Clone, Debug)]
pub(crate) struct Probed {
pub(crate) hit: Hit,
pub(crate) depth: f32,
pub(crate) end: u32,
}
#[derive(Clone, Debug, Default)]
pub(crate) struct Answer {
pub(crate) hit: Option<Probed>,
pub(crate) under: f32,
}
#[derive(Default)]
pub(crate) struct Probing {
pub(crate) wanted: Option<[f32; 2]>,
reading: Option<([f32; 2], Readback)>,
pub(crate) latest: Option<([f32; 2], Answer)>,
}
impl Probing {
pub(crate) fn due(&self) -> bool {
self.wanted.is_some() && self.reading.is_none()
}
pub(crate) fn hovered(&self) -> Option<&Probed> {
self.latest.as_ref().and_then(|(_, answer)| answer.hit.as_ref())
}
pub(crate) fn at(&self, point: [f32; 2]) -> Option<Answer> {
let (asked, answer) = self.latest.as_ref()?;
(self.wanted.is_none() && self.reading.is_none() && near(*asked, point, 2.0)).then(|| answer.clone())
}
}
pub(crate) fn near(a: [f32; 2], b: [f32; 2], within: f32) -> bool {
(a[0] - b[0]).hypot(a[1] - b[1]) <= within
}
impl View {
pub(super) fn probe_point(&mut self, answers: bool) -> Option<[f32; 2]> {
if (self.changed || self.easing())
&& matches!(self.gestures.now, Gesture::Idle)
&& self.gestures.keys.at.is_none()
&& let Some(pointer) = self.pointer
{
self.probing.wanted.get_or_insert(pointer.point);
}
if self.probing.reading.is_some() || !answers {
return None;
}
let point = self.probing.wanted.take();
point.filter(|_| self.marks.iter().any(Shown::drawable))
}
pub(super) fn probe_radius(&self) -> f32 {
let touch = self.pointer.is_some_and(|pointer| pointer.device == Device::Touch);
self.scene.controls.pick_radius + if touch { 8.0 } else { 0.0 }
}
pub(super) fn probing(&mut self, point: [f32; 2], readback: Readback) {
self.probing.reading = Some((point, readback));
}
pub(super) fn settle_probe(&mut self) {
let Some((point, readback)) = &self.probing.reading else { return };
let Some(bytes) = readback.take() else { return };
let point = *point;
self.probing.reading = None;
let answer = bytes.map_or_else(|_| Answer::default(), |bytes| self.answer(bytemuck::cast_slice(&bytes)));
self.landed(point, answer);
}
fn answer(&self, words: &[u32]) -> Answer {
let word = |i: u32| words.get(i as usize).copied().unwrap_or(0);
Answer { hit: self.hit(&word), under: f32::from_bits(word(wgsl::PROBE_UNDER)) }
}
fn hit(&self, word: &impl Fn(u32) -> u32) -> Option<Probed> {
let key = word(wgsl::PROBE_KEY);
if key == wgsl::NONE || word(wgsl::PROBE_INSTANCE) == 0 {
return None;
}
let row = word(wgsl::PROBE_ROW);
let mark = 255 - (key & 255) as usize;
let shown = self.marks.get(mark)?;
let style = shown.mark.style();
let id = self.drawn(&style.data)?.table(&style.table)?.ids.id(row as usize)?;
let depth = f32::from_bits(word(wgsl::PROBE_DEPTH));
let point = [f32::from_bits(word(wgsl::PROBE_X)), f32::from_bits(word(wgsl::PROBE_Y))];
let view = self.projected();
let position = view.position(view.unproject(point.map(f64::from), f64::from(depth)));
let hit = Hit {
row: Row { data: style.data.clone(), table: style.table.clone(), id },
mark: style.name.clone(),
index: mark,
point,
position,
distance: (key >> 8) as f32 / 64.0,
port: self.port(mark, word(wgsl::PROBE_SEGMENT)),
sample: self.sample(mark, row, word(wgsl::PROBE_SEGMENT)),
};
Some(Probed { hit, depth, end: word(wgsl::PROBE_SEGMENT) })
}
pub(super) fn port(&self, mark: usize, number: u32) -> Option<crate::Name> {
let shown = self.marks.get(mark)?;
if shown.kind() != crate::scene::mark::MarkKind::Vertices || number == 0 {
return None;
}
let compiled = shown.compiled.as_ref()?;
compiled.drawing.ports.get(number as usize - 1).map(|(name, ..)| name.clone())
}
pub(super) fn sample(&self, mark: usize, row: u32, point: u32) -> Option<Sample> {
let shown = self.marks.get(mark)?;
let crate::Mark::Lines(lines) = &shown.mark else { return None };
let (level, index, side) = shown.tables.plan()?.entry(point)?;
let style = shown.mark.style();
let table = self.drawn(&style.data)?.table(&style.table)?;
let crate::data::Field::Sampled(samples, _) = table.field_named(&lines.traced())? else { return None };
let span = crate::data::bucket_frames(level);
let frame = index * span + span / 2;
let value = samples.entry(row as usize, level, side, index)?;
Some(Sample { frame, at: samples.drawn_at(level, index)?, value })
}
pub(super) fn anchor(&self, point: [f32; 2], answer: Option<&Answer>) -> [f64; 3] {
let view = self.projected();
let p = point.map(f64::from);
let drawn = answer.and_then(|a| if a.under > 0.0 { Some(a.under) } else { a.hit.as_ref().map(|h| h.depth) });
if let Some(depth) = drawn {
return view.position(view.unproject(p, f64::from(depth)));
}
let Camera::Map { center, .. } = self.camera else {
return view.on_plane(p, 0.0).map_or([0.0; 3], |w| view.position(w));
};
let reach = 50.0 * (view.eye - view.world(center)).length();
let level = if view.globe >= 0.5 { view.surface(p) } else { view.on_plane(p, center[2]) };
if let Some(w) = level.filter(|w| view.grazing(p) > 0.1 && (*w - view.eye).length() < reach) {
view.position(w)
} else {
let depth = view.screen(view.world(center)).map_or(0.5, |s| s[2]);
view.position(view.unproject(p, depth))
}
}
}
impl super::marks::Shown {
pub(crate) fn probed(&self) -> bool {
self.drawable() && self.mark.picks() && self.rows > 0
}
}