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
use crate::*;
use bevy::prelude::*;
pub fn cursor_plugin(app: &mut App) {
app.init_resource::<CursorGroundCoords>();
app.add_systems(Update, cursor_to_ground_plane);
app.add_systems(Update, look_at_cursor);
}
fn look_at_cursor(
gc: Res<CursorGroundCoords>,
mut q: Query<(&mut Transform, &Cc), With<Player>>,
time: Res<Time>,
) {
let Ok((mut transform, cc)) = q.get_single_mut() else {
return;
};
let look_target = if let Some(d) = cc.destination {
d
} else {
gc.global.xz()
};
let target_position = Vec3::new(look_target.x, transform.translation.y, look_target.y);
let direction = target_position - transform.translation;
// Check if the direction vector is not zero
if direction.length_squared() > 0.0 {
let normalized_direction = direction.normalize();
// Calculate the desired rotation
let desired_rotation = Quat::from_rotation_arc(Vec3::Z, normalized_direction);
// Maximum rotation speed in radians per second
let max_rotation_speed = 5.0; // Adjust this value as needed
let dt = time.delta_secs();
// Interpolate the current rotation towards the desired rotation
transform.rotation = transform
.rotation
.slerp(desired_rotation, max_rotation_speed * dt);
}
}
/// Here we will store the position of the mouse cursor on the 3D ground plane.
#[derive(Resource, Default)]
pub struct CursorGroundCoords {
// Global (world-space) coordinates
pub global: Vec3,
// Local (relative to the ground plane) coordinates
pub local: Vec2,
}
/// Used to help identify our ground plane
#[derive(Component)]
pub struct GroundPlaneMarker;
fn cursor_to_ground_plane(
mut mycoords: ResMut<CursorGroundCoords>,
// query to get the window (so we can read the current cursor position)
// (we will only work with the primary window)
q_window: Query<&Window, With<PrimaryWindow>>,
// query to get camera transform
q_camera: Query<(&Camera, &GlobalTransform), With<Camera3d>>,
// query to get ground plane's transform
q_plane: Query<&GlobalTransform, With<GroundPlaneMarker>>,
) {
// get the camera info and transform
// assuming there is exactly one main camera entity, so Query::single() is OK
let (camera, camera_transform) = q_camera.single();
// Ditto for the ground plane's transform
let ground_transform = q_plane.single();
// There is only one primary window, so we can similarly get it from the query:
let window = q_window.single();
// check if the cursor is inside the window and get its position
let Some(cursor_position) = window.cursor_position() else {
// if the cursor is not inside the window, we can't do anything
return;
};
// Mathematically, we can represent the ground as an infinite flat plane.
// To do that, we need a point (to position the plane) and a normal vector
// (the "up" direction, perpendicular to the ground plane).
// We can get the correct values from the ground entity's GlobalTransform
let plane_origin = ground_transform.translation();
let plane = InfinitePlane3d::new(ground_transform.up());
// Ask Bevy to give us a ray pointing from the viewport (screen) into the world
let Ok(ray) = camera.viewport_to_world(camera_transform, cursor_position) else {
// if it was impossible to compute for whatever reason; we can't do anything
return;
};
// do a ray-plane intersection test, giving us the distance to the ground
let Some(distance) = ray.intersect_plane(plane_origin, plane) else {
// If the ray does not intersect the ground
// (the camera is not looking towards the ground), we can't do anything
return;
};
// use the distance to compute the actual point on the ground in world-space
let global_cursor = ray.get_point(distance);
mycoords.global = global_cursor;
// eprintln!(
// "Global cursor coords: {}/{}/{}",
// global_cursor.x, global_cursor.y, global_cursor.z
// );
// to compute the local coordinates, we need the inverse of the plane's transform
let inverse_transform_matrix = ground_transform.compute_matrix().inverse();
let local_cursor = inverse_transform_matrix.transform_point3(global_cursor);
// we can discard the Y coordinate, because it should always be zero
// (our point is supposed to be on the plane)
mycoords.local = local_cursor.xz();
// eprintln!("Local cursor coords: {}/{}", local_cursor.x, local_cursor.z);
}