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
use bevy::prelude::*;
use de_core::{
    frustum,
    gamestate::GameState,
    objects::{ObjectTypeComponent, Playable},
    schedule::InputSchedule,
    screengeom::ScreenRect,
};
use de_objects::SolidObjects;
use de_types::objects::ObjectType;

use crate::{
    frustum::ScreenFrustum,
    selection::{SelectEvent, SelectionMode, SelectionSet},
};

pub(super) struct AreaPlugin;

impl Plugin for AreaPlugin {
    fn build(&self, app: &mut App) {
        app.add_event::<SelectInRectEvent>().add_systems(
            InputSchedule,
            select_in_area
                .run_if(in_state(GameState::Playing))
                .in_set(AreaSelectSet::SelectInArea)
                .before(SelectionSet::Update),
        );
    }
}

#[derive(Copy, Clone, Hash, Debug, PartialEq, Eq, SystemSet)]
pub(crate) enum AreaSelectSet {
    SelectInArea,
}

#[derive(Event)]
pub(crate) struct SelectInRectEvent {
    rect: ScreenRect,
    mode: SelectionMode,
    filter_object_type: Option<ObjectType>,
}

impl SelectInRectEvent {
    pub(crate) fn new(
        rect: ScreenRect,
        mode: SelectionMode,
        filter_object_type: Option<ObjectType>,
    ) -> Self {
        Self {
            rect,
            mode,
            filter_object_type,
        }
    }

    fn rect(&self) -> ScreenRect {
        self.rect
    }

    fn mode(&self) -> SelectionMode {
        self.mode
    }

    fn filter_object_type(&self) -> Option<ObjectType> {
        self.filter_object_type
    }
}

fn select_in_area(
    screen_frustum: ScreenFrustum,
    solids: SolidObjects,
    candidates: Query<(Entity, &ObjectTypeComponent, &Transform), With<Playable>>,
    mut in_events: EventReader<SelectInRectEvent>,
    mut out_events: EventWriter<SelectEvent>,
) {
    for in_event in in_events.read() {
        let event_frustum = screen_frustum.rect(in_event.rect());
        let entities: Vec<Entity> = candidates
            .iter()
            .filter(|(_, &object_type, _)| {
                in_event
                    .filter_object_type()
                    .map_or(true, |filter| filter == *object_type)
            })
            .filter_map(|(entity, &object_type, &transform)| {
                let aabb = solids.get(*object_type).collider().aabb();
                if frustum::intersects_parry(&event_frustum, transform, &aabb) {
                    Some(entity)
                } else {
                    None
                }
            })
            .collect();
        out_events.send(SelectEvent::many(entities, in_event.mode()));
    }
}