1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
use crate::diagnostics::LookupError;
use crate::picking::{CursorPosition, Hit, Viewport};
use super::InteractiveGltfViewer;
impl InteractiveGltfViewer {
/// Registers a callback fired by [`Self::click_at`].
///
/// The callback receives the same typed result returned by `click_at`:
/// `Ok(Some(hit))` for a hit, `Ok(None)` for an empty click, and `Err(_)`
/// for picking failures. This keeps application glue on the viewer API
/// without bypassing the existing asset-aware picking path.
///
/// # Examples
///
/// ```no_run
/// # use scena::InteractiveGltfViewer;
/// # fn example(viewer: &mut InteractiveGltfViewer) {
/// viewer.on_click(|result| {
/// if let Ok(Some(hit)) = result {
/// println!("picked {:?}", hit.target());
/// }
/// });
/// # }
/// ```
pub fn on_click<F>(&mut self, callback: F) -> &mut Self
where
F: FnMut(std::result::Result<Option<Hit>, LookupError>) + 'static,
{
self.click_callback = Some(Box::new(callback));
self
}
/// Registers a callback fired by [`Self::hover_at`].
///
/// # Examples
///
/// ```no_run
/// # use scena::InteractiveGltfViewer;
/// # fn example(viewer: &mut InteractiveGltfViewer) {
/// viewer.on_hover(|result| {
/// if matches!(result, Ok(Some(_))) {
/// println!("hovering over a hit");
/// }
/// });
/// # }
/// ```
pub fn on_hover<F>(&mut self, callback: F) -> &mut Self
where
F: FnMut(std::result::Result<Option<Hit>, LookupError>) + 'static,
{
self.hover_callback = Some(Box::new(callback));
self
}
/// Clears the callback registered with [`Self::on_click`].
pub fn clear_click_callback(&mut self) -> &mut Self {
self.click_callback = None;
self
}
/// Clears the callback registered with [`Self::on_hover`].
pub fn clear_hover_callback(&mut self) -> &mut Self {
self.hover_callback = None;
self
}
/// Ray-picks the active scene at the given physical pointer coordinates
/// using the renderer's current target dimensions as the viewport. The
/// pick is asset-aware so glTF-imported mesh and instance-set nodes
/// participate alongside scene-owned renderables. `device_pixel_ratio` is
/// fixed at 1.0; high-DPR consumers should use
/// `viewer.scene.pick_with_assets(...)` directly.
pub fn pick_at(&self, physical_x: f32, physical_y: f32) -> Result<Option<Hit>, LookupError> {
let viewport = self.viewport_for_pick()?;
self.scene.pick_with_assets(
self.camera,
CursorPosition::physical(physical_x, physical_y),
viewport,
&self.assets,
)
}
/// Convenience for `pick_at` followed by promoting the hit to the scene's
/// primary selection and hover target. Mirrors
/// `Scene::pick_and_select_with_assets` against the active camera and
/// renderer dimensions.
pub fn pick_and_select_at(
&mut self,
physical_x: f32,
physical_y: f32,
) -> Result<Option<Hit>, LookupError> {
let viewport = self.viewport_for_pick()?;
self.scene.pick_and_select_with_assets(
self.camera,
CursorPosition::physical(physical_x, physical_y),
viewport,
&self.assets,
)
}
/// Picks and selects at physical pointer coordinates, then fires the
/// registered click callback with the same result.
pub fn click_at(
&mut self,
physical_x: f32,
physical_y: f32,
) -> Result<Option<Hit>, LookupError> {
let result = self.pick_and_select_at(physical_x, physical_y);
if let Some(callback) = &mut self.click_callback {
callback(result.clone());
}
result
}
/// Convenience for `pick_at` followed by setting the hovered hit. Mirrors
/// `Scene::pick_and_hover_with_assets` against the active camera and
/// renderer dimensions.
pub fn pick_and_hover_at(
&mut self,
physical_x: f32,
physical_y: f32,
) -> Result<Option<Hit>, LookupError> {
let viewport = self.viewport_for_pick()?;
self.scene.pick_and_hover_with_assets(
self.camera,
CursorPosition::physical(physical_x, physical_y),
viewport,
&self.assets,
)
}
/// Picks and updates hover at physical pointer coordinates, then fires the
/// registered hover callback with the same result.
pub fn hover_at(
&mut self,
physical_x: f32,
physical_y: f32,
) -> Result<Option<Hit>, LookupError> {
let result = self.pick_and_hover_at(physical_x, physical_y);
if let Some(callback) = &mut self.hover_callback {
callback(result.clone());
}
result
}
fn viewport_for_pick(&self) -> Result<Viewport, LookupError> {
let stats = self.renderer.stats();
Viewport::new(stats.target_width, stats.target_height, 1.0).ok_or(
LookupError::InvalidViewport {
width: stats.target_width,
height: stats.target_height,
},
)
}
}