1use ambient_core::{
2 camera::{get_active_camera, clip_space_ray},
3 player::{local_user_id, user_id},
4 transform::local_to_world,
5 ui_scene,
6 window::{cursor_position, window_logical_size},
7};
8use ambient_ecs::{components, query, Debuggable, Entity, EntityId, MaybeResource, SystemGroup};
9use ambient_std::shapes::{RayIntersectable, AABB};
10use glam::Vec2;
11
12pub use ambient_ecs::generated::components::core::input::{mouse_over, mouse_pickable_max, mouse_pickable_min};
13
14components!("input", {
15 @[MaybeResource, Debuggable]
16 picker_intersecting: Option<PickerIntersection>,
17
18 @[Debuggable]
19 mouse_pickable: AABB,
20});
21
22#[derive(Debug, Clone, Copy)]
23pub struct PickerIntersection {
24 pub entity: EntityId,
25 pub distance: f32,
26}
27
28pub fn resources() -> Entity {
29 Entity::new().with_default(picker_intersecting())
30}
31
32pub fn frame_systems() -> SystemGroup {
33 SystemGroup::new(
34 "picking",
35 vec![
36 query((mouse_pickable_min().changed(), mouse_pickable_max().changed())).to_system(|q, world, qs, _| {
37 for (id, (min, max)) in q.collect_cloned(world, qs) {
38 world.add_component(id, mouse_pickable(), AABB { min, max }).unwrap();
39 }
40 }),
41 query((window_logical_size(), cursor_position())).to_system(|q, world, qs, _| {
42 for (id, (window_size, mouse_position)) in q.collect_cloned(world, qs) {
43 let mut mouse_origin = -Vec2::ONE + (mouse_position / window_size.as_vec2()) * 2.;
44 mouse_origin.y = -mouse_origin.y;
45 let camera = match get_active_camera(
46 world,
47 ui_scene(),
48 world.get_ref(id, user_id()).ok().or_else(|| world.resource_opt(local_user_id())),
49 ) {
50 Some(cam) => cam,
51 None => return,
52 };
53 let ray = clip_space_ray(world, camera, mouse_origin).unwrap_or_default();
54
55 let prev_intersecting = world.get(id, picker_intersecting()).unwrap_or_default();
56
57 let mut intersecting: Option<PickerIntersection> = None;
58 for (id2, (pickable, local_to_world)) in query((mouse_pickable(), local_to_world())).iter(world, None) {
59 if local_to_world.is_nan() {
60 continue;
61 }
62 let ray = ray.transform(local_to_world.inverse());
63 if let Some(dist) = pickable.ray_intersect(ray) {
64 if intersecting.is_none() || dist < intersecting.as_ref().unwrap().distance {
65 intersecting = Some(PickerIntersection { entity: id2, distance: dist });
66 }
67 }
68 }
69 let prev_intersecting_entity = prev_intersecting.map(|x| x.entity);
70 let intersecting_entity = intersecting.map(|x| x.entity);
71 if prev_intersecting_entity != intersecting_entity {
72 if let Some(prev) = prev_intersecting_entity {
73 if let Ok(prev_mouse_over) = world.get(prev, mouse_over()) {
74 if prev_mouse_over > 0 {
75 world.add_component(prev, mouse_over(), prev_mouse_over - 1).unwrap();
76 }
77 }
78 }
79 if let Some(new) = intersecting_entity {
80 let new_mouse_over = world.get(new, mouse_over()).unwrap_or_default();
81 world.add_component(new, mouse_over(), new_mouse_over + 1).unwrap();
82 }
83 }
84 world.add_component(id, picker_intersecting(), intersecting).unwrap();
85 }
86 }),
87 ],
88 )
89}