Skip to content

Commit 4552d67

Browse files
authored
Add touch controlls
1 parent ade294e commit 4552d67

File tree

4 files changed

+198
-11
lines changed

4 files changed

+198
-11
lines changed

public/index.html

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
<head>
55
<meta charset="utf-8">
66
<title>web-splat</title>
7-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
7+
<meta name="viewport"
8+
content="width=device-width, initial-scale=1.0, user-scalable=no, maximum-scale=1.0, minimum-scale=1.0">
89
<link rel="stylesheet" href="https://unpkg.com/normalize.css@8/normalize.css">
910
<style>
1011
body {
@@ -14,6 +15,16 @@
1415
background-color: black;
1516
color: white;
1617
font-family: Arial, Helvetica, sans-serif;
18+
touch-action: none;
19+
/* Prevent default touch behaviors like scrolling and zooming */
20+
-webkit-touch-callout: none;
21+
/* Disable callout on iOS */
22+
-webkit-user-select: none;
23+
/* Disable selection on iOS */
24+
-khtml-user-select: none;
25+
-moz-user-select: none;
26+
-ms-user-select: none;
27+
user-select: none;
1728
}
1829

1930
#spinner {
@@ -105,6 +116,17 @@
105116
text-align: center;
106117
height: 100%;
107118
}
119+
120+
canvas {
121+
touch-action: none;
122+
/* Prevent default touch behaviors */
123+
-webkit-touch-callout: none;
124+
-webkit-user-select: none;
125+
-khtml-user-select: none;
126+
-moz-user-select: none;
127+
-ms-user-select: none;
128+
user-select: none;
129+
}
108130
</style>
109131
<div id="spinner" style="display: none;">
110132
<svg>

src/controller.rs

Lines changed: 132 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,49 @@
11
use cgmath::*;
2-
#[cfg(target_arch = "wasm32")]
3-
use web_time::Duration;
42
use num_traits::Float;
53
use std::f32::consts::PI;
64
#[cfg(not(target_arch = "wasm32"))]
75
use std::time::Duration;
6+
#[cfg(target_arch = "wasm32")]
7+
use web_time::Duration;
88

99
use winit::keyboard::KeyCode;
1010

1111
use crate::camera::PerspectiveCamera;
1212

