aboutsummaryrefslogtreecommitdiff
path: root/src/lib.rs
blob: de42adf598768ed7d8ec6b4aab9bcdd02ee4819d (plain)
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
131
#![warn(missing_docs)]

//! This library provides basic "interaction" functionality
//! Entities which can trigger interactions get the `Interactor` component, and entities which can
//! Be interacted with get the `Interactable` component. An `InteractorFiredEvent` can be invoked for
//! a given `Interactor`, which does the following:
//!   1. Checks to make sure there is at least 1 `Interactable` in range
//!   2. If the nearest `Interactable` is "exclusive", an `InteractionEvent` is invoked for only that
//!      entity
//!   3. If the nearest `Interactable` is *not* "exclusive", an individual `InteractionEvent` is invoked for
//!      that entity and each "non-exclusive" `Interactable` entity within range

use std::f32::consts::PI;

use bevy::prelude::*;
use components::{Interactable, Interactor};
use events::{InteractionEvent, InteractorFiredEvent};

pub mod components;
pub mod events;

/// Plugin which enables interaction functionality.
/// Sets up event handling for `InteractorFiredEvent` to automatically trigger the correct
/// `InteractionEvent`s
pub struct InteractionPlugin;

impl Plugin for InteractionPlugin {
    fn build(&self, app: &mut App) {
        app.add_systems(
            Update,
            (handle_interactor_events, update_interactor_targets, update_interactable_predicates),
        )
        .add_event::<InteractorFiredEvent>()
        .add_event::<InteractionEvent>();
    }
}

fn handle_interactor_events(
    mut interactor_events: EventReader<InteractorFiredEvent>,
    interactable_query: Query<&Interactable>,
    interactor_query: Query<&Interactor>,
    mut event_writer: EventWriter<InteractionEvent>,
) {
    for InteractorFiredEvent(interactor_entity) in interactor_events.read() {
        let interactor = interactor_query.get(*interactor_entity).unwrap();

        if let Some(interactable_entity) = interactor.closest {
            let interactable = interactable_query.get(interactable_entity).unwrap();
            if interactable.exclusive && interactable.possible {
                event_writer.send(InteractionEvent {
                    interactor: *interactor_entity,
                    interactable: interactable_entity,
                });
                continue;
            } else {
                for interactable_entity in &interactor.targets {
                    let interactable = interactable_query.get(*interactable_entity).unwrap();
                    if !interactable.exclusive && interactable.possible {
                        event_writer.send(InteractionEvent {
                            interactor: *interactor_entity,
                            interactable: *interactable_entity,
                        });
                    }
                }
            }
        }
    }
}

fn update_interactor_targets(
    mut interactable_query: Query<(Entity, &Interactable)>,
    mut interactor_query: Query<(Entity, &mut Interactor)>,
    transform_query: Query<&GlobalTransform>,
) {
    for (interactor_entity, mut interactor) in interactor_query.iter_mut() {
        let interactor_transform = transform_query.get(interactor_entity).unwrap();

        let mut closest_active_interactable: Option<(f32, Entity)> = None;
        interactor.targets.clear();
        for (interactable_entity, interactable) in interactable_query.iter_mut() {
            if !interactable.enabled {
                continue;
            }
            let interactable_transform = transform_query.get(interactable_entity).unwrap();
            let interactable_distance_squared = interactable_transform
                .translation()
                .distance_squared(interactor_transform.translation());
            let interactable_arccosine = f32::acos(
                interactor_transform.forward().dot(
                    (interactable_transform.translation() - interactor_transform.translation())
                        .normalize(),
                ),
            );
            if interactable_distance_squared < interactable.max_distance_squared
                && interactable_arccosine < PI / 4.0
            {
                interactor.targets.insert(interactable_entity);
                if let Some((arccosine, _)) = closest_active_interactable {
                    if interactable_arccosine < arccosine {
                        closest_active_interactable =
                            Some((interactable_arccosine, interactable_entity));
                    }
                } else {
                    closest_active_interactable =
                        Some((interactable_arccosine, interactable_entity));
                }
            }
        }
        interactor.closest = if let Some((_, interactable_entity)) = closest_active_interactable {
            Some(interactable_entity)
        } else {
            None
        }
    }
}

fn update_interactable_predicates(world: &mut World) {
    let mut interactable_query = world.query::<(Entity, &mut Interactable)>();
    let mut interactables = vec![];
    for (interactable_entity, interactable) in interactable_query.iter(world) {
        interactables.push((interactable_entity, (*interactable).clone()));
    }
    for (interactable_entity, interactable) in interactables.iter_mut() {
        if let Some(predicate) = &interactable.predicate {
            interactable.possible = predicate(*interactable_entity, world);
        }
    }
    for ((_, mut interactable), (_, temp)) in interactable_query.iter_mut(world).zip(interactables.into_iter()) {
        *interactable = temp;
    }
}