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
131use bevy::{
input::{
gestures::PinchGesture,
mouse::{MouseScrollUnit, MouseWheel},
},
picking::events::{Drag, Pointer},
prelude::*,
};
#[derive(Default)]
pub struct PanCamPlugin;
impl Plugin for PanCamPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Update, (camera_movement, camera_zoom))
.add_systems(Startup, setup);
}
}
fn setup(mut commands: Commands) {
let pos = Vec3::new(-2.0, 1.0, 6.0);
commands.spawn((
Camera3d::default(),
Transform::from_translation(pos).looking_at(Vec3::ZERO, Vec3::Y),
PanCam {
bounds_min: pos.truncate(),
bounds_max: pos.truncate(),
..default()
},
));
}
fn camera_zoom(
mut query: Query<(&mut PanCam, &mut Transform)>,
mut scroll_events: EventReader<MouseWheel>,
mut pinch_events: EventReader<PinchGesture>,
) {
let pixels_per_line = 100.;
let mut scroll = scroll_events
.read()
.map(|ev| match ev.unit {
MouseScrollUnit::Pixel => ev.y,
MouseScrollUnit::Line => ev.y * pixels_per_line,
})
.sum::<f32>();
scroll += pinch_events.read().map(|gesture| gesture.0).sum::<f32>() * -100.;
if scroll == 0. {
return;
}
for (mut cam, mut pos) in &mut query {
let anim_start_pos = Vec3::new(-2.0, 1.0, 6.0);
let anim_start_transform =
Transform::from_translation(anim_start_pos).looking_at(Vec3::ZERO, Vec3::Y);
let anim_start_rotation = anim_start_transform.rotation;
let anim_end_pos = Vec3::new(0.0, 0.0, 6.0);
let anim_end_rotation = Quat::IDENTITY;
let start_bounds_min = Vec2::new(0., 0.);
let start_bounds_max = Vec2::new(0., 0.);
let end_bounds_min = Vec2::new(-2., -2.);
let end_bounds_max = Vec2::new(2., 2.);
let max_z = anim_end_pos.z;
let min_z = 1.0;
cam.current_zoom = (cam.current_zoom - scroll / 500.).clamp(0., 1.0);
let actual_zoom = ((cam.current_zoom - 0.2) / 0.8).clamp(0.0, 1.0);
let anim_progress = (cam.current_zoom / 0.2).clamp(0.0, 1.0);
let (bounds_min, bounds_max) = if anim_progress < 1. {
(
anim_start_pos.lerp(anim_end_pos, anim_progress).truncate(),
anim_start_pos.lerp(anim_end_pos, anim_progress).truncate(),
)
} else {
(
start_bounds_min.lerp(end_bounds_min, actual_zoom),
start_bounds_max.lerp(end_bounds_max, actual_zoom),
)
};
cam.bounds_min = bounds_min;
cam.bounds_max = bounds_max;
let rot = anim_start_rotation.slerp(anim_end_rotation, anim_progress);
pos.translation.x = pos.translation.x.max(bounds_min.x).min(bounds_max.x);
pos.translation.y = pos.translation.y.max(bounds_min.y).min(bounds_max.y);
pos.translation.z = max_z.lerp(min_z, actual_zoom);
pos.rotation = rot;
}
}
fn camera_movement(
mut query: Query<(&mut PanCam, &mut Transform)>,
mut drag_events: EventReader<Pointer<Drag>>,
) {
for e in drag_events.read() {
for (cam, mut transform) in &mut query {
// TODO observed board movement should be 1-1 with cursor movement
let delta = e.event.delta * Vec2::new(1., -1.) / cam.bounds_max.x / 40.;
let proposed_cam_transform = transform.translation - delta.extend(0.);
transform.translation = proposed_cam_transform;
transform.translation.x = transform
.translation
.x
.max(cam.bounds_min.x)
.min(cam.bounds_max.x);
transform.translation.y = transform
.translation
.y
.max(cam.bounds_min.y)
.min(cam.bounds_max.y);
}
}
}
#[derive(Default, Component)]
struct PanCam {
current_zoom: f32,
bounds_min: Vec2,
bounds_max: Vec2,
}