1use crate::diagnostics::LookupError;
4use crate::scene::Vec3;
5use crate::scene::{CameraKey, Scene, Transform};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8#[non_exhaustive]
9pub enum PointerButton {
10 Primary,
11 Secondary,
12 Auxiliary,
13}
14
15#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16#[non_exhaustive]
17pub enum PointerEventKind {
18 Pressed,
19 Released,
20 Moved,
21 Wheel,
22 Cancelled,
23}
24
25#[derive(Debug, Clone, Copy, PartialEq)]
26pub struct PointerEvent {
27 pub kind: PointerEventKind,
28 pub position: (f32, f32),
29 pub button: Option<PointerButton>,
30 pub delta: (f32, f32),
31 pub scroll_delta: f32,
32}
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35#[non_exhaustive]
36pub enum TouchEventKind {
37 Started,
38 Moved,
39 Pinched,
40 Ended,
41 Cancelled,
42}
43
44#[derive(Debug, Clone, Copy, PartialEq)]
45pub struct TouchEvent {
46 pub kind: TouchEventKind,
47 pub position: (f32, f32),
48 pub delta: (f32, f32),
49 pub pinch_delta: f32,
50}
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53#[non_exhaustive]
54pub enum OrbitControlAction {
55 None,
56 BeginOrbit,
57 Orbit,
58 Pan,
59 Zoom,
60 End,
61}
62
63#[derive(Debug, Clone, Copy, PartialEq)]
64pub struct OrbitControls {
65 target: Vec3,
66 distance: f32,
67 yaw_radians: f32,
68 pitch_radians: f32,
69 damping_factor: f32,
70 orbiting: bool,
71 panning: bool,
72}
73
74impl OrbitControls {
75 pub fn new(target: Vec3, distance: f32) -> Self {
76 Self {
77 target,
78 distance: distance.max(MIN_DISTANCE),
79 yaw_radians: 0.0,
80 pitch_radians: 0.0,
81 damping_factor: 0.0,
82 orbiting: false,
83 panning: false,
84 }
85 }
86
87 pub fn focus(mut self, target: Vec3, distance: f32) -> Self {
88 self.target = target;
89 self.distance = distance.max(MIN_DISTANCE);
90 self
91 }
92
93 pub fn with_damping(mut self, factor: f32) -> Self {
94 self.damping_factor = if factor.is_finite() {
95 factor.clamp(0.0, 1.0)
96 } else {
97 0.0
98 };
99 self
100 }
101
102 pub fn handle_pointer(&mut self, event: PointerEvent) -> OrbitControlAction {
103 match event.kind {
104 PointerEventKind::Pressed => match event.button {
105 Some(PointerButton::Primary) => {
106 self.orbiting = true;
107 OrbitControlAction::BeginOrbit
108 }
109 Some(PointerButton::Secondary) => {
110 self.panning = true;
111 OrbitControlAction::Pan
112 }
113 Some(PointerButton::Auxiliary) | None => OrbitControlAction::None,
114 },
115 PointerEventKind::Moved if self.orbiting => {
116 self.yaw_radians += event.delta.0 * ORBIT_RADIANS_PER_PIXEL;
117 self.pitch_radians = (self.pitch_radians + event.delta.1 * ORBIT_RADIANS_PER_PIXEL)
118 .clamp(-MAX_PITCH_RADIANS, MAX_PITCH_RADIANS);
119 OrbitControlAction::Orbit
120 }
121 PointerEventKind::Moved if self.panning => {
122 self.target.x -= event.delta.0 * PAN_UNITS_PER_PIXEL * self.distance;
123 self.target.y += event.delta.1 * PAN_UNITS_PER_PIXEL * self.distance;
124 OrbitControlAction::Pan
125 }
126 PointerEventKind::Wheel => {
127 let zoom = (1.0 + event.scroll_delta * ZOOM_SCALE).max(0.05);
128 self.distance = (self.distance * zoom).max(MIN_DISTANCE);
129 OrbitControlAction::Zoom
130 }
131 PointerEventKind::Released | PointerEventKind::Cancelled => {
132 self.orbiting = false;
133 self.panning = false;
134 OrbitControlAction::End
135 }
136 PointerEventKind::Moved => OrbitControlAction::None,
137 }
138 }
139
140 pub fn handle_touch(&mut self, event: TouchEvent) -> OrbitControlAction {
141 match event.kind {
142 TouchEventKind::Started => {
143 self.orbiting = true;
144 OrbitControlAction::BeginOrbit
145 }
146 TouchEventKind::Moved if self.orbiting => {
147 self.apply_orbit_delta(event.delta);
148 OrbitControlAction::Orbit
149 }
150 TouchEventKind::Pinched => {
151 self.apply_zoom_delta(event.pinch_delta);
152 OrbitControlAction::Zoom
153 }
154 TouchEventKind::Ended | TouchEventKind::Cancelled => {
155 self.orbiting = false;
156 self.panning = false;
157 OrbitControlAction::End
158 }
159 TouchEventKind::Moved => OrbitControlAction::None,
160 }
161 }
162
163 pub const fn target(&self) -> Vec3 {
164 self.target
165 }
166
167 pub const fn distance(&self) -> f32 {
168 self.distance
169 }
170
171 pub const fn yaw_radians(&self) -> f32 {
172 self.yaw_radians
173 }
174
175 pub const fn pitch_radians(&self) -> f32 {
176 self.pitch_radians
177 }
178
179 pub const fn damping_factor(&self) -> f32 {
180 self.damping_factor
181 }
182
183 pub fn apply_to_scene(&self, scene: &mut Scene, camera: CameraKey) -> Result<(), LookupError> {
184 let camera_node = scene
185 .camera_node(camera)
186 .ok_or(LookupError::CameraNotFound(camera))?;
187 let offset = self.camera_offset();
188 scene.align_to(
189 camera_node,
190 Transform::at(Vec3::new(
191 self.target.x + offset.x,
192 self.target.y + offset.y,
193 self.target.z + offset.z,
194 )),
195 )?;
196 scene.ensure_camera_depth_reaches(camera, self.distance)?;
197 scene.look_at_point(camera, self.target)
198 }
199
200 fn camera_offset(&self) -> Vec3 {
201 let pitch_cos = self.pitch_radians.cos();
202 Vec3::new(
203 self.distance * self.yaw_radians.sin() * pitch_cos,
204 self.distance * self.pitch_radians.sin(),
205 self.distance * self.yaw_radians.cos() * pitch_cos,
206 )
207 }
208
209 fn apply_orbit_delta(&mut self, delta: (f32, f32)) {
210 self.yaw_radians += delta.0 * ORBIT_RADIANS_PER_PIXEL;
211 self.pitch_radians = (self.pitch_radians + delta.1 * ORBIT_RADIANS_PER_PIXEL)
212 .clamp(-MAX_PITCH_RADIANS, MAX_PITCH_RADIANS);
213 }
214
215 fn apply_zoom_delta(&mut self, delta: f32) {
216 let zoom = (1.0 + delta * ZOOM_SCALE).max(0.05);
217 self.distance = (self.distance * zoom).max(MIN_DISTANCE);
218 }
219}
220
221impl PointerEvent {
222 pub const fn primary_pressed(x: f32, y: f32) -> Self {
223 Self::pressed(x, y, PointerButton::Primary)
224 }
225
226 pub const fn secondary_pressed(x: f32, y: f32) -> Self {
227 Self::pressed(x, y, PointerButton::Secondary)
228 }
229
230 pub const fn released(x: f32, y: f32) -> Self {
231 Self {
232 kind: PointerEventKind::Released,
233 position: (x, y),
234 button: None,
235 delta: (0.0, 0.0),
236 scroll_delta: 0.0,
237 }
238 }
239
240 pub const fn moved(x: f32, y: f32, delta_x: f32, delta_y: f32) -> Self {
241 Self {
242 kind: PointerEventKind::Moved,
243 position: (x, y),
244 button: None,
245 delta: (delta_x, delta_y),
246 scroll_delta: 0.0,
247 }
248 }
249
250 pub const fn wheel(x: f32, y: f32, scroll_delta: f32) -> Self {
251 Self {
252 kind: PointerEventKind::Wheel,
253 position: (x, y),
254 button: None,
255 delta: (0.0, 0.0),
256 scroll_delta,
257 }
258 }
259
260 const fn pressed(x: f32, y: f32, button: PointerButton) -> Self {
261 Self {
262 kind: PointerEventKind::Pressed,
263 position: (x, y),
264 button: Some(button),
265 delta: (0.0, 0.0),
266 scroll_delta: 0.0,
267 }
268 }
269}
270
271impl TouchEvent {
272 pub const fn start(x: f32, y: f32) -> Self {
273 Self {
274 kind: TouchEventKind::Started,
275 position: (x, y),
276 delta: (0.0, 0.0),
277 pinch_delta: 0.0,
278 }
279 }
280
281 pub const fn move_by(x: f32, y: f32, delta_x: f32, delta_y: f32) -> Self {
282 Self {
283 kind: TouchEventKind::Moved,
284 position: (x, y),
285 delta: (delta_x, delta_y),
286 pinch_delta: 0.0,
287 }
288 }
289
290 pub const fn pinch(x: f32, y: f32, pinch_delta: f32) -> Self {
291 Self {
292 kind: TouchEventKind::Pinched,
293 position: (x, y),
294 delta: (0.0, 0.0),
295 pinch_delta,
296 }
297 }
298
299 pub const fn end(x: f32, y: f32) -> Self {
300 Self {
301 kind: TouchEventKind::Ended,
302 position: (x, y),
303 delta: (0.0, 0.0),
304 pinch_delta: 0.0,
305 }
306 }
307
308 pub const fn cancel(x: f32, y: f32) -> Self {
309 Self {
310 kind: TouchEventKind::Cancelled,
311 position: (x, y),
312 delta: (0.0, 0.0),
313 pinch_delta: 0.0,
314 }
315 }
316}
317
318const ORBIT_RADIANS_PER_PIXEL: f32 = 0.01;
319const PAN_UNITS_PER_PIXEL: f32 = 0.001;
320const ZOOM_SCALE: f32 = 0.1;
321const MIN_DISTANCE: f32 = 0.001;
322const MAX_PITCH_RADIANS: f32 = 1.553_343;