13+
#[derive(Debug, Clone)]
14+
pub struct TouchState {
15+
pub touches: Vec<Touch>,
16+
pub last_touch_count: usize,
17+
pub last_pinch_distance: Option<f32>,
18+
pub last_touch_center: Option<(f32, f32)>,
19+
}
20+
21+
#[derive(Debug, Clone)]
22+
pub struct Touch {
23+
pub id: u64,
24+
pub position: (f32, f32),
25+
pub phase: TouchPhase,
26+
}
27+
28+
#[derive(Debug, Clone, Copy, PartialEq)]
29+
pub enum TouchPhase {
30+
Started,
31+
Moved,
32+
Ended,
33+
Cancelled,
34+
}
35+
36+
impl TouchState {
37+
pub fn new() -> Self {
38+
Self {
39+
touches: Vec::new(),
40+
last_touch_count: 0,
41+
last_pinch_distance: None,
42+
last_touch_center: None,
43+
}
44+
}
45+
}
46+
1347
#[derive(Debug)]
1448
pub struct CameraController {
1549
pub center: Point3<f32>,
@@ -25,6 +59,9 @@ pub struct CameraController {
2559
pub right_mouse_pressed: bool,
2660
pub alt_pressed: bool,
2761
pub user_inptut: bool,
62+
63+
// Touch support
64+
pub touch_state: TouchState,
2865
}
2966

3067
impl CameraController {
@@ -42,6 +79,7 @@ impl CameraController {
4279
right_mouse_pressed: false,
4380
alt_pressed: false,
4481
user_inptut: false,
82+
touch_state: TouchState::new(),
4583
}
4684
}
4785

@@ -104,6 +142,98 @@ impl CameraController {
104142
self.user_inptut = true;
105143
}
106144

145+
pub fn process_touch(&mut self, touch: Touch) {
146+
// Update touch state
147+
match touch.phase {
148+
TouchPhase::Started => {
149+
self.touch_state.touches.push(touch);
150+
}
151+
TouchPhase::Moved => {
152+
if let Some(existing_touch) = self
153+
.touch_state
154+
.touches
155+
.iter_mut()
156+
.find(|t| t.id == touch.id)
157+
{
158+
existing_touch.position = touch.position;
159+
}
160+
}
161+
TouchPhase::Ended | TouchPhase::Cancelled => {
162+
self.touch_state.touches.retain(|t| t.id != touch.id);
163+
}
164+
}
165+
166+
self.handle_touch_gestures();
167+
self.user_inptut = true;
168+
}
169+
170+
fn handle_touch_gestures(&mut self) {
171+
let touch_count = self.touch_state.touches.len();
172+
173+
match touch_count {
174+
1 => {
175+
// Single touch - camera rotation
176+
let touch = &self.touch_state.touches[0];
177+
if let Some(last_center) = self.touch_state.last_touch_center {
178+
let dx = touch.position.0 - last_center.0;
179+
let dy = touch.position.1 - last_center.1;
180+
181+
// Scale the touch movement similar to mouse movement but with better mobile sensitivity
182+
self.rotation.x += dx * 0.3; // Reduced sensitivity for more precise control
183+
self.rotation.y += dy * 0.3;
184+
}
185+
self.touch_state.last_touch_center = Some(touch.position);
186+
}
187+
2 => {
188+
// Two touches - pinch to zoom and pan
189+
let touch1 = &self.touch_state.touches[0];
190+
let touch2 = &self.touch_state.touches[1];
191+
192+
let center_x = (touch1.position.0 + touch2.position.0) / 2.0;
193+
let center_y = (touch1.position.1 + touch2.position.1) / 2.0;
194+
let current_center = (center_x, center_y);
195+
196+
// Calculate distance for pinch gesture
197+
let dx = touch2.position.0 - touch1.position.0;
198+
let dy = touch2.position.1 - touch1.position.1;
199+
let current_distance = (dx * dx + dy * dy).sqrt();
200+
201+
if let Some(last_distance) = self.touch_state.last_pinch_distance {
202+
// Pinch to zoom with improved sensitivity
203+
let distance_change = current_distance - last_distance;
204+
let zoom_factor = distance_change * 0.005; // Adjusted for better mobile zoom control
205+
self.scroll += zoom_factor;
206+
}
207+
208+
if let Some(last_center) = self.touch_state.last_touch_center {
209+
// Pan with two fingers - improved sensitivity for mobile
210+
let center_dx = current_center.0 - last_center.0;
211+
let center_dy = current_center.1 - last_center.1;
212+
213+
self.shift.y += -center_dx * 0.3; // Reduced sensitivity for more precise panning
214+
self.shift.x += center_dy * 0.3;
215+
}
216+
217+
self.touch_state.last_pinch_distance = Some(current_distance);
218+
self.touch_state.last_touch_center = Some(current_center);
219+
}
220+
_ => {
221+
// No touches or more than 2 touches - reset state
222+
self.touch_state.last_pinch_distance = None;
223+
self.touch_state.last_touch_center = None;
224+
}
225+
}
226+
227+
self.touch_state.last_touch_count = touch_count;
228+
}
229+
230+
pub fn clear_touch_state(&mut self) {
231+
self.touch_state.touches.clear();
232+
self.touch_state.last_touch_count = 0;
233+
self.touch_state.last_pinch_distance = None;
234+
self.touch_state.last_touch_center = None;
235+
}
236+
107237
/// moves the controller center to the closest point on a line defined by the camera position and rotation
108238
/// ajusts the controller up vector by projecting the current up vector onto the plane defined by the camera right vector
109239
pub fn reset_to_camera(&mut self, camera: PerspectiveCamera) {

src/lib.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ use utils::RingBuffer;
2424
use wasm_bindgen::prelude::wasm_bindgen;
2525
use winit::{
2626
dpi::{LogicalSize, PhysicalSize},
27-
event::{DeviceEvent, ElementState, Event, WindowEvent},
27+
event::{DeviceEvent, ElementState, Event, WindowEvent, TouchPhase as WinitTouchPhase},
2828
event_loop::{ControlFlow, EventLoop},
2929
keyboard::{KeyCode, PhysicalKey},
3030
window::Window,
@@ -849,6 +849,22 @@ pub async fn open_window<R: Read + Seek + Send + Sync + 'static>(
849849
_=>{}
850850
}
851851
}
852+
WindowEvent::Touch(touch) => {
853+
let touch_phase = match touch.phase {
854+
WinitTouchPhase::Started => controller::TouchPhase::Started,
855+
WinitTouchPhase::Moved => controller::TouchPhase::Moved,
856+
WinitTouchPhase::Ended => controller::TouchPhase::Ended,
857+
WinitTouchPhase::Cancelled => controller::TouchPhase::Cancelled,
858+
};
859+
860+
let controller_touch = controller::Touch {
861+
id: touch.id,
862+
position: (touch.location.x as f32, touch.location.y as f32),
863+
phase: touch_phase,
864+
};
865+
866+
state.controller.process_touch(controller_touch);
867+
}
852868
WindowEvent::RedrawRequested => {
853869
if !config.no_vsync{
854870
// make sure the next redraw is called with a small delay

src/ui.rs

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -334,29 +334,48 @@ pub(crate) fn ui(state: &mut WindowContext) -> bool {
334334
.num_columns(2)
335335
.striped(true)
336336
.show(ui, |ui| {
337-
ui.strong("Camera");
337+
ui.strong("Camera Controls");
338338
ui.end_row();
339+
340+
// Desktop controls
339341
ui.label("Rotate Camera");
340-
ui.label("Left click + drag");
342+
ui.label("Left click + drag / Touch + drag");
341343
ui.end_row();
342344

343345
ui.label("Move Target/Center");
344-
ui.label("Right click + drag");
346+
ui.label("Right click + drag / Two finger drag");
345347
ui.end_row();
346348

347349
ui.label("Tilt Camera");
348350
ui.label("Alt + drag mouse");
349351
ui.end_row();
350352

351353
ui.label("Zoom");
352-
ui.label("Mouse wheel");
354+
ui.label("Mouse wheel / Pinch gesture");
353355
ui.end_row();
354356

355-
ui.label("Toggle UI");
356-
ui.label("U");
357+
ui.separator();
358+
ui.end_row();
359+
360+
ui.strong("Mobile Touch Controls");
361+
ui.end_row();
362+
ui.label("Rotate");
363+
ui.label("Single finger drag");
364+
ui.end_row();
365+
ui.label("Pan/Move");
366+
ui.label("Two finger drag");
367+
ui.end_row();
368+
ui.label("Zoom");
369+
ui.label("Pinch to zoom");
370+
ui.end_row();
371+
372+
ui.separator();
357373
ui.end_row();
358374

359-
ui.strong("Scene Views");
375+
ui.strong("Scene Navigation");
376+
ui.end_row();
377+
ui.label("Toggle UI");
378+
ui.label("U");
360379
ui.end_row();
361380
ui.label("Views 0-9");
362381
ui.label("0-9");

0 commit comments

Comments
 (0)