Skip to main content

traaains/editor/track/placing/
mod.rs

1//! Takes care of the placing functionality of the track editor.
2//!
3//! Implemented using bevy's state machines. The current shape and current step are tracked via their respective states, [CurrentShape] and [CurrentStep].
4//! Keybinds change states and systems responding to state changes change the virtual node, segment, etc. [Observers](observers) are then used to change
5//! between virtual and physical from/to node types.
6
7use bevy::{
8    app::{Plugin, Update},
9    ecs::{
10        component::Component,
11        entity::Entity,
12        query::With,
13        resource::Resource,
14        schedule::{IntoScheduleConfigs, common_conditions::not},
15        system::{Res, Single},
16    },
17    input::{common_conditions::input_just_pressed, mouse::MouseButton},
18    math::Vec2,
19    state::{
20        app::AppExtStates,
21        condition::in_state,
22        state::{OnEnter, OnExit, OnTransition, State},
23    },
24    ui::widget::Text,
25};
26
27use crate::{
28    editor::{StateText, track::TrackEditorState},
29    shape::Shapes,
30    ui::IsHoveringUi,
31};
32
33use self::{
34    gizmos::draw_virtual_segment_gizmos,
35    input_handlers::{advance_step, back_step, switch_shape},
36    move_to_cursor::{virtual_node_to_cursor, virtual_segment_to_cursor},
37    states::{
38        current_shape::CurrentShape,
39        current_step::{
40            CurrentStep, on_enter_constant, on_exit_constant, on_linear_to_quadratic,
41            on_quadratic_to_cubic, on_quadratic_to_linear,
42        },
43        current_step::{on_cubic_to_linear, on_cubic_to_quadratic},
44        final_step::place_segment,
45        final_step::{FinalStep, on_enter_final_step, on_exit_final_step},
46        on_enter_placing, on_leave_placing,
47    },
48};
49
50mod gizmos;
51mod input_handlers;
52mod move_to_cursor;
53mod observers;
54mod states;
55
56/// Marker component for entities which pertain to this editor.
57#[derive(Component)]
58struct Placing;
59
60/// A virtual track node. Used to provide the user visual feedback on where the next node is going to be placed.
61#[derive(Component)]
62struct VirtualNode;
63
64/// A node type. Used together with [FromNodeType] and in the [VirtualSegment] to determine if the node is a virtual one (currently not existing
65/// and needing to be placed), or a physical one (already placed).
66#[derive(Default, PartialEq, Eq)]
67enum NodeType {
68    #[default]
69    Virtual,
70    Physical(Entity),
71}
72
73/// Used to determine whether to place the segment from a physical node or the virtual one (cursor position).
74#[derive(Default, Resource)]
75struct FromNodeType(NodeType);
76
77/// A virtual track segment. Stores the information needed to place a new track segment, like its neighbouring nodes and current shape.
78#[derive(Component)]
79struct VirtualSegment {
80    pub shape: Shapes,
81    pub from: Entity,
82    pub to: NodeType,
83}
84
85/// Keeps track of the last connected block so that segments which either do not have any node to take blocks from
86/// or the choice of a block would be too ambiguous can join this one.
87#[derive(Default, Resource)]
88struct LastConnectedBlock(pub Option<Entity>);
89
90fn take_points<const N: usize>(shape: &Shapes) -> [Vec2; N] {
91    // don't create multiple iterators...
92    let mut coeffs = shape.coeffs().iter();
93    std::array::from_fn(|_| {
94        *coeffs
95            .next()
96            .expect("Failed to extract enough information from spline")
97    })
98}
99
100fn update_state_text(
101    mut text: Single<&mut Text, With<StateText>>,
102    current_shape: Res<State<CurrentShape>>,
103    current_step: Res<State<CurrentStep>>,
104) {
105    text.0 = format!(
106        "Placing tracks\n[J] Previous shape, [L] Next shape\n[9] Remove tracks\n[Esc] to exit\nCurrently placing: {}, Current step: {}",
107        current_shape.get(),
108        current_step.get(),
109    );
110}
111
112pub struct PlacingPlugin;
113
114fn register_state_handlers(app: &mut bevy::app::App) {
115    app.add_sub_state::<CurrentShape>()
116        .add_sub_state::<CurrentStep>()
117        .add_computed_state::<FinalStep>()
118        .add_systems(OnEnter(TrackEditorState::Placing), on_enter_placing)
119        .add_systems(OnExit(TrackEditorState::Placing), on_leave_placing)
120        .add_systems(OnEnter(CurrentStep::Zero), on_enter_constant)
121        .add_systems(OnExit(CurrentStep::Zero), on_exit_constant)
122        .add_systems(
123            OnTransition {
124                exited: CurrentStep::Linear,
125                entered: CurrentStep::Quadratic,
126            },
127            on_linear_to_quadratic,
128        )
129        .add_systems(
130            OnTransition {
131                exited: CurrentStep::Quadratic,
132                entered: CurrentStep::Linear,
133            },
134            on_quadratic_to_linear,
135        )
136        .add_systems(
137            OnTransition {
138                exited: CurrentStep::Quadratic,
139                entered: CurrentStep::Cubic,
140            },
141            on_quadratic_to_cubic,
142        )
143        .add_systems(
144            OnTransition {
145                exited: CurrentStep::Cubic,
146                entered: CurrentStep::Quadratic,
147            },
148            on_cubic_to_quadratic,
149        )
150        .add_systems(
151            OnTransition {
152                exited: CurrentStep::Cubic,
153                entered: CurrentStep::Linear,
154            },
155            on_cubic_to_linear,
156        )
157        .add_systems(OnEnter(FinalStep), on_enter_final_step)
158        .add_systems(OnExit(FinalStep), on_exit_final_step);
159}
160
161impl Plugin for PlacingPlugin {
162    fn build(&self, app: &mut bevy::app::App) {
163        register_state_handlers(app);
164
165        app.add_systems(Update, (virtual_node_to_cursor, virtual_segment_to_cursor))
166            .add_systems(
167                Update,
168                (update_state_text, switch_shape).run_if(in_state(TrackEditorState::Placing)),
169            )
170            .add_systems(
171                Update,
172                (
173                    (
174                        advance_step.run_if(not(in_state(FinalStep))),
175                        place_segment.run_if(in_state(FinalStep)),
176                    )
177                        .run_if(input_just_pressed(MouseButton::Left)),
178                    back_step.run_if(input_just_pressed(MouseButton::Right)),
179                )
180                    .run_if(in_state(TrackEditorState::Placing))
181                    .run_if(in_state(IsHoveringUi(false))),
182            )
183            .add_systems(Update, draw_virtual_segment_gizmos);
184    }
185